您的位置:首页 > 编程语言

GPU Shader 编程基础

2016-03-17 10:10 267 查看
转载自:http://www.cnblogs.com/youthlion/archive/2012/12/07/2807919.html

几个基本概念:

Vertexbuffer:存储顶点的数组。当构成模型的所有顶点都放进vertexbuffer后,就可以把vertexbuffer送进GPU,然后GPU就可以渲染模型了。

Indexbuffer:这个buffer的作用是索引。记录每个顶点在vertexbuffer中的位置。DXSDK里说使用indexbuffer可以增加顶点数据被缓存在显存里的概率,所以就效率而言应该使用indexbuffer。

VertexShader:vertexshader是一类小程序,主要用来把vertexbuffer里的顶点变换到3D空间中去。也可以用vertexshader干点别的,比如算顶点的法向量。对于每个要处理的顶点,GPU都会调用vertexshader。比如5000个三角形的网格模型,每一帧就得调用vertexshader15000次,每秒60帧的话,vertexshader还真得写得靠谱点。

PixelShader:是一般用来处理多边形颜色的小程序。对于场景里每个要画到屏幕上的可见像素,GPU都会调用PixelShader来处理。像着色、光照,还有大多数用在多边形上的其他效果,都要靠PixelShader来搞定。没错,这个东西也得写的靠谱点。效率,效率啊。要不可能GPU还没有CPU算得快。

HLSL:这就是用来写各种shader程序的语言了。HLSL程序包括全局变量、类型定义、vertexshaders,pixelshaders还有geometryshaders。

程序的整体结构:





这一次接着上一篇笔记的程序继续扩展。在这篇笔记中,我们会用shader绘制一个绿色的三角形。这里三角形是要绘制的对象,实际上是一个简单的多边形模型,也就是数据,所以把它封装到一个模型类(ModelClass)中,并集成到文档类里去。视图类负责显示,而显示的功能是由shader完成的。正如前面说的,shader是一段小程序,上面的图中集成到视图类中的ColorShaderClass负责调用shader,也就是让这段shader小程序运行起来。这就是这篇笔记中程序的最宏观结构。

第一个shader程序:

在工程中添加一个名为color.fx的源文件,看来shader程序源文件的扩展名是.fx了。这个shader的目的是绘制一个绿色的三角形。

这个shader首先声明了三个全局矩阵变量,便于其他类从外部访问,再传回shader。

/////////////

//GLOBALS//

/////////////

matrixworldMatrix;

matrixviewMatrix;

matrixprojectionMatrix;


下面几行代码里,使用HLSL中的float4类型创建了一个位置向量,包括x、y、z、w,以及一个颜色向量,包括red、green、blue、alpha分量。其中POSITION、COLOR和SV_POSITION是传递给GPU的语义信息,让GPU知道这些变量是干嘛用的。下面的两个类型貌似作用是一样的,但是必须分别创建。因为对于vertexshader和pixelshader,需要不同的语义。POSITION是对应vertexshader,SV_POSITION适用于pixelshader,COLOR则是两者通用。如果需要同一类型的多个成员,就得在类型后面加上个数值后缀,像COLOR0、COLOR1这种。

//////////////

//TYPEDEFS//

//////////////

structVertexInputType

{

float4position:POSITION;

float4color:COLOR;

};


structPixelInputType

{

float4position:SV_POSITION;

float4color:COLOR;

};


当vertexbuffer中的数据被送进GPU进行处理的时候,GPU会调用vertexshader。下面定义了一个名为ColorVertexShader的函数,该函数在处理vertexbuffer中每个顶点的时候都会被调用。vertexshader的输入必须与vertexbuffer缓冲区中的数据以及shader源文件中的类型定义相匹配。这里就是VertexInputType。vertexshader的输出会被送进pixelshader,这里输出类型为上面定义的PixelInputType。

下面代码中的vertexshader流程是这样的:他先创建一个输出变量,类型为PixelInputType,然后拿到输入顶点的坐标,把世界矩阵、视点矩阵、投影矩阵挨个乘上去,对顶点进行变换,最后顶点会被变换到我们视点所观察的3D空间中的正确位置。然后拿到输入的颜色值,放进输出变量里,把输出变量返回,返回的输出变量接下来会被送进pixelshader。

////////////////////////////////////////////////////////////////////////////////

//VertexShader

////////////////////////////////////////////////////////////////////////////////

PixelInputTypeColorVertexShader(VertexInputTypeinput)

{

PixelInputTypeoutput;



//Changethepositionvectortobe4unitsforpropermatrixcalculations.

input.position.w=1.0f;


//Calculatethepositionofthevertexagainsttheworld,view,andprojectionmatrices.

output.position=mul(input.position,worldMatrix);

output.position=mul(output.position,viewMatrix);

output.position=mul(output.position,projectionMatrix);


//Storetheinputcolorforthepixelshadertouse.

output.color=input.color;


returnoutput;

}


