1、折射原理
在Unity Shader中,折射效果模拟了光线通过透明或半透明材质时的弯曲行为。一般用来模拟水面、透明玻璃球、眼镜、钻石、水晶球、空气扰动等等效果。它一般会配合其他表现效果一起使用。
折射效果的原理还是利用 立方体纹理(CubeMap)进行环境映射,我们利用摄像机看向物体表面顶点的方向向量作为入射向量,结合顶点法线向量算出折射向量,然后利用折射方向向量在立方体纹理中进行采样,得到最终反射的颜色。
我们在计算折射方向时,会用到斯涅耳定律(Snell’s Law):当光从介质1沿着表面法线夹角为θ1的方向斜射入介质2时我们可以利用数学公式 n1sinθ1=n2sinθ2 计算出折射光线和法线的夹角 θ2,其中n1和n2为两种介质的折射率
PS:我们在Unity中处理折射效果的做法是直接用得到的折射方向对立方体纹理进行采样,这样做其实不符合物理规律,因为对于一个透明物体来说,更准确的模拟方式应该是进行两侧折射,一次是光线进入内部,一次是光线从物体内部射出。但是,在实时渲染中模拟第二次折射方向较为复杂,而我们仅模拟一次折射得到的效果在视觉上看起来也是可以接受的!因此,在实时渲染中,我们通常仅模拟第一次折射来得到最终的结果!
2、折射的基础实现
折射向量计算函数:CG中提供了内置函数 refract 用于进行折射向量的计算
refract(入射方向单位向量, 顶点法线单位向量, 入射介质折射率 / 射入介质折射率)
便可以得到在目标介质中的折射向量
(1)属性声明,4个关键属性:介质A折射率、介质B折射率、立方体纹理贴图、折射程度
(2)编译指令、内置文件、属性映射、结构体相关
(3)顶点着色器
- 顶点坐标转裁剪坐标
- 顶点法线转世界坐标
- 顶点坐标转世界坐标
- 世界空间下 视角方向计算
- 利用折射函数计算折射向量
(4)片元着色器
- 立方体纹理采样(利用texCUBE函数)
- 结合折射程度返回最终颜色
Shader "ShaderProj/5/RefractBase"
{Properties{_RefractiveIndexA("RefractiveIndexA", Range(1,2)) = 1 // 介质A 折射率_RefractiveIndexB("RefractiveIndexA", Range(1,2)) = 1.3 // 介质B 折射率_Cube("CubeMap", Cube) = ""{}_RefracAmount("RefracAmout", Range(0, 1)) = 1 // 折射程度}SubShader{Tags { "RenderType"="Opaque" "Queue"="Geometry"}Pass{CGPROGRAM#pragma vertex vert#pragma fragment frag#include "UnityCG.cginc"samplerCUBE _Cube;fixed _RefractiveIndexA;fixed _RefractiveIndexB;fixed _RefracAmount;struct v2f{float4 pos : SV_POSITION;float3 worldRefr : TEXCOORD0;};v2f vert (appdata_base v){v2f data;data.pos = UnityObjectToClipPos(v.vertex);fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);fixed3 worldPos = mul(unity_ObjectToWorld, v.vertex);fixed3 worldViewDir = UnityWorldSpaceViewDir(worldPos);data.worldRefr = refract(-normalize(worldViewDir), normalize(worldNormal), _RefractiveIndexA / _RefractiveIndexB);return data;}fixed4 frag (v2f i) : SV_Target{fixed4 cubeMapColor = texCUBE(_Cube, i.worldRefr);return cubeMapColor * _RefracAmount;}ENDCG}}
}
3、带漫反射和阴影的折射效果
Shader "ShaderProj/5/Refract"
{Properties{_Color("Color", Color) = (1,1,1,1)_RefractColor("RefractColor", Color) = (1,1,1,1)//将折射率A和折射率B 合并为一个折射率比值变量【介质A折射率/介质B折射率】//以后在外部算好了传入,避免内部计算浪费性能_RefractRatio("RefractRatio", Range(0.1, 1)) = 0.5_Cube("CubeMap", Cube) = ""{}_RefracAmount("RefracAmout", Range(0, 1)) = 1 // 折射程度}SubShader{Tags { "RenderType"="Opaque" "Queue"="Geometry"}Pass{CGPROGRAM#pragma vertex vert#pragma fragment frag#pragma multi_compile_fwdbase#include "UnityCG.cginc"#include "Lighting.cginc"#include "AutoLight.cginc"fixed4 _Color;fixed4 _RefractColor;samplerCUBE _Cube;fixed _RefractRatio;fixed _RefracAmount;struct v2f{float4 pos : SV_POSITION;fixed3 worldNormal : NORMAL;float3 worldPos : TEXCOORD0;float3 worldRefr : TEXCOORD1;SHADOW_COORDS(2)};v2f vert (appdata_base v){v2f data;data.pos = UnityObjectToClipPos(v.vertex);data.worldNormal = UnityObjectToWorldNormal(v.normal);data.worldPos = mul(unity_ObjectToWorld, v.vertex);fixed3 worldViewDir = UnityWorldSpaceViewDir(data.worldPos);data.worldRefr = refract(-normalize(worldViewDir), normalize(data.worldNormal), _RefractRatio);TRANSFER_SHADOW(data);return data;}fixed4 frag (v2f i) : SV_Target{// 漫反射fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));fixed3 diffuse = _LightColor0 * _Color * max(0, dot(normalize(i.worldNormal), worldLightDir));fixed3 cubeMapColor = texCUBE(_Cube, i.worldRefr) * _RefractColor;UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);fixed3 color = UNITY_LIGHTMODEL_AMBIENT + lerp(diffuse, cubeMapColor, _RefracAmount) * atten;return fixed4(color, 1);}ENDCG}}Fallback "Reflective/VertexLit"
}
4、菲涅尔反射
菲涅耳反射是一种光学现象。简单的讲,就是视线垂直于观察表面时,反射较弱,(更多的光会透射到进入新介质);而当视线非垂直观察表面时,夹角越小,反射越明显。
真实世界中的例子:
如果你站在湖边,低头看脚下的水,你会发现水是透明的,反射不是特别强烈;如果你看远处的湖面,你会发现水并不是透明的,但反射非常强烈,这就是“菲涅耳效应”。
菲涅耳反射原理是描述光在两种介质的界面上反射和折射的行为。反射和折射的强度取决于光的入射角和两种介质的折射率差异。
从物理角度来看,世界上所有物体在光线照射下都会遵循菲涅耳反射原理!
这意味着光在任何两种介质的界面上都会发生反射和折射,只不过具体反射效果会因为介质的性
质和光的入射角度不同而有所变化。
无论是透明的、半透明的还是不透明的物体,只要有光线入射到其表面,菲涅耳反射都会发生。
在Unity Shader中,菲涅耳反射一般用来增强真实感,使物体表面在不同角度和光照条件下呈现出更加真实和自然的外观,增强视觉效果。
因为刚才我们提到真实世界中,万物皆遵循菲涅耳反射原理,
那么我们在游戏中 使用菲涅耳反射 比 直接使用反射 能更加接近真实世界的效果,在实现一些 金属感、陶瓷感、玻璃感 时效果更好!也就是说菲涅耳反射在Unity Shader当中实现出来的效果基于反射,但是会比反射更加接近真实的效果。
我们将使用菲涅耳等式来计算反射率。但是物理学中的菲涅耳等式是非常复杂的,如果在实时渲染中使用,计算非常复杂,因此我们通常会使用近似公式来计算,在Unity Shader当中我们会使用
Schlick 菲涅耳近似等式 来计算反射率,该等式是由 Christophe Schlick(克里斯托夫·施利克)于1994年提出的,它提出的菲涅耳近似等式极大的简化了菲涅耳反射率的计算,非常适用于实时渲染,同时它还保留了足够的物理真实性!
注意:通常情况下,我们所说的物体的反射率(或反射系数)通常是指垂直入射光线时的反射率
Shader "ShaderProj/5/FresnelBase"
{Properties{_Cube("Cubemap", Cube) = ""{}_FresnelScale("FresnelScale", Range(0,1)) = 1 //菲涅耳反射中 对应介质的反射率}SubShader{Tags{"RenderType"="Opaque" "Queue"="Geometry"}Pass{Tags{"LightMode"="ForwardBase"}CGPROGRAM#pragma vertex vert#pragma fragment frag#include "UnityCG.cginc"#include "Lighting.cginc"samplerCUBE _Cube;float _FresnelScale;struct v2f{float4 pos:SV_POSITION;float3 worldNormal:NORMAL;float3 worldViewDir:TEXCOORD0;//世界空间下的反射向量//我们将把反射向量的计算放在顶点着色器函数中 节约性能 表现效果也不会太差 肉眼几乎分辨不出来float3 worldRefl:TEXCOORD1;};v2f vert(appdata_base v){v2f data;data.pos = UnityObjectToClipPos(v.vertex);data.worldNormal = UnityObjectToWorldNormal(v.normal);fixed3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;data.worldViewDir = UnityWorldSpaceViewDir(worldPos);data.worldRefl = reflect(-data.worldViewDir, data.worldNormal);return data;}fixed4 frag(v2f i):SV_TARGET{fixed4 cubemapColor = texCUBE(_Cube, i.worldRefl);fixed fresnel = _FresnelScale + (1-_FresnelScale) * pow(1-dot(normalize(i.worldViewDir), normalize(i.worldNormal)), 5);return cubemapColor * fresnel;}ENDCG}}
}
5、带漫反射和阴影的菲涅尔反射
Shader "ShaderProj/5/Fresnel"
{Properties{_Color("Color", Color) = (1,1,1,1)_Cube("Cubemap", Cube) = ""{}_FresnelScale("FresnelScale", Range(0,1)) = 1}SubShader{Tags{"RenderType"="Opaque" "Queue"="Geometry"}Pass{Tags{"LightMode"="ForwardBase"}CGPROGRAM#pragma vertex vert#pragma fragment frag#pragma multi_compile_fwdbase#include "UnityCG.cginc"#include "Lighting.cginc"#include "AutoLight.cginc"fixed4 _Color;samplerCUBE _Cube;float _FresnelScale;struct v2f{float4 pos:SV_POSITION;fixed3 worldNormal:NORMAL;float3 worldPos:TEXCOORD0;float3 worldViewDir:TEXCOORD1;float3 worldRefl:TEXCOORD2;SHADOW_COORDS(3)};v2f vert(appdata_base v){v2f o;o.pos = UnityObjectToClipPos(v.vertex);o.worldNormal = UnityObjectToWorldNormal(v.normal);o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;o.worldViewDir = UnityWorldSpaceViewDir(o.worldPos);o.worldRefl = reflect(-o.worldViewDir, o.worldNormal);TRANSFER_SHADOW(o);return o;}fixed4 frag(v2f i):SV_TARGET{fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));fixed3 diffuse = _LightColor0.rgb * _Color.rgb * max(0, dot(normalize(i.worldNormal), worldLightDir));fixed3 cubemapColor = texCUBE(_Cube, i.worldRefl).rgb;UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);fixed fresnel = _FresnelScale + (1-_FresnelScale)*pow(1 - dot(normalize(i.worldViewDir), normalize(i.worldNormal)), 5);fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb + lerp(diffuse, cubemapColor, fresnel) * atten;return fixed4(color, 1.0);}ENDCG}}FallBack "Reflective/VertexLit"
}