几种卡通渲染方法的解析

代码全部出自RenderMonkey的样例文件NPR.rfx,下载地址:
http://developer.amd.com/tools-and-sdks/archive/games-cgi/rendermonkey-toolsuite/


龙书上讲的卡通渲染的方法一直没有看懂,两个faceNormal一直不明白是怎么算出来的 =_=||| ,看了其他DX sample,只看到有计算切法线之类的,也并没有看到能计算“临近边”的法线的。。。而且也想不通如果一个顶点被很多人公用的话怎么想都没法算的啊。。。如果有大神看到我的这段话还望不吝赐教 T_T
以下是对RenderMonkey中所展示的几种卡通渲染的总结:

1. Silhouette

几种卡通渲染方法的解析_第1张图片
关键:先将所有物体所在的像素置为白色而其他区域置为黑色;再遍历屏幕每一个像素的顶点,亦即遍历上一个pass得到的结果,并据它计算出图像的导数。从而得到轮廓。其求导的方法使用的是Sobel滤波器的方法。

流程

pass0:

使用茶壶模型,
在vs中,(Positon->Position),根据Positon,将顶点位置进行观察变换和投影变换,输出屏幕位置Positon;
在ps中,(Color->Color),将物体所在的位置都为白色;

pass1:

使用一个特殊的网格(ScreenAlignedQuad.3ds),这个网格的顶点坐标的xy分别遍历0到1范围。也因此,在vs中,不对其进行观察变换及投影变换。
在vs中,(Position,TexCoord->Position,TexCoord),根据输入Position的xy,将其转换到0到1之间,作为输出TexCoord;
在ps中,(TexCoord->Color),使用pass0渲染出的结果,通过tex2D对该像素点进行八个采样并分别计算xy方向的导数,如果两个方向的导数平方和大于0.07*0.07那么输出黑色,否则输出白色。

疑问

0.07*0.07不知道是从何得来的。。。
而且这个只能描最外边的轮廓额~

2. ToonWithDynamicSpecular

几种卡通渲染方法的解析_第2张图片
将物体的diffuse通过一个映射贴图映射到少数几个颜色中。

流程

pass0

在vs中,(Position,Normal->Position,TexCoord),输出Position为输入Position变换后的结果;输出的uv的u为diffuse值,v为0;
在ps中,(TexCoord->Color),使用上述uv对阶梯状的颜色映射纹理采样输出颜色。

3. ToonWithSilhouetteRendering

几种卡通渲染方法的解析_第3张图片
将 ToonWithDynamicSpecular 和 Silhouette 结合起来。

流程

pass0

与 ToonWithDynamicSpecular 一致;
多加了一步:其w分量置为1.0,相当于 Silhouette 中的 pass0,从而为下一步节省了一个pass。

pass1

与Silhouette的pass1一致。

pass2

将上两个pass所得的结果,比较如果有 Silhouette 的值的话,就置为0.0(黑线),否则使用 ToonWithDynamicSpecular 的结果。

疑问

明明与Silhouette是一样的流程,为什么这个的锯齿这么严重?放大时尤其明显。。
貌似不是纹理采样时的走样造成的。。。把mipmap开大之后确实好了一些,但是还是远远不及Silhouette。。
纹理反走样前:几种卡通渲染方法的解析_第4张图片纹理反走样后:几种卡通渲染方法的解析_第5张图片Silhouette:几种卡通渲染方法的解析_第6张图片
赶脚就像,Silhouette的pass0是根据分辨率调整的一样。。。

4. Hatch

几种卡通渲染方法的解析_第7张图片
使用密度逐渐增大的几张铅笔纹理(本例是6张),依据Diffuse值来决定每张纹理的比重,最终生成这种铅笔画风格。

流程

pass0

关键在如何对diffuse进行映射,来得到每张纹理的比重。
首先使用 f(x)=x^4 * 6 这个变换,将diffuse变换到[0.0, 6.0]范围内;
在[0.0,6.0]的范围内,6个整数处分别代表渐进的6张纹理,将新的diffuse值,看做是对离散的整数节点,进行的三角形插值。亦即实际上所采用的纹理只有至多2个。

