Glass Refraction Shader

在阅读《Unity Shader入门精要》时,跟着此书实现了案例shader,今天把第十章介绍的GlassRefractionShader复现了一遍。这是一个很有趣的效果,表现玻璃的纹理折射。记录一下。感谢乐乐女神~

shader效果:

material参数:

关键技术点

对于实现玻璃的纹理折射,主要是两块,一是对环境的反射reflection,二是对背景的折射refraction。将二者融合在一起,就是最后的效果了。

反射

反射的话,反射的是对象周围的环境,环境存在cubemap中,在shader中采样即可:

1
2
3
4
bump = normalize(half3(dot(i.TtoW0.xyz,bump),dot(i.TtoW1.xyz,bump),dot(i.TtoW2.xyz,bump)));
fixed3 reflectDir = reflect(-worldViewDir, bump);
fixed4 texColor = tex2D(_MainTex,i.uv.xy);
fixed3 reflColor = texCUBE(_Cubemap,reflectDir).rgb * texColor.rgb;

主要是,反射需要采样点的法线,如果只是正方体的法线的话,效果就不有趣了,书中用的是法线纹理贴图使得cube表面凹凸不平,反射的效果就更加有趣。并且由于文章所用的法线纹理定义在切线空间,因此需要将切线空间的normal转到世界空间。

折射

折射的话,折射的是背景图片,在shader中加上GrabPass,存在一张纹理中,同样在shader中采样即可。

1
2
3
4
fixed3 bump = UnpackNormal(tex2D(_BumpTex, i.uv.zw));
float2 offset = bump.xy * _Disortion * _RefractionTex_TexelSize.xy;
i.scrPos.xy = i.scrPos.xy + offset.xy;
fixed3 refrColor = tex2D(_RefractionTex,i.scrPos.xy / i.scrPos.w).rgb;

如第二章图所示,在材质面板定义了_Disortion(emmm,我的命名应该写成distortion才对==),这个参数是用来对折射进行偏移处理的。_Disortion越小,对于背景折射的就越清晰,看起来cube表面就越光滑;反之_Disortion越大,cube表面看起来就越凹凸不平。由上面这段代码,可见变量offset中偏移的方向与数值是由采样点的法向决定的,因此,offset比重越大,对表面法向的反应就越明显。
_Disortion的有趣效果如下图所示:
_Distortion=100, _RefractionAmount=0.691:

_Distortion=1, _RefractionAmount=0.691:

完整shader

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
Shader "chp10/GlassRefractionShader"
{
Properties
{
_MainTex("Texture", 2D) = "white" {}
_BumpTex("Normal map",2D) = "while"{}
_Cubemap("Environment Cubemap",Cube) = "_Skybox"{}

_Disortion("Disortion",Range(0,100)) = 10
_RefractionAmount("RefractionAmount",Range(0,1)) = 0.5
}
SubShader
{
Tags {"Queue" = "Transparent" "RenderType" = "Opaque" }

GrabPass{"_RefractionTex"}

Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag

#include "UnityCG.cginc"

sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _BumpTex;
float4 _BumpTex_ST;
samplerCUBE _Cubemap;

float _Disortion;
fixed _RefractionAmount;

sampler2D _RefractionTex;
float4 _RefractionTex_TexelSize;

struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float2 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, _BumpTex);

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);
fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(worldPos));

fixed3 bump = UnpackNormal(tex2D(_BumpTex, i.uv.zw));
float2 offset = bump.xy * _Disortion * _RefractionTex_TexelSize.xy;
i.scrPos.xy = i.scrPos.xy + offset.xy;


//折射:从grabmap读
fixed3 refrColor = tex2D(_RefractionTex,i.scrPos.xy / i.scrPos.w).rgb;

//反射:从cubemap读
bump = normalize(half3(dot(i.TtoW0.xyz,bump),dot(i.TtoW1.xyz,bump),dot(i.TtoW2.xyz,bump)));
fixed3 reflectDir = reflect(-worldViewDir, bump);
fixed4 texColor = tex2D(_MainTex,i.uv.xy);
fixed3 reflColor = texCUBE(_Cubemap,reflectDir).rgb * texColor.rgb;
//混合
fixed3 finalColor = reflColor * (1 - _RefractionAmount) + refrColor * _RefractionAmount;
return fixed4(finalColor, 1);
}
ENDCG
}
}
}


参考文献

Unity Shader 入门精要