实时渲染(第三版):第三章 图形处理单元 3.7 3.8

 

3.7 合并阶段

   如节2.4.4所述,合并阶段是各片段(由顶点着色器生成)的深度和颜色和帧缓冲进行组合的地方。另外,此阶段还可以发生模板缓冲和z缓冲的操作,以及颜色混合。颜色混合通常用于透明和合成操作(参考节5.7)。

   合并阶段是介于固定功能阶段(如剪切)和完全可编程着色器阶段之间的一个有趣的中间点。虽然它不可编程,但其操作却是高度可配置的。特别地,可以配置颜色混合来执行大量不同的操作。最常用的是涉及颜色和alpha值的加、减、乘;其他还有一些操作,比如最大和最小值、位逻辑等操作。DirecX10添加了一种叫做双色混合的功能,它可以将来自像素着色器的两个颜色和帧缓冲颜色进行混合。

   如果使用了MRT功能,则可以对多个缓冲执行混合。DirecX10.1引入了对每个MRT缓冲执行不同混合操作的能力。在以前的版本,总是对所有的缓冲执行相同的混合操作(注意,双色混合和MRT不兼容)。

3.8 效果

   到目前位置,管道之旅一直聚焦于各种各样的可编程阶段。为控制这些阶段,顶点、几何和像素着色器程序是必须的,但它们也不是从石头里蹦出来的。首先,一个孤立的着色器程序并没有什么用处:顶点着色器程序将其结果填充到像素着色器;必须加载所有的程序才能做成事情。程序员必须让顶点着色器的输出和像素着色器的输入相匹配。一个特别渲染的效果可能产生于在好几个通道上执行的、未知数目的着色器程序。除了着色器程序自身,有时还必须特别地设置状态变量以让这些程序正确地工作。比如,渲染器状态包括是否以及如何使用z缓冲和模板缓冲,片段如何影响已经存在的像素的值(如,替换,相加或混合)。

   正是由于这些原因,各种组织才开发了效果语言,如HLSL FX,CgFX,和COLLADA FX。效果文件尝试封装用于执行一个特殊渲染算法[261, 971]的所有相关信息。它通常定义了一些全局参数,这些参数可以由应用程序指定。例如,某个效果文件可能定义顶点和像素着色器所需的、用来渲染一个非常逼真的塑料材质。它将公开塑料颜色、粗糙度之类的参数,这样,我们就可以根据各个被渲染的模型更改这些值,但使用同一个效果文件。

   为显示效果文件的作用,我们将给出一个精简的示例,该示例来自于NVIDIA的FX Composer 2效果系统。该DirectX 9 HLSL效果文件实现了一个非常简化形式的Gooch着色[423]。其中的一部分使用表面法线,并将之和光照位置进行比较。如果法线指向光照,则使用暖色调着色表面;否则使用冷色调。


图 3.8 Gooch着色器,从暖橙色变化到冷蓝色。

   在这两个用户定义色之间对内部的角度进行插值。该着色器技术是非逼真渲染的一种,它是第十一章的主题。这种效果的一个例子显示于图3.8。

   效果变量在效果文件的开始处定义。最先的几个变量是不可调整的,是和镜头位置相关的参数:

float4x4 WorldXf : World;
float4x4 WorldlTXf : WorldlnverseTranspose;
float4x4 WvpXf : WorldViewProjection;

   语法是:type id : semantic。类型float4x4用于矩阵,名称由用户定义,semantic段是内建的名称。正如semantic的名字所显示,WorldXf 是一个模型到世界的转换矩阵;WorldlTXf 是WorldXf的逆转矩阵;WvpXf 是模型空间到相机剪切空间的转换矩阵。 这些拥有可辨识semantic的值需要由应用程序提供,但不会显示在用户界面中。

   接下来是用户定义的变量:

   此处,在尖括号<>之间提供了一些额外的注解,然后指定默认值。注解属于特定应用,对效果或着色器编译器没有任何效果。它们可以被应用程序查询。在此处的示例中,注解描述如何在用户界面中公开这些变量。

   之后,定义了着色器输入和输出的数据结构:

   appdata定义了模型中的单个顶点的数据,因此也就定义了顶点着色器程序的输入数据。vertexOutput 是顶点着色器生成、像素着色器使用的数据定义。输出名称中使用的TEXCOORD* 是管道演变的人工产物。起初,表面可以附加多个纹理,因此这些额外的数据字段被叫做纹理坐标。在实际使用中,这些字段可以保存任何顶点着色器传递给像素着色器的数据。

   再接下来,定义了各种着色器程序代码元素。我们此处只有一个顶点着色器程序:

