Shader实验室:reflect函数

Number of views 171

通过给定的入射光线与法向量求取反射向量

image.png

介绍

在基础函数篇中我们已经对remap, smoothstep等函数做出了细致分析,这篇一起介绍下reflect函数。reflect函数除了用于光照计算,在实现光滑表面或水面等效果时加上反射,可以使画面效果得到较大的提升。不管是glsl还是hlsl或是cg都有在其自己的着色语言中定义该函数,下面一起分析下这个函数的内部是如何实现的。

原理

image.png

结合上图可知,我们要求取OA向量(反射向量),可以通过如下方式求取:

OA = OE + EA

已知OE = -OC。剩下的就是求取EA向量,因为EA = 2*OB,而OB = ON * |OB|,又因为|OB| = |OC| * cosθ = cosθ,而cosθ = dot(OC,ON),归纳后:

EA = 2 * ON * dot(OC, ON);

最后结果如下:

OA = 2 * ON * dot(OC, ON)- OC
// 也就是(这里的L与N都是标准化向量)
OA = 2 * N * dot(L, N) - L

在CG中我们看到它对reflect函数的实现是通过以下方式:

// i为入射光线,n为法线
float3 reflect( float3 i, float3 n )
{
  return i - 2.0 * n * dot(n,i);
}

CG中的reflect函数i为入射光线,而我们定义的OL向量是入射光线的相反向量,转换求取的公式OA = 2 * N * dot(L, N) - L为:

// 切记:此时的L向量为入射光线
OA = 2 * N * dot(-L,N) + L
// 最后求得
OA = L - 2 * N * dot(L, N)

有些小伙伴会对2 * N * dot(-L, N) = - 2 * N * dot(L, N)会有疑问。因为dot(L, N) = cosθ,dot(-L, N)=cos(π - θ),因为cosθ = - cos(π - θ),所以上面的转换就可以成立了,可能还有小伙伴不相信cosθ = - cos(π - θ),那么我们继续上图:

image.png

实验

  • 新建Unlit Shader与Material,打开该Shader文件,修改代码如下:
Shader "Unlit/reflect"
{
    Properties
    {
        // 定义Cube类型的贴图
	_CubeTex ("Cube Tex", Cube) = "white"{}
    }
    
    SubShader
    {
	Tags { "RenderType"="Opaque" }
 
	Pass
	{
	    CGPROGRAM
	    #pragma vertex vert
	    #pragma fragment frag
			
	    #include "UnityCG.cginc"

            struct a2v {
              float4 vertex: POSITION;
              float3 normal: NORMAL;
            };

	    struct v2f
	    {
	      float4 vertex : SV_POSITION;
	      float3 rDir : TEXCOORD0;
	    };
			
	    uniform samplerCUBE _CubeTex;
	    // 定义了反射函数,内部是前面推导后的计算方式
	    float3 reflection(float3 i, float3 n) {
	      return i - 2 * n * dot(i, n);
	    }
	    v2f vert (a2v v)
	    {
	      v2f o;
	      o.vertex = UnityObjectToClipPos(v.vertex);
	      float3 nor = mul(v.normal, (float3x3)unity_WorldToObject);
	      // 从顶点到相机的标准化向量
	      float3 viewDir = normalize(WorldSpaceViewDir(v.vertex));
	      // 作为入射向量进行计算
	      o.rDir = reflection(-viewDir, normalize(nor));
              return o;
	    }
			
	    fixed4 frag (v2f i) : SV_Target
	    {
	      fixed4 col = texCUBE(_CubeTex, i.rDir);
	      return col;
	    }
	    ENDCG
	  }
      }
}
  • 我们在代码中定义了reflection函数,这里只是为了展示reflect函数的内部实现,在实际开发中能用内置函数就不要自己去定义实现,因为内置函数的计算是有硬件支持或加速的。

  • 最终效果如下:

image.png

更多内容请记得关注下面那张神奇的二维码。

image.png

0 Answers