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。
下面几行代码里,使用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这种。
当vertexbuffer中的数据被送进GPU进行处理的时候,GPU会调用vertexshader。下面定义了一个名为ColorVertexShader的函数,该函数在处理vertexbuffer中每个顶点的时候都会被调用。vertexshader的输入必须与vertexbuffer缓冲区中的数据以及shader源文件中的类型定义相匹配。这里就是VertexInputType。vertexshader的输出会被送进pixelshader,这里输出类型为上面定义的PixelInputType。
下面代码中的vertexshader流程是这样的:他先创建一个输出变量,类型为PixelInputType,然后拿到输入顶点的坐标,把世界矩阵、视点矩阵、投影矩阵挨个乘上去,对顶点进行变换,最后顶点会被变换到我们视点所观察的3D空间中的正确位置。然后拿到输入的颜色值,放进输出变量里,把输出变量返回,返回的输出变量接下来会被送进pixelshader。
接下来就是pixelshader接班,由pixelshader把多边形上那些要渲染到屏幕上的像素绘制出来。下面的这个pixelshader以PixelInputType作为输入,返回一个float4类型,这个float4就是最后的像素颜色值。下面这个pixelshader仅仅是把像素着色为输入的颜色值。重申,vertexshader的输出是pixelshader的输入。
下面几行代码里的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也是类似。
以上是这个例子的shader部分。也就是负责实际渲染工作的模块。那么shader渲染的是神马?恩,模型。
所以要在工程里再添加个模型类:
这个例子里,我们的模型仅仅是个三角形,暂时用原教程给的一个模型类ModelClass,后面如果需要,争取把这个模型类用CGAL的Polyhedron替换掉。下面先看一下ModelClass的头文件:
首先在ModelClass中添加顶点类型的定义。这也是vertexbuffer的类型。
构造和析构函数:
下面的几个函数负责初始化和释放模型的vertex和indexbuffer。Render函数负责把模型的几何属性送到显卡上,准备让shader绘制。
上面的几个公有函数的功能通过调用下面的几个私有函数实现:
添加几个私有变量,分别作为vertexbuffer和indexbuffer的指针,另外还有两个整型,用来记录两块buffer的大小。注意DX10里buffer一般用通用的ID3D10Buffer类型,这种类型的变量在创建的时候可以用bufferdescription进行描述。
ModelClass类的实现部分,先是构造和析构函数:
初始化函数:
释放buffer:
Render函数实际是在框架的绘制模块里调用的,也就是我第二篇笔记中的视图类,再具体点,应该就是在视图类的OnPaint方法里。
GetIndexCount函数返回index的数量:
接下来是Initialize、ShutDown和Render对应的几个私有方法的具体实现,首先是InitializeBuffers,这个函数负责创建vertexbuffer和indexbuffer。在实际的应用里,一般是从数据文件里把模型读进来(.off,.obj,.ply等等)然后创建buffer,在这个例子里,因为模型只是一个三角形,所以直接在vertexbuffer和indexbuffer里人工设置了三个点。
首先创建两个数组,用来存储顶点和索引数据。后面会用这两个数组去填充最终的buffer。
然后分别对顶点属性和顶点索引赋值。留心,下面的代码是按照顺时针的顺序创建顶点的。如果逆时针创建的话,程序会认为这个三角形是屁股朝着屏幕。如果恰好又设置了背面剔除的话,程序就不再绘制这个三角形了。所以说,被送进GPU的顶点顺序是有讲究的。
vetex数组和index数组搞定后,可以用它们来创建vertexbuffer和indexbuffer。两种buffer的创建方式是一样的:首先填好buffer的description。在这个description里面ByteWidth(buffer的大小)和BindFlags(buffer类型)必须得填对。填好description后,还要分别填一个subresource指针,这量个指针分别指向前面创建的vertex数组和index数组。description和subresource都填好之后,就可以用D3D
device调用CreateBuffer,这个函数会返回指向新创建buffer的指针。
vertexbuffer和indexbuffer创建后,就可以卸磨杀驴,干掉vertex数组和index数组了:
接下来是负责释放vertexbuffer和indexbuffer的ShutdownBuffers函数:
下面是对应Render函数的私有函数RenderBuffers。这个函数的作用是把GPU中inputassembler上的vertexbuffer和indexbuffer设置为激活状态。一旦有了一块激活的vertexbuffer,GPU就可以用我们写的HLSLshader去渲染这块buffer。RenderBuffers函数还规定了这些buffer的绘制方式,比如绘制三角形、绘制直线神马的。这一篇笔记里,我们在inputassembler上激活indexbuffer和vertexbuffer,并通过DX10的IASetPrimitiveTopology函数告诉GPU,这块buffer要以三角形的方式绘制。
Model类搞定。整理一下宏观的思路:现在我们有了模型,也有了shader,可以用shader去渲染模型了。
问题在于,shader是怎样开始运行的呢?
我们用下面这个ColorShaderClass类来调用shader。
这个类的Initialize和Shutdown两个成员函数完成对shader的初始化和关闭,Render成员函数负责设置shader的参数,然后用shader去绘制模型。
ColorShaderClass类包含的头文件及类声明如下:
这个类和模型类的结构类似。在Initialize函数里,真正负责shader初始化的是InitializeShader,我们要传给这个函数三个参数:device、窗口句柄和shader的文件名。
Shutdown调用ShutdownShader关闭shader:
Render函数里做两件事:1、设置shader参数,通过SetShaderParameters完成;2、用shader绘制绿三角,调用RenderShader完成:
在下面的InitializeShader函数中我们可以看到,shader实际上是在这里加载的。在这个函数里,我们还需要设置一个layout,这个layout需要与模型类及color.fx类中定义的顶点类相匹配:
在D3DX10CreateEffectFromFile函数中,shader程序被编译为一个effect。这个函数的几个重要参数包括shader文件名、shader版本(DX10是4.0)、还要制定要把shader编译到哪个effect里去(对应ColorShaderClass类的m_effect成员)。如果在编译shader的过程中失败的话,D3DX10CreateEffectFromFile会把一条错误消息放到errorMessage里,我们会把这个字符串塞给另一个函数OutputShaderErrorMessage去输出错误信息。要是编译失败了,却还没有错误信息的话,可能是找不到shader文件,对这种情况我们会弹出一个对话框作为提示。
当shader文件成功编译为effect后,就可以用这个effect找到shader里的那个technique。我们后面用这个technique进行绘制:
下一步,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的其他字段暂时不会用到,这里使用默认设置:
layout数组设置好之后,我们计算一下它包含的元素个数,然后用device创建inputlayout。
下面要做的是获取shader里面那三个矩阵的指针,这样以后就能用这三个指针设置矩阵的值:
ShutdownShader函数负责释放资源:
在编译vertexshader或pixelshader时若发生问题,错误信息由OutputShaderErrorMessage函数输出:
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)。
到这里,我们搞定了一个HLSLshader,设置了vertexbuffer和indexbuffer,并了解了如何调用shader绘制两种buffer中的数据。除此之外,还有一些辅助性的工作要做。第一个问题是,我们绘制的那些内容,是相对于哪个视点的?
好吧,所以我们还需要来个镜头类:
镜头类告诉DX10,镜头是从哪里、以及怎样去观察场景的。镜头类会始终跟踪镜头的位置及其旋转,使用位置和旋转信息生成一个视点矩阵,这个视点矩阵会传进shader,用于渲染。
镜头类声明如下:
其中,SetPosition和SetRotation函数用来设置镜头对象的位置和旋转。Render函数基于位置和旋转信息创建视点矩阵。GetViewMatrix用来访问视点矩阵。
构造函数把位置和旋转设置为场景的原点:
两个set函数:
两个get函数:
Render函数用位置和旋转信息构造和更新视点矩阵。这里除了位置和旋转,还需要指定一个“上”方向和镜头朝向。接下来,首先在原点处根据x,y,z的值旋转镜头,旋转之后再把镜头移动到三维空间中的指定位置上。当位置、旋转、方向“上”和所观察的位置都确定下来后,就可以用DX10中的D3DXMatrixLookAtLH函数创建视点矩阵了:
至此,我们搞定了负责渲染的shader、负责调用shader的ColorShaderClass、用来存储模型的ModelClass,以及负责管理视点信息的CamaraClass。
下面的故事是,在MFC的MDI框架中,应该怎样用这些类?
从功能上看,文档和视图分别对应数据和显示,在这个例子里,模型是数据(ModelClass),ColorShaderClass实现显示(实际上是shader,color.fx),所以模型嵌入到文档类,而ColorShaderClass集成进视图类。
在文档类中添加ModelClass类指针,这里为了访问方便,直接设置为public:
目前文档类要改的有三处:
构造函数:
在新建文档时创建模型:
关闭文档时释放内存:
接下来是显示相关的内容。镜头类和ColorShaderClass类都集成到视图类中。
构造函数:
给视图类添加一个建立camera、shader对象并进行初始化的函数ShaderInitialize,由于模型是在文档类里创建和销毁的,所以这里只要弄一个临时指针指向模型对象就行了,不需要操心资源管理的事儿:
这个初始化函数在OnInitialUpdate中DX环境初始化后调用:
再添加一个相应的资源释放函数,该函数在视图类的析构函数中调用:
最后在OnPaint中完成绘制功能:
运行效果,弱爆了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,根据自己的需要进行了小小改动。
几个基本概念:
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:英文原文教程地址
相关文章推荐
- Andoird 混淆代码学习心得(上)
- 2016-AspNet-MVC教学-1-算术加法提交及超链接测试
- ongodb内存管理和使用情况情况查询
- FU-A分包方式,以及从RTP包里面得到H.264数据和AAC数据的方法
- eclipse 常见问题解决
- editplus利用正则表达式快速定位
- 谈谈C++超前引用
- Java之美[从菜鸟到高手演变]之设计模式
- Java获取当前时间的年月日时分秒方法
- 演示volatile 关键字作用的一段C代码
- PHP中this,self,parent的区别
- Java之美[从菜鸟到高手演变]之设计模式
- IntelliJ IDEA中怎么恢复本地代码
- Django配置Session的3种存储方法
- C# 文本框退出按钮
- cxf引用webservice生成java代码
- C++服务器(二):Windows下的socket编程
- [Java]读取文件方法大全
- JavaWeb监听器、过滤器、
- Java的四个基本特性和对多态的理解