永远也不完美的程序

不断学习,不断实践,不断的重构……

常用链接

统计

积分与排名

好友链接

最新评论

Shaderey――非真实渲染

Shaderey――非真实渲染

 

本文版权归原作者所有,仅供个人学习使用,请勿转载,勿用于任何商业用途。
由于本人水平有限,难免出错,不清楚的地方请大家以原著为准。欢迎大家和我多多交流。
作者:Aras Pranckevicius
翻译:clayman
Blog:http://blog.csdn.net/soilwork
clayman_joe@yahoo.com.cn  

     本文描述了以非真实渲染(none-photorealistic rendering)风格,对户外场景进行着色的技术。在2003年秋天的Beyond3D/ATI shader compititon中,Shaderey程序最先使用了这些技术来进行渲染。在Shaderey的户外场景中,包含了地形,云,树木,房屋,天空顶,以及湖水,如图所示:

         确切的说,这里使用的NPR技术都是在图片空间(image space)进行的操作,它依赖于场景中两张重要的图片:一张包含了颜色信息,一张包含法线和深度信息。处理过程分为两部分:
渲染: 把场景渲染到颜色和法线/深度目标中。
后期处理: 在图片空间进行一系列过滤操作,获得最终的非真实效果。

         后期处理包括:HSV空间下的颜色扭曲,屏幕空间中简单“阴影线(hatching)”的渲染,以及在法线/深度不连续处的轮廓线绘制。我们将在后面详细讨论这些过滤操作。首先,先来看看Shaderey的场景渲染方式。

场景渲染

         场景中所有的树木和房屋都经过了可视体裁剪(frustum-culled)。地形是一张512 x 512的高度图,但分为若干尺寸固定的(32 x 32)小块(chunk)。所有通过视见体裁剪的地形小块都没有进行任何形式的LOD。整个场景使用了一张1024 x 1024的阴影帖图。房屋和树木都将产生阴影,并且投影到地面上。场景中的树木和木屋投射阴影,而地形接收这些影子。我们使用pick-nearest采样器,对阴影贴图进行四次有偏移的采样,然后再shader中对这些值进行均值采样,以提高影子边界上的质量。阴影贴图并不需要覆盖整个地形的大小,在我们的实现中,它将随观察者的位置移动,以保证观察者前方总是有正确的阴影。

         为了模拟湖面的简单反射效果,可以把摄像机反转到水面之下,把场景渲染为一张较小的平面反射贴图。我们把这张阴影贴图投影到水面上,另外使用两张卷动的EMBM风格的凹凸贴图来模拟波纹。为了减少几何数据,渲染到反射贴图中的地形将使用较低的LOD层次。对所有物体来说,大气光照散射效果都是在顶点级别计算的。

         除了把颜色渲染到后备缓冲之外,还需要把场景中物体的法线和深度渲染到一张和屏幕大小相同的A8R8B8G8纹理中。世界坐标下的法线信息保存在RGB通道中,深度值的导数保存在alpha通道中。

         下面是在vertex shader中,使用HLSL正确计算法线和深度值倒数的代码:


         //output normal in RGB, sort-of-depth in A, p – final ( clip space) position,  n—world space normal

         static inline float4 gNormalZ( float4 p, float3 n)

         {
                   float4 o;
                   o.xyz = n * 0.5 + 0.5;  // in to 0….1 range
                  o.w = 100.0 / ( p.w + 100 );  // kind-of-depth

         }

         如果支持DirectX 9中的Multiple Render Target(MRT),可以在渲染场景颜色的同时,渲染法线和深度。如果不支持MRT,则需要分两次渲染(译注:从demo来看,使用MRT将会严重影响渲染质量,应该是由于MRT不支持多重采样造成的)。当把地形渲染到法线/深度纹理中时,需要使用<< Non-Photorealistic Rendering with Pixel and Vertex Shader>>中所描述的方法,在pixel shader中对阴影贴图进行采样,对阴影中的像素来说,需要对插值之后深度值取反(译注:在Non-Photo原文中是对法线值取反)。这样做的原因在后面描述后期处理的部分会讲解。

图片后期处理

         目前已经把场景渲染为颜色和法线/深度图片了,接下来就可以对这些图片进行一系列处理了,包括把颜色转到HSV颜色空间下进行风格化处理,绘制边缘轮廓线,实现阴影线。

颜色失真

