楔子:
差不多从年中开始由于工作需要,开始研究Direct3D,这是继大二开始自学DX开始,睽违了6年后再重新学习DX。虽然时间很久了,但是幸亏还是有点基础,所以上手还是比较顺利的。当然由于DX本身难度就比较大,再经过了一两个月的熟悉后,后来发现刚开始写的代码很累赘很复杂且不知所以,所以说脱离实际的自学与实际中根据目标的自学所学到的东西是完全两码子事儿,这就是所谓的“博学而笃志,近问而切思,仁在其中矣”,不要好高骛远,不要脱离实际。
好了,废话说了这么多,该说正经的了。在重新出发学习DX的时候,尤其是Shader的时候,因为隐约记得大学自学的时候并没有接触这一块,所以上手就稍微费力了一点,特别是关于Shader的资料很难找到入门级的,大多数都在DXSDK中零星的分布。当然这里我并不想也没有那个水平去写一篇Shader的教程,只是想分享一下自己在学习Shader过程中使用到的一个工具--AMD的RenderMonkey。
关于RenderMonkey的使用,最基本的当然是参看其自带的SDK文档了,但是这个SDK除了介绍RenderMonkey的使用方法外,几乎没有其它与Shader相关的部分,这是很痛苦的。后来从网上狗狗也只找到些许与RenderMonkey相关的文章,当然写的很好,有基础入门的,也有提高篇的,但是有个唯一的缺点就是,它们使用的都是老版本的RenderMonkey,而我现在则使用AMD最新版本的RenderMonkey,其界面和使用方法与老版本是有一定差异的,所以只有靠自己摸索了。下面正式开始~
////////////////////////////////////////////////// 华丽的分割线 /////////////////////////////////////////////////////////
首先,从AMD开发者网站上下载最新版本的RenderMonkey,截至到本文发布之时,可以下到1.82版本的RenderMonkey。安装很简单,一路Next下去就OK。双击运行,弹出的Splash,果然就是一只猴子,囧~程序的界面,嗯,长的和VC6很像嘛!!
Effect Workspace就和VC6中的Solution的概念相似,即可以在这个名为"Effect Workspace"的解决方案下添加不同的“Project”。当然,在RenderMonkey中Effect Workspace是作为“根”(Root)节点出现的,其下可以添加其它的子节点,这点上并不像VC的Project了。
现在可以尝试在“Effect Workspace”这个根节点下添加其它的子节点,就是在这个根节点上点击鼠标右键,会发现弹出一个多级菜单,这其中有许多选项我都不打算去触碰,因为我也没有完全搞清楚
。现在先来看看“Add Effect Group”,其实这个很像VC6中添加一个文件夹,其实只是为了方便管理代码而已,本身不会影响代码的运行。
回想在VC中,当我们创建一个空的Project后,可以自己添加.h/.cpp/.rc。但如果我们使用了VC自带的工程向导的话,就很简单地就可以创建一个应用程序,例如一个基于MFC的对话框程序等等,然后我们就可以在这个工程上根据自己的需要修改相应的.h/.cpp来实现自己所需要的功能。同样地,在RenderMonkey也有类似的操作,你既可以在一个空的Effect Workspace上单独添加一个一个节点,也可以直接使用RenderMonkey提供的默认所谓的“Default Effect”,然后我们在这个Default Effect上根据自己的需要进行修改。这里,我们使用RenderMonkey默认的最简单的DX效果Add Default Effect->DirectX->DirectX。
经过这样的操作后,可以看到在渲染窗口中出现了一个红色的球体。展开Default_Direct_Effect节点后的结果也如图所示,可以看到有很多奇怪的节点图标,下面就一一为您详解它们的前世今生
。(还请先阅读一下RenderMonkey的SDK文档了解相关知识)
matViewProjection是RenderMonkey提供的预定义变量(类型为4*4矩阵),之所以知道是“预定义”,是因为这个图标左下角有一个绿色的P字母,代表PreDefined。这里出现的matViewProjection节点相当于固定管线中声明并定义了一个4*4的矩阵变量。
接下来的Stream Mapping节点不知道翻译为“流映射”节点是否合适?该节点的作用是绑定数据流到输入寄存器中供shaders使用。RenderMonkey将会直接使用来自模型的数据来自动产生这个流(例如前面我们下面将要讲到的Model节点就只有位置信息,那么如果你双击这个Stream Mapping节点,就会看到POSITION信息了。)或者你通过用户接口来定义流通道,然后由应用程序获取和输出该流中的信息。按照我的理解,这个Stream Mapping实际上对应了固定管线中声明FVF的过程,而下面的流映射参考节点(Stream Mapping Reference)则对应了固定管线中的IDirect3DDevice9::SetFVF函数的功能。
Model节点是做什么的呢?一个Effect要通过一个几何模型才能过表现出来,即要有一个载体。RenderMonkey用模型(Model)和模型参考(Model Reference)节点来允许用户指定哪个模型被渲染。按照我的理解,设置Model节点就是固定管线中的根据FVF创建顶点缓冲并对其赋值的过程,而Model Reference则是固定管线中IDirect3DDevice9::SetStreamSource的调用,指示当前的渲染对象是哪些顶点(这些Vertext即构成了这里的Model)。
一个效果至少要有一个Pass(这说明RenderMonkey中的效果实际上也和固定管线中IEffect的概念一样),默认为Pass 0。在Pass节点中包含的大多是引用类型的节点(除了Shader之外)。
Pass中一般有两个Shader:Vertex Shader和Pixel Shader,分别代表顶点着色器和像素着色器,这两个节点是整个Effect中最重要也是唯一需要编写代码的地方。
在Vertex Shader中:首先声明了一个类型为float4x4的全局变量matViewProjection,可以看到这个变量的名字和左边Workspace 内的matViewProjection节点的名字一样,这并不是巧合!回想在固定管线中向Shader传送变量的过程,我们首先从IEffect接口中获取Shader中某个全局变量的对应的D3DXHANDLE,然后就可以向这个D3DXHANDLE中传递参数(被映射到Shader的全局变量上),而这个参数最终被用在Shader中。在RenderMonkey中,没有固定渲染管线这个玩意儿,那么它是怎样实现向Shader中传入参数的操作的呢?答案就是Workspace中的变量节点和Shader中定义的全局变量是同名名字。就是说如果你想在Shader中使用外部传入的变量参数的话,就必须声明一个名字和类型都和Effect节点下一致的节点,而我们在Workspace中是可以直接双击某个可编辑的节点进行编辑的(当然这个节点必须是可以“被编辑的 ”,而例如RM预定义的那些变量就是不可被编辑的),那么就相当于实时的改变了Shader中相应的全局变量。
Pixel Shader的用法也大概和Vertex Shader和一样。
在Effect中添加一个纹理也是很简单的,例如如果想添加一个2D纹理,那么就直接Add Texture->Add 2D Texture->2D Texture(这里不使用RM资源库中预先定义好的纹理),这样就创建了一个空的纹理,而如果想将其绑定到某个图像文件,就可以直接双击该节点并选择一个图像即可。这一步就相当于固定管线中的D3DXCreateTextureFromFile一样。类似于模型参考和流映射参考,我们还需要在Pass中添加一个纹理对象(Texture Object,为什么不叫Texture Reference呢?不晓的),并将其指向刚刚创建的纹理。感觉这一步就像是固定管线中的IDirect3DDevice9::SetTexture一样。而双击这个纹理对象后,就更会发现一些与固定管线中函数SetSamplerState和SetTextureStageStatus相似的东东了,而且可以对它们进行设置,就像SetSamplerState和SetTextureStageStatus所做的事情一样。
OK,基本上,DirectX的Default Effect上的基本元素就这么多了,个人所理解的也只有这么多了
,下面来看看Shader中最常用到的Render Target究竟时个什么玩意儿!
关于RenderTarget的概念,可以参见RenderMonkey中默认的DirectX Effect--“Render To Texture”实例。我们可以先删除这个例子下的RenderTarget相关的节点,然后来学习如何使用RenderTarget--首先需要在Workspace下创建一个Renderable Texture节点,双击这个节点会发现一些有趣的东西,如Width/Height/Format,这些怎么这么眼熟?不就是IDirect3DDevice9::CreateTexture中的一些参数一致么?既然创建了RenderTarget,那么就要首先在这个RenderTarget中做一些渲染的操作,具体来说,就是在第一个Pass中Add Render Target并将其“指向”刚才添加的Renderable Texture(这似乎和以前的添加参考对象是一样的含义?),双击这个节点(就是前面有一个红色圆心的节点),又可以发现一些与固定管线相像的玩意儿:Clear?Depth Buffer?可以猜想,这个操作实际与固定管线中如下代码是一样的含义:
g_pD3D9Device->GetRenderTarget(0, &pOldRenderTarget);
g_pRenderTarget->GetSurfaceLevel(0, &pRenderSurface);
g_pD3D9Device->SetRenderTarget(0, pRenderSurface);
g_pD3D9Device->Clear(0, 0, D3DCLEARTARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_X#000000, 1, 0);
RenderMonkey中并没有显式的获取Old RenderTarget和恢复Old RenderTarget的过程,但是这却是固定管线中必须有的,所以可以肯定RenderMonkey也一定做了相同的动作,只是不给我们看而已。
渲染完RenderTarget后,就需要回到原来的帧缓存中(即调用IDirect3D9Device::SetRenderTarget将原来保存的Old RenderTarget恢复回去,这一步在RenderMonkey里面是看不到的),并将已经渲染好的Renderable Texture设置到设备的某个纹理层上即可。在RenderMonkey中表现为在第二个Pass中(注意,该Pass的Index号必须比上述渲染RenderTarget的Pass要大,即一定要先渲染RenderTarget再渲染帧缓存),创建一个普通的纹理对象Add Texture Object并将其指向Renderable Texture,就和使用普通的纹理一样。
多个RenderTarget的渲染和单个比起来并没有什么不同,只是多了几个节点而已。但是有一点要强调的是当在一个Pass中出现多个纹理对象的时候,它们实际上是安装多“层”纹理而不是多“次”纹理来渲染的,因为当我们把鼠标移动到Texture Object节点上时候可以看到会提示到“Texture Stage x”,其中x为0开始的数字,这难道不会让你联想到固定管线中的SetTexture的第一个参数或者SetTextureStage函数么?
OK,关于RenderMonkey的几个基本节点(实际上个人才疏学浅,目前也只了解这些了)就到此到一段落了。以后有时间再讲讲如何将RenderMonkey中编写好的Shader文件直接交予应用程序使用,因为毕竟使用RenderMonkey来设计编写调试Shader代码是比较方便的
。
参考资料:
1.《RenderMonkey Toolsuite》
http://developer.amd.com/gpu/rendermonkey/Pages/default.aspx
2.《用RenderMonkey进行shader开发》
http://school.ogdev.net/ArticleShow.asp?id=5551&categoryid=5
http://school.ogdev.net/ArticleShow.asp?id=5552&categoryid=5
3.《Direct3D提高篇:HLSL编程实现PhotoShop滤镜效果》
http://tech.it168.com/n/2007-03-29/200703291522292.shtml