毕业设计选的是HLSL编程相关的,于是开始学习。
理论方面的不介绍了,这方面资料很多。只是记录一些编程实践相关的东西。
首先要准备一个HLSL程序运行的基本框架。这里我不使用MS提供的DXUT,太过复杂且使你对底层的来龙去脉懵懵懂懂,DX方面的龙书《Introduction to 3D Game Programming with DirectX 9.0》介绍了一个框架十分简洁明了,但考虑到以后我的程序的复杂度和可扩展性我使用一个面向对象的DX程序框架,主要参考《DX实时渲染技术详解》(kelly dempski著)。基本有4个文件
1.Application.h 提供一个基本的CHostApplication类 主要包括 初始化D3D,创建设备,创建窗口,枚举模式等基本函数以及 场景初始化,渲染,丢失,销毁的虚函数。其中最主要的是提供一个Go()函数,这是处理消息循环,场景渲染的主函数。 包括的成员变量有 D3D对象,D3D设备对象,窗口句柄,窗口长宽等。
2.Application.cpp 实现CHostApplication类提供的成员函数。
3.Executable.h 提供2个函数声明。程序入口函数及消息处理回调函数。
4.Executable.CPP 程序执行主体。在main()中创建CHostApplication派生类的对象,之后进入消息循环及场景渲染主体Go().
一些基本的D3D初始化及窗口函数在CHostApplication基类中已经实现,我们只要将重心移到CHostApplication的派生类,在派生类中重写CHostApplication基类的渲染初始化,渲染操作,结束渲染等函数用以实现特定场景的绘制工作。
下面介绍这个HLSL程序:旋转的三角形。
1.在VertexApplication.h中。构建CHostApplication类的派生类 CVertexApplication
#include "Application.h"
class CVertexApplication : public CHostApplication
{
public:
BOOL FillVertexBuffer();
BOOL CreateVertexBuffer();
void DestroyVertexBuffer();
CVertexApplication();
virtual ~CVertexApplication();
//This function will be called after the D3D object is
//created. Here we'll actually create the vertex buffer.
virtual BOOL PostInitialize();
//This function will clean up the vertex buffer prior to
//the destruction of the device
virtual BOOL PreTerminate();
//These functions will ensure that lost vertex buffers are
//recreated once the device is reset
virtual BOOL PreReset();
virtual BOOL PostReset();
//This function will actually render the data.
virtual void Render();
//This is the pointer to our vertex buffer
LPDIRECT3DVERTEXBUFFER9 m_pVertexBuffer;
IDirect3DVertexShader9* m_pVertexShader;
LPD3DXCONSTANTTABLE m_pConstantTable_VS;
D3DXMATRIXA16 m_matWorld,m_matView,m_matProj;
};
注意最后我添加了用于构建HLSL程序的顶点渲染器对象及常量表。
2. 在VertexApplication.cpp 文件中实现上述相关函数。给出关键代码
BOOL CVertexApplication::PostInitialize()
{
if (FAILED(EasyCreateWindowed(m_hWnd, D3DDEVTYPE_HAL,
D3DCREATE_HARDWARE_VERTEXPROCESSING)))
return FALSE;
// 创建顶点缓冲区
if(!CreateVertexBuffer())
return FALSE;
// 填充顶点缓冲区
if(!FillVertexBuffer())
return FALSE;
//编译顶点渲染器代码
LPD3DXBUFFER pCode_VS;
DWORD dwShaderFlags = D3DXSHADER_SKIPOPTIMIZATION|D3DXSHADER_DEBUG;
if(FAILED((D3DXCompileShaderFromFile( L"HLSLTransform.vsh", NULL, NULL, "VS",
"vs_2_0", dwShaderFlags, &pCode_VS,
NULL, &m_pConstantTable_VS))))
return FALSE;
//创建顶点渲染器
if(FAILED((m_pD3DDevice->CreateVertexShader((DWORD*)pCode_VS->GetBufferPointer(),
&m_pVertexShader ))))
return FALSE;
pCode_VS->Release();
//构造观察矩阵
D3DXVECTOR3 vEyePt( 0.0f, 0.0f,-5 );
D3DXVECTOR3 vLookatPt( 0.0f, 0.0f, 0.0f );
D3DXVECTOR3 vUpVec( 0.0f, 1.0f, 0.0f );
D3DXMatrixLookAtLH( &m_matView, &vEyePt, &vLookatPt, &vUpVec );
//构造投影矩阵
float fAspectRatio = (float)m_WindowWidth/m_WindowHeight;
D3DXMatrixPerspectiveFovLH( &m_matProj, D3DX_PI/4, fAspectRatio, 1.0f, 100.0f );
//关闭光照处理, 默认情况下使用光照处理
m_pD3DDevice->SetRenderState( D3DRS_LIGHTING, false );
//设置剔出模式,为不剔出任何面
m_pD3DDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_NONE );
return TRUE;
}
以上是渲染场景前的初始化操作。编译了HLSL代码并创建了顶点渲染器对象。
下面是场景绘制代码
void CVertexApplication::Render()
{
//Here we regenerate the data and draw the points
//FillVertexBuffer();
HRESULT hr = 0;
//建立世界矩阵
UINT iTime = timeGetTime() % 2000;
FLOAT fAngle = iTime * (2.0f * D3DX_PI) / 2000.0f;
D3DXMatrixIdentity( &m_matWorld );
D3DXMatrixRotationY( &m_matWorld, fAngle );
//设置全局变量
D3DXMATRIX mWorldViewProj = m_matWorld * m_matView * m_matProj;
m_pConstantTable_VS->SetMatrix( m_pD3DDevice, "matWorldViewProj", &mWorldViewProj);
//设置顶点渲染器对象
hr = m_pD3DDevice->SetVertexShader(m_pVertexShader);
if(FAILED(hr))
return;
// 绘制三角形
m_pD3DDevice->SetStreamSource(0,m_pVertexBuffer,0,sizeof(SIMPLE_VERTEX));
m_pD3DDevice->SetFVF(D3DFVF_SIMPLEVERTEX);
m_pD3DDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 1);
}
在绘制场景的过程中,通过不断改变世界矩阵而改变mWorldViewProj 局部变量值,然后绑定该变量到HLSL代码文件中的全局变量matWorldViewProj,我们可以先来看看HLSL代码文件:
//==============================================================
// Desc: 顶点渲染器代码
//==============================================================
//坐标变换矩阵
float4x4 matWorldViewProj;
//--------------------------------------------------------------
// Desc: 输出结构
//--------------------------------------------------------------
struct VS_OUTPUT
{
float4 Pos : POSITION;
float4 Color : COLOR;
};
//--------------------------------------------------------------
// Desc: 顶点渲染
//--------------------------------------------------------------
VS_OUTPUT VS( float4 Pos: POSITION, float4 Color: COLOR )
{
VS_OUTPUT Out = (VS_OUTPUT) 0;
Out.Pos = mul(Pos, matWorldViewProj);
Out.Color = Color;
return Out;
}
可以看到,对于固定流水线中顶点缓存中的每个顶点的位置POSITION和颜色COLOR作为输入参数传递给VS函数,然后将顶点位置通过Pos*matWorldViewProj达到变换顶点坐标的效果之后返回该顶点坐标,而HLSL代码文件中的matWorldViewProj全局变量绑定到了固定流水线中mWorldViewProj这个局部变量的地址,随着mWorldViewProj的改变(mWorldViewProj每一帧都在变化),matWorldViewProj同样改变着,故而使顶点坐标达到了变换的效果,之后在顶点缓存区中的顶点坐标为变换后的坐标,将之Draw出来即可。
效果图:
完整的工程文件地址: