Shader Model 已经有五个版本了:分别是Shader Model 1.0(DirectX8.0)、Shader Model 2.0(DirectX9.0b)、Shader Model 3.0(DirectX9.0c)、Shader Model 4.0(DirectX10)、Shader Model 4.1(DirectX10.1)和Shader Model 5.0(DirectX11)。N3的版本是 DirectX 9.0c, 也就没有赶上 GS (Geomtry Shader)。
N3 中涉及这个系统的文件主要是如下:Shader, ShaderInstance, ShaderVariable, ShaderVariableInstance, ShaderServer, ShaderVariation,shadersemantics,ShaderFeature, 这些基本是对ID3DXEffect一个操作封装,如果要理解工作原理,需要先熟悉DX的Shader Model. 同时N3还设计了一个编译系统,用来生成.fx文件,也就是DX的shader代码文件,这些文件通过一组规则,用XML配置产生。
所以本文,打算,分三部分,第一步解析DX的Shader系统,第二步,解析N3的工具,第三步解析N3的Shader以及这个系统与渲染的整合过程。
着色器
着色程序,分为顶点着色和像素着色,到4.0版本出现了 几何着色,但是由于效率问题,在5.0版本出现一个新技术Tesselltion。简单的回顾一下概念,SM 4.0 中的GE这里就不深入了。
vetex着色: 给定顶点,顶点着色接收渲染管线传送过来的顶点,颜色,纹理坐标等数据,结合Shader变量,给出当前顶点的坐标,颜色值
pixel 着色: 给定像素,像素着色接收顶点着色的输出作为输入,综合设置的Shader 变量,给出当前顶点的颜色值 , 这里颜色值
着色变量,可以是各种类型,包括自定义结构体,通常会包括,各种矩阵以及逆矩阵,相机的坐标,采样纹理等数据,
DX 8.0 取消了固定流水线,取代的是一个可编程的流水线管道 : 一个shader,可以是多个 tench, 一个 tench 可以是 多个 pass, 每一个 pass 都有定义的 vetex shader 和 pixel shader 。
N3的着色器
N3的着色器设计主要是: N3的Shader的导出工具 , N3着色与渲染的整合
要理解N3的设计,需要先知道N3中的Shader的导出工具,具体工程是 nsc3:
其中有几个比较重要的概念 Shader , ShaderNode, ShaderSlot
一个Shadr 由 几个 ShaderNode组成,
ShaderNode 代表的是 ShaderFragment, 而一个ShaderFragment,可以包含片段程序,包括 VertexShader 和 PixelShader
而一个ShaderSlot 代表一个输入或者输出的参数,这些参数链接,不同的ShaderNode
这样就构建了Shader的编辑框架,类似U3中的材质编辑器,用线条链接各种Shader片段
先来结构一下这个框架的设计
一个顶点程序 :
vsMain (vsInput , vsOutput)
{
vsSourceCode;
}
类似上面的伪代码,是由三部分构成,vsInput, vsOut, vsSourceCode, 所以在xml中配置好这三部分就可以生成一个顶点Shader。
一个像素程序:
psMain(psInput, psOutput)
{
psSourceCode;
}
如上面的伪代码,也是由三部分构成,psInput, psOutput, psSourceCode, 所以在xml中配置好这三部分就可以生成一个像素Shader。
刚刚上面提到的是基本的着色程序片段。
要生成一个可执行的着色程序,需要一个连贯性。
顶点程序,从图形管道获取原始顶点数据,生成变换后的顶点,UV坐标,纹理等顶点相关的数据,供给像素着色程序使用,像素着色程序结合顶点程序的数据以及其他常量,计算像素的最后颜色数据。
所以构建一个Shader : 需要从外部获取数据vsInput,顶点着色生成一个 vsOutPut, 然后像素着色根据 vsOutput (= psInput) , 生成一个 psOutput, 然后显示设备拿这个psOutput的颜色数据放入帧缓冲区。
有了这些基础的信息。
我们接着说N3的事情,
N3把 着色程序,分解到各种片段,对片段的输入输出重定向配置各种Node,然后把各个Node 通过输入和输出Slot链接起来。为了统一执行上述链接过程。
N3引入了五个标准Node: Vertex, Interpolator, Sampler, Constant, Shared, Result
其中 Vertex 代表的是 从图形设备的数据管道流入的数据,也就是外部输入数据
而 Interpolater 代表的是 顶点着色程序的输出数据,同时也是 像素程序的输入数据
Sampler 代表的 对各种纹理贴图的采样
Constant 代表的是 常量
Shared 代表的共享变量
Result 代表的是 像素程序的输入数据
所以VertexNode 只有输出没有输入, Result 值有输入没有输出, 而 Interpolater 的输入和输出是一致的,如果Interpolater的某一个变量有输出,但是没有输入,也就是没有顶点着色程序生成这个数据,则需要在Vertex中补充一个输出,这样后面的像素着色才可以从Interpolater中获取相应的数据。
图形分解
下面用图形来展示整个过程:
上图展示的整个着色过程。 其中我们先分解成两部分,顶点着色和像素着色:
我们接着细分 顶点着色程序:
和前面的分解是一样的,把整个处理过程分解成三部分 输入参数,着色代码,输出参数。顶点着色的输出参数和像素的输入参数是一致的(除了哪些来自外部的常量)
细分 像素着色程序:
通过前面的图示,我们已经完美的分解了整个着色程序。把着色程序分解成为不同的片段和结构。
N3 设计的材质编辑器的目的就是 通过输入和输出参数的重定向,可以拼接不同的着色片段为一个完整的Shader。N 3的着色片段就是 顶点着色程序和像素着色片段
我们先来看个最简单的例子 , 把一个只有 顶点着色的程序片段和一个只有像素着色的片段连接起来, 如下图所示:
要链接两者组成一个完整的着色程序,只需把 像素着色1 的 VS 输入参数1 链接到 顶点着色1的输出参数 PS输出1, 以及像素着色1的 VS输入2 链接到 顶点着色1的输出参数 PS输出2 两者就链接起来了。这个输入与输出的链接,就是说把其中一个的 输出参数当做 另外一个的输入参数。
链接后如下图所示:
如上图一个标准的shader程序就已经完成了。对于上述情况也会存在一些特别地方:比如把一个像素着色程序链接到一个顶点着色程序的时候,如果像素着色的参数,顶点程序无法满足时,比如像素着色需要一个UV坐标,而顶点着色没有UV坐标的生成,这个时候就需要给顶点程序的输入加一个参数,让这个参数以顶点着色程序的名义流入像素着色程序,当顶点程序的输出大于像素着色的输入的时候,这个时候可以忽略,因为只是一个参数没用到而已,下面用图把这两种情况列举出来
上面的红色框代表一个顶点着色程序,有一个Vertex输入,一个const输入, 有三个PS输出。而橙绿色框代表一个像素着色程序,有三个VS输入,一个PS输入. 要链接两个程序,我们需要链接 VS 输入到 PS输入1 , VS输入2链接到PS输入2,但是 VS输入3和PS输出3是不匹配的,这个时候,我们给顶点程序加一个输入参数,名字叫 Vertex-vS 输入3,让这个参数从顶点着色程序经过,然后再直接输出 Vetex-VS输出3,然后再把VS输入3和Vertex-VS输出3链接起来。这样前面提到的像素着色需要的参数顶点着色没提供,顶点着色输出的参数像素着色么有的情形。
有了前面的分析,我们再深入一步,链接多个像素着色和顶点着色片段。我们已经列举了顶点着色和像素着色的链接,下面我们单独说明顶点着色片段之间怎么链接,以及像素着色片段之间怎么链接,有如下的连个顶点着色片段:
现在顶点着色片段2的Vertex输入1 和 顶点着色1的PS输入1是一致的,这样链接两者后的图如下:
这其中涉及的规则就是所有的输入来自 const, vertex, shared, 而这些输入都是唯一的,在整理顶点着色的输入参数的时候,就是整理Vertex节点中有多少个输出链接。而在整理输出的时候就是整理 Interpolater 节点中有多少个输入链接。 [这个规则其实就是前面重复过的对于五个特殊Node功能的定位]
这样 两个顶点着色片段链接后,变成一个着色片段。 这个着色片段有 4个输入, 3个输出。在整个着色片段中,会定义2个变量的输入结构体,其余两个为 const 变量,会定义三个变量的输出结构体(前提是三个输出不一样)。详细的结构如右边的框图。
对于像素着色程序的链接和顶点程序是一样的原理。如下有三个像素着色片段:
他们之间存储如下的链接关系:
接着把图分解成 完整的 顶点着色程序:
完整的像素着色片段已经完成。
有了前面的理论,我们现在对N3的材质编辑器的工作原理做一个简要说明:
先是顶点着色程序,我们顶点着色程序,分为三部分,输入参数,代码,输出参数。顶点着色程序的输入参数会被绑定到节点Vertex, Const, Shared, Sampler, 其中 Vertex 是特殊节点中的特殊节点,他代表的是渲染管道传送过来的渲染数据,主要包括顶点坐标等。而顶点程序的输出参数绑定到Interpolater 节点。当把几个顶点着色程序连接起来的时候,就产生了一个调用链。比如顶点程序1 的输入绑定到顶点程序2的输出参数。通过这种方式,链接起来的调用链,可以通过节点Interpolater的所有输出slot的逆向绑定关系。然后通过遍历Vertex节点的输出slot就可以知道整个程序的输入参数,而访问Interpolater的输出slot,就可以知道整个顶点着色程序的输出参数。
有了上述的情况说明,N3的材质工具的原理就已经说明了。对于游戏开发中,遇到的材质编辑器也就有了理论上的相关认识。