【unity shader 入门精要 读书笔记】折射

当光线从一种介质【例如空气】斜射入另外一种介质【例如玻璃】时,传播方向一般会发生改变。

当给定入射角时,可以使用 斯涅耳定律【Snell's Law】来计算反射角。

当光从介质 1 沿着和表面法线夹角的方向斜射入介质 2 时,可以使用如下公式计算折射光线和法线的夹角

【unity shader 入门精要 读书笔记】折射

 

其中 n1 和 n2 分别是两个介质的折射率【index of refraction】

折射率是一项重要的物理常数,例如:

真空中的折射率为1,玻璃的折射率一般是1.5

如图:

【unity shader 入门精要 读书笔记】折射

 

当得到折射方向后就会这件使用它来对立方体纹理进行采样,但这是不符合物理规律的。

对一个透明的物体来说,一种更准确的模拟方法需要计算两次折射,一次是当光线进入它的内部时,而另一次则是从它内部射出时,当时,想要在实时渲染中模拟出第二次折射方向是比较复杂的,因此通常仅模拟第一次折射。

 

Shader "Custom/Refraction" {
    Properties {
        _Color ("Color Tint", Color) = (1, 1, 1, 1)
        //控制折射的颜色
        _RefractColor ("Refraction Color", Color) = (1, 1, 1, 1)
        //控制材质的折射程度
        _RefractAmount ("Refraction Amount", Range(0, 1)) = 1
        //不同介质的投射比,以此计算折射方向
        _RefractRatio ("Refraction Ratio", Range(0.1, 1)) = 0.5
        //用于模拟折射的环境纹理映射
        _Cubemap ("Refraction Cubemap", Cube) = "_Skybox" {}
    }
    SubShader {
        Tags { "RenderType"="Opaque" "Queue"="Geometry"}
        
        Pass { 
            Tags { "LightMode"="ForwardBase" }
        
            CGPROGRAM
            
            #pragma multi_compile_fwdbase    
            
            #pragma vertex vert
            #pragma fragment frag
            
            #include "Lighting.cginc"
            #include "AutoLight.cginc"
            
            fixed4 _Color;
            fixed4 _RefractColor;
            float _RefractAmount;
            fixed _RefractRatio;
            samplerCUBE _Cubemap;
            
            struct a2v {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };
            //在顶点着色器中计算折射方向
            struct v2f {
                float4 pos : SV_POSITION;
                float3 worldPos : TEXCOORD0;
                fixed3 worldNormal : TEXCOORD1;
                fixed3 worldViewDir : TEXCOORD2;
                fixed3 worldRefr : TEXCOORD3;
                SHADOW_COORDS(4)
            };
            
            v2f vert(a2v v) {
                v2f o;
                o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
                
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                
                o.worldPos = mul(_Object2World, v.vertex).xyz;
                
                o.worldViewDir = UnityWorldSpaceViewDir(o.worldPos);
                //使用CG的refract 函数计算折射方向
                //参数一为入射光线的方向,它必须是归一化后的矢量
                //参数二是表面法线,同样需要归一化
                //参数三是入射光线所在介质的折射率和折射光线所在介质的折射率之间的比值。
                //【例如,如果光从空气中射到玻璃表面,那么这个比值是 1/1.5】
                //最后,这个函数返回值就是计算而得的折射方向,它的模则等于入射光线的模
                o.worldRefr = refract(-normalize(o.worldViewDir), normalize(o.worldNormal), _RefractRatio);
                
                TRANSFER_SHADOW(o);
                
                return o;
            }
            //在片元着色器中使用折射方向对立方体纹理进行采样
            fixed4 frag(v2f i) : SV_Target {
                fixed3 worldNormal = normalize(i.worldNormal);
                fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
                fixed3 worldViewDir = normalize(i.worldViewDir);
                                
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                
                fixed3 diffuse = _LightColor0.rgb * _Color.rgb * max(0, dot(worldNormal, worldLightDir));
                //因为对立方体纹理的采样只需要提供方向即可,不要进行归一化
                fixed3 refraction = texCUBE(_Cubemap, i.worldRefr).rgb * _RefractColor.rgb;
                
                UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
                
                //使用_RefractAmount混合漫反射颜色和折射颜色,并和环境光照相加后返回
                fixed3 color = ambient + lerp(diffuse, refraction, _RefractAmount) * atten;
                
                return fixed4(color, 1.0);
            }
            
            ENDCG
        }
    } 
    FallBack "Reflective/VertexLit"
}

 

调节参数可以得到以下效果

 

【unity shader 入门精要 读书笔记】折射