原帖地址:http://school.ogdev.net/ArticleShow.asp?id=5551&categoryid=5
用RenderMonkey 进行shader开发
Natalya Tatarchuk
3D Application Research Group
ATI Research
作者:董宝成
Email:[email protected]
介绍
目前3D图形应用程序开发人员面临的挑战主要集中于创建和使用可编程图形shaders。这些可编程的图形shaders是将来图形编程的核心竞争力。
有能力创建和使用这些可编程shaders的开发人员将能够享受所有硬件所提供的高级特性和创建颠覆传统实时图形艺术的应用程序。为可帮助开发者充分释放这些竞争力潜在优势,ATI开发了一组工具— RenderMonkey 工具集。
尽管写汇编或高级shader语言代码是开发过程的核心,但shaders远不止这么简单。压缩一个基于shader的效果是一个复杂的任务因为包括捕获所有包括这些渲染特效系统的状态。这就导致一个普遍存在于shader开发者之间的问题 — 交换和共性数据不是一件简单的事。另一个问题是在开发shader程序的工程中它必须与艺术家紧密的交流。由于没有适合艺术家使用的工具,就造成了开发过程中合作的困难。现在需要的是一个开发环境,他不仅仅适合程序员而且艺术家和游戏设计者也能够一起合作,从而创造出预想的效果。RenderMonkey就是被设计用来解决这些问题的。用这个工具,我们提供了一个创建shaders的强大的程序员开发环境,它能被作为一个标准的传输机制允许开发组之间共享基于shader的特效。我们也提供了一个灵活的,可扩展的框架他能够非常容易的将自定义的组件集成而且还为以后的工具开发提供了一个基本的固件集。RenderMonkey 能很容易定制和集成到任何开发者的工作流中。RenderMonkey集成开发环境的设计使得将当前的和以后的API结合在一起变得非常容易。这篇文章是关于1.0版本的,他能够支持DirectX9.0 shader 特效(用汇编和HLSL都行),也支持基于GL2编写的基于OpenGL的特效。
集成开发环境预览
RenderMonkey应用程序接口对那些曾经用过像Microsoft Visual Studio之类的集成开发环境的开发者而言将是非常直观的。图1是一个接口渲染一个云特效时的快照。
图1 — 主要应用程序界面
这个主要接口有以下几个组件组成:
l 一个编辑特效的工作区视图
l 一个用于显示编译结果和来自应用程序的文本信息的输出窗口
l 一个用于预览被编辑特效的预览窗口
l 另外的编辑模块还有用于编辑代码的编辑器和用于shader参数的GUI编辑器。Shader参数能被指定为“艺术家可编辑”标记然后它就能够用艺术家编辑器模块进行编辑。
创建一个基本的照明特效
在这节,我们将创建一个简单的照明特效在RenderMonkey 的预览窗口渲染一个明亮的材质。尽管这个简单计算光照强度的方法来自用于初学者图形学书籍,但是它作为学习用RenderMonkey 集成开发环境快速开发shader的指南非常好。它还充分展示了用微软的HLSL开发shader程序的的优雅,我们能用熟悉的光照公式而且用很少的几行代码就能够实现很好的视觉效果。
我们将用Phong光照模型来实现这个特效。如果我们仔细研究任何一本图形学的书籍都能找到计算该光照模型的公式,该公式如下:
I = Iambient + Idiffuse + Ispecular (1)
每个光照组件贡献的计算如下:
Iambient = Ka*Ia (2)
Idifuse = Kd*Id * (N dot L) (3)
Ispecular = Ks*Is * (V dot R)n (4)
Ka ,Kd ,Ks 分别环境,漫射和镜面光照的系数。根据我们表面的反射系数,这些参数被指定为范围在0到1的常量值。如果我们想要一个高反射率的表面,我们要设置Kd和Ks的值接近1。这将产生一个光亮的表面,在光直接照射的地方有一个高强度的高光 。为了模拟一个高吸光率的表面,我们设置反射率的值接近0。
Id是漫反射光照强度,Is是镜面反射强度,Ia是环境光强度。N,V,R分别为法向量,视向量和反射向量。 n是镜面反射系数,调整视向量和反射向量的之间的角度值。光泽的表面有一个狭窄的光照范围(这两个向量之间的夹角很小),一个以南的表面有宽的反射范围。因此一个非常光泽模型有一个大的n值(100左右),一个黯淡的表面有一个大概等于0.5的值。
我们在像素shader中用公式(2)——(4)来计算该光照模型的每个像素的最终颜色。但首先我们要建立应用程序和特效工作区。
运行时数据库预览
在RenderMonkey 中的每一组视觉特效都被压缩到一个单独的XML工作区中。除了纹理和材质存储在一个单独的文件中之外,所有这些信息对创建特效都非常关键。它使用户可读的,而且每一个游戏开发人员都能够创建自己的转换工具将RenderMonkey 的文件格式转换成他们各自的游戏引擎的脚本格式。我们有充分的理由选择用XML来存储特效工作区。最重要的,XML是工业标准且不失易用性(RenderMonkey 用微软的XML剖析器还有其他一些可选的理由)。他表示数据很容易而且是用户可扩展的。最酷的就是,任何人都能用IE直接打开RenderMonkey的XML文件进行阅读 — 它只是另一种ASCII文件格式。
要创建一个基于shader的特效,我们首先要启动一个应用程序,它将自动为你创建一个空的工组区。所有和特效有关的数据都会用RenderMonkey的运行时数据格式存储在该工作区中。每个特效工作区由下列一些元素组成:
l 变量节点
l 流映射
l 模型
l 纹理变量
l 特效组
一个特效组把一系列相关的特效组合在一起。比如,你可能想把所有用一个噪点函数渲染的混乱特效组合在一起,像云,火或等离子。
每一个特效组由一个或多个特效节点组成。每一个特效节点用来描述一个单独的视觉特效。你可能想用一个单独的过程特效,或用几个绘制掉用来产生你想要的结果。每个绘制调用可能有下列数据组成:
l 一个渲染状态块(可选)
l 一个顶点shader(必须)
l 一个像素shader(必须)
l 一个几何模型参考(必须)
l 一个流映射参考(必须)
l 一个或多个有有效纹理参考的纹理对象(可选)
l 变量节点(可选)
所有在RenderMonkey特效工作区涉及到的单独项目都被指定为节点。
工作区视图
特效工作区中的主要窗口就是工作区视图。他是一个可停靠的窗口,通常位于主界面的左侧,它包括一个用于显示特效数据库的高级视图的节点树。图2显示了工作区视图窗口:
图2.工作区视图
工作区视图能访问特效工作区中的所有元素。这样设计的目的是能根据每个单独特效之间的共同属性将他们组合在一起。
工作区视图中有两个标签:特效标签和艺术标签。特效标签用于显示全部的工作区——所有变量和过程。艺术视图只显示那些在工作区中被指定了艺术家可编辑标志的变量。程序员用特效标签开发了一种特效,然后把它交给艺术家,艺术家就能够简单的通过调节艺术标签中的艺术家可编辑变量来调出他们想要的效果。
好了,让我们开始实现我们的特效。如果你在工组区节点处单击右键,将探出一个菜单,然后选择 Add Effect Group 菜单项。该菜单如图3所示:
图3 用于添加新的特效组的上下文菜单
当你在工组区中添加一个新的特效组的时候,RenderMonkey将自动在你的工作区中添加一些节点。他自动添加一个含有一个过程的特效样本。该过程中包含一个顶点和像素shader的示例,和一个模型。如果你有一块ATI RADEON 8500或更好的图形芯片,你将能在预览窗口中看见一个红色的茶壶。如果没有,你需要把像素shader换成 PS_1_1(我将在后面的文章中教你怎么换)。RenderMonkey也增加一个叫做view_proj_matrix的矩阵变量和一个标准的流映射节点——standard mapping。还增加了一个模型节点。这是你能在功能完善的特效基础上创建比一个红色茶壶更好的视觉效果。
变量的创建和管理
你的任何一个基于shader的特效都要有一些用于当前渲染的参数。在RenderMonkey中这些参数都被指定为变量节点。你能够在节点树的任何级别增加你的变量。
既然我们已经知道了要在我们的shader中需要一些输入变量,那么,现在就让我们加入它们吧。要想加入变量你需要在想要把变量增加到的节点上单击右键,再弹出的菜单中选择“Add Variable“菜单项。(见图4)
图4 增加变量
然后会弹出如图5所示的对话框
图5 增加变量对话框
你要为你的变量节点选择一个RenderMonkey支持的数据类型:
l 标量(一个简单的浮点变量)
l 向量(4D 浮点变量)
l 矩阵(4×4 浮点矩阵)
l 颜色(4D 浮点变量,分别用表示 RGBA)
l 纹理变量:
Ø 2D 纹理贴图
Ø 立方贴图
Ø 体积纹理
节点左边的图标可以帮你快速的确定节点的类型。例如,矩阵类型左边会有一个 图标,而颜色则为 。
默认情况下,新创建的标量,向量和矩阵变量没有被指定为艺术家可编辑,而颜色,纹理,立方贴图和体积纹理变量则被指定为了艺术家可编辑。你能够在变量创建时指定该变量是否为艺术家可编辑(在 Add Variable 对话框中指定Artist Editable)。这个标记将决定该变量是否在艺术标签中显示。如果你想指定一个以创建的变量为艺术家可编辑的,你可以右键单击该变量然后选择Artist Variable 菜单项。如果想移除艺术家可编辑属性,可再次右键单击该变量,然后再次选择Artist Variable 菜单项即可。一个黄色的小三角图标江指出该变量是否为艺术家可编辑,如果该变量是艺术家可编辑的则在该变量图标的左上角就会出现这个黄色的小三角: ,否则没有。
预定义的RenderMonkey 变量
你可能已经注意到了,当你在创建一个新的特效组的时候,RenderMonkey 自动为你增加了一个叫做 view_proj_matrix 的矩阵变量。这就是一个预定义变量的例子。与定义的变量是一些常量值,他们在程序运行时就已经被指定好了。而且你不能直接改变它们的值。RenderMonkey为了方便用户提供了一组预定义的变量:
Ø view_proj_matrix: 一个组合了摄像机和投影矩阵的矩阵类型
Ø view_matrix: 一个摄像机矩阵
Ø inv_view_matrix: 摄像机矩阵的你矩阵
Ø proj_matrix: 投影矩阵
Ø time: 这是一个向量类型的变量,它提供了当前的时间值,它的值能在RenderMonkey 的优先选择对话框中修改,其默认值为当前时间值与120调制的值。
Ø cos_time: 一个向量类型的变量,它是时间的余弦值
Ø sin_time: 一个向量类型的变量,它是时间的正弦值
Ø tan_time: 一个向量类型的变量,它是时间的正切值
最简单的添加预定义变量的方法是在 Add Variable 对话框中选择适当的
量类型,然后 Name 下拉列表框中选择你想要添加的变量的名字。(如图6所示)注意,你选择的变量的名字必须与上面选择的类型一致,否则RenderMonkey将不能将其正确的初始化,因为RenderMonkey从变量的名字和类型两方面来判定预定义的变量。
图6 选择预定义的变量
与定义的变量在工作区中也很容易辨识,他将在变量类型图标的左下方有一个绿色的小图标,如图 。
流映射模块
另一个自动添加到特效工作组中的节点是流映射节点。流映射节点能在工作区的任何地方创建(直接在特效工作区中,直接在特效工作组中,或者在各个过程中)。该节点的作用是绑定数据流到输入寄存器中供shaders使用。RenderMonkey使用直接来自模型数据来自动产生这个流或者你通过用户接口来定义流通道,然后由应用程序计算该流。
流映射模块被用来给一个过程装配几何模型。要创建一个流映射节点,右键单击一个父节点(一个特效,一个特效工作区或一个特效组)再弹出的菜单中选择 Add Stream Mapping 菜单项。图7 是一个在特效工作区中创建一个空的流映射节点的例子。
图7 增加流映射节点
一旦你创建了一流映射节点,你就能够双击或右键单击选择 Edit 调出流映射节点编辑器来编辑该节点,见图8
图8 流映射节点编辑器
我们知道要正确地计算光照,需要顶点的法线和定点的位置。让我们把它们添加到我们的工作区中。双击流映射节点调出流编辑器。我们单击 Add Channel 按钮增加一个新的通道。然后选择想要的输入寄存器,然后使用法,索引和类型。如果你想删除一个通道,单击该通道后面的 X 按钮。图9 中我已经增加了一顶点的法线通道,不要忘了把法线通道的类型设置成 FLOAT3。
图9 增加一个法线通道
要真正的要流映射起作用,你需要在你的过程中增加一个流映射参考。要增加一个流映射参考,你要确保已经在工作区树的某个地方创建了一个流映射节点。然后在你想放置该参考的过程节点上单击右键,再弹出的菜单中选择 Add Stream Mapping Reference菜单项(如图10 )
图10 在过程中增加一个流映射参考
一个新的流映射参考被创建。初始情况下这个参考是没有连接任何流映射节点的。图标上红色的斜杠()表示该参考节点没有被正确的连接。要正确地连接,右键单击过程中的流映射节点,再弹出的菜单中选者Reference Node 菜单项,然后选择你要连接的节点名称(如图11)。你也可以双击流映射参考节点然后将其重命名为流映射节点来直接连接它。
图11 连接一个流映射参考到一个流映射节点
为了确定一个过程中流映射的范围,RenderMonkey 首先要检查该过程中是否有一个流映射实例。如果在过程中既没有找到流映射的实例也没有找到流映射参考,应用程序将到整个工作区中去查找第一个流映射节点或参考。所以你必须考虑放置流映射节点或参考的位置,因为错误的使用流映射节点将导致渲染结果的错误。
如果一个流映射节点被找到且被判定是正确的,将出现一个流映射参考节点的图标:。注意,图标中的小箭头说明这只是一个参考而不是一个真正的流映射节点。在RenderMonkey 中所有的参考节点的图标上都有一个小箭头,所以通过它们你可以很容易的找到工作区中的参考节点。
模型管理
一个特效要通过一个几何模型才能过表现出来。RenderMonkey用模型和模型参考节点来允许用户指定哪个模型被渲染。正如你所看到的,再主工作区节点下包含一个模型节点,在过程节点中包含一个模型参考节点。通过一个红色的小茶壶图标你能很容易的找到模型节点,和参考模型节点(茶壶下有一个小箭头,前文已经提到过每个参考节点图标的左下角都有一个这样的小箭头)。要在模型节点中增加一个新的模型,双击模型节点再弹出的对话框中原则一个你要添加的模型文件。为了绑定数据流到shaders,RenderMonkey将在一个工程中成对的增加一个模型参考节点和一个流映射参考节点以保证必要的数据在运行时就存在且能即时地将其绑定到流源。
管理特效
尽管我们在这次练习中不使用任何额外的特效,我们也暂时讨论一下关于RenderMonkey中的特效管理。工作区中的每个特效都被用来绘制一单独的,连续的视觉特效。他有一个或更多的绘制调用组成。要创建一个新的特效,在特效工作组节点上单击右键,再弹出的菜单中选择Add Effect(见图12)。
图12 在工作区中增加一个新的特效
你能很容易地将一个特效重命名。默认的,RenderMonkey在增加一个新的特效的同时会增加一带有HLSL顶点和像素shaders的一个单独的工程节点。要想看到新增的特效节点的效果,你要将该节点设置成活动的。要激活该节点,在该节点上单击右键然后再弹出的菜单中选择Set as Active Effect。你能很容易的辨认出一个激活的特效节点,因为它将粗体显示。
现在我们已经接近了可编程流水线的核心——shaders自身。RenderMonkey同时支持汇编和高级着色语言shaders。为了增加新的像素或顶点shaders,在你想要将其添加到的特效节点上单击右键,选择Add Vertex Shader 或Add Pixel Shader。(见图13)
图13 增加Shaders
现在,你要选择你想要添加的shader的类型。在如图14的对话框中选择一种你想要的shader类型(汇编或HLSL)。
图14 增加一个新的Shader
单击OK按钮增加一个新的shader。通过图标你能很容易的辨认出Shaders的类型:DirectX汇编语言的顶点shaders的图标为 ,汇编的像素shaders的图标为 ;DirectX HLSL的顶点shaders的图标为 ,像素shaders的图标为 。
RenderMonkey将根据语言自动地选择合适的shader编辑器。注意,每个过程中只能有一组顶点和像素shaders。如果你想改变一个shader的类型,你需要删除旧的shader然后增加一个新的。
编辑shaders
现在我们已经有了一个包含一对shaders的过程,那么现在就让我们看看实际的代码吧。要编辑一个shader,双击该shader节点,然后RenderMonkey将打开一个适当的shader编辑器。一个单独的特效中的所有过程都共享一个shader编辑器窗口。图15显示了一个shader编辑器用户界面的快照。
图15 shader 编辑器窗口(HLSL shader)
在上面的图形用户界面中,shader编辑器有两个标签叶;一个用于顶点shader另一个用于像素shader。这个用户界面根据你选择的shader类型的变化而变化。图16 是一个汇编语言shader编辑器用户界面的快照。
图16 汇编语言shader编辑器窗口
要编辑不同过程的shaders,你只需要简单得选中你想要编辑的过程。顶点和像素shader标签将被更新为新过程的顶点和像素shader标签叶。你能用Ctrl和Tab键的组合来快速的在顶点和像素shader标签叶之间切换。
顶点shader设置和编辑
让我们在我们地一个过程的顶点shader中增加一些代码。下表显示了我们将要增加的代码。(表1):
float4x4 view_proj_matrix;
float4 lightDir;
struct VS_OUTPUT
{
float4 Pos : POSITION;
float3 Norm : TEXCOORD0;
float3 View : TEXCOORD1;
float3 Light : TEXCOORD2;
};
VS_OUTPUT main(
float4 inPos : POSITION,
float3 inNorm : NORMAL )
{
VS_OUTPUT Out = (VS_OUTPUT) 0 ;
// Output transformed position:
Out.Pos = mul( view_proj_matrix, inPos );
// Output light vector:
Out.Light = - lightDir;
// Compute position in view space:
float3 Pview = mul( view_matrix, inPos );
// Transform the input normal to view space:
Out.Norm = normalize( mul( view_matrix, inNorm ) );
// Compute the view direction in view space:
Out.View = - normalize( Pview );
return Out;
}
表1于计算Phong光照的顶点shader
顶点shader变换顶点位置,然后将其输出。然后她用一个名字为lightDir的shader变量计算了光向量(我们将在我们的创建完成我们的shaders后增加shader昌亮)。他还用RenderMonkey的一个预定义变量——view_matrix计算了顶点,视向量和法向量在视空间的位置,然后将视向量和法向量输出到像素shader中。
在我们实际增加这些代码之前,让我们仔细看看RenderMonkey用于编辑HLSL shaders的用户界面。
高级着色语言(HLSL)编辑器包括三部分。在编辑器顶部有一个用于管理shader参数的小装置。中间的文本编辑器用于显示HLSL shader的参数声明。该部分是不可编辑的,他仅被上部管理shader参数的小装置控制。这样能有效的保证RenderMonkey的变量节点和纹理对象正确的映射到HLSL参数。底部的窗口用于编辑实际的shader文本(见图15)。注意,一旦完成了常量和采样器的映射,你就能将常量编辑器块最小化取消其前面的钩选。
要映射一个变量节点(向量,颜色,矩阵,或标量)单击变量名标签后的箭头按钮:再弹出的一个包含了在被编辑的shader范围内的所有变量节点的菜单中选择一个你想映射的节点。(图17)
图17增加变量到HLSLshaders
这是,“name”标签栏将会变成你选择的节点的名字。接下来你要点击“Add ”按钮将新增加的变量节点加入到声明块,且将其映射为一个shader常量。然后,你将在实际的文本声明中看到该声明的变量。
现在让我们增加一个光向量,将其映射为我们写的顶点shader中的一个常量。在特效工作区节点上单击右键然后选择Add Variable。然后选择vector变量类型,将其命名为lightDir。若果你愿意也可以不选Artist Editable。单击OK按钮将在你的工作区中增加一个新的光向量,你将看到一个像这样的节点。转到我们已经打开的顶点shader编辑器,一步一步地将这个光向量映射为一个顶点shader的常量。当你点击Add按钮后,声明块中将增加一个像这样的文本——float4 lightDir;我们已将在我们的shader中增加了对一个常量!
接下来该shader需要的一个变量是视矩阵。应为它是RenderMonkey的一个预定义变量,所以,你不能直接改变它的值。好了,让我们在工作区中增加该变量。右击特效工作区节点选择Add Variable。选择Matrix变量类型。你将看到名称编辑框变成组合框。展开这个组合框,在出现的变量列表中选择view_matrix。然后单击OK搞定。
你将在工作区中看到这样一个节点。左下角绿色的小”p”表示它是一个RenderMonkey的预定义变量。根据上述步骤增加这个变量到顶点shader的声明区。下表列出了声明块中的全部变量(顺序并不重要):
Float4×4 view_proj_matrix: register(c0);
Float4 lightDir;
Float4×4 view_matrix;
现在你能简单的书写余下的shader代码(顶点shader输出结构声明和主函数)到shader的文本编辑器窗口。
用户应该已经注意到了,RenderMonkey中将要被映射到高级着色语言中的参数的节点必须有一个合法的名称。请参考HLSL语言的用户手册来了解更多的关于命名规则的信息。
默认情况下,一个HLSL shader 的入口函数名为main,(顶点和像素shader中都适用)。如果你想改变入口函数名,在入口点编辑区改变一个你想要的名字即可:.
因为每个shader都需要一个编译目标。所以,我们也需要为它指定一个。默认的,RenderMonkey的 HLSL shaders 提供了vs_1_1和ps_1_4。要改变目标的版本,从目标组合框中的列表中选择一个你想要的目标。你要为顶点和像素shader分别指定目标。请参考HLSL文档来了解每个目标。
底部的区域用于编辑shader的文本。该文本中至少要包含一个用于指明shader编译的入口点的函数。这个文本编辑器有HLSL风格的语法颜色。