Renderman 的 NPR(非照片级)渲染 (BMRT) by hmaya, Zhu Qin
近来,人们除了对照片级真实渲染的不断追求之外,还在3D渲染器中追求非照片级真实渲染(Non Photo-realistically Rendering),简称NPR,例如卡通渲染、水彩、铅笔等笔触的模拟。
Render Man对渲染的控制可谓自由、开放之极,尤其在完成最后的项目之后,我们更是不得不赞叹Render Man的三头六臂,样样都可以应付。
2002年第一次使用MAYA时,那时还是MAYA4.0。但是至今为止,我最喜欢的MAYA开机界面,还是MAYA4.0的,如图。
MAYA4.0的开机界面 这样我就一直在想,这幅画面是如何生成的。我的注意力放在了画面的条纹上。我在Photoshop里做过尝试,可以做一个单独的条纹的图层,然后图层混合模式设为叠加,可以大致得到类似的效果,如图果,如图。
在Photoshop中叠加条纹
但是,问题来了,这样的效果虽然近似,但也很死板,例如,如果在原来画面的非常亮的部分,不想叠加条纹;或者反过来,我们想在暗部显示条纹,亮部不显示条纹,在Photoshop中就比较难实现了。
总结一下,问题就是,能否只让某一段亮度范围的图像与条纹叠加,而该亮度以外的图像不与条纹叠加?我们在Render Man中尝试了这个问题,并且很好的解决了,从而可以自由控制叠加条纹的亮度范围了。实现思路是这样的,首先在Render Man中制作重复的条纹。这个问题比较简单,把P切换到屏幕空间,然后使用mod()函数制作重复条纹,使用mix()函数叠加条纹。代码如下:
point Pscreen = transform("screen", P);/*将P转换到屏幕空间*/
float ss=xcomp(Pscreen);
float tt=ycomp(Pscreen);
ss=mod(ss*frequency,1);/*重复后的每个单元的s*/
tt=mod(tt*frequency,1);/*重复后的每个单元的t*/
Ci=mix(Ci,linecolor,weight);
条纹制作好之后,就要解决如何控制混合范围的问题了。Mix()函数中有个混合权重,可以把混合权重与图像的亮度建立联系,即达到用亮度控制条纹的目的了。
获取图像亮度的一种快捷方法,是直接对RGB操作,将RGB三色通道转换为一个单通道的灰度值,作为亮度通道(Luminance)输出。具体的数学公式是:
Luminance = 0.3* Red + 0.59* Green + 0.11* Blue ;
最后结合Luminance范围修正混合权重即可。解决方案如下:
float RR=comp(Ci,0);/*提取R分量*/
float GG=comp(Ci,1);/*提取G分量*/
float BB=comp(Ci,2);/*提取B分量*/
float Luminance = 0.3* RR + 0.59* GG + 0.11* BB ;
float alpha=smoothstep(0,Luminance_min,Luminance)
-smoothstep(Luminance_max,1,Luminance);/*控制叠加的亮度范围*/
weight=alpha*weight;/*亮度与混合权重建立连接*/
Ci=mix(Ci,linecolor,weight);/* 混合条纹与图像*/
Render Man的测试结果如下:不控制叠加的亮度范围,条纹简单的叠加如图:
条纹简单的叠加
控制叠加的亮度范围之后,叠加条纹的效果如图:
控制叠加的亮度范围之后,叠加条纹
当然,叠加和制作条纹时,我们使用了smoothstep()函数,来反锯齿,或者进行渐变过渡的叠加。对三维物体的渲染测如图:
下面给出原代码line.sl
surface
line(
float Ka = 1;
float Kd = .5;
float Ks = .5;
float fuzz = 0.1;/*控制线框的模糊宽度*/
float frequency = 80; /*控制线框的重复次数*/
float width = .1; /*控制线框宽度*/
color linecolor =color (0.3,0.4,0.7);/*控制线的颜色*/
float roughness = .1;
float Luminance_max =0.8;/*亮度混合的最大阀值,0.8以上的值不混合*/
float Luminance_min =0.4;/*亮度混合的最小阀值,0.4以下的值不混合*/
string mapname = "green.tiff";
color specularcolor =(1,1,1))
{
normal Nf = faceforward (normalize(N),I);
vector V = -normalize(I);
color Ct;
point Pscreen = transform("screen", P);/*将P转换到屏幕空间*/
/*setzcomp(Pscreen, 0);
Pscreen = normalize(Pscreen);*/
float ss=xcomp(Pscreen);
float tt=ycomp(Pscreen);
ss=mod(ss*frequency,1);/*重复后的每个单元的s*/
tt=mod(tt*frequency,1);/*重复后的每个单元的t*/
float weight; /*混合的权重*/
if(tt<(0.5-fuzz-width)||tt>(0.5+fuzz+width))
weight=0;
else
{
float smooth(float e0,e1,e2,e3,x) {
return smoothstep(e0,e1,x)-smoothstep(e2,e3,x);
} /*e0到e1之间平滑过渡,e1到e2之间为1,e2到e3间平滑过渡 */
weight=smooth(0.5-fuzz-width,0.5-width,0.5+width,0.5+width+fuzz,tt);
weight=clamp(weight,0,1);
};
if( mapname != "" )
Ct = color texture(mapname,s/2,t/2);
/* Use s and t texture( mapname,s,t)*/
else
Ct = Cs;
Oi = Os;
Ci = Oi * ( Ct * (Ka*ambient() + Kd*diffuse(Nf)) +
specularcolor * Ks*specular(Nf,V,roughness));
float RR=comp(Ci,0);/*提取R分量*/
float GG=comp(Ci,1);/*提取G分量*/
float BB=comp(Ci,2);/*提取B分量*/
float Luminance = 0.3* RR + 0.59* GG + 0.11* BB ;
float alpha=smoothstep(0,Luminance_min,Luminance)-smoothstep(Luminance_max,1,Luminance);
weight=alpha*weight;
Ci=mix(Ci,linecolor,weight);
/*——修改亮度的另外一种快捷方法,直接对RGB操作.—–*/
/* 将RGB三色通道转换为一个单通道的灰度值,
作为亮度通道(Luminance)输出。具体的数学公式是:
Luminance = 0.3* Red + 0.59* Green + 0.11* Blue ;
RR=0.30*a*RR;//a为亮度比例因子
GG=0.59*a*GG;
BB=0.11*a*BB;
*/
}