作者: insky(李文耀)
主页:
本程序实现了一整套3D渲染流水线,首先抽象出一个画点函数DrawPoint( int x, int y, DWORD color )

1 软件效果及速度

算法实现的最终效果如上图所示,在扫描线Z缓冲算法基础上增加了T&L
CPU 显卡 内存
Intel Core 2 E7300(双核,2.66GHz) NVIDIA GeForce 9600 GT 2G
2 编程环境,操作说明
①
vs 2005 + Directx 9.0(c) + DXUT(跟glut类似,DX框架)。
②
中间是当前查看的OBJ文件模型,可以通过鼠标左键旋转模型,也可通过右键水平移动模型以及通过滚轮前后 移动模型。
右边是功能按钮,单击 打开文件
选择按钮
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
// 把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__
评论