通过给定的入射光线与法向量求取反射向量
介绍
在基础函数篇中我们已经对remap, smoothstep等函数做出了细致分析,这篇一起介绍下reflect函数。reflect函数除了用于光照计算,在实现光滑表面或水面等效果时加上反射,可以使画面效果得到较大的提升。不管是glsl还是hlsl或是cg都有在其自己的着色语言中定义该函数,下面一起分析下这个函数的内部是如何实现的。
原理
结合上图可知,我们要求取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(π - θ),那么我们继续上图:
实验
- 新建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函数的内部实现,在实际开发中能用内置函数就不要自己去定义实现,因为内置函数的计算是有硬件支持或加速的。
-
最终效果如下:
更多内容请记得关注下面那张神奇的二维码。