“动画渲染”并没有特定的定义。它大体上是指以非照片真实感的风格渲染对象,效果类似于动画片和漫画书
。通常,它使用大面积单一色块和对象轮廓线进行简单的着色。在这里讨论的架构中,渲染可分为两个问题:
1.按照一定风格使用分明的颜色条渲染对象“体”。(有意思的是,大多数情况下人们不希望出现颜色条,而
在这里我们却要突出它。)
2.将轮廓的边渲染为粗黑线。
1、渲染对象体
我们进行法向漫射光照计算(在此例中使用一个单向光源)。但是我们不将结果输入漫射迭代颜色,而是将光
照值作为简单光照纹理条的纹理坐标。使用加载到漫射迭代器中的所需材质颜色调制此纹理可进行染色。下面是顶
点着
色器程序:
; 输入: v0 = 位置
; v1 = 法向
; c0 = (0,0.5,xxx,xxx) 有用的常量
; c1-4 = WorldView 矩阵
; c5-9 = WorldViewProjection 矩阵
; c9 = 光照/材料颜色
; c10 = 光照方向(在观察空间中)
vs.1.0; 着色器版本 1.0
m4x4 oPos , v0 , c5 ; 计算投影位置
m3x3 r1 , v1 , c1 ; r1 = 观察空间法向
dp3r2 ,-r1 , c10; r2 = 漫射光照计算
maxr2 , r2 , c0.x ; 将 r2 固定为 0
mov oT0.x , r2; 纹理坐标 0 为 (r2,0.5)
mov oT0.y, c0.y
mov oD0 , c9; 漫射颜色 = c9
为简化起见,光照计算是在视图空间中进行的。在对象空间中,显然要进行优化,使着色器不用执行第二次矩
阵乘法运算(在视图空间我们要进行后向变换以提供光源方向)。关键的步骤是我们将光照计算的结果加载到纹理
坐标的“u”分量。这将有效地将坐标变为一维的查找表格,根据它我们可以准确获得需要的纹理条。
最后,我们将材质的颜色(也可以把它看作一种光照)加载到漫射迭代器,我们将用它来调制从纹理中读取的
强度值。如果我们愿意,可以从顶点元素读取这个值。但是为了简化,我们假设此对象只有单一颜色,因此只需使
用一个常数。
请注意如何将多个有用的常数压缩成一个 c0 常数注册项 。既然我们想使它成为纹理坐标中的“v”分量(将
其放置于纹理中部,避免某些边界抽样问题),那么我们将零加载到 c0.x,来限制它的光照点积,同时将 0.5 加
载到 c0.y。
2、渲染轮廓
要渲染对象轮廓,我们需要检测轮廓的边。如果一条边毗邻的两个面具有不同的方向性,例如一个是后向面而
另一个不是,那么这条边是轮廓的一部分。对于平滑对象,如果顶点处的法线垂直于指向顶点的视图矢量,就可以
近似处理为该顶点位于轮廓上。换句话说,如果屏幕空间位置与法向的点积为零,则假设顶点位于轮廓上。
这样,我们就可以用顶点着色器来计算正交化视图矢量和顶点法向的点积。等于或近似于零的值表明我们位于
或者接近轮廓。粗略地说,点积值表明我们与轮廓边界的距离。为了加粗轮廓边界,以产生良好的“卡通”效果,
我们设置一个阈值,只要是低于这个值的点就认为它是轮廓的一部分,阈值越高则轮廓的边越粗。
既然我们想获得轮廓分明的效果, 那么就对每一个像素测试这个阈值。好在已经有执行这种测试的 Alpha 测
试机制。如果我们将点积(从 [-1,-1] 重新调节至 [0,1])加载到漫射迭代器的 Alpha 通道,那么我们可以通过
Alpha 测试排除不在轮廓上的点,然后把剩下的像素渲染为纯黑色。
下面是顶点着色器程序:
; 输入: v0 = 位置
; v1 = 法向
; c0 = 有用的常量 (0,xxx,xxx,xxx)
; c1-4 = WorldView 矩阵
; c5-9 = WorldViewProjection 矩阵
vs.1.0; 着色器版本 1.0
m4x4 r0 , v0 , c1 ; r0 = 观察空间位置
m3x3 r1 , v1 , c1 ; r1 = 观察空间法向
m4x4 oPos , v0 , c5 ; 计算投影位置
dp3 r2.x , r0 , r0; 正交化 r0(位置)
rsq r2.x , r2.x
mul r0 , r0 , r2.x
dp3 r3.x , r0 , -r1 ; 计算点积
mad oD0.w, r3.x , c0.y , c0.y ; 缩放到 [0,1],并进行 Alpha 处理
mov oD0.xyz , c0.x; 漫射 RGB 为 0
请注意输出位置是用 World+View+Projection 合并矩阵的直接变换计算的, 而不是由投影矩阵的视图空间乘
积以递增方式计算。原因是我们要确保执行位置计算的方法与我们为对象体渲染采用的方法“完全”一样,否则可
能会产生重影效果。
进行此处理的的像素管道设置为复制漫射颜色和进行 Alpha 测试,因此代码应为:
m_pd3dDevice->SetRenderState( D3DRS_ALPHATESTENABLE , TRUE );
m_pd3dDevice->SetRenderState( D3DRS_ALPHAFUNC , D3DCMP_LESS );
m_pd3dDevice->SetRenderState( D3DRS_ALPHAREF , m_dwSilhouetteAmount );
我们可以通过更改 Alpha 测试参考值来控制轮廓边的粗细,即 m_dwSilhouetteAmount。
3、限制
这种技术有一些限制。特别是对于具有大面积平坦表面或由镶嵌面构成的对象,渲染效果通常不理想,因为这
些面上法线方向变化不大或根本没有变化,结果整个对象体都被染成单一颜色,也可能把大面积的区域标记为“轮
廓”。有时候这种效果可能符合我们期望的风格,但在一般情况下,只能说是差强人意。运用更复杂的点光源光照
方案,可以在一定程度上消除对象体单一着色的问题。但是,对于大面积平坦表面,应用此处介绍的轮廓渲染技术
仍有困难。然而,如果是基本圆滑的对象,那么效果很好。
4、示例应用程序
“Toon”示例应用程序以 Microsoft DirectX 8 SDK 中的示例应用程序框架为基础, 演示了这种技术对简单
网格的应用。要编译该示例程序,请在“MSSDK\Samples\Multimedia\Direct3D\”路径下创建一个目录,然后把源
文件复制到该目录中(此示例程序引用了一些 DirectX 8 SDK 的公用示例应用程序框架文件)。
请在 MSDN Online Code Center(英文)中查看 Toon 的示例代码。
Toon的控制方式如下:
操作 控制
增大/减小阈值 Q / A
切换内置网格(圆环面或圆环体) T
旋转对象 单击左键并拖动
加载 .X 文件 L
渲染加载的 .X 文件 O