通用折射模拟

通用折射模拟(Generic Refraction Simulation)

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

          折射,当光线从一种介质传播到另一种介质时发生的光学现象,是事实计算机图形渲染中的挑战之一。虽然有很多实现反射的模拟的技术(比如平面反射贴图,立方反射贴图),而且这些技术在大多数情况下都工作的很好。但是,对于折射来说,就没有那么多比较好的技术来解决这个问题。本文介绍了一种把场景中非折射物体渲染为一张纹理,并且通过扰动纹理坐标来实现折射的技术。这个方法非常的高效,并且在大多数情况下都工作的很好。这里所介绍的技术就是Far Cry中,用来渲染水面,水蒸汽,瞄准镜等效果的方法。

有几种模拟折射的方法:有些机遇预先渲染好的环境贴图,有些基于在运行时渲染的环境贴图。这些方法的缺点是需要额外的纹理储存空间,以及性能上的损失,特别是当环境中有多个折射平面,每个都需要不同环境贴图的情况。
此外,当前渲染水面折射技术的问题之一就是需要使用两个 pass :一个用来生成水面下几何体的反射贴图,一个用来渲染水面。这个方法效率很低,特别是在场景比较复杂的情况下。
本文描述了用于解决这些问题的方法。我们先介绍基本的技术――使用当前的后备缓冲作为折射贴图,接下来添加纹理坐标扰动,从而模拟折射。这个方法可能会出现一些问题,因此,我们接下来讨论了如何对折射贴图中的物体进行遮罩( mask )。最后,我们演示了如何使用折射技术来模拟真实的水面,以及玻璃。
 
19.1 基本技术
基本折射技术的第一步,就是跳过场景中所有会发生折射的物体,把场景渲染为一张纹理。我们将使用这张纹理来判断折射体之后的那些物体是可见的,并且在之后的 pass 中使用它。我们把这张纹理标记为 S
第二步就是渲染折射体,对纹理坐标进行扰动,对纹理 S 进行查找,模拟折射。可以使用一张法线图 N 来实现扰动,其中, N 中的红色和蓝色( XY )元素,被用于为纹理坐标添加微小位移。这个方法在 shader 中很容易实现:( 1 )对纹理 N 进行采样,( 2 )对 XY 元素进行微小的缩放(比如 0.05 ),( 3 )把偏移量加到 S 的纹理坐标上。下面的 shader 展示了这个方法:
half4 main(float2 bumpUV : TEXCOORD0,
     float4 screenPos   : TEXCOORD1,
     uniform sampler2D tex0,
     uniform sampler2D tex1,
     uniform float4 vScale) : COLOR
{
     //fetch bump texture, unpack from [0..1]to[-1,1]
     half bumTex = 2.0 * tex2D(tex0, bumpUV.xy) - 1.0;
     //displace texture coordinates
     half2 newUV = (screenPos.xy/screenPos.w) + bumpTex.xy * vScale.xy;
     //fetch refraction map
     return tex2D(tex1, newUV);
}
 
19.2 折射遮罩(Refraction Mask)
前面描述的基本技术在大多数情况下都工作的很好,但当有物体位于折射对象前方,并且凹凸扰动的尺寸很大时,可能会出现一些问题。位于折射对象之前的物体,也可能产生折射。
产生这种效果的原因是场景中除了折射对象之外的所有物体,包括那些位于折射体前面的对象,都被渲染到了纹理 S 中,并且我们不加选择的就对其中所有像素都进行了扰动。这就导致我们的折射产生了“泄漏”。一种直接的解决方案就是减小扰动的数量,让这种效果变的不太明显。但是,很难为所有环境下选取一个合适的缩放值,同时,这种方案也限制了表面折射可能的凹凸程度。
较好的解决方案是保证不对不正确的像素进行扰动。因此,我们在纹理 S alpha 通道中,为所有折射体添加了一个遮罩,以保证所有纹理坐标扰动,只在屏幕上,折射体所覆盖的区域起作用。
为了实现这个方法,需要对渲染进行一些修改:首先,确保纹理 S alpha 通道已经被清除为白色,接下来,把用黑色把折射体渲染到 alpha 通道中。当渲染折射体时,使用储存在 S alpha 通道中的额外信息,来分辨哪些像素将会被处理。检查 alpha 是否为白色,如果是,我们就不对这个区域进行扰动。只有当 alpha 值为黑色时,才进行扰动。虽然获得的结果是不精确的——折射有可能让那些并不是刚好在折射体之后的物体变的可见――但在实践中,这个方法非常的高效。下面的代码实现了这个方法:
half4 main( float2 bumpUV : TEXCOORD0,
     float4 screenPos   : TEXCOORD1,
     uniform sampler2D tex0,
     uniform sampler2D tex1,
     uniform float4 vScale) : COLOR
{
     //fetch bump texture
     half4 bumpTex = 2.0 * tex2D(tex0, bumpUV.xy) - 1.0;
     //compute projected texture coordinates
     half2 vProj = (screenPos.xy / screenPos.w);
     //fetch refraction map
     half4 vRefrA = tex2D(tex1, vProj.xy + bumpTex.xy * vScale.xy);
     half4 vrefrB = tex2d(tex1,vProj.xy);
     return vRefrB * vRefra.w + vrefrA * (1-vRefrA.w);
}
这个方法解决了之前可能出现的问题,在大多数情况下都能很好的模拟折射。
 