图片处理的第一步是进行颜色失真,获得风格化的样式。
1.降低采样率,把图片缩为一张521x512的纹理。
2.把颜色从RGB空间转换到HSV空间,并且量化(quantize)颜色值。颜色空间的转换将通过对一张体积材质的查找来实现。把原像素的RGB值作为立方纹理坐标。立方纹理中的像素为HSV颜色空间。这里我们将使用一张32x32x32的纹理,并且不进行任何过滤,所以颜色转换的同时将会量化颜色值。
3.使用2D偏移纹理,对同一纹理中当前像素的两个偏移位置进行采样。用来访问偏移纹理的纹理坐标由程序控制,它们将和观察者的位置有关(观察点的yaw值将在水平方向影响偏移,pitch值在垂直方向影响)。这些额外的采样颜色也必须转换到HSV空间。
4.替换图片中的颜色。目前我们有2个额外的偏移采样。首先,我们检察两个偏移值之间差分的差值,如果小于某个限制,就什么也不做。如果它们之间的差别足够大,则输出S和V通道的均值,保留中心原像素的H值。这个方法能高效的在颜色区域边缘替换原像素的饱和度。
5.再使用一张立方纹理把颜色转换回HSV空间。
    第2~5步的pixel shader代码如下,需要pixel shader 2.0的支持。

struct PS_INPUT
{
         float2 uv[2] : TEXCOORD0; //base uv,displace uv
};

 

float4 psMain( PS_INTPUT i) : COLOR
{

         //sample rgb,convert into hsv
         half base = tex2D( smpBase, i.uv[0] ).rgb;
         base = tex3D( smpRGB2HSV, base ).rgb;

         //get 2 displaced sample locations
         half2 bleedB = tex2D ( smpBleedB, i.uv[1] ).rg * 2 -1;
         half2 bleedC = tex2D ( smpBleedC, i.uv[1] ).rg * 2 -1;
         float2 uvB = i.uv[0] + bleedB * (8.0/512);
         float2 uvC = i.uv[0] + bleedC * (-7.0/512);

         //sample base at displaced locations ,convert to hsv
         half3 baseB = tex2D( smpBase,uvB).rgb;
         baseB = tex3D( smpRGB2HSV,baseB);
         half3 baseC = tex2D( smpBase, uvC).rgb;
         baseC = tex3D( smpRGB2HSV,baseC);
         half3 bleed = baseB * 0.5 + baseC * 0.5;
      
         //final color is base if differences in hsv values are smller than tresholds
         //else average of displace values
         half3 diff = abs(base - baseC) - half( 1/8.0,1/3.0,1/3.0)
         half3 final = all( diff < float3 ( 0,0,0) ? base : bleed;

         //leave original hue channel
         final.r = base.r;
         //convert back to rgb
         return tex3D ( smpHSV2RGB),final);

}

边缘检测和轮廓线
         为了获得NPR风格的样式,必须在图片上渲染出深色的轮廓线和阴影线,表现出场景的着色效果。在Shaderey中,我们将同时绘制边缘轮廓线和阴影线。这里需要使用之前计算的法线/深度图来计算边缘,用光线和法线的点积来计算那些区域需要绘制阴影线。阴影线是一张简单的纹理。在这一步处理中,边缘和轮廓线都是白色。最终合成时,进行反色处理,轮廓线变为纯黑色,轮廓线颜色根据场景的着色进行衰减。
         以下是绘制轮廓线和阴影线的pixel shader代码:

half4 psMain ( float2 uv[3]:TEXCOORD): COLOR
{
         //sample center and 2 neightbours
         half4 cbase = tex2D( smpBase, i.uv[0]);
         half4 cb1 = tex2D(smpBase, i.uv[1]);
         half4 cb3 = tex2D(smpBase, i.uv[2]);

         //normal into -1..1 range
         half3 nbase = cbase.xyz * 2 -1;
         half3 nb1 = cb1.xyz * 2 - 1;
         half3 nb3 = cb3.xyz * 2 - 1;

         //edges from normals
         half2 ndiff;
         ndiff.x = dot( nbase,nb1);
         ndiff.y = dot( nbase,nb3);
         ndiff -= 0.6;
         ndiff = ndiff > half2(0,0) ? half2(0,0):half2(1,1);
         half ndiff1 = ndiff.x + ndiff.y;
       
         //edges from z
         float2 zdiff;
         zdiff.x = cbase.a - cd1.a;
         zdiff.y = cbase.a - cb3.a;
         adiff = abs(zdiff) - 0.02;
         zdiff = zdiff > half2(0,0) ? half2(1,1) : half2(0,0);

         //sampler hatch
         half4 chatch = tex2D( smpHatch, i.uv[0]);
         //dot normal with light
         half dotNL = dot( nbase, vLightDir);
         //hatch blend factor
         half factor = saturate( (1.0 - 0.9 - dotNL) * 2);
         chatch *= factor;
         return chatch + ndiff1 + dot(zdiff,half2(1,1));
}

最终合成
         在处理完了两张图片之后,把失真之后的颜色与反转之后的边缘/轮廓线进行调制,合成出最终图像。

 点击这里下载完成程序和代码。

posted on 2008-08-26 17:47 狂烂球 阅读(856) 评论(0)  编辑 收藏 引用 所属分类: 图形编程


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理