一、实现思想
模拟水面的时候也是需要使用到噪声纹理的。
我们使用噪声纹理作为一个高度图,用来不断修改水面的法线方向。
为了模拟水不断流动的效果,我们使用和时间相关的变量来对噪声纹理进行采样,得到法线信息后,再进行正常的反射+折射计算,最后得到水面波动效果。
这里要使用菲涅尔反射,因为我们需要模拟出水的折射反射效果。
链接:之前菲涅尔的相关介绍
这里需要注意 斯涅尔定律 和 菲涅尔反射 的区别:
斯涅尔是同来计算折射时不同介质之间入射角与出射角的角度的,
而菲涅尔反射是用来模拟反射程度的,在车漆水面等等平面计算不同位置反射时用到的。
相关具体都在上面链接。
具体很类似之前的 透明玻璃效果,
先使用一张立方体纹理作为环境纹理,模拟反射。
我们使用GrabPass获取当前屏幕的渲染纹理,并使用切线空间下的法线方向对像素的屏幕坐标进行偏移,再使用该坐标对渲染纹理进行屏幕采样,从而模拟折射效果。
不同在于我们的水波法线纹理使用的是 一张噪声纹理生成而得,而且会随着时间变化不断平移,模拟水光粼粼效果,
同样,我们使用之前菲涅尔类似的动态系数计算公式进行混合反射和折射效果:
frenel=pow(1-max(0,v·n),4)
v和n是视角方向和法线方向,计算公式和之前的菲涅尔系数计算缺少了菲涅尔效果影响大小Scale的计算(可见上面链接),还改变了指数的大小(5变成4)。
用噪声纹理的灰度值来生成法线纹理,具体是通过纹理面板的纹理类型设置成Normal map,然后选中 Create from grayscale。
二、代码(详细见代码注释)
Shader "Unity Shaders Book/Chapter 15/Water Wave" {
Properties {
_Color ("Main Color", Color) = (0, 0.15, 0.115, 1) //控制水面颜色
_MainTex ("Base (RGB)", 2D) = "white" {} //水面波纹材质主纹理
_WaveMap ("Wave Map", 2D) = "bump" {} //噪声纹理生成的法线纹理
_Cubemap ("Environment Cubemap", Cube) = "_Skybox" {} //立方体纹理用于采样周围的反射环境
_WaveXSpeed ("Wave Horizontal Speed", Range(-0.1, 0.1)) = 0.01 //控制法线纹理在X、Y轴方向的平移速度
_WaveYSpeed ("Wave Vertical Speed", Range(-0.1, 0.1)) = 0.01
_Distortion ("Distortion", Range(0, 100)) = 10 //控制折射程度
}
SubShader {
Tags { "Queue"="Transparent" "RenderType"="Opaque" }
//队列设置设置,确保该物体渲染时,其他不透明物体已经被渲染到屏幕上了,保证可以透过水面看到其他
//渲染类型设置为了在使用着色器替换时,物体可以被正常渲染,这通常发生在需要获得摄像机的深度和法线纹理。
GrabPass { "_RefractionTex" } //获取深度图像
Pass {
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#include "UnityCG.cginc"
#include "Lighting.cginc"
#pragma multi_compile_fwdbase
#pragma vertex vert
#pragma fragment frag
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _WaveMap;
float4 _WaveMap_ST;
samplerCUBE _Cubemap;
fixed _WaveXSpeed;
fixed _WaveYSpeed;
float _Distortion;
sampler2D _RefractionTex;
float4 _RefractionTex_TexelSize; //纹理纹素大小
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float4 texcoord : TEXCOORD0;
};
struct v2f {
float4 pos : SV_POSITION;
float4 scrPos : TEXCOORD0;
float4 uv : TEXCOORD1;
float4 TtoW0 : TEXCOORD2;
float4 TtoW1 : TEXCOORD3;
float4 TtoW2 : TEXCOORD4;
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex); //裁剪空间转换
o.scrPos = ComputeGrabScreenPos(o.pos); //得到对应被抓取屏幕图像的采样坐标
o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex); //偏移缩放等计算
o.uv.zw = TRANSFORM_TEX(v.texcoord, _WaveMap);
float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; //得到顶点和切线空间到世界空间变换矩阵
fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w;
o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);
return o;
}
fixed4 frag(v2f i) : SV_Target {
float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
//通过w得到坐标顶点的 世界空间坐标
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
//得到视角方向
float2 speed = _Time.y * float2(_WaveXSpeed, _WaveYSpeed);
//得到偏移量
fixed3 bump1 = UnpackNormal(tex2D(_WaveMap, i.uv.zw + speed)).rgb;
//使用偏移量进行采样再从像素转换为法线变量
fixed3 bump2 = UnpackNormal(tex2D(_WaveMap, i.uv.zw - speed)).rgb;
//这里上面进行两次采样是因为我们模拟两层交叉的水面波动的效果
fixed3 bump = normalize(bump1 + bump2);
//相加后归一化,此时得到的是切线空间的法线纹理。
float2 offset = bump.xy * _Distortion * _RefractionTex_TexelSize.xy;
//使用我们获得的法线纹理对屏幕采样坐标进行偏移,模拟折射效果
i.scrPos.xy = offset * i.scrPos.z + i.scrPos.xy;
//将屏幕坐标加上偏移
fixed3 refrCol = tex2D( _RefractionTex, i.scrPos.xy/i.scrPos.w).rgb;
//再使用刚刚得到的屏幕坐标进行采样GrabPass得到的纹理,得到折射颜色
bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump)));
//将法线转换到世界空间
fixed4 texColor = tex2D(_MainTex, i.uv.xy + speed);
//采样主纹理,这里主纹理采样坐标也是要加上偏移的
fixed3 reflDir = reflect(-viewDir, bump);
//求出反射方向
fixed3 reflCol = texCUBE(_Cubemap, reflDir).rgb * texColor.rgb * _Color.rgb;
//使用反射方向采样立方体纹理,并乘上主材质纹理颜色,得到反射颜色
fixed fresnel = pow(1 - saturate(dot(viewDir, bump)), 4); //计算菲涅尔系数
fixed3 finalColor = reflCol * fresnel + refrCol * (1 - fresnel); //使用菲涅尔系数进行折射反射颜色混合
return fixed4(finalColor, 1);
}
ENDCG
}
}
//不需要投射阴影,所以我们 FallBack Off
FallBack Off
}