XNA Shader编程教程16-折射
本教程我们将实现折射,首先要建立环境映射shader,所以请先理解教程15。
折射
折射是指当光线从一种介质进入另一种介质时发生传播方向发生改变的现象,改变量的大小是基于介质密度的(对应光在此介质中的传播速度),对应的系数叫做折射率。在现实世界中,当你将一支钢笔放入水中时,游泳时,透过玻璃宝石观察时就会看到折射现象。
每种介质的折射率不同,下面是常见介质的折射率表:
真空 | 1.00000 |
标准状态下的空气 | 1.00029 |
冰 | 1.31 |
20℃的水 | 1.33 |
丙酮 | 1.36 |
酒精 | 1.36 |
糖溶液(浓度30%) | 1.38 |
萤石 | 1.433 |
熔石英 | 1.46 |
甘油 | 1.473 |
斯涅耳定律(http://en.wikipedia.org/wiki/Snell's_law) :
n1sinα1=n2sinα2
公式中n1和n2分别是两个介质的折射系数,α1 是L和N之间的夹角,α2是Q和N的夹角。
HLSL有一个内置函数refract,这个函数根据斯涅耳定律基于入射向量、法线和折射率的比值计算折射向量。
n1和n2的相对折射率可根据下式计算:
Ratio R=n1/n2
在本例中,我们的光线是从空气射入玻璃。如果看一下上面的表格,空气的折射率是1.00029,玻璃是1.52,相对折射率是0.66。
Refract函数会返回一个向量,作为立方映射的查询向量。
实现shader
首先是the vertex shader:
OUT VertexShaderRefract( float4 Pos: POSITION, float2 Tex : TEXCOORD, float3 N:NORMAL ) { OUT Out = (OUT) 0; Out.Pos = mul(Pos, matWorldViewProj); Out.N = normalize(mul(matInverseWorld, N)); Out.V = vecEye - Pos; return Out; }
我们需要法线和观察向量计算折射。也可以使用光线向量,但这里我想折射观察向量。下一步是pixel shader:
float4 PixelShaderRefract(float2 Tex: TEXCOORD0,float3 L: TEXCOORD1, float3 N: TEXCOORD2, float3 V: TEXCOORD3) : COLOR { float3 ViewDir = normalize(V); float3 Refract = refract(ViewDir, N, 0.66); float3 RefractColor = texCUBE(ReflectionCubeMapSampler, Refract); // return the color return float4(RefractColor,1); }
在pixel shader中,我们使用refract函数获取从空气射向水的折射向量,并将这个向量存储在Refract变量中,接着使用这个变量查询像素的颜色。
大多数情况中,你会将折射和其他shader一起使用,包括反射,颜色贴图,法线贴图等。在本教程中,我们在一个post-process shader中组合这些效果。我们已经知道,透射shader将一个包含环境的纹理作为透射体,将这个纹理传递到shader计算透射。我们现在可以在这个纹理传递到shader之前添加折射效果。
现在,我们可以使用这个背景纹理作为显示在透射网格后面的纹理,要做到这点方法很多,但为了保持简单并抓住重点,我们使用了以下方法。下面是technique:
technique RefractionMapShader { pass P0 { VertexShader = compile vs_2_0 VertexShaderRefract(); PixelShader = compile ps_2_0 PixelShaderRefract(); } }
这就是折射shader,很简单不是吗?这可以让你完全控制shader,让你可以折射RGB的不同分量。下面是绘制结果的区别。一个有反射透射,另一个除了有反射透射还有折射:
左图没有折射,右图有折射
使用shader
我们需要为折射贴图创建一个渲染目标和纹理,使用这个纹理作为透射shader的背景纹理。
// render refraction map graphics.GraphicsDevice.SetRenderTarget(0, RefractionRenderTarget); graphics.GraphicsDevice.Clear(Color.White); spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.SaveState); { spriteBatch.Draw(BackGroundRenderTexture, new Rectangle(0, 0, 800, 600), Color.White); } spriteBatch.End(); GraphicsDevice.RenderState.CullMode = CullMode.None; effect.CurrentTechnique = refractMapShader; DrawScene(true); graphics.GraphicsDevice.SetRenderTarget(0, null); RefractionRenderTexture = RefractionRenderTarget.GetTexture();
我们创建了折射贴图,在背景纹理中添加折射效果并把它保存到一个叫做RefractionRenderTexture的新纹理中。现在,当使用透射post-process shader时,我们把这个纹理作为背景纹理:
effectPost.Parameters["BGScene"].SetValue(RefractionRenderTexture);
发布时间:2009/5/15 下午3:42:49 阅读次数:9290