Unity3D教程:实现水面渲染(四)

发表于2016-09-03
评论15 1.89w浏览

想免费获取内部独家PPT资料库?观看行业大牛直播?点击加入腾讯游戏学院游戏程序行业精英群

711501594

  水面渲染在很多游戏项目中都会碰到,下面就分四篇文章去给大家介绍在Unity3D中水面渲染的实现方法,现在介绍的水面渲染(四)。


一、前言

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

二、概述

先来看看最后的实现效果:

 

目前为止,我们已经实现了水波的模拟,反射以及折射。上一张没有散射的效果图:

我们常看到的海水是呈现一种通透的蓝色,而我们现在还没有实现这种水的颜色,这章我们就来讨论下如何实现。

上面我们说到的水的颜色,其实是指的光线在水中散射后被我们眼睛所接受到的颜色。这个跟大气散射的原理其实是类似的。由于水中有微小的颗粒,光线射到这些颗粒上面就会产生散射,不同大小的颗粒产生散射的颜色是不同的,当然颗粒本身的颜色也会影响我们观察到的水的颜色。这种现象最明显的要数黄龙的五彩池了。

三、散射的计算

   

如上图,e点是我们视点所在位置,w是我们观察到的水底的某个点。现在我们已知水面的高度water_heighte点的位置_WorldSpaceCameraPos,以及w点的世界坐标worldPos,我们第一步需要求出wp线段的长度:

根据相似三角的定律,我们得出:

ow : hw = we :wp;

所以有:

wp = we * hw /ow;

根据上述已知条件有:

1
2
3
4
5
we =length(_WorldSpaceCameraPos.xyz - worldPos);
 
wh= water_height- worldPos.y;
 
ow =_WorldSpaceCameraPos.y;

带入后就可以求出wp的长度。

 

在得到wp的长度后,光线从w点穿过水,到达p点射出水面进入我们的眼中。在这里我们使用了一个假设条件:光线从p点射出后并未发生折射,这会大大简化我们的计算。在这个光线的传播过程中,不断有光线从wp线段上散射出去,我们可以使用指数函数exp来模拟这个光的衰减过程:

1
outScattering =exp(-attenuation*wp);

其中attenuation系数表示光的衰减系数。

除了上面光的out,在wp这个光路中,还会有不同的光从四面八方汇聚到wp线段上,进入我们的视点,我们称这个过程为in,这里给出一个简化的公式:

1
inScattering=diff_radiance*(1-outScattering*exp(-wh*kd));

上述的公式中,diff_radiance指水的辐射度。

四、实现散射

在实现上其实有两种大体的思路来实现散射,一种是专门为海底的物体写一个shader,这个shader里包含散射的计算;另外一种是专门用一个相机来渲染海底的场景并计算散射。在这里我选择第二种思路,第一种方法可能效率更高,不过第二种方法适用性更广更方便。

由于之前有了折射的相机来渲染海底的场景,所以我在这里就直接使用折射相机来渲染散射,然后直接把散射的计算结果叠加在折射图上。散射的参数则通过Shader.SetGlobal来传入。

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
sampler2D_MainTex;
 
float4_MainTex_ST;
 
sampler2D_UnScatteringRefrTex;
 
float3_WaterScatteringKd;
 
float3_WaterScatteringAttenuation;
 
float3_WaterScatteringDiffuseRadiance;
 
float_WaterAltitude;
 
                           
 
voidSimpleWaterScattering(half viewDist, half3 worldPos, half depth, half3diffuseRadiance,
 
                  half3attenuation_c, half3 kd, out half3 outScattering, out half3 inScattering)
 
{
 
         floatt = depth / (_WorldSpaceCameraPos.y - worldPos.y);
 
  
 
         //Water scattering
 
         floatd = viewDist*t;  // one way!
 
         outScattering= exp(-attenuation_c*d);
 
         inScattering= diffuseRadiance* (1 - outScattering*exp(-depth*kd));
 
}
 
                           
 
v2f vert(appdata v)
 
{
 
         v2fo;
 
         o.vertex= mul(UNITY_MATRIX_MVP, v.vertex);
 
         o.uv= TRANSFORM_TEX(v.uv, _MainTex);
 
         o.screenPos= ComputeScreenPos(o.vertex).xyw;
 
                                    
 
         float3worldPos = mul(_Object2World, v.vertex);
 
         float3outScattering = float3(1,0,0);
 
         float3inScattering = float3(1,0,0);
 
         float3viewVector = worldPos - _WorldSpaceCameraPos.xyz;
 
         floatdepth = max(0, _WaterAltitude - worldPos.y);
 
                           
 
         SimpleWaterScattering(length(viewVector),worldPos.xyz,depth,_WaterScatteringDiffuseRadiance,_WaterScatteringAttenuation, _WaterScatteringKd, outScattering, inScattering);
 
                                    
 
         o.outScattering= outScattering;
 
         o.inScattering= inScattering;
 
                                    
 
         returno;
 
}
 
                           
 
half4 frag (v2fi) : SV_Target
 
{
 
         //sample the texture
 
         half4result = half4(0,0,0,1);
 
         float2srcUV = i.screenPos.xy/i.screenPos.z;
 
                                    
 
         half4refractionColor = tex2D(_UnScatteringRefrTex, srcUV);
 
         result.xyz= refractionColor.xyz*i.outScattering+i.inScattering;
 
         result.w= refractionColor.w;
 
                           
 
         returnresult;
 
}


五、实现高光

高光的实现其实很简单,使用Blinn-Phone公式就行了:

 

1
2
3
float3 halfVector= normalize(-viewVector+normalize(_WorldSpaceLightPos0.xyz));
 
float3specularColor = pow(max(0, dot(halfVector, worldNormal)), _Shiness);

半角向量halfVector是指点到相机的向量和光照方向向量的和。在这里我直接是用的平行光,并没有考虑光源是点光源的情况。需要注意的是点到相机的向量的计算不要搞错方向了,正确的是(_WorldSpaceCameraPos - worldPos)

 

完整的源码下载:

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

游戏学院公众号二维码
腾讯游戏学院
微信公众号

提供更专业的游戏知识学习平台