Unity3D教程:如何实现衍射效果
在一些项目开发中需要考虑光的衍射,那么这种衍射效果要怎么做呢,下面就给大家介绍下Unity3D中实现衍射效果的实现方法,一起来看看吧。
一、前言
本文旨在与大家一起探讨学习新知识,如有疏漏或者谬误,请大家不吝指出。
以下内容参考了GPU精粹中第8章衍射的相关内容。
下面是一些效果截图:
二、概述
回忆一下物理学中关于波的衍射。当波在通过一个窄缝的时候会发生不同程度的弯散传播。窄缝的大小一般为波的波长同等级大小。可见光的波长在0.5微米到1微米之间,那么可见光要发生衍射现象,则窄缝的大小数量级也应该在微米范围内。图1展示了这种现象。
图1
可以看到,在图1中,光通过窄缝后以球面波的形式继续传播。这是一条很重要的结论。
图2
图3
当有两个窄缝时,两个球面波会产生干涉现象,图2展示的是经典的双缝干涉现象。两个波互相叠加或者减弱,结果就会出现彩色的条带,如图3所示。
图4
图4是一个CD的背面,CD表面很光滑,但是在微米数量级上,它对于可见光来说就是一排排平行的光栅。可见光在射到这些光栅上面时也会产生衍射现象。我们今天要实现的就是一个CD的衍射效果。
三、原理
图5
如图5所示,P1和P2是CD表面上的两个点,Sun Light是可见光的照射方向,View是P点到视点的方向。由于衍射发生在微米级别,就是说P1、P2之间的距离是以微米计量的,所以我们可以认为Sun Light和View都是平行的。现在我们考虑衍射的现象。在P1、P2点可见光发生衍射,我们只考虑衍射后光波相位相同的情况,当光波相位相同时,波峰再叠加波峰,可见光得到增强。如图6所示。
图6
所以为了保证B点和P2点光波相位相同,我们需要下列等式成立:
其中n是任意的正整数,入是光的波长(单个特殊字符显示不出来,这里用入替代)。由于可见光的波长范围在0.5~1 um之间,所以可以算出n的范围:
当确定了P1P2的长度后,带入不同的整数n,求出接收点的波长,然后将所有波长对应的颜色相加即可得出可见光衍射的颜色。
所以我们还有一个问题要解决,就是波长对应的颜色。什么是波长对应的颜色?我们说波长1um的可见光是红色的,这里就存在一个对应关系,即1um对应RGB值(1,0,0)。所以在我们求出接受点的波长后,我们还需要将波长转换成计算机能够理解的RGB值。而GPU精粹中给出的一个映射函数是:
其中波长参数的值范围是0~1,而可见光的范围是0.5~1,所以这里对波长再做一个箝位:
在GPU精粹给出的代码中,并不是直接计算,而是做了一些转换,转换的推导过程如下:
l 公式1是三角函数的和差化积公式;
l 公式2是三角函数的正余弦转换公式;
l 公式3直接根据余弦定义得出,即余弦值等于直角三角形的侧边除以斜边;
l 公式4大家很熟悉了,可以通过图很容易得出半角向量与切线向量的夹角与α、β的关系;
带入后最终得到公式5。
四、实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 | float3 blend3(float3 x) { float3y = 1-x*x; y= max(y, float3(0,0,0)); returny; } //Fresnel approximation, power = 5 floatFastFresnel(float3 V, float3 H, float R0) { float icosVN = saturate(1-dot(V, H)); float i2 = icosVN*icosVN, i4 = i2*i2; return R0 + (1-R0)*(i4*icosVN); } v2fvert (appdata v) { v2fo; o.vertex= mul(UNITY_MATRIX_MVP, v.vertex); //o.uv= TRANSFORM_TEX(v.uv, _MainTex); // o.normal= mul((float3x3)UNITY_MATRIX_IT_MV, v.normal); //idont know how to build tangent in 3dmax, so... v.tangent= cross(v.normal, normalize(v.vertex)); o.tangent= mul((float3x3)UNITY_MATRIX_IT_MV, v.tangent); o.pos= mul(UNITY_MATRIX_MV, v.vertex); // float3worldPos = mul(_Object2World, v.vertex); float3viewDir = normalize(_WorldSpaceCameraPos.xyz - worldPos); float3worldNormal = (mul((float3x3)_Object2World, v.normal)); float3half = normalize(viewDir + _WorldSpaceLightPos0.xyz); o.worldRefl.xyz= reflect(-viewDir, worldNormal); o.worldRefl.w= FastFresnel(viewDir, half, _fresnelFactor); UNITY_TRANSFER_FOG(o,o.vertex); returno; } float4frag (v2f i) : SV_Target { //sample the texture float4col = float4(0,0,0,1); //tex2D(_MainTex, i.uv); float3pos = i.pos; float3lightPos = mul(_World2Object, _WorldSpaceLightPos0.xyz); lightPos= mul(UNITY_MATRIX_MV, lightPos); float3cameraPos = mul(_World2Object, _WorldSpaceCameraPos.xyz); cameraPos= mul(UNITY_MATRIX_MV, cameraPos); float3lightDir = normalize(lightPos - pos); float3viewDir = normalize(cameraPos - pos); float3half = lightDir + viewDir; float3normal = normalize(i.normal); float3tangent = normalize(i.tangent); floatu = dot(tangent, normalize(half)); u= 2*u*dot(normalize(half), viewDir); floatvz = dot(normal, half); floate = u*_roughX/vz; floatc = exp(-e*e); float4anis = _hiliteColor*c.xxxx; anis.w= 1; u= u*_spacingX; if (u< 0) u= -u; floatvx0; float3envColor = texCUBE(_Env, i.worldRefl.xyz); floatfresnel = i.worldRefl.w; float4cdiff = float4(0,0,0,1); for (inti=0; i < 7; i++) { vx0= 2*u/i-1; cdiff.xyz+= blend3(float3(4*(vx0-0.75), 4*(vx0-0.5), 4*(vx0-0.25))); } col= (0.8*cdiff + anis); col.xyz= _Color + col.xyz; col.xyz= lerp(col.xyz, envColor, fresnel); //apply fog UNITY_APPLY_FOG(i.fogCoord,col); returncol; } |
这里基本上将全部代码都给出来了。除了光的衍射以外,代码中还简单计算了光的各向异性,即anis项的值。公式大家直接看代码吧,由于不是本章的重点,不多做说明。另外,我额外加入了反射的计算,反射需要考虑到fresnel效应,这里也不做说明,大家有兴趣可以搜一下菲涅尔公式。
五、改进
在顶点着色器代码中有这么一句v.tangent = cross(v.normal, normalize(v.vertex))。
这是手动计算CD的切线。因为我并不知道怎样在3dmax中可以设置CD的切线,让它绕着CD中心。切线的问题确实是一个比较烦人的问题,如果这个不解决,很难将衍射以及各向异性光照推广到普通模型。后面可能会考虑是否能用法线贴图来模拟切线方向。当然,就我这么懒的性子来说,来日不可期。大家有兴趣可以试试,成功了记得告诉我。
下面照例给出完整的实例程序。