接下来就是pixelshader接班,由pixelshader把多边形上那些要渲染到屏幕上的像素绘制出来。下面的这个pixelshader以PixelInputType作为输入,返回一个float4类型,这个float4就是最后的像素颜色值。下面这个pixelshader仅仅是把像素着色为输入的颜色值。重申,vertexshader的输出是pixelshader的输入。

////////////////////////////////////////////////////////////////////////////////

//PixelShader

////////////////////////////////////////////////////////////////////////////////

float4ColorPixelShader(PixelInputTypeinput):SV_Target

{

returninput.color;

}


下面几行代码里的technique才是真正意义的shader。这个东西是用来渲染多边形、调用vertexshader和pixelshader的,可以把它看做是HLSL的main()函数。在technique里面可以设定多个pass,调用各种vertexshader和pixelshader来组合出想要的效果。这个例子里只使用了一个pass,也只调用了上面写好的vertex和pixelshader。geometryshader暂时不用,这里也没有调用。

还有个值得注意的事儿,代码里用vs_4_0指定vertexshader的版本为4.0,这是SetVertexShader函数的第一个参数。这样我们才可以使用DX10HLSL中vertexshader4.0相应的功能。pixelshader也是类似。

////////////////////////////////////////////////////////////////////////////////

//Technique

////////////////////////////////////////////////////////////////////////////////

technique10ColorTechnique

{

passpass0

{

SetVertexShader(CompileShader(vs_4_0,ColorVertexShader()));

SetPixelShader(CompileShader(ps_4_0,ColorPixelShader()));

SetGeometryShader(NULL);

}

}


以上是这个例子的shader部分。也就是负责实际渲染工作的模块。那么shader渲染的是神马?恩,模型。

所以要在工程里再添加个模型类:

这个例子里,我们的模型仅仅是个三角形,暂时用原教程给的一个模型类ModelClass,后面如果需要,争取把这个模型类用CGAL的Polyhedron替换掉。下面先看一下ModelClass的头文件:

首先在ModelClass中添加顶点类型的定义。这也是vertexbuffer的类型。

structVertexType

{

D3DXVECTOR3position;

D3DXVECTOR4color;

};


构造和析构函数:

ModelClass();

ModelClass(constModelClass&);

~ModelClass();


下面的几个函数负责初始化和释放模型的vertex和indexbuffer。Render函数负责把模型的几何属性送到显卡上,准备让shader绘制。

boolInitialize(ID3D10Device*);

voidShutdown();

voidRender(ID3D10Device*);


intGetIndexCount();


上面的几个公有函数的功能通过调用下面的几个私有函数实现:

private:

boolInitializeBuffers(ID3D10Device*);

voidShutdownBuffers();

voidRenderBuffers(ID3D10Device*);


添加几个私有变量,分别作为vertexbuffer和indexbuffer的指针,另外还有两个整型,用来记录两块buffer的大小。注意DX10里buffer一般用通用的ID3D10Buffer类型,这种类型的变量在创建的时候可以用bufferdescription进行描述。

private:

ID3D10Buffer*m_vertexBuffer,*m_indexBuffer;

intm_vertexCount,m_indexCount;


ModelClass类的实现部分,先是构造和析构函数:

ModelClass::ModelClass()

{

m_vertexBuffer=NULL;

m_indexBuffer=NULL;

}



ModelClass::ModelClass(constModelClass&other)

{

}



ModelClass::~ModelClass()

{

}


初始化函数:

boolModelClass::Initialize(ID3D10Device*device)

{

boolresult;



//Initializethevertexandindexbufferthatholdthegeometryforthetriangle.

result=InitializeBuffers(device);

if(!result)

{

returnfalse;

}


returntrue;

}


释放buffer:

voidModelClass::Shutdown()

{

//Releasethevertexandindexbuffers.

ShutdownBuffers();


return;

}


Render函数实际是在框架的绘制模块里调用的,也就是我第二篇笔记中的视图类,再具体点,应该就是在视图类的OnPaint方法里。

voidModelClass::Render(ID3D10Device*device)

{

//Putthevertexandindexbuffersonthegraphicspipelinetopreparethemfordrawing.

RenderBuffers(device);


return;

}


GetIndexCount函数返回index的数量:

intModelClass::GetIndexCount()

{

returnm_indexCount;

}


接下来是Initialize、ShutDown和Render对应的几个私有方法的具体实现,首先是InitializeBuffers,这个函数负责创建vertexbuffer和indexbuffer。在实际的应用里,一般是从数据文件里把模型读进来(.off,.obj,.ply等等)然后创建buffer,在这个例子里,因为模型只是一个三角形,所以直接在vertexbuffer和indexbuffer里人工设置了三个点。

boolModelClass::InitializeBuffers(ID3D10Device*device)

