人气 247

[游戏程序] Shaderey――非真实渲染 [复制链接]

九艺网 2017-3-10 17:01:08

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?注册

x
本文版权归原作者所有,仅供个人学习使用,请勿转载,勿用于任何商业用途。
由于本人水平有限,难免出错,不清楚的地方请大家以原著为准。欢迎大家和我多多交流。
作者:Aras Pranckevicius
翻译:clayman
Blog:http://blog.csdn.net/soilwork
[email protected]  
     本文描述了以非真实渲染(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不支持多重采样造成的)。当把地形渲染到法线/深度纹理中时,需要使用>中所描述的方法,在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));
}
最终合成
         在处理完了两张图片之后,把失真之后的颜色与反转之后的边缘/轮廓线进行调制,合成出最终图像。
点击这里下载完成程序和代码。

                               
登录/注册后可看大图


                               
登录/注册后可看大图


回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

QQ|手机版|小黑屋|九艺游戏动画论坛 ( 津ICP备2022000452号-1 )

GMT+8, 2024-3-29 15:45 , Processed in 0.128748 second(s), 23 queries .

Powered by Discuz! X3.4  © 2001-2017 Discuz Team.