软件渲染引擎实现说明作者: insky(李文耀) 主页:gamecoding.cn 下载 本程序实现了一整套3D渲染流水线,首先抽象出一个画点函数DrawPoint( int x, int y, DWORD color ),后继模型的所有绘制操作都是通过调用DrawPoint来完成的。实现功能及流程可由下图表示: 1 软件效果及速度 算法实现的最终效果如上图所示,在扫描线Z缓冲算法基础上增加了T&L,背面剔除,屏幕空间裁剪,颜色插值,全屏反走样等一整套3D渲染流水线。最终效果跟D3D9固定流水线的渲染效果已经相当接近,除了一些少数的黑点无法剔除,主要原因是顶点浮点坐标跟屏幕整型坐标的转换带来的锯齿。另外在速度上,对于有92856个面片的兔子模型,软件渲染依然有6.16 fps. 测试电脑配置如下: CPU 显卡 内存Intel Core 2 E7300(双核,2.66GHz) NVIDIA GeForce 9600 GT 2G2 编程环境,操作说明 ①编程环境为 vs 2005 + Directx 9.0(c) + DXUT(跟glut类似,DX框架)。 ②用户界面使用说明 中间是当前查看的OBJ文件模型,可以通过鼠标左键旋转模型,也可通过右键水平移动模型以及通过滚轮前后 移动模型。 右边是功能按钮,单击 打开文件 按钮会跳出文件选择对话框,用于加载新的OBJ模型。也可通过 下拉框 直 接选择程序默认的4个OBJ模型。 选择按钮允许用户选择使用D3D9渲染还是使用软件渲染。下面是一堆渲染选项开关:自动旋转,使用光照模 型,全屏反走样,背面剔除,深度测试。 3 软件渲染过程中使用的数据结构与算法说明 所有的操作都封装在类ZLBuffer中,代码1000行左右,其接口只有四个如下: ZLBuffer(); // 构造函数 HRESULT LoadFromOBJFile( const WCHAR* strFileName ); //加载OBJ模型 void RenderByD3D9( IDirect3DDevice9* pd3dDevice, float fElapsedTime ); // D3D9渲染 void RenderBySoftWare( IDirect3DDevice9* pd3dDevice, float fElapsedTime );// 软件渲染 bool MsgProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ); // windows消息处理 3.1 加载OBJ模型,填充原始顶点数据结构和面数据结构 HRESULT ZLBuffer::LoadFromOBJFile( const WCHAR* strFileName )函数负责加载模型,填充下面两个数据结构: std::vector<PrimaryVertex> m_PrimaryVertexSet; // 模型原始顶点集合 std::vector<Face> m_FaceSet; // 模型原始面集合 // 原始顶点结构 struct PrimaryVertex { D3DXVECTOR3 position; D3DXVECTOR3 normal; int t; }; // 原始面结构 struct Face { int p0,p1,p2; }; 其中,D3DXVECTOR3定义如下,t成员是用来记录该顶点属于多少个面,如此计算法法线向量的时候可以直接把每个面的法线向量加到normal再除t来完成。 struct D3DXVECTOR3 { float x; float y; float z; }; 3.2 软件渲染RenderBySoftWare代码仅三行,如下 TANDL(); // 坐标变换和光照过程 DoZLBuffer(); // 光栅化过程结果写到m_ColorBuffer二维数组 Flush( pd3dDevice ); // 把m_ColorBuffer数组写到后台颜色缓存,先进行// 全屏反走样处理 DoZLBuffer就是扫描线Z缓冲算法,其中加入了背面剔除跟裁剪,很大程度上提高了渲染速度。 3.3写后台缓存代码如下: // 把m_ColorBuffer二维颜色数组里的数据刷到后台颜色缓存 void ZLBuffer::Flush( IDirect3DDevice9* pd3dDevice ) { HRESULT hr = S_OK; D3DLOCKED_RECT rect; IDirect3DSurface9 *pSurface = NULL; //获取后台表面 V( pd3dDevice->GetBackBuffer( 0, 0, D3DBACKBUFFER_TYPE_MONO, &pSurface ) ); V( pSurface->LockRect( &rect, 0, D3DLOCK_DISCARD ) ); DWORD *pixels = (DWORD *)(rect.pBits); // 后台数据指针 rect.Pitch >>= 2; // 每一行的跨度,字节数 int num = 0; for (int y = 0; y < HEIGHT; y++) { for (int x = 0; x < WIDTH; x++) { pixels[num + x] = m_ColorBuffer[y][x]; } num += rect.Pitch; } pSurface->UnlockRect(); V( pSurface->Release() ); // 释放表面指针 } 附:ZLBuffer.h文件源代码(完整工程 和 可执行程序请下载附件): //------------------------------------------------------------------------- // File: ZLBuffer.h // ZLBuffer类及相关结构体的声明 // 作者: 李文耀(gamecoding.cn) // 时间:2009-01-02 // // 该类实现了一个简单的3D流水线,自己抽象出了一个画点函数DrawPoint( int x, int y, DWORD color ), // 模型的所有绘制操作都是调用DrawPoint来完成的。模拟了D流水线中的“T&L”并用扫描线Z缓冲算法 // 实现rasterization,同时实现OBJ模型文件的读取。 // 为了优化渲染速度,实现了背面剔除跟裁剪; // 为了改善画面,实现了,线性插值,全屏幕反走样。 // // 使用 // 1,加载模型:调用LoadFromOBJFile( const WCHAR* strFileName )加载模型。 // 2,渲染模型:为了便于比较,提供了两个渲染函数RenderByD3D9( IDirect3DDevice9* pd3dDevice ), // RenderBySoftWare。前者使用D3D9固定流水线渲染,后者为纯软件渲染。 // 3,操作:可以使用鼠标旋转,拖拉模型,可开启或关闭开关:光照,背面剔除,反走样,深度测试 // // 软件渲染RenderBySoftWare中使用到D3D9的内容罗列如下: // 1, 写后台缓存,这是唯一与D3D9流水线交互的地方。 // 2,矩阵类D3DXMATRIX以及矩阵操作相关函数如:生成视野矩阵的D3DXMatrixLookAtLH // 3,向量类D3DXVECTOR3以及向量操作相关函数如:计算叉积D3DXVec3Dot // // 软件渲染RenderBySoftWare代码仅行,如下: // TANDL(); // 坐标变换和光照过程 // DoZLBuffer(); // 光栅化过程结果写到m_ColorBuffer数组 // Flush( pd3dDevice ); // 把m_ColorBuffer数组写到后台颜色缓存 //-------------------------------------------------------------------------------------- #ifndef __ZLBufferH__ #define __ZLBufferH__ #pragma warning(disable: 4995) #include <vector> #include <list> #pragma warning(default: 4995) #define WIDTH 800 #define HEIGHT 600 #define MINZ 0 #define MAXZ 65535 #define BACKGROUND BLUE // 颜色常量 const D3DXCOLOR WHITE( D3DCOLOR_XRGB( 255, 255, 255 ) ); const D3DXCOLOR BLACK( D3DCOLOR_XRGB( 0, 0, 0 ) ); const D3DXCOLOR RED( D3DCOLOR_XRGB( 255, 0, 0 ) ); const D3DXCOLOR GREEN( D3DCOLOR_XRGB( 0, 255, 0 ) ); const D3DXCOLOR BLUE( D3DCOLOR_XRGB( 0, 0, 255 ) ); const D3DXCOLOR YELLOW( D3DCOLOR_XRGB( 255, 255, 0 ) ); const D3DXCOLOR CYAN( D3DCOLOR_XRGB( 0, 255, 255 ) ); const D3DXCOLOR MAGENTA( D3DCOLOR_XRGB( 255, 0, 255 ) ); // 原始顶点结构 struct PrimaryVertex { D3DXVECTOR3 position; D3DXVECTOR3 normal; int t; }; // 原始面结构 struct Face { int p0,p1,p2; }; // 经过“T&L”流水线后的顶点结构 struct TLVertex { D3DXVECTOR3 position; float color; }; // 边表成员结构体 struct Edge { int id; // 所属多边形ID int minY; // 最小Y int x,z; // 边的minY端点的x,z坐标(minY对应的x坐标) int dy; // 边跨越的扫描线数目 float dx,dz; // 两相邻扫描线交点的x坐标之差,z坐标之差 float color; // 边的minY端点颜色值 float dc; // 两相邻扫描线交点的颜色值之差 }; // 多边形表成员结构体 struct Triangle { int id; // 多边形ID char state; // 当前状态,1,2 float a,b,c,d; // 多边形平面参数 float dzX,dzY; // Z值沿X轴,Y轴每像素改变值 DWORD color; // 颜色 }; // 活化边表成员结构体 struct ActiveEdge { int id; // 多边形ID float xL; // 左交点的x坐标 int dyL; // 和左交点所在边相交的剩余扫描线数 float dxL; // 左边两相邻扫描线交点的x坐标之差 float xR; // 右交点的x坐标 int dyR; // 和右交点所在边相交的剩余扫描线数 float dxR; // 右边两相邻扫描线交点的x坐标之差 float zL; // 左交点深度值 float dzX,dzY;// 沿扫描线向右每像素深度增量,沿y方向每像素深度增量 float cL,dcL; // 左交点颜色值,左边两相邻扫描线交点的颜色值之差 float cR,dcR; // 右交点颜色值,右边两相邻扫描线交点的颜色值之差 }; class ZLBuffer { public: ZLBuffer(); // 加载指定的OBJ文件,会先调用Clear清空原有数据 HRESULT LoadFromOBJFile( const WCHAR* strFileName ); // 用D3D9绘制当前模型 void RenderByD3D9( IDirect3DDevice9* pd3dDevice, float fElapsedTime ); // 用软件绘制当前模型 void RenderBySoftWare( IDirect3DDevice9* pd3dDevice, float fElapsedTime ); // 键盘,鼠标消息处理函数 bool MsgProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ); private: std::vector<PrimaryVertex> m_PrimaryVertexSet; // 模型原始顶点集合 std::vector<Face> m_FaceSet; // 模型原始面集合 std::vector<TLVertex> m_TLVertexSet; // 经T&L流水线后的顶点集合 std::vector<Triangle> m_TriangeSet; // 多边形集合 std::list<Edge> m_EdgeSet[HEIGHT]; // 边集合 std::list<ActiveEdge> m_ActiveEdgeSet; // 活跃边集合 D3DXVECTOR3 m_position; // 模型的世界坐标 float m_rotateY; // 模型绕Y轴旋转角度 float m_rotateX; // 模型绕X轴旋转角度 // 与后台颜色缓存等大的颜色数组,先渲染到此数组然后再一次性写到后台颜色缓存 DWORD m_ColorBuffer[HEIGHT][WIDTH]; DWORD m_TempColorBuffer[HEIGHT][WIDTH]; // 临时颜色数组 bool m_bIsLightAble; // 是否使用光照 bool m_bIsAntiAliasing; // 是否抗锯齿 bool m_bIsBackCull; // 是否背面剔除 bool m_bIsDepthTest; // 是否开启深度测试 bool m_bIsAutoRotate; // 是否自动旋转 WCHAR m_fileName[100]; // 当前模型文件名 // 软件渲染相关函数 private: void DrawPoint( int x, int y, DWORD color ); // 打点(屏幕空间),写到m_ColorBuffer数组 void Flush( IDirect3DDevice9* pd3dDevice ); // 将m_ColorBuffer数组刷到后台缓存 void TANDL(); // T&L过程,实现了平行光光照模型 void DoZLBuffer(); // 光栅化过程即扫描线Z缓冲算法 public: inline void Clear(); // 清空所有模型相关数据,并初始化 void SetLightAble( bool isLightAble ) { m_bIsLightAble = isLightAble; } void SetAntiAliasingAble( bool isAnti ) { m_bIsAntiAliasing = isAnti; } void SetBackCullAble( bool isBackCull ) { m_bIsBackCull = isBackCull; } void SetDepthTestAble( bool isDepthTest ) { m_bIsDepthTest = isDepthTest; } void SetAutoRotateAble( bool isAutoRotate ) { m_bIsAutoRotate = isAutoRotate; } int GetVertexCount() { return m_TLVertexSet.size(); } int GetFaceCount() { return m_FaceSet.size(); } const WCHAR * GetRenderFileName() { return m_fileName; } }; #endif // __ZLBufferH__

评论