玻璃效果实现原理:
先抓取一张玻璃后面景象的贴图tex;将tex根据玻璃法线纹理做一个扭曲得到 color1;
计算玻璃本身的反射颜色(使用上篇说得天空盒子)color2;
将color1和color2按照一定比例混合;

Shader "Unity Shaders Book/Chapter 10/Glass Refraction" {
	Properties {
		_MainTex ("Main Tex", 2D) = "white" {}
		_BumpMap ("Normal Map", 2D) = "bump" {}
		_Cubemap ("Environment Cubemap", Cube) = "_Skybox" {}
		_Distortion ("Distortion", Range(0, 10000)) = 10 //扭曲程度
		_RefractAmount ("Refract Amount", Range(0.0, 1.0)) = 1.0 //混合系数,反射和屏幕截取图混合系数
	}
	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 _BumpMap;
			float4 _BumpMap_ST;
			samplerCUBE _Cubemap;
			float _Distortion;
			fixed _RefractAmount;
			sampler2D _RefractionTex;
			float4 _RefractionTex_TexelSize;//纹素  纹素=1/像素 256*256图片的纹素大小为(1/256,1/256),就是单位像素的大小,作为屏幕大小的度量单位
			
			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, _BumpMap);
				
				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(_BumpMap, i.uv.zw));
				
				// 计算法线空间中的扭曲偏移量
				// 详解:法线的x,y 方向 * 扭曲 * 纹素(单位) = 纹素单位下的偏移
				float2 offset = bump.xy * _Distortion * _RefractionTex_TexelSize.xy;
				//对原屏幕获取坐标的z方向做一个偏移,其实不使用i.scrPos.z 也可以的,这里的作用暂时不清楚,希望有好心人解惑!
				i.scrPos.xy = i.scrPos.xy + offset * i.scrPos.z; 
				//计算折射颜色,关于i.scrPos.xy/i.scrPos.w 其实是屏幕坐标,其原理在后面会详说
				fixed3 refrCol = tex2D(_RefractionTex, i.scrPos.xy/i.scrPos.w).rgb;
				
				//将切线空间法线转为世界空间下
				bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump)));
				fixed3 reflDir = reflect(-worldViewDir, bump);
				fixed4 texColor = tex2D(_MainTex, i.uv.xy);
				//计算反射颜色
				fixed3 reflCol = texCUBE(_Cubemap, reflDir).rgb * texColor.rgb;
				
				//反射折射的混合比例
				fixed3 finalColor = reflCol * (1 - _RefractAmount) + refrCol * _RefractAmount;
				
				return fixed4(finalColor, 1);
			}
			
			ENDCG
		}
	}
	
	FallBack "Diffuse"
}

关于GrabPass的使用说明如下图,摘自《UnityShader入门精要》 冯乐乐!

Unity ugui雨玻璃效果 unity怎么做玻璃_shader


Unity ugui雨玻璃效果 unity怎么做玻璃_Unity ugui雨玻璃效果_02

填坑(下面的推导都是建立在透视投影的基础上)

1、视口坐标推导

文章中唯一比较难懂的地方是下面代码的 i.scrPos.xy/i.scrPos.w ,其实在之前的镜像水面的时候已经用过了,但是当时没有去深入研究,几天来填这个坑。

o.pos = UnityObjectToClipPos(v.vertex);
o.scrPos = ComputeGrabScreenPos(o.pos);
.
.
.
.
fixed3 refrCol = tex2D(_RefractionTex, i.scrPos.xy/i.scrPos.w).rgb;

首先,我们要知道这个东西计算的是 模型的 “视口坐标”! ,在Unity当中,视口空间的左下角是(0,0),右上角是(1,1),就是像素宽,像素高。
下面开始推导:

1、整个过程梳理:
①模型空间->剪裁空间: UnityObjectToClipPos(v.vertex);
②剪裁空间->视口空间 (使用齐次除法转化成2D坐标)
PS. ComputeGrabScreenPos(o.pos);这步其实只是为计算屏幕2D坐标做了准备
其中①又可以分成(Unity已经帮我们封装好了方法,但是要知根知底):
(1)模型空间->世界空间
(2)世界空间->观察空间/相机空间
(3)观察空间->剪裁空间 (使用投影矩阵进行转换)

2、我们令o.pos = (clipx,clipy,clipz,clipw)
PS后面会有剪裁空间相关的推导

3、把顶点从 剪裁空间 投影到 视口空间 生成对应的2D坐标,我们常用的方法是 齐次除法(透视除法) ,就是用齐次坐标系的 x、y、z 分量去除以 w 分量。 在OpenGL中,我们把这一步叫做 归一化的设备坐标(NDC)

