多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
我们已经完善了光照的参数,以及模型空间-世界空间-视角空间-裁剪空间-屏幕空间的转换。并且搭建了我们demo的框架,即一个正方体的各部分的信息结构,如顶点+索引+贴图+材质等。这次,我们来把一切联系起来,创建出真正的软渲染demo。 在上述结构中,我们定义出Moe3DDevice与Moe3DDeviceContext,用来表示设备信息。 设备上下文DC是一个Windows数据结构,它包含了某个设备的绘制属性。通常,绘制调用都是借助于上下文对象,而这些设备上下文对象封装了用于画线、形状、文本等的Windows API。设备上下文是设备无关的,所以它既可以用于绘制屏幕,也可以用于绘制打印机甚至元文件。设备上下文在内存中创建,而内存经常受到扰动,所以它的地址是不固定的。因此,一个设备上下文句柄不是直接指向设备上下文对象,而是指向另外一个跟踪设备上下文地址的指针。 我个人认为设备上下文相当于画图过程中的画布(画纸),在VS中,这个画布可以是显示器,也可以使打印机,设备上下文决定了画布的属性,而且封装了在画布上画画的方法,比如画线,画点,等等,例如: pDc->LineTo(512,0); //从左下角到右上角的一条红色直线 。我们在VS中画图时,首先要得到这块画布才可以画画,所以要进行获取设备环境。 1.1Moe3DDevice.h 描述设备信息 #pragma once #include <windows.h> #include "MVector.h" #include "Vertex.h" class Moe3DDevice { public: Moe3DDevice(int width, int height); ~Moe3DDevice(); public: void DrawPixel(int x, int y, MVector color); float GetZ(int x, int y) const; void SetZ(int x, int y, float z); inline UINT*& GetFrameBuffer() { return m_pFramebuffer; } inline int GetClientWidth() { return m_width; } inline int getClientHeight() { return m_height; } void ClearBuffer(MVector color); private: int m_width; int m_height; UINT* m_pFramebuffer; float **m_zBuffer; //z缓存 }; 1.2Moe3DDevice.cpp 获取设备信息 #include "Moe3DDevice.h" #include "MathUtil.h" using namespace MathUtil; Moe3DDevice::Moe3DDevice(int width, int height) { m_width = width; m_height = height; m_zBuffer = new float*[width]; for (int i = 0; i < width; ++i) { m_zBuffer[i] = new float[height]; } } Moe3DDevice::~Moe3DDevice() { if (m_pFramebuffer) delete m_pFramebuffer; if (m_zBuffer) for (int i = 0; i < m_width; ++i) { delete[] m_zBuffer[i]; } } //画像素 void Moe3DDevice::DrawPixel(int x, int y, MVector color) { m_pFramebuffer[m_width*y + x] = MathUtil::ColorToUINT(color); } float Moe3DDevice::GetZ(int x, int y) const { if (x >= 0 && x < m_width && y >= 0 && y < m_height) return m_zBuffer[x][y]; else return 1.f; } void Moe3DDevice::SetZ(int x, int y, float z) { if (x >= 0 && x < m_width && y >= 0 && y < m_height) { m_zBuffer[x][y] = z; } } void Moe3DDevice::ClearBuffer(MVector color) { for (int x = 0; x < m_width; ++x) { for (int y = 0; y < m_height; ++y) { m_pFramebuffer[m_width*y + x] = MathUtil::ColorToUINT(color); m_zBuffer[x][y] = 0; } } } 2.1Moe3DDeviceContext.h 相当于在此作画 #pragma once #include "Moe3DDevice.h" #include "Vertex.h" #include <vector> #include "ShaderBase.h" enum MOE3D_FILL_MODE //渲染模式 { MOE3D_FILL_WIREFRAME,//线框模式 MOE3D_FILL_SOLIDE //实体模式 }; class Moe3DDeviceContext { public: Moe3DDeviceContext(); ~Moe3DDeviceContext(); public: void Init(Moe3DDevice* pDevice); //初始化 void SetRenderMode(MOE3D_FILL_MODE mode); //设置渲染模式 void SetVertexBuffer(std::vector<VertexIn> vertices); //设置顶点缓冲 void SetCameraPos(const MVector& pos); //设置相机位置 void SetIndexBuffer(std::vector<UINT> indices); //设置索引缓冲 void SetShader(ShaderBase* base); //设置着色器 void DrawIndexed(UINT indexCount,UINT startIndexLocation,UINT startVertexLocation); //绘制图形 private: void ToCVV(VertexOut& v); //投影后的坐标转化为cvv bool Clip(const VertexOut& v); //cvv裁剪 VertexOut TransformToProj(const VertexIn& v); //转到齐次裁剪空间 void TransformToScreen(const MMatrix& m,VertexOut& v); //转换到屏幕坐标 bool BackFaceCulling(const VertexIn& v1, const VertexIn& v2, const VertexIn& v3); //背面消隐测试 void BresenhamDrawLine(int x1, int y1, int x2, int y2); //画线 void ScanlineFill(const VertexOut& left, const VertexOut& right, int yIndex); //扫描线 void DrawTriangle(const VertexOut& v1, const VertexOut& v2, const VertexOut& v3); //画三角形 void DrawTriangleTop(const VertexOut& v1, const VertexOut& v2, const VertexOut& v3); //画平顶三角形 void DrawTriangleBottom(const VertexOut& v1, const VertexOut& v2, const VertexOut& v3); //画平底三角形 void TriangleRasterization(const VertexOut& v1, const VertexOut& v2, const VertexOut& v3); //光栅化三角形 private: Moe3DDevice* m_pDevice; //设备 MOE3D_FILL_MODE m_renderMode; //渲染状态 std::vector<VertexIn> m_vertices; //顶点缓冲 std::vector<UINT> m_indices; //索引缓冲 ShaderBase* m_pShader; //着色器 MVector m_cameraPos; //相机位置 用于背面消隐 }; 2.2Moe3DDeviceContext.h 具体实现 #include "Moe3DDeviceContext.h" #include "MathUtil.h" #include <algorithm> Moe3DDeviceContext::Moe3DDeviceContext():m_renderMode(MOE3D_FILL_WIREFRAME),m_cameraPos(MVector(0.f,0.f,0.f,1.f)) { } Moe3DDeviceContext::~Moe3DDeviceContext() { } void Moe3DDeviceContext::Init(Moe3DDevice* pDevice) { m_pDevice = pDevice; } //设置渲染模式 void Moe3DDeviceContext::SetRenderMode(MOE3D_FILL_MODE mode) { m_renderMode = mode; } //设置顶点缓冲 void Moe3DDeviceContext::SetVertexBuffer(std::vector<VertexIn> vertices) { m_vertices = vertices; } //设置相机位置 void Moe3DDeviceContext::SetCameraPos(const MVector& pos) { m_cameraPos = pos; } //设置索引缓冲 void Moe3DDeviceContext::SetIndexBuffer(std::vector<UINT> indices) { m_indices = indices; } //设置着色器 void Moe3DDeviceContext::SetShader(ShaderBase* base) { m_pShader = base; } //绘制顶点缓冲中的三角形 void Moe3DDeviceContext::DrawIndexed(UINT indexCount, UINT startIndexLocation, UINT startVertexLocation) { //得到屏幕变换矩阵 MMatrix screenTransformMat = MathUtil::MMatrixScreenTransform(m_pDevice->GetClientWidth(), m_pDevice->getClientHeight()); for (int i = startIndexLocation; i < indexCount / 3; ++i) { VertexIn p1 = m_vertices[startVertexLocation + m_indices[3 * i]]; VertexIn p2 = m_vertices[startVertexLocation + m_indices[3 * i + 1]]; VertexIn p3 = m_vertices[startVertexLocation + m_indices[3 * i + 2]]; //背面消隐 if (BackFaceCulling(p1, p2, p3) == false) { continue; } //转换到齐次裁剪空间,即投影后的坐标 VertexOut v1 = TransformToProj(p1); VertexOut v2 = TransformToProj(p2); VertexOut v3 = TransformToProj(p3); //判断是否通过cvv裁剪 if (Clip(v1) == false || Clip(v2) == false || Clip(v3) == false) { continue; } //进行透视除法 转到cvv ToCVV(v1); ToCVV(v2); ToCVV(v3); //将投影得到的坐标转化为屏幕坐标 TransformToScreen(screenTransformMat, v1); TransformToScreen(screenTransformMat, v2); TransformToScreen(screenTransformMat, v3); DrawTriangle(v1, v2, v3); } } //转化到cvv void Moe3DDeviceContext::ToCVV(VertexOut& v) { v.posH.x /= v.posH.w; v.posH.y /= v.posH.w; v.posH.z /= v.posH.w; v.posH.w = 1; } //简单cvv裁剪 bool Moe3DDeviceContext::Clip(const VertexOut& v) { //cvv为 x-1,1 y-1,1 z0,1 if (v.posH.x >= -v.posH.w && v.posH.x <= v.posH.w && v.posH.y >= -v.posH.w && v.posH.y <= v.posH.w && v.posH.z >= 0.f && v.posH.z <= v.posH.w) { return true; } return false; } //转到齐次裁剪空间 VertexOut Moe3DDeviceContext::TransformToProj(const VertexIn& v) { VertexOut out = m_pShader->VS(v); //设置oneDivZ out.oneDivZ = 1 / out.posH.w; //由于1/z和x,y成线性关系 //这里将需要插值的信息都乘以1/z 得到 s/z和t/z等,方便光栅化阶段进行插值 out.color.x *= out.oneDivZ; out.color.y *= out.oneDivZ; out.color.z *= out.oneDivZ; out.normal.x *= out.oneDivZ; out.normal.y *= out.oneDivZ; out.normal.z *= out.oneDivZ; out.tex.u *= out.oneDivZ; out.tex.v *= out.oneDivZ; return out; } //转换到屏幕坐标 void Moe3DDeviceContext::TransformToScreen(const MMatrix& m, VertexOut& v) { v.posH = v.posH * m; } //背面消隐 bool Moe3DDeviceContext::BackFaceCulling(const VertexIn& v1, const VertexIn& v2, const VertexIn& v3) { //线框模式不进行背面消隐 if (m_renderMode == MOE3D_FILL_WIREFRAME) { return true; } else { MVector vector1 = v2.pos - v1.pos; MVector vector2 = v3.pos - v2.pos; //顶点缓存中顺序为顺时针 //叉积得到的方向与右手系一致 MVector normal = vector1.Cross(vector2); MVector viewDir = v1.pos - m_cameraPos; if (normal.Dot(viewDir) < 0) { return true; } return false; } } //画三角形 void Moe3DDeviceContext::DrawTriangle(const VertexOut& v1, const VertexOut& v2, const VertexOut& v3) { //线框模式 if (m_renderMode == MOE3D_FILL_WIREFRAME) { BresenhamDrawLine(v1.posH.x, v1.posH.y, v2.posH.x, v2.posH.y); BresenhamDrawLine(v1.posH.x, v1.posH.y, v3.posH.x, v3.posH.y); BresenhamDrawLine(v2.posH.x, v2.posH.y, v3.posH.x, v3.posH.y); } else if (m_renderMode == MOE3D_FILL_SOLIDE) { TriangleRasterization(v1, v2, v3); } } //bresenham画线 void Moe3DDeviceContext::BresenhamDrawLine(int x1, int y1, int x2, int y2) { int dx = x2 - x1; int dy = y2 - y1; int stepx = 1; int stepy = 1; if (dx >= 0) { stepx = 1; } else { stepx = -1; dx = abs(dx); } if (dy >= 0) { stepy = 1; } else { stepy = -1; dy = abs(dy); } int deltaX = 2 * dx; int deltaY = 2 * dy; if (dx > dy) { int error = deltaY - dx; for (int i = 0; i <= dx; ++i) { if(x1 >= 0 && x1 < m_pDevice->GetClientWidth() && y1 >= 0 && y1 < m_pDevice->getClientHeight()) m_pDevice->DrawPixel(x1, y1, MVector(0.f, 0.f, 0.f,1.f)); if (error >= 0) { error -= deltaX; y1 += stepy; } error += deltaY; x1 += stepx; } } else { int error = deltaX - dy; for (int i = 0; i <= dy; i++) { if (x1 >= 0 && x1 < m_pDevice->GetClientWidth() && y1 >= 0 && y1 < m_pDevice->getClientHeight()) m_pDevice->DrawPixel(x1, y1, MVector(0.f, 0.f, 0.f,1.f)); if (error >= 0) { error -= deltaY; x1 += stepx; } error += deltaX; y1 += stepy; } } } //扫描线填充 //left 左端点 right 右端点 void Moe3DDeviceContext::ScanlineFill(const VertexOut& left, const VertexOut& right, int yIndex) { float dx = right.posH.x - left.posH.x; for (float x = left.posH.x; x <= right.posH.x; x += 1.f) { //四舍五入 int xIndex = static_cast<int>(x + .5f); if(xIndex >= 0 && xIndex < m_pDevice->GetClientWidth()) { //插值系数 float lerpFactor = 0; if (dx != 0) { lerpFactor = (x - left.posH.x) / dx; } //深度测试 //1/z’与x’和y'是线性关系的 float oneDivZ = MathUtil::Lerp(left.oneDivZ, right.oneDivZ, lerpFactor); if (oneDivZ >= m_pDevice->GetZ(xIndex,yIndex)) { m_pDevice->SetZ(xIndex, yIndex, oneDivZ); float w = 1 / oneDivZ; //插值顶点 原先需要插值的信息均乘以oneDivZ //现在得到插值后的信息需要除以oneDivZ得到真实值 VertexOut out = MathUtil::Lerp(left, right, lerpFactor); out.posH.y = yIndex; out.tex.u *= w; out.tex.v *= w; out.normal.x *= w; out.normal.y *= w; out.normal.z *= w; out.color.x *= w; out.color.y *= w; out.color.z *= w; //画像素点颜色 m_pDevice->DrawPixel(xIndex, yIndex, m_pShader->PS(out)); } } } } //画平顶三角形 v3为下顶点 //y方向每次增加一个像素 根据y插值顶点 void Moe3DDeviceContext::DrawTriangleTop(const VertexOut& v1, const VertexOut& v2, const VertexOut& v3) { float dy = 0;//每次y增加一个像素 for (float y = v1.posH.y; y <= v3.posH.y; y += 1.f) { //四舍五入 int yIndex = static_cast<int>(y + 0.5f); if (yIndex >= 0 && yIndex < m_pDevice->getClientHeight()) { float t = dy / (v3.posH.y - v1.posH.y); //插值生成左右顶点 VertexOut new1 = MathUtil::Lerp(v1, v3, t); VertexOut new2 = MathUtil::Lerp(v2, v3, t); dy += 1.f; //扫描线填充 if (new1.posH.x < new2.posH.x) { ScanlineFill(new1, new2, yIndex); } else { ScanlineFill(new2, new1, yIndex); } } } } //画平底三角形 v1为上顶点 void Moe3DDeviceContext::DrawTriangleBottom(const VertexOut& v1, const VertexOut& v2, const VertexOut& v3) { float dy = 0;//每次y增加一个像素 for (float y = v1.posH.y; y <= v2.posH.y; y += 1.f) { //四舍五入 int yIndex = static_cast<int>(y + 0.5f); if (yIndex >= 0 && yIndex < m_pDevice->getClientHeight()) { float t = dy / (v2.posH.y - v1.posH.y); //插值左右顶点 VertexOut new1 = MathUtil::Lerp(v1, v2, t); VertexOut new2 = MathUtil::Lerp(v1, v3, t); dy += 1.f; //扫描线填充 if (new1.posH.x < new2.posH.x) { ScanlineFill(new1, new2, yIndex); } else { ScanlineFill(new2, new1, yIndex); } } } } //光栅化三角形 void Moe3DDeviceContext::TriangleRasterization(const VertexOut& v1, const VertexOut& v2, const VertexOut& v3) { //判断是否是平底或者平顶三角形 if (v1.posH.y == v2.posH.y) { if (v1.posH.y < v3.posH.y) {//平顶 DrawTriangleTop(v1, v2, v3); } else {//平底 DrawTriangleBottom(v3, v1, v2); } } else if (v1.posH.y == v3.posH.y) { if (v1.posH.y < v2.posH.y) {//平顶 DrawTriangleTop(v1, v3, v2); } else {//平底 DrawTriangleBottom(v2, v1, v3); } } else if (v2.posH.y == v3.posH.y) { if (v2.posH.y < v1.posH.y) {//平顶 DrawTriangleTop(v2, v3, v1); } else {//平底 DrawTriangleBottom(v1, v2, v3); } } //一般三角形 将其分割成平底三角形和平顶三角形 else { //根据y值将三个顶点排序 std::vector<VertexOut> vertices{v1,v2,v3}; std::sort(vertices.begin(), vertices.end(), [](VertexOut v1,VertexOut v2) { return v1.posH.y < v2.posH.y;}); VertexOut top = vertices[0]; VertexOut middle = vertices[1]; VertexOut bottom = vertices[2]; //插值求中间点 float middleX = (middle.posH.y - top.posH.y) * (bottom.posH.x - top.posH.x) / (bottom.posH.y - top.posH.y) + top.posH.x; float dy = middle.posH.y - top.posH.y; float t = dy / (bottom.posH.y - top.posH.y); VertexOut newMiddle = MathUtil::Lerp(top, bottom, t); newMiddle.posH.x = middleX; newMiddle.posH.y = middle.posH.y; //平顶 DrawTriangleTop(middle, newMiddle, bottom); //平底 DrawTriangleBottom(top, middle, newMiddle); } } 3.1BoxDemo.cpp 实现我们的demo 过程:顶点->索引->着色器->纹理->光源->材质。 并且实现鼠标操作,来改变我们的视角。 #include <vector> #include "BoxDemo.h" #include "BoxShader.h" #include "Moe3DDevice.h" #include "Moe3DDeviceContext.h" BoxDemo::BoxDemo():m_theta(1.5f * MathUtil::PI),m_phi(0.4*MathUtil::PI),m_radius(5.0f) { m_lastMousePos.x = 0; m_lastMousePos.y = 0; m_world = MathUtil::MMatrixIdentity(); //平行光 m_dirLight.ambient = MVector(0.2f, 0.2f, 0.2f, 1.0f); m_dirLight.diffuse = MVector(0.5f, 0.5f, 0.5f, 1.0f); m_dirLight.specular = MVector(0.5f, 0.5f, 0.5f, 1.0f); m_dirLight.direction = MVector(0.57735f, 0.57735f, 0.57735f); //材质 m_material.ambient = MVector(0.7f, 0.85f, 0.7f, 1.0f); m_material.diffuse = MVector(0.7f, 0.85f, 0.7f, 1.0f); m_material.specular = MVector(0.8f, 0.8f, 0.8f, 16.0f); } BoxDemo::~BoxDemo() { Clear(); } bool BoxDemo::Init(HINSTANCE hInstance,HWND hWnd) { m_hWnd = hWnd; m_hInstance = hInstance; RECT rc; GetClientRect(m_hWnd, &rc); m_width = rc.right - rc.left; m_height = rc.bottom - rc.top; m_pDevice = new Moe3DDevice(m_width, m_height); m_pImmediateContext = new Moe3DDeviceContext(); m_pImmediateContext->Init(m_pDevice); m_pShader = new BoxShader(); //创建顶点缓存 GeometryGenerator::GetInstance()->CreateBox(2.f, 2.f, 2.f, m_box); m_vertices.resize(m_box.vertices.size()); for (UINT i = 0; i < m_box.vertices.size(); ++i) { m_vertices[i].pos = m_box.vertices[i].pos; m_vertices[i].tex = m_box.vertices[i].tex; m_vertices[i].normal = m_box.vertices[i].normal; m_vertices[i].color = m_box.vertices[i].color; } m_pImmediateContext->SetVertexBuffer(m_vertices); //创建索引缓存 //create index buffer m_indices.resize(m_box.indices.size()); for (UINT i = 0; i < m_box.indices.size(); ++i) { m_indices[i] = m_box.indices[i]; } m_pImmediateContext->SetIndexBuffer(m_indices); //设置着色器 m_pImmediateContext->SetShader(m_pShader); //加载纹理 m_tex = MathUtil::LoadBitmapToColorArray(L"../Texture/nico.bmp"); //设置着色器纹理 //由于纹理加载一次不在改变,故不用再Update中设置 m_pShader->SetTexture(m_tex); //设置着色器光源、材质 m_pShader->SetDirLight(m_dirLight); m_pShader->SetMaterial(m_material); return true; } void BoxDemo::Update(float dt) { float x = m_radius * sinf(m_phi) * cosf(m_theta); float z = m_radius * sinf(m_phi) * sinf(m_theta); float y = m_radius * cosf(m_phi); MVector pos(x, y, z, 1.f); MVector target(0.f, 0.f, 0.f, 1.f); MVector up(0.f, 1.f, 0.f, 0.f); MMatrix view = MathUtil::MMatrixLookAtLH(pos, target, up); MMatrix proj = MathUtil::MMatrixPerspectiveFovLH(MathUtil::PI / 4, m_pDevice->GetClientWidth() / static_cast<float>(m_pDevice->getClientHeight()), 1.f, 100.f); MMatrix world = MathUtil::MMatrixIdentity(); m_worldViewProj = world*view*proj; m_world = world; //计算逆矩阵的转置 m_worldInvTranspose = MathUtil::MMatrixTranspose(MathUtil::MMatrixInverse(world)); //设置相机位置 以便背面消隐 m_pImmediateContext->SetCameraPos(pos); //设置着色器中eyePos m_pShader->SetEyePos(pos); } void BoxDemo::Render() { //清空后缓冲图片 m_pDevice->ClearBuffer(MVector(0.75f, 0.75f, 0.75f,1.f)); //设置渲染状态 m_pImmediateContext->SetRenderMode(MOE3D_FILL_SOLIDE); //设置着色器变量 m_pShader->SetWorldViewProj(m_worldViewProj); m_pShader->SetWorld(m_world); m_pShader->SetWorldInvTranspose(m_worldInvTranspose); m_pImmediateContext->DrawIndexed(m_indices.size(), 0, 0); } void BoxDemo::Clear() { if(m_pDevice) delete m_pDevice; if(m_pImmediateContext) delete m_pImmediateContext; if (m_pShader) delete m_pShader; } void BoxDemo::OnMouseDown(WPARAM btnState, int x, int y) { m_lastMousePos.x = x; m_lastMousePos.y = y; SetCapture(m_hWnd); } void BoxDemo::OnMouseUp(WPARAM btnState, int x, int y) { ReleaseCapture(); } void BoxDemo::OnMouseMove(WPARAM btnState, int x, int y) { if ((btnState & MK_LBUTTON) != 0) { //角度转弧度 float dx = MathUtil::MConvertToRadians(0.25f*static_cast<float>(x - m_lastMousePos.x)); float dy = MathUtil::MConvertToRadians(0.25f*static_cast<float>(y - m_lastMousePos.y)); m_theta -= dx; m_phi += dy; m_phi = MathUtil::Clamp(m_phi, 0.1f, MathUtil::PI - 0.1f); } else if ((btnState & MK_RBUTTON) != 0) { float dx = 0.2f*static_cast<float>(x - m_lastMousePos.x); float dy = 0.2f*static_cast<float>(y - m_lastMousePos.y); m_radius += dx - dy; m_radius = MathUtil::Clamp(m_radius, 3.0f, 300.0f); } m_lastMousePos.x = x; m_lastMousePos.y = y; } 以上,我们已经完成基本所有的软渲染demo条件,包括软渲染的流程、设备上下文模拟、以及demo的结构。