{

VertexType*vertices;

unsignedlong*indices;

D3D10_BUFFER_DESCvertexBufferDesc,indexBufferDesc;

D3D10_SUBRESOURCE_DATAvertexData,indexData;

HRESULTresult;


首先创建两个数组,用来存储顶点和索引数据。后面会用这两个数组去填充最终的buffer。

//Setthenumberofverticesinthevertexarray.

m_vertexCount=3;


//Setthenumberofindicesintheindexarray.

m_indexCount=3;


//Createthevertexarray.

vertices=newVertexType[m_vertexCount];

if(!vertices)

{

returnfalse;

}


//Createtheindexarray.

indices=newunsignedlong[m_indexCount];

if(!indices)

{

returnfalse;

}


然后分别对顶点属性和顶点索引赋值。留心,下面的代码是按照顺时针的顺序创建顶点的。如果逆时针创建的话,程序会认为这个三角形是屁股朝着屏幕。如果恰好又设置了背面剔除的话,程序就不再绘制这个三角形了。所以说,被送进GPU的顶点顺序是有讲究的。

//Loadthevertexarraywithdata.

vertices[0].position=D3DXVECTOR3(-1.0f,-1.0f,0.0f);//Bottomleft.

vertices[0].color=D3DXVECTOR4(0.0f,1.0f,0.0f,1.0f);


vertices[1].position=D3DXVECTOR3(0.0f,1.0f,0.0f);//Topmiddle.

vertices[1].color=D3DXVECTOR4(0.0f,1.0f,0.0f,1.0f);


vertices[2].position=D3DXVECTOR3(1.0f,-1.0f,0.0f);//Bottomright.

vertices[2].color=D3DXVECTOR4(0.0f,1.0f,0.0f,1.0f);


//Loadtheindexarraywithdata.

indices[0]=0;//Bottomleft.

indices[1]=1;//Topmiddle.

indices[2]=2;//Bottomright.


vetex数组和index数组搞定后,可以用它们来创建vertexbuffer和indexbuffer。两种buffer的创建方式是一样的:首先填好buffer的description。在这个description里面ByteWidth(buffer的大小)和BindFlags(buffer类型)必须得填对。填好description后,还要分别填一个subresource指针,这量个指针分别指向前面创建的vertex数组和index数组。description和subresource都填好之后,就可以用D3D
device调用CreateBuffer,这个函数会返回指向新创建buffer的指针。

//Setupthedescriptionofthevertexbuffer.

vertexBufferDesc.Usage=D3D10_USAGE_DEFAULT;

vertexBufferDesc.ByteWidth=sizeof(VertexType)*m_vertexCount;

vertexBufferDesc.BindFlags=D3D10_BIND_VERTEX_BUFFER;

vertexBufferDesc.CPUAccessFlags=0;

vertexBufferDesc.MiscFlags=0;


//Givethesubresourcestructureapointertothevertexdata.

vertexData.pSysMem=vertices;


//Nowfinallycreatethevertexbuffer.

result=device->CreateBuffer(&vertexBufferDesc,&vertexData,&m_vertexBuffer);

if(FAILED(result))

{

returnfalse;

}


//Setupthedescriptionoftheindexbuffer.

indexBufferDesc.Usage=D3D10_USAGE_DEFAULT;

indexBufferDesc.ByteWidth=sizeof(unsignedlong)*m_indexCount;

indexBufferDesc.BindFlags=D3D10_BIND_INDEX_BUFFER;

indexBufferDesc.CPUAccessFlags=0;

indexBufferDesc.MiscFlags=0;


//Givethesubresourcestructureapointertotheindexdata.

indexData.pSysMem=indices;


//Createtheindexbuffer.

result=device->CreateBuffer(&indexBufferDesc,&indexData,&m_indexBuffer);

if(FAILED(result))

{

returnfalse;

}


vertexbuffer和indexbuffer创建后,就可以卸磨杀驴,干掉vertex数组和index数组了:

//Releasethearraysnowthatthevertexandindexbuffershavebeencreatedandloaded.

delete[]vertices;

vertices=0;


delete[]indices;

indices=0;


returntrue;

}


接下来是负责释放vertexbuffer和indexbuffer的ShutdownBuffers函数:

voidModelClass::ShutdownBuffers()

{

//Releasetheindexbuffer.

if(m_indexBuffer)

{

m_indexBuffer->Release();

m_indexBuffer=0;

}


//Releasethevertexbuffer.

if(m_vertexBuffer)

{

m_vertexBuffer->Release();

m_vertexBuffer=0;

}


return;

}


下面是对应Render函数的私有函数RenderBuffers。这个函数的作用是把GPU中inputassembler上的vertexbuffer和indexbuffer设置为激活状态。一旦有了一块激活的vertexbuffer,GPU就可以用我们写的HLSLshader去渲染这块buffer。RenderBuffers函数还规定了这些buffer的绘制方式,比如绘制三角形、绘制直线神马的。这一篇笔记里,我们在inputassembler上激活indexbuffer和vertexbuffer,并通过DX10的IASetPrimitiveTopology函数告诉GPU,这块buffer要以三角形的方式绘制。