vertexOutput std_VS(appdata IN) {
   vertexOutput OUT;
   float4 No = float4(IN.Normal,0);
   OUT.WorldNormal = mul(No,WorldITXf).xyz;
   float4 Po = float4(IN.Position,1);
   float4 Pw = mul(Po,WorldXf);
   OUT.LightVec = (LampOPos - Pw.xyz);
   OUT.HPosition = mul(Po,WvpXf);
   return OUT;
}

   该程序首先使用矩阵相乘计算表面在世界空间中的法线。转换是下章的主题,因此,我们不会解释此处为什么使用逆转。世界空间中的位置也被使用离屏转换进行计算。该位置减去光的位置,就是表面到光的方向向量。最后,对象的位置被转换到剪切空间,用于光栅化。它是顶点着色器程序的一个必须的输出。

   知道世界空间中的光方向和表面法线后,像素着色器程序计算表面颜色:

float4 gooch_PS(vertexOutput IN) : COLOR
{
   float3 Ln = normalize(IN.LightVec);
   float3 Nn = normalize(IN.WorldNormal);
   float ldn = dot(Ln,Nn);
   float mixer = 0.5 * (ldn + 1.0);
   float4 result = lerp(CoolColor, WarmColor, mixer);
   return result;
}

   向量Ln是标准化的光方向,Nn是标准化的表面法线。通过标准化,这两个向量的点积ldn就是它们之间的夹角的余弦。我们想要在冷暖色调之间使用余弦值进行线性插值。函数lerp()接受一个0到1之间的混合值。其中0表示使用CoolColor,1表示使用WarmColor,0和1之间的值则表示它们的混合。余弦的值范围是[-1, 1],混合值将这个范围转换到[0,1]。转换后的值随后用来混合两种颜色,生成了一个片段。这些着色器是函数。一个效果文件可以由任意数目的函数组成,还可以包含其他效果文件中使用的函数。

   一个pass通常由一个顶点和像素(和几何)着色器以及各种所需的状态配置组成。一个technique是生成所需效果的一个或多个pass的集合。我们的这个文件很简单,只有一个technique,且这个technique只有一个pass

technique Gooch < string Script = "Pass=p0;n; > {
   pass pO < string Script = "Draw=geometry;"; > {
      VertexShader = compile vs_2_0 std_VS();
      PixelShader = compile ps_2_a gooch_PS();
      ZEnable = true;
      ZWriteEnable = true;
      ZFunc = LessEqual;
      AlphaBlendEnable = false;
   }
}

   这些状态配置强制以普通方式使用z缓冲--启用z缓冲,使之可读可写,并且,如果片段的深度小于等于现存的z深度,则传递。alpha混合被关闭,因为使用该technique的模型被假定为不透明的。这些规则意味着,如果片段的z深度等于现存的物体或者比现存的物体离相机更近,则使用计算出的片段颜色替换相应的像素的颜色。换句话说,就是使用z缓冲的标准用法。

   在同一个效果文件中,可以存储若干个technique。这些technique通常是同一个效果的变种(对应不同的着色器模型)。可以有广泛的效果。图3.9展现了现代可编程着色器管道的强大功能,这还只是九牛一毛。一个效果通常封装了相关的technique。有各种各样的方法被开发出来以管理着色器集[845, 847, 887, 974, 1271]。


图 3.9 可编程着色器可以实现广泛的材质和后处理效果。

   关于GPU本身的介绍到此就结束了。但GPPU还有其他很多事情可以做,还有很多方式我们可以使用和组合。利用这些能力的相关原理和算法是本书的中心主题。有了这些基本概念之后,我们将聚焦于提供对转换和可视化外观(它们是管道中的关键元素)的深入理解。

进一步阅读和资源

   略...

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值