19.3 示例
使用前面阐述的技术,可以实现很多有趣的效果。比如:使用法线图来动画纹理,或者改变纹理坐标。这一部分,我们将讨论一些具体的例子
19.3.1 水面模拟
         使用这里介绍的技术模拟水面折射是想当高效的,因为当前的方法都需要使用额外的 pass ,把水下的物体再渲染一次,来产生折射贴图。这里,我们用一个 pass 就能渲染水面,因为额外需要的工作,就是把水平面渲染到纹理 S alpha 通道中,作为折射遮罩 /
         Far Cry 里,我们使用了一张动画的凹凸纹理,效果相当好。在之后的实验中,我们使用了多个凹凸贴图层来模拟水面,并且根据菲涅耳对反射和折射进行混合,获得了更好的结果。
         为了渲染水面,先用如前所述的方法渲染场景,并把水面渲染到后背缓冲中的 alpha 通道作为折射遮罩。接下来,把水面之上的物体翻转到水面下,生成水面的反射贴图。最后,没有必要再用一个 pass 生成一张折射贴图。
         主要的纹理创建了之后,就可以渲染水面了。这里,我们使用了 4 个凹凸层。每层的的纹理坐标都在 vertex shader 中进行缩放,并且依次增加缩放值,这样才能生成低频到高频的水面波纹。为了模拟波纹的运动,我们还对纹理坐标进行了变换。下面的代码实现了这个方法:
Fresnel Approximation Computaton for Water Rendering
half Fresnel(half NdotL, half fresnelBias, half fresnelPow)
{
     half facing = (1.0 - NdotL);
     return max(fresnelBias + (1.0 - fresnelBias) * pow(facing,fresnelPow), 0.0;
}
The Fragment Program for Refraction/Reflection water
half4 main(float3 Eye: TEXCOORD0,
     float4 Wave0 : TEXCOORD1,
     float2 Wave1 : TEXCOORD2,
     float2 Wave2 : TEXCOORD3,
     float2 Wave3 : TEXCOORD4,
     float4 ScreenPos: TEXCOORD5,
     uniform sampler2D tex0,
     uniform sampler2D tex1,
     uniform sampler2D tex2) : COLOR
{
     half3 vEye = normalize(Eye);
     //get bump layer
     half3 vBumpTexA = tex2D(tex0, Wave0.xy).xyz;
     half3 vBumpTexB = tex2D(tex0, Wave1.xy).xyz;
     half3 vBumpTexC = tex2D(tex0, Wave2.xy).xyz;
     half3 vBumpTexD = tex2D(tex0, Wave3.xy)xyz;
    
     //average bump layer
     half3 vBumpTex = normalize(2.0 * (vBumpTexA.xyz + vBumpTexB.xyz+
                                           vBumpTexC.xyz + vBumpTexD.xyz) - 4.0);
     //apply individual bump scale for refraction and reflection
     half3 vRefrBump = vBumpTex.xyz * half3(0.075,0.075,1.0);
     half3 vReflBump = vBumptex.xyz * half3(0.02,0.02,1.0);
    
     //compute projected coordinates
     half2 vProj = (ScreenPos.xy/ScreenPos.w);
     half4 vReflection = tex2D(tex2, vProj.xy + vReflBump.xy);
     half4 vRefrA = tex2D(tex1, vProj.xy + vRefrBump.xy);
     half4 vRefrB = tex2D(tex1, vProj.xy);
    
     //Mask occluders from refracion map
     half4 vRefraction = vRefrB * vRefrA.w + vRefrA * (1-vRefrA.w);
    
     //compute fresnel term
     half NdotL = max(dot(vEye, vReflBump),0);
     half facing = (1.0 - NdotL);
     half fresnel = Fresnel(NdotL, 0.2, 5.0);
    
     //use distance to lerp between refraction and deep water color
     half fDistScale = saturate(10.0/Wave0,w);
     half3 WaterDeepColor = (vRefraction.xyz * fDistScale +
                               (1 - fDistScale) * half3(0,0.15,0.115);
    
     //lerp between water color and deep water color
     half3 WaterColor = half3(0, 0.15,0.115);
     half3 waterColor = (WaterColor * facing + WaterDeepColor * ( 1.0 - facing);
     half3 cReflect = fresnel * vReflection;
    
     //final water = reflection_color * fresnel + water_color
     return half4(cReflect + waterColor, 1);
}
 

点击这里下载完整代码

=======================================邪恶的分割线=====================

       本来还有一小节关于玻璃模拟的例子,因为原理和模拟水面差不多,就没有再翻了,呵呵。
       最近变懒了,每天下班,吃了饭,打阵wow就睡,导致Direct3D后面的部分一直没有弄出来。成都好热啊,唉,想回家了~~~
        哈哈,买了《ShaderX 4》和《The COMPLETE Effect and HLSL Guide》,不过可能要下个月才能寄过来,保佑这次邮递员能看懂e文,不要又弄错了-_-#
       最近MDX方面好像都没什么动静,8月的DX SDK出来了,依然没有多大变化,期待XNA Framwork的出现。
       在学OpenGL,习惯了对象技术,一下子看这种全部以函数形式组织的API,感觉好混乱。而且OpenGL1.1以上的函数还不能直接调用,实在是麻烦。

评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值