voidModelClass::RenderBuffers(ID3D10Device*device)

{

unsignedintstride;

unsignedintoffset;



//Setvertexbufferstrideandoffset.

stride=sizeof(VertexType);

offset=0;


//Setthevertexbuffertoactiveintheinputassemblersoitcanberendered.

device->IASetVertexBuffers(0,1,&m_vertexBuffer,&stride,&offset);


//Settheindexbuffertoactiveintheinputassemblersoitcanberendered.

device->IASetIndexBuffer(m_indexBuffer,DXGI_FORMAT_R32_UINT,0);


//Setthetypeofprimitivethatshouldberenderedfromthisvertexbuffer,inthiscasetriangles.

device->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST);


return;

}


Model类搞定。整理一下宏观的思路:现在我们有了模型,也有了shader,可以用shader去渲染模型了。

问题在于,shader是怎样开始运行的呢?

我们用下面这个ColorShaderClass类来调用shader。

这个类的Initialize和Shutdown两个成员函数完成对shader的初始化和关闭,Render成员函数负责设置shader的参数,然后用shader去绘制模型。

ColorShaderClass类包含的头文件及类声明如下:

#include<d3d10.h>

#include<d3dx10math.h>

#include<fstream>

usingnamespacestd;


classColorShaderClass

{

public:

ColorShaderClass();

ColorShaderClass(constColorShaderClass&);

~ColorShaderClass();


boolInitialize(ID3D10Device*,HWND);

voidShutdown();

voidRender(ID3D10Device*,int,D3DXMATRIX,D3DXMATRIX,D3DXMATRIX);


private:

boolInitializeShader(ID3D10Device*,HWND,WCHAR*);

voidShutdownShader();

voidOutputShaderErrorMessage(ID3D10Blob*,HWND,WCHAR*);


voidSetShaderParameters(D3DXMATRIX,D3DXMATRIX,D3DXMATRIX);

voidRenderShader(ID3D10Device*,int);


private:

ID3D10Effect*m_effect;

ID3D10EffectTechnique*m_technique;

ID3D10InputLayout*m_layout;


ID3D10EffectMatrixVariable*m_worldMatrixPtr;

ID3D10EffectMatrixVariable*m_viewMatrixPtr;

ID3D10EffectMatrixVariable*m_projectionMatrixPtr;

};


这个类和模型类的结构类似。在Initialize函数里,真正负责shader初始化的是InitializeShader,我们要传给这个函数三个参数:device、窗口句柄和shader的文件名。

boolColorShaderClass::Initialize(ID3D10Device*device,HWNDhwnd)

{

boolresult;



//Initializetheshaderthatwillbeusedtodrawthetriangle.

result=InitializeShader(device,hwnd,L"../02_01/color.fx");

if(!result)

{

returnfalse;

}


returntrue;

}


Shutdown调用ShutdownShader关闭shader:

voidColorShaderClass::Shutdown()

{

//Shutdowntheshadereffect.

ShutdownShader();


return;

}


Render函数里做两件事:1、设置shader参数,通过SetShaderParameters完成;2、用shader绘制绿三角,调用RenderShader完成:

voidColorShaderClass::Render(ID3D10Device*device,intindexCount,D3DXMATRIXworldMatrix,D3DXMATRIXviewMatrix,D3DXMATRIXprojectionMatrix)

{

//Settheshaderparametersthatitwilluseforrendering.

SetShaderParameters(worldMatrix,viewMatrix,projectionMatrix);


//Nowrenderthepreparedbufferswiththeshader.

RenderShader(device,indexCount);


return;

}


在下面的InitializeShader函数中我们可以看到,shader实际上是在这里加载的。在这个函数里,我们还需要设置一个layout,这个layout需要与模型类及color.fx类中定义的顶点类相匹配:

boolColorShaderClass::InitializeShader(ID3D10Device*device,HWNDhwnd,WCHAR*filename)

