Unity3D Shader之光照模型——理论与实践:用两种方式来实现漫反射Diffuse Reflection

时间:2021-12-10 05:27:03

漫反射原理解析

众所周知,漫反射即光线射到粗糙的物体表面时发生的反射现象,入射光线为平行光线,而出射光线为各个方向。光在空气中以直线传播,光线的反射也遵守物理规则。如下图所示:

Unity3D Shader之光照模型——理论与实践:用两种方式来实现漫反射Diffuse Reflection
图片来源于网络。

上图左侧入射方向的是平行光,反射光也是平行光,这种现象称为镜面反射。而左侧反射光不平行无规则的则为漫反射。

Unity3D Shader之光照模型——理论与实践:用两种方式来实现漫反射Diffuse ReflectionUnity3D Shader之光照模型——理论与实践:用两种方式来实现漫反射Diffuse Reflection
图片来源于网络。

上图左侧表示,入射光线与出射光线形成的夹角,被垂直于反射面的光线均分。上图右侧表示在圆弧或不规则表面时,反射平面为反射点的切线,此时反射点的法线为经过此点垂直于其切线的直线。

然而在图形处理中,实际情况是模型远远没有现实这么复杂,有的时候甚至想用简单的模型(甚至是完全平面的模型)来表现出复杂或是特殊材质的物体。对于漫反射而言,不可能真的去做出具有无数个顶点,表面具有极其细微的凹凸不平之处的模型,如果真这样做,无论是人力还是计算机的处理能力都是非常有限的。因此就要使用一定的算法来模拟漫反射。

根据反射的规则和实际生活情况可知,入射光线与反射光线的夹角越小,则反射强度越大。并且物体表面越粗糙,那么反射后的亮度越弱,这是因为粗糙的表面将光线反射到了各个地方去。因此,我们要模拟的就是:

  • 反射规则:入射光线与反射光线的关系
  • 光线逸散:反射后由物体表面粗糙的原因导致光线光线逸散,反射后光线亮度变低

结合上两点,在实际生活中观察到的漫反射现象是: 入射光线与法线的夹角越小,则反射后的光线亮度越大。于是便采用 入射光线与法线的点积来 来计算反射系数,因为点积的几何意义是:点积的值越大,则两个向量的方向越相近。


结合我前面的文章《Unity3D内建参数文档翻译与解析——Built-in shader variables》来使用两种不同的Shader形式实现漫反射。

可编程Shader实现方式

代码

Shader "Custom/DiffuseReflection"
{
Properties
{
_Color("Color", Color) = (1,1,1,1)
_MainTex("Texture", 2D) = "white" {}
}

SubShader
{
// 设定LightMode为ForwardBase,相关细节可以查看我之前的文章《Unity3D光照前置知识——Rendering Paths(渲染路径)及LightMode(光照模式)译解》
Tags{ "LightMode" = "ForwardBase" }

Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag

#include "UnityCG.cginc"
// 使用Unity3D的头文件"Lighting.cginc",包含有我们需要的光照参数
#include "Lighting.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
half3 normal : NORMAL;
};

struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
float4 diff : TEXCOORD1;
float4 diffColor : TEXCOORD2;
};

v2f vert(appdata v)
{
v2f o;
o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv = v.uv;
// 本例中将光照计算过程写在vert函数内,因为这样相比写在frag函数内更节约性能。不过当然效果肯定没有在frag内好。
// 计算物体法线(用来代表在物体上入射点处的法线)与光照方向的点积,得到漫反射系数diff,用来表示漫反射的光照衰减,并且保证其值大于0。
float diff = max(0, dot(v.normal, _WorldSpaceLightPos0.xyz));
// 利用系数计算光线衰减后的色值
o.diffColor = _LightColor0 * 2 * diff;
return o;
}

sampler2D _MainTex;
fixed4 _Color;
fixed4 frag(v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv) * _Color;

// 得到最终颜色
col.rgb = i.diffColor * col.rgb;
return col;
}
ENDCG
}
}
}

结果

Unity3D Shader之光照模型——理论与实践:用两种方式来实现漫反射Diffuse Reflection


Surface Shader实现方式

代码

surface shader是Unity3D特有的一种Shader编写形式,封装了许多光照相关的功能。以后会开坑细讲。

Shader "Custom/DiffuseReflection" {
Properties {
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200

CGPROGRAM
// 声明自定义光照函数名
#pragma surface surf DiffuseReflection
#pragma target 3.0

sampler2D _MainTex;

struct Input {
float2 uv_MainTex;
};

fixed4 _Color;

// 自定义光照函数
half4 LightingDiffuseReflection(SurfaceOutput s, half3 lightDir, half3 viewDir, half atten)
{
half diff = max(0, dot(s.Normal, lightDir));
half3 c = s.Albedo * diff * _LightColor0.rgb * 2;
return fixed4(c, s.Alpha);
}

void surf (Input IN, inout SurfaceOutput o) {
fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
o.Alpha = c.a;
}
ENDCG
}
FallBack "Diffuse"
}

结果

Unity3D Shader之光照模型——理论与实践:用两种方式来实现漫反射Diffuse Reflection