Unity3D教程:如何实现衍射效果

发表于2016-06-13
评论0 2.9k浏览

在一些项目开发中需要考虑光的衍射,那么这种衍射效果要怎么做呢,下面就给大家介绍下Unity3D中实现衍射效果的实现方法,一起来看看吧。


一、前言

本文旨在与大家一起探讨学习新知识,如有疏漏或者谬误,请大家不吝指出。

以下内容参考了GPU精粹中第8章衍射的相关内容。

下面是一些效果截图:

 

二、概述

回忆一下物理学中关于波的衍射。当波在通过一个窄缝的时候会发生不同程度的弯散传播。窄缝的大小一般为波的波长同等级大小。可见光的波长在0.5微米到1微米之间,那么可见光要发生衍射现象,则窄缝的大小数量级也应该在微米范围内。图1展示了这种现象。

1

 

可以看到,在图1中,光通过窄缝后以球面波的形式继续传播。这是一条很重要的结论。

2

3

当有两个窄缝时,两个球面波会产生干涉现象,图2展示的是经典的双缝干涉现象。两个波互相叠加或者减弱,结果就会出现彩色的条带,如图3所示。

4

4是一个CD的背面,CD表面很光滑,但是在微米数量级上,它对于可见光来说就是一排排平行的光栅。可见光在射到这些光栅上面时也会产生衍射现象。我们今天要实现的就是一个CD的衍射效果。

三、原理

5

如图5所示,P1P2CD表面上的两个点,Sun Light是可见光的照射方向,ViewP点到视点的方向。由于衍射发生在微米级别,就是说P1P2之间的距离是以微米计量的,所以我们可以认为Sun LightView都是平行的。现在我们考虑衍射的现象。在P1P2点可见光发生衍射,我们只考虑衍射后光波相位相同的情况,当光波相位相同时,波峰再叠加波峰,可见光得到增强。如图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中心。切线的问题确实是一个比较烦人的问题,如果这个不解决,很难将衍射以及各向异性光照推广到普通模型。后面可能会考虑是否能用法线贴图来模拟切线方向。当然,就我这么懒的性子来说,来日不可期。大家有兴趣可以试试,成功了记得告诉我。

下面照例给出完整的实例程序。

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引