{

HRESULTresult;

ID3D10Blob*errorMessage;

D3D10_INPUT_ELEMENT_DESCpolygonLayout[2];

unsignedintnumElements;

D3D10_PASS_DESCpassDesc;



//Initializetheerrormessage.

errorMessage=0;


在D3DX10CreateEffectFromFile函数中,shader程序被编译为一个effect。这个函数的几个重要参数包括shader文件名、shader版本(DX10是4.0)、还要制定要把shader编译到哪个effect里去(对应ColorShaderClass类的m_effect成员)。如果在编译shader的过程中失败的话,D3DX10CreateEffectFromFile会把一条错误消息放到errorMessage里,我们会把这个字符串塞给另一个函数OutputShaderErrorMessage去输出错误信息。要是编译失败了,却还没有错误信息的话,可能是找不到shader文件,对这种情况我们会弹出一个对话框作为提示。

//Loadtheshaderinfromthefile.

result=D3DX10CreateEffectFromFile(filename,NULL,NULL,"fx_4_0",D3D10_SHADER_ENABLE_STRICTNESS,0,

device,NULL,NULL,&m_effect,&errorMessage,NULL);

if(FAILED(result))

{

//Iftheshaderfailedtocompileitshouldhavewritensomethingtotheerrormessage.

if(errorMessage)

{

OutputShaderErrorMessage(errorMessage,hwnd,filename);

}

//Iftherewasnothingintheerrormessagethenitsimplycouldnotfindtheshaderfileitself.

else

{

MessageBox(hwnd,filename,L"MissingShaderFile",MB_OK);

}


returnfalse;

}


当shader文件成功编译为effect后,就可以用这个effect找到shader里的那个technique。我们后面用这个technique进行绘制:

//Getapointertothetechniqueinsidetheshader.

m_technique=m_effect->GetTechniqueByName("ColorTechnique");

if(!m_technique)

{

returnfalse;

}


下一步,shader所处理的顶点,还需要创建并设置一个layout。在这一篇笔记里,shader使用了一个位置向量和一个颜色向量,所以我们在layout中也要创建对应的元素,用来指明位置和颜色信息的内存占用情况。首先要填充的是语义信息,这样shader才能知道这个layout元素的用途。对于位置信息,我们使用POSITION,颜色信息用COLOR。另一个重要信息是格式,位置信息我们用DXGI_FORMAT_R32G32B32_FLOAT,颜色信息用DXGI_FORMAT_R32G32B32A32_FLOAT。最后要注意的是AlignedByteOffset,这个字段指定了buffer中数据存储的起点。对于本例来说,前12个字节是位置,随后的16个字节是颜色。这个字段可以用D3D10_APPEND_ALIGNED_ELEMENT代替,表示DX10会自动计算。layout的其他字段暂时不会用到,这里使用默认设置:

//Nowsetupthelayoutofthedatathatgoesintotheshader.

//ThissetupneedstomatchtheVertexTypestuctureintheModelClassandintheshader.

polygonLayout[0].SemanticName="POSITION";

polygonLayout[0].SemanticIndex=0;

polygonLayout[0].Format=DXGI_FORMAT_R32G32B32_FLOAT;

polygonLayout[0].InputSlot=0;

polygonLayout[0].AlignedByteOffset=0;

polygonLayout[0].InputSlotClass=D3D10_INPUT_PER_VERTEX_DATA;

polygonLayout[0].InstanceDataStepRate=0;


polygonLayout[1].SemanticName="COLOR";

polygonLayout[1].SemanticIndex=0;

polygonLayout[1].Format=DXGI_FORMAT_R32G32B32A32_FLOAT;

polygonLayout[1].InputSlot=0;

polygonLayout[1].AlignedByteOffset=D3D10_APPEND_ALIGNED_ELEMENT;

polygonLayout[1].InputSlotClass=D3D10_INPUT_PER_VERTEX_DATA;

polygonLayout[1].InstanceDataStepRate=0;


layout数组设置好之后,我们计算一下它包含的元素个数,然后用device创建inputlayout。

//Getacountoftheelementsinthelayout.

numElements=sizeof(polygonLayout)/sizeof(polygonLayout[0]);


//Getthedescriptionofthefirstpassdescribedintheshadertechnique.

m_technique->GetPassByIndex(0)->GetDesc(&passDesc);


//Createtheinputlayout.

result=device->CreateInputLayout(polygonLayout,numElements,passDesc.pIAInputSignature,passDesc.IAInputSignatureSize,

&m_layout);

if(FAILED(result))

{

returnfalse;

}


下面要做的是获取shader里面那三个矩阵的指针,这样以后就能用这三个指针设置矩阵的值:

//Getpointerstothethreematricesinsidetheshadersowecanupdatethemfromthisclass.

m_worldMatrixPtr=m_effect->GetVariableByName("worldMatrix")->AsMatrix();

m_viewMatrixPtr=m_effect->GetVariableByName("viewMatrix")->AsMatrix();

m_projectionMatrixPtr=m_effect->GetVariableByName("projectionMatrix")->AsMatrix();


returntrue;

}


ShutdownShader函数负责释放资源:

voidColorShaderClass::ShutdownShader()

{

//Releasethepointerstothematricesinsidetheshader.

m_worldMatrixPtr=0;

m_viewMatrixPtr=0;

m_projectionMatrixPtr=0;


//Releasethepointertotheshaderlayout.

if(m_layout)

{

m_layout->Release();

m_layout=0;

}


//Releasethepointertotheshadertechnique.

m_technique=0;


//Releasethepointertotheshader.

if(m_effect)

{

m_effect->Release();

m_effect=0;

}


return;

}


在编译vertexshader或pixelshader时若发生问题,错误信息由OutputShaderErrorMessage函数输出:

voidColorShaderClass::OutputShaderErrorMessage(ID3D10Blob*errorMessage,HWNDhwnd,WCHAR*shaderFilename)