因此我们可以得到屏幕空间的 (x,y,z) = (clipx/clipw, clipy/clipw, clipz/clipw)

剪裁空间 经过 齐次除法(透视除法) 后会变换到一个立方体内。在OpenGL中,这个立方体的x、y、z∈[-1,1],而在DirectX中x、y、z∈[0,1],Unity采用了OpenGL的齐次剪裁空间,如下图。

Unity ugui雨玻璃效果 unity怎么做玻璃_unity_03


4、接下来只要把 x、y∈[-1,1]转化到 [0,1]的区间内(视口空间的下的坐标是∈[0,1]) ,我们使用线性插值计算一下视口空间坐标

Unity ugui雨玻璃效果 unity怎么做玻璃_图形学_04

Unity ugui雨玻璃效果 unity怎么做玻璃_图形学_05

Unity ugui雨玻璃效果 unity怎么做玻璃_图形学_06

Unity ugui雨玻璃效果 unity怎么做玻璃_d3_07

Unity ugui雨玻璃效果 unity怎么做玻璃_d3_085、我们再来看看 i.scrPos 是什么东西

Unity ugui雨玻璃效果 unity怎么做玻璃_图形学_09


上图是从Unity的 “UnityCG.cginc” 库中找到的关于计算屏幕坐标的定义,其中宏定义UNITY_UV_STARTS_AT_TOP是判断图形api平台是否为non-OpenGL,我们是使用的OpenGL,所以scale = 1

我们得到 o = (clipx/2 + clipw/2 , clipy/2 + clipw/2 , clipz , clipw )

所以 i.scrPos.xy/i.scrPos.w 就是

Unity ugui雨玻璃效果 unity怎么做玻璃_unity_10

Unity ugui雨玻璃效果 unity怎么做玻璃_unity_11

是不是与我们上面计算的视口坐标一致!

2、透视投影的投影矩阵的推导

如下图, 透视投影 是将一个视椎体变换到一个正方体上

Unity ugui雨玻璃效果 unity怎么做玻璃_d3_12


Unity ugui雨玻璃效果 unity怎么做玻璃_d3_13


如上图所示,投影矩阵是将 视椎体 内的点 投射到 近平面 上,以上图的 P 点为例,投影点 P’ 点是 P与原点O在近平面上的交点,设P=(x,y,z),P’=(x’,y’,z’)

利用差值算法
Unity ugui雨玻璃效果 unity怎么做玻璃_shader_14
Unity ugui雨玻璃效果 unity怎么做玻璃_unity_15
下面我们用反推的方法来计算
Unity ugui雨玻璃效果 unity怎么做玻璃_图形学_16
由上面最后一次经齐次除法可知,?2 = -z ,所以可以推出 b1=b2=b4=0 ,b3=-1,所以上式转为
Unity ugui雨玻璃效果 unity怎么做玻璃_unity_17

我们知道P(x,y,z)是三角片元上的三条边上的某一个点,因此xyz两两之间存在着线性关系,我们可以得到
Unity ugui雨玻璃效果 unity怎么做玻璃_d3_18
Unity ugui雨玻璃效果 unity怎么做玻璃_d3_19
Unity ugui雨玻璃效果 unity怎么做玻璃_shader_20
所以我们可以令 a1 =a2=0 a3=a a4=b
Unity ugui雨玻璃效果 unity怎么做玻璃_Unity ugui雨玻璃效果_21
Unity ugui雨玻璃效果 unity怎么做玻璃_unity_22
接下来我们还要把x、y限制在[-1,1],我们设限制后的点为(x’’,y’’)
Unity ugui雨玻璃效果 unity怎么做玻璃_图形学_23
Unity ugui雨玻璃效果 unity怎么做玻璃_Unity ugui雨玻璃效果_24
因此我们得到完整的投影矩阵为
Unity ugui雨玻璃效果 unity怎么做玻璃_d3_25

针对《UnityShader入门精要》中的情形做变形

只要把对应的

n=Near

f=Far

r=nearClipPlaneWidth/2

l=-nearClipPlaneWidth/2

t=nearClipPlaneHeight/2

b-nearClipPlaneHeight/2

∠FOV对应的转化带入及可到Mfrustum

计算涉及到的相关图片已经在下面列出

Unity ugui雨玻璃效果 unity怎么做玻璃_d3_26


Unity ugui雨玻璃效果 unity怎么做玻璃_d3_27


Unity ugui雨玻璃效果 unity怎么做玻璃_shader_28


Unity ugui雨玻璃效果 unity怎么做玻璃_d3_29


Unity ugui雨玻璃效果 unity怎么做玻璃_d3_30