疑问

在uv突变的地方(例如壶嘴和壶身的连接处),会显得露馅儿。

5. HatchWithSilhouetteRendering

几种卡通渲染方法的解析_第8张图片
又是组合大法2333

6. MetallicCartoon

几种卡通渲染方法的解析_第9张图片
金属色的卡通。。。好奇怪的画风 0 0 它的关键在于,对diffuse值映射到一个特殊的轮廓贴图中。

流程

pass0

计算diffuse,然后将其映射到一个轮廓贴图中。(本例使用了3个光源)
轮廓贴图左白右黑,只在中间有一个陡峭渐变。

7. DilateErode

几种卡通渲染方法的解析_第10张图片
直译是……扩大……腐蚀?总之就是,先把模型晕开一圈,再缩小一圈,最后将两个渲染结果相减。

流程

pass0

同Silhouette,不过多加了一步,不明白是为啥。。。
float pixelSize: register(c0);
Out.texCoord.x = 0.5 * (1 + Pos.x + pixelSize);
Out.texCoord.y = 0.5 * (1 - Pos.y + pixelSize);

pass1

使用遍历屏幕的那个模型ScreenAlignedQuad.3ds;
对当前像素及其附近的8个点,映射到pass0生成的纹理中,取所有值中最大值;算是放大了一圈;

pass2

使用遍历屏幕的那个模型ScreenAlignedQuad.3ds;
对当前像素及其附近的8个点,映射到pass0生成的纹理中,取所有值中的最小值;算是缩小了一圈;
然后!!重点来了!!!
这一步打开了Alpha混合,将混合操作符变为 rev_substract,将src和dest的混合系数都变为1,亦即,这一步实际上执行的是,源像素值-目标像素值;
亦即,pass1中放大的那一圈和pass2中缩小的那一圈之差。

疑问

不太懂为什么在vs做了一个这个。。。
float pixelSize: register(c0);
Out.texCoord.x = 0.5 * (1 + Pos.x + pixelSize);
Out.texCoord.y = 0.5 * (1 - Pos.y + pixelSize);

8. WhiteBoard

几种卡通渲染方法的解析_第11张图片
看上去和上边那个很像但是非常不同,因为这个终于不再是只能描边最外侧轮廓了!这是一个使用depth来进行Sobel滤波,来确定边缘的方案。

流程

pass0

将模型的点变换到投影空间,然后输出其z值到每个颜色通道;

pass1

使用Sobel滤波,导数平方大于0.07*0.07的点为1其他为0;

pass2

这一步进行一个简单的反走样,具体而言,先定义一个12个点的固定的sample pattern,然后对其平均。

9. ShowerDoor

几种卡通渲染方法的解析_第12张图片
浴室门23333。总体而言就是正常的渲染,然后在最后一步,使用噪声贴图来增加一个扰动,然后用扰动后的结果重新采样。

流程

pass0

使用3d纹理渲染环境背景
Out.Pos = mul(view_proj_matrix, float4(In.Pos.xyz + view_position, 1));
Out.TexCoord = In.Pos.xyz;

pass1

将整个大象渲染为红色

pass2

遍历屏幕上的每一个点,对输入值使用噪声纹理进行映射,增加一个扰动;
使用扰动后的值来对上边得到的结果进行采样。

9. Silhouette With Normal

几种卡通渲染方法的解析_第13张图片
使用normal值来进行Sobel滤波找边缘。比上边那个用depth值来查找边缘,要更加……锋利一点的样子?

流程

pass0

计算深度、法线;
茶壶像素全都输出红色;

pass1

计算深度、法线;
把法线变换到0到1上,获得法线的xy值的一个纹理;

pass2

使用Sobel计算pass0所得的图的轮廓;

pass3

将四周的8个像素的法线值,分别于当前像素的法线值进行点乘,减去一个阈值后钳位到大于0,作为该点的“值”;
将上述值求导,取其平方和大于 0.07 * 0.07 的点。

疑问

不太清楚上述阈值是怎么设定的。。以及为什么又出现了 0.07*0.07?


完。
是时候去补充shader的理论知识了~

你可能感兴趣的:(shader)