{

char*compileErrors;

unsignedlongbufferSize,i;

ofstreamfout;



//Getapointertotheerrormessagetextbuffer.

compileErrors=(char*)(errorMessage->GetBufferPointer());


//Getthelengthofthemessage.

bufferSize=errorMessage->GetBufferSize();


//Openafiletowritetheerrormessageto.

fout.open("shader-error.txt");


//Writeouttheerrormessage.

for(i=0;i<bufferSize;i++)

{

fout<<compileErrors[i];

}


//Closethefile.

fout.close();


//Releasetheerrormessage.

errorMessage->Release();

errorMessage=0;


//Popamessageuponthescreentonotifytheusertocheckthetextfileforcompileerrors.

MessageBox(hwnd,L"Errorcompilingshader.Checkshader-error.txtformessage.",shaderFilename,MB_OK);


return;

}


SetShaderParameters函数便于我们设置shader中的全局变量。这个函数中的三个矩阵是在上一篇笔记中的视图类里创建的,矩阵被创建之后,负责绘图的代码会调用这个函数,把这三个矩阵送进shader。


voidColorShaderClass::SetShaderParameters(D3DXMATRIXworldMatrix,D3DXMATRIXviewMatrix,D3DXMATRIXprojectionMatrix)

{

//Settheworldmatrixvariableinsidetheshader.

m_worldMatrixPtr->SetMatrix((float*)&worldMatrix);


//Settheviewmatrixvariableinsidetheshader.

m_viewMatrixPtr->SetMatrix((float*)&viewMatrix);


//Settheprojectionmatrixvariableinsidetheshader.

m_projectionMatrixPtr->SetMatrix((float*)&projectionMatrix);


return;

}


SetShaderParameters函数执行后,各种参数(这里实际就是那仨矩阵)设置完成,ColorShaderClass类随后调用RenderShader,RenderShader通过technique指针调用color.fx文件中的shader程序。

RenderShader函数上来先把inputlayout激活,这样GPU才能知道vertexbuffer里数据的格式。接下来要从shader中获取technique的描述,这个technique告诉GPU调用哪个vertexshader或pixelshader来绘制vertexbuffer里的数据。本例中我们获取的是color.fx中ColorTechnique的描述,然后通过device调用DrawIndexed函数,循环调用technique中的各个pass来渲染三角形。目前的例子里,shader只有一个pass(pass0)。

voidColorShaderClass::RenderShader(ID3D10Device*device,intindexCount)

{

D3D10_TECHNIQUE_DESCtechniqueDesc;

unsignedinti;



//Settheinputlayout.

device->IASetInputLayout(m_layout);


//Getthedescriptionstructureofthetechniquefrominsidetheshadersoitcanbeusedforrendering.

m_technique->GetDesc(&techniqueDesc);


//Gothrougheachpassinthetechnique(shouldbejustonecurrently)andrenderthetriangles.

for(i=0;i<techniqueDesc.Passes;++i)

{

m_technique->GetPassByIndex(i)->Apply(0);

device->DrawIndexed(indexCount,0,0);

}


return;

}


到这里,我们搞定了一个HLSLshader,设置了vertexbuffer和indexbuffer,并了解了如何调用shader绘制两种buffer中的数据。除此之外,还有一些辅助性的工作要做。第一个问题是,我们绘制的那些内容,是相对于哪个视点的?

好吧,所以我们还需要来个镜头类:

镜头类告诉DX10,镜头是从哪里、以及怎样去观察场景的。镜头类会始终跟踪镜头的位置及其旋转,使用位置和旋转信息生成一个视点矩阵,这个视点矩阵会传进shader,用于渲染。

镜头类声明如下:

#include<d3dx10math.h>

classCameraClass

{

public:

CameraClass();

CameraClass(constCameraClass&);

~CameraClass();


voidSetPosition(float,float,float);

voidSetRotation(float,float,float);


D3DXVECTOR3GetPosition();

D3DXVECTOR3GetRotation();


voidRender();

voidGetViewMatrix(D3DXMATRIX&);


private:

floatm_positionX,m_positionY,m_positionZ;

floatm_rotationX,m_rotationY,m_rotationZ;

D3DXMATRIXm_viewMatrix;

};


其中,SetPosition和SetRotation函数用来设置镜头对象的位置和旋转。Render函数基于位置和旋转信息创建视点矩阵。GetViewMatrix用来访问视点矩阵。

构造函数把位置和旋转设置为场景的原点:

CameraClass::CameraClass()

{

m_positionX=0.0f;

m_positionY=0.0f;

m_positionZ=0.0f;


m_rotationX=0.0f;

m_rotationY=0.0f;

m_rotationZ=0.0f;

}



CameraClass::CameraClass(constCameraClass&other)

{

}



CameraClass::~CameraClass()

{

}


两个set函数:

voidCameraClass::SetPosition(floatx,floaty,floatz)

{

m_positionX=x;

m_positionY=y;

m_positionZ=z;

return;

}



voidCameraClass::SetRotation(floatx,floaty,floatz)

