正文

一个简单的软件渲染引擎2009-01-08 12:17:00

【评论】 【打印】 【字体: 】 本文链接:http://blog.pfan.cn/insky/40227.html

分享到:

软件渲染引擎实现说明

作者: 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  2G

2 编程环境,操作说明
         ①编程环境为
    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__

阅读(3754) | 评论(1)


版权声明:编程爱好者网站为此博客服务提供商,如本文牵涉到版权问题,编程爱好者网站不承担相关责任,如有版权问题请直接与本文作者联系解决。谢谢!

评论

loading...
您需要登录后才能评论,请 登录 或者 注册