代码全部出自RenderMonkey的样例文件NPR.rfx,下载地址:
http://developer.amd.com/tools-and-sdks/archive/games-cgi/rendermonkey-toolsuite/
龙书上讲的卡通渲染的方法一直没有看懂,两个faceNormal一直不明白是怎么算出来的 =_=||| ,看了其他DX sample,只看到有计算切法线之类的,也并没有看到能计算“临近边”的法线的。。。而且也想不通如果一个顶点被很多人公用的话怎么想都没法算的啊。。。如果有大神看到我的这段话还望不吝赐教 T_T
以下是对RenderMonkey中所展示的几种卡通渲染的总结:
关键:先将所有物体所在的像素置为白色而其他区域置为黑色;再遍历屏幕每一个像素的顶点,亦即遍历上一个pass得到的结果,并据它计算出图像的导数。从而得到轮廓。其求导的方法使用的是Sobel滤波器的方法。
使用茶壶模型,
在vs中,(Positon->Position),根据Positon,将顶点位置进行观察变换和投影变换,输出屏幕位置Positon;
在ps中,(Color->Color),将物体所在的位置都为白色;
使用一个特殊的网格(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不知道是从何得来的。。。
而且这个只能描最外边的轮廓额~
将物体的diffuse通过一个映射贴图映射到少数几个颜色中。
在vs中,(Position,Normal->Position,TexCoord),输出Position为输入Position变换后的结果;输出的uv的u为diffuse值,v为0;
在ps中,(TexCoord->Color),使用上述uv对阶梯状的颜色映射纹理采样输出颜色。
将 ToonWithDynamicSpecular 和 Silhouette 结合起来。
与 ToonWithDynamicSpecular 一致;
多加了一步:其w分量置为1.0,相当于 Silhouette 中的 pass0,从而为下一步节省了一个pass。
与Silhouette的pass1一致。
将上两个pass所得的结果,比较如果有 Silhouette 的值的话,就置为0.0(黑线),否则使用 ToonWithDynamicSpecular 的结果。
明明与Silhouette是一样的流程,为什么这个的锯齿这么严重?放大时尤其明显。。
貌似不是纹理采样时的走样造成的。。。把mipmap开大之后确实好了一些,但是还是远远不及Silhouette。。
纹理反走样前:纹理反走样后:Silhouette:
赶脚就像,Silhouette的pass0是根据分辨率调整的一样。。。
使用密度逐渐增大的几张铅笔纹理(本例是6张),依据Diffuse值来决定每张纹理的比重,最终生成这种铅笔画风格。
关键在如何对diffuse进行映射,来得到每张纹理的比重。
首先使用 f(x)=x^4 * 6 这个变换,将diffuse变换到[0.0, 6.0]范围内;
在[0.0,6.0]的范围内,6个整数处分别代表渐进的6张纹理,将新的diffuse值,看做是对离散的整数节点,进行的三角形插值。亦即实际上所采用的纹理只有至多2个。
在uv突变的地方(例如壶嘴和壶身的连接处),会显得露馅儿。
金属色的卡通。。。好奇怪的画风 0 0 它的关键在于,对diffuse值映射到一个特殊的轮廓贴图中。
计算diffuse,然后将其映射到一个轮廓贴图中。(本例使用了3个光源)
轮廓贴图左白右黑,只在中间有一个陡峭渐变。
直译是……扩大……腐蚀?总之就是,先把模型晕开一圈,再缩小一圈,最后将两个渲染结果相减。
同Silhouette,不过多加了一步,不明白是为啥。。。
float pixelSize: register(c0);
Out.texCoord.x = 0.5 * (1 + Pos.x + pixelSize);
Out.texCoord.y = 0.5 * (1 - Pos.y + pixelSize);
使用遍历屏幕的那个模型ScreenAlignedQuad.3ds;
对当前像素及其附近的8个点,映射到pass0生成的纹理中,取所有值中最大值;算是放大了一圈;
使用遍历屏幕的那个模型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);
看上去和上边那个很像但是非常不同,因为这个终于不再是只能描边最外侧轮廓了!这是一个使用depth来进行Sobel滤波,来确定边缘的方案。
将模型的点变换到投影空间,然后输出其z值到每个颜色通道;
使用Sobel滤波,导数平方大于0.07*0.07的点为1其他为0;
这一步进行一个简单的反走样,具体而言,先定义一个12个点的固定的sample pattern,然后对其平均。
浴室门23333。总体而言就是正常的渲染,然后在最后一步,使用噪声贴图来增加一个扰动,然后用扰动后的结果重新采样。
使用3d纹理渲染环境背景
Out.Pos = mul(view_proj_matrix, float4(In.Pos.xyz + view_position, 1));
Out.TexCoord = In.Pos.xyz;
将整个大象渲染为红色
遍历屏幕上的每一个点,对输入值使用噪声纹理进行映射,增加一个扰动;
使用扰动后的值来对上边得到的结果进行采样。
使用normal值来进行Sobel滤波找边缘。比上边那个用depth值来查找边缘,要更加……锋利一点的样子?
计算深度、法线;
茶壶像素全都输出红色;
计算深度、法线;
把法线变换到0到1上,获得法线的xy值的一个纹理;
使用Sobel计算pass0所得的图的轮廓;
将四周的8个像素的法线值,分别于当前像素的法线值进行点乘,减去一个阈值后钳位到大于0,作为该点的“值”;
将上述值求导,取其平方和大于 0.07 * 0.07 的点。
不太清楚上述阈值是怎么设定的。。以及为什么又出现了 0.07*0.07?
完。
是时候去补充shader的理论知识了~