{

m_rotationX=x;

m_rotationY=y;

m_rotationZ=z;

return;

}


两个get函数:

D3DXVECTOR3CameraClass::GetPosition()

{

returnD3DXVECTOR3(m_positionX,m_positionY,m_positionZ);

}



D3DXVECTOR3CameraClass::GetRotation()

{

returnD3DXVECTOR3(m_rotationX,m_rotationY,m_rotationZ);

}


Render函数用位置和旋转信息构造和更新视点矩阵。这里除了位置和旋转,还需要指定一个“上”方向和镜头朝向。接下来,首先在原点处根据x,y,z的值旋转镜头,旋转之后再把镜头移动到三维空间中的指定位置上。当位置、旋转、方向“上”和所观察的位置都确定下来后,就可以用DX10中的D3DXMatrixLookAtLH函数创建视点矩阵了:

voidCameraClass::Render()

{

D3DXVECTOR3up,position,lookAt;

floatyaw,pitch,roll;

D3DXMATRIXrotationMatrix;



//Setupthevectorthatpointsupwards.

up.x=0.0f;

up.y=1.0f;

up.z=0.0f;


//Setupthepositionofthecameraintheworld.

position.x=m_positionX;

position.y=m_positionY;

position.z=m_positionZ;


//Setupwherethecameraislookingbydefault.

lookAt.x=0.0f;

lookAt.y=0.0f;

lookAt.z=1.0f;


//Settheyaw(Yaxis),pitch(Xaxis),androll(Zaxis)rotationsinradians.

pitch=m_rotationX*0.0174532925f;

yaw=m_rotationY*0.0174532925f;

roll=m_rotationZ*0.0174532925f;


//Createtherotationmatrixfromtheyaw,pitch,androllvalues.

D3DXMatrixRotationYawPitchRoll(&rotationMatrix,yaw,pitch,roll);


//TransformthelookAtandupvectorbytherotationmatrixsotheviewiscorrectlyrotatedattheorigin.

D3DXVec3TransformCoord(&lookAt,&lookAt,&rotationMatrix);

D3DXVec3TransformCoord(&up,&up,&rotationMatrix);


//Translatetherotatedcamerapositiontothelocationoftheviewer.

lookAt=position+lookAt;


//Finallycreatetheviewmatrixfromthethreeupdatedvectors.

D3DXMatrixLookAtLH(&m_viewMatrix,&position,&lookAt,&up);


return;

}


GetViewMatrix:


voidCameraClass::GetViewMatrix(D3DXMATRIX&viewMatrix)

{

viewMatrix=m_viewMatrix;

return;

}


至此,我们搞定了负责渲染的shader、负责调用shader的ColorShaderClass、用来存储模型的ModelClass,以及负责管理视点信息的CamaraClass。

下面的故事是,在MFC的MDI框架中,应该怎样用这些类?

从功能上看,文档和视图分别对应数据和显示,在这个例子里,模型是数据(ModelClass),ColorShaderClass实现显示(实际上是shader,color.fx),所以模型嵌入到文档类,而ColorShaderClass集成进视图类。

在文档类中添加ModelClass类指针,这里为了访问方便,直接设置为public:

#include"ModelClass.h"

classCMy02_01Doc:publicCDocument

{

...

public:

ModelClass*m_pMesh;

...

};


目前文档类要改的有三处:

构造函数:

CMy02_01Doc::CMy02_01Doc()

{

//TODO:addone-timeconstructioncodehere

m_pMesh=NULL;

}


在新建文档时创建模型:

BOOLCMy02_01Doc::OnNewDocument()

{

if(!CDocument::OnNewDocument())

returnFALSE;


//TODO:addreinitializationcodehere

//(SDIdocumentswillreusethisdocument)

m_pMesh=newModelClass();

returnTRUE;

}


关闭文档时释放内存:

CMy02_01Doc::~CMy02_01Doc()

{

if(m_pMesh)

{

m_pMesh->Shutdown();

deletem_pMesh;

m_pMesh=NULL;

}

}


接下来是显示相关的内容。镜头类和ColorShaderClass类都集成到视图类中。

#include<CameraClass.h>

#include<Colorshaderclass.h>


classCMy02_01View:publicCView

{

...

CameraClass*m_Camara;

ColorShaderClass*m_ColorShader;

...

};


构造函数:

CMy02_01View::CMy02_01View()

{

//TODO:addconstructioncodehere

...


m_Camara=NULL;

m_ColorShader=NULL;

}


给视图类添加一个建立camera、shader对象并进行初始化的函数ShaderInitialize,由于模型是在文档类里创建和销毁的,所以这里只要弄一个临时指针指向模型对象就行了,不需要操心资源管理的事儿:

boolCMy02_01View::ShaderInitialize()

{

boolresult;


HWNDhwnd=GetSafeHwnd();


//Createthecameraobject.

m_Camera=newCameraClass();

if(!m_Camera)

{

returnfalse;

}


//Settheinitialpositionofthecamera.

m_Camera->SetPosition(0.0f,0.0f,-10.0f);


//Createthemodelobject.

ModelClass*pmesh=((CMy02_01Doc*)GetDocument())->m_pMesh;

if(!pmesh)

{

MessageBox(L"NULLModel!!");

returnfalse;

}

result=pmesh->Initialize(m_device);

if(!result)

{

MessageBox(L"Couldnotinitializethemodelobject.");

returnfalse;

}


//Createthecolorshaderobject.

m_ColorShader=newColorShaderClass();

if(!m_ColorShader)

{

returnfalse;

}


//Initializethecolorshaderobject.

result=m_ColorShader->Initialize(m_device,hwnd);

if(!result)

{

MessageBox(L"Couldnotinitializethecolorshaderobject.");

returnfalse;

}


returntrue;

}


这个初始化函数在OnInitialUpdate中DX环境初始化后调用:

voidCMy02_01View::OnInitialUpdate()

{

CView::OnInitialUpdate();


//TODO:Addyourspecializedcodehereand/orcallthebaseclass

InitDX();

ShaderInitialize();

}


再添加一个相应的资源释放函数,该函数在视图类的析构函数中调用:

voidCMy02_01View::ShutDownShader()

{

//Releasethecolorshaderobject.

if(m_ColorShader)

{

m_ColorShader->Shutdown();

deletem_ColorShader;

m_ColorShader=NULL;

}


//Releasethecameraobject.

if(m_Camera)

{

deletem_Camera;

m_Camera=0;

}

}


最后在OnPaint中完成绘制功能:

voidCMy02_01View::OnPaint()

{

D3DXMATRIXviewMatrix,projectionMatrix,worldMatrix;


CPaintDCdc(this);//devicecontextforpainting

//TODO:Addyourmessagehandlercodehere

//DonotcallCView::OnPaint()forpaintingmessages

floatcolor[4];



//Setupthecolortoclearthebufferto.

color[0]=1.0f;

color[1]=0.0f;

color[2]=0.0f;

color[3]=1.0f;


//Clearthebackbuffer.

m_device->ClearRenderTargetView(m_renderTargetView,color);


//Clearthedepthbuffer.

m_device->ClearDepthStencilView(m_depthStencilView,D3D10_CLEAR_DEPTH,1.0f,0);


//Generatetheviewmatrixbasedonthecamera'sposition.

m_Camera->Render();


//Gettheworld,view,andprojectionmatricesfromthecameraandd3dobjects.

m_Camera->GetViewMatrix(viewMatrix);

GetWorldMatrix(worldMatrix);

GetProjectionMatrix(projectionMatrix);


ModelClass*pmesh=((CMy02_01Doc*)GetDocument())->m_pMesh;

pmesh->Render(m_device);


//Renderthemodelusingthecolorshader.

m_ColorShader->Render(m_device,pmesh->GetIndexCount(),worldMatrix,viewMatrix,projectionMatrix);


if(m_vsync_enabled)

{

//Locktoscreenrefreshrate.

m_swapChain->Present(1,0);

}

else

{

//Presentasfastaspossible.

m_swapChain->Present(0,0);

}

}


运行效果,弱爆了T_T





下面理一理头绪,回忆一下,上面的shader、ColorShaderClass、ModelClass、CameraClass都是咋回事儿来着?

shader:

(1)它是一个扩展名为.fx的文件

(2)它的入口是technique,这玩意好比main

(3)shader里面还为vertexshader和pixelshader分别定义了顶点类型

(4)分别实现了vertexshader和pixelshader函数

(5)vertexbuffer里的数据送进GPU后,会先让vertexshader处理,然后再送进pixelshader

(6)别忘了类型匹配那些事儿

ModelClass:

(1)这是一个模型类,虽然现在这个模型很简单

(2)模型类创建了vertexbuffer(注意顶点顺序)和indexbuffer,并且设置了具体的值(也就是把三角形的各个顶点坐标和颜色值都写进了buffer里面)

(3)模型类激活了vertexbuffer和indexbuffer,让GPU知道,这块数据可以进行绘制了。

(4)模型类告诉GPU,用三角形的方式绘制buffer里的内容

ColorShaderClass:

(1)ColorShaderClass是用来调用shader的

(2)ColorShaderClass要创建并设置与shader中定义的顶点类型匹配的layout,让GPU知道vertexbuffer中数据的格式

(3)ColorShaderClass会获取shader中那三个全局矩阵的指针,并设置这三个矩阵的值

(4)ColorShaderClass会获取technique的描述,让GPU知道调用哪些shader函数去绘制,然后循环调用technique中的各个pass进行绘制

CameraClass:

(1)它会设置镜头位置和旋转角度

(2)它会根据镜头位置和旋转角度生成视点变换矩阵

最后,还有一幅恶心的大图,描述了程序的整个流程和关键数据的传输途径:





PS:英文原文教程地址http://www.rastertek.com/dx10tut04.html,根据自己的需要进行了小小改动。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: