前言

说起渲染领域的光照模型,最经典的当属Lambert、Blin、Phong,这几个模型属于经验模型,物理不够准确,效果比较简陋,在早期的三维软件中比较常用,虽然如今的三维软件中依然存在,但已经较少使用了。

即使如此,了解这些经典光照模型的技术原理依然是必要的,因为这些原理在如今先进的基于物理的光照模型(PBR)中也会用到。

因此接下来会用几篇文章整理一下这几个经典光照模型的技术原理以及在UE4和U3D中的实现方式。

 

基础概念

DotProduct(点积、点乘、Dot)

所谓点积,是两向量之间的一种运算方式,结果为一个-1到1之间的标量,它的几何意义为AB夹角的cos余弦值,或者说是A向量在B向量上的投影的相对长度(即投影长度 / B向量长度,也就是标准化),两向量方向相同时点积为1,方向相反时点积为-1,方向垂直时点积为0(即cos0=1,cos90=0,cos180=-1)。

点积的一大性质为交换性,也就是Dot(A, B) = Dot(B, A)

 

常用向量

  • nDir:法线方向
  • lDir:光照方向,实际为入射光线的逆向向量
  • vDir:摄像机观察方向
  • rDir:光反射方向
  • hDir:半角方向,lDIr与vDir的中间角方向

 

所在空间

  • OS:ObjectSpace 物体空间
  • WS:WorldSpace 世界空间
  • VS:ViewSpace 观察空间
  • CS:HomogenousClipSpace 齐次裁剪空间
  • TS:TangentSpace 切线空间
  • TXS:TextureSpace 纹理空间

常用向量和所在空间搭配使用,例如nDirWS为世界空间下的法线方向。

 

简易Lambert光照模型

所谓Lambert光照模型即纯漫反射模型,光线照射到物体上时,漫反射光强度与入射光角度和入射点表面法线夹角成反比,即夹角越小表面越亮。

计算方式就是根据模型表面的法线向量(nDir)光线的相反向量(lDir)点积,即dot(nDir, lDir),得到的结果即为输出的像素亮度。

得到的结果如上图所示,正面法线与光线平行处点积为1,纯白色;明暗交界线处法线与光线垂直点积为0,纯黑;两者之间点积为0到1之间,为灰色;背面法线与光线点积小于0,也为纯黑。

由于点积为0和小于0都为纯黑,因此通常将小于0的值改为0,即Max(0, dot(nDir, lDir)),再将得到的亮度通过Multiply节点乘上光照颜色、光照强度以及shader固有色,最终计算出的就是显示在屏幕上的颜色,这就是简易的Lambert光照模型。

 

在U3D中模拟Lambert

在U3D中可以使用ShaderForge实现,其中法线向量可以使用Normal Dir节点,光线相反向量可以使用Light Direction节点,点积运算则为Dot Product节点,再将Dot Product节点的输出连接clamp 0-1节点(或者Saturate以及Max等节点都可以),再与灯光颜色和自定义的固有色相乘,最后连到shader(shader类型应为Unlit)的Emission自发光属性上,连接完成后效果如下:

 

在UE4中模拟Lambert

在UE4中可以使用自带的材质编辑器实现,其中法线向量可以使用PixelNormalWS节点,光线相反向量lDir可以使用一个三维标量(Constant3Vector)代替 ,点积运算则为DotProduct节点,再将DotProduct节点的输出连接到clamp节点(或者Saturate以及Max等节点也可以),使用Multiply节点乘上灯光的颜色、灯光的强度以及shader的固有色,最后连到shader的Emissive Color自发光属性上(注意shader的Material Domain改为Light Function),连接完成后效果如下:

 

半Lambert光照模型

可以看到上面的图中球体背面处于完全的黑色,不利于观察,因此有了半Lambert光照模型这一变体,原理就是将-1到1的亮度区域映射到0到1的亮度区域,这样一来明暗交界线处为亮度0.5的灰色,如下图所示。

如上图,左侧为半Lambert,右侧为Lambert。

简易的半Lambert光照模型计算方式为dot(nDir, lDir) * 0.5 + 0.5。节点连接方面则去掉Clamp节点,在Dot Product节点后连接Multiply节点和Add节点(或者只使用Remap节点映射取值范围)。

半Lambert光照模型虽然避免了暗部的细节丢失,但在物理真实以及视觉方面偏差更大,因此使用较少。

 

假SSS效果

基于半Lambert的技术原理,可以使用同色调的彩色渐变替换黑白亮度,从而实现假SSS的透光效果,性能开销很小。

原理是通过将点积得到的标量视为u坐标,再使用Append Vector节点附加常量作为v坐标合并成uv二维坐标,并使用此uv坐标对渐变贴图进行采样,在UE4中的节点连接如下。

U3D同理不再赘述。

如果将渐变贴图的v方向做一定变化处理,此时得到的就会是二维渐变,再把Append Vector节点附加的常量改成变量,就可以用来制作一些色彩变化或者肤色效果,也就是预积分shader(Pre-Integrated SkinShader)的一种常见使用方式。

例如像上图这样,根据模型的厚度来决定v方向的数值,从而让薄的模型相比厚的模型有更多的肤色渐变,从而模拟次表面散射效果。

而模型的厚度一般是在DCC软件中通过烘焙生成厚度贴图,在shader计算时采样厚度贴图得到,也算是一种空间换时间算法,效果和代价还可以接受。

其他用处还有用来做卡通材质、假高光等等。

 

在U3D中通过代码实现Lambert

除了通过ShaderForge连线以外,还可以通过编写shader代码实现Lambert算法,虽然编写代码门槛较高较为复杂易错,但优点是更加灵活和方便性能优化。

在U3D中实现简易Lambert的shader代码如下:

// shader的路径和名称
Shader "Shader Forge/test" {
    // 自定义Base Color属性
    Properties {
        _BaseColor ("Base Color", Color) = (0.7, 0.4, 0.4)
    }
    SubShader {
        Tags {
            "RenderType"="Opaque"
        }
        Pass {
            Name "FORWARD"
            Tags {
                "LightMode"="ForwardBase"
            }
            
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            #pragma multi_compile_fwdbase_fullshadows
            #pragma target 3.0

            // 声明变量
            uniform float4 _LightColor0;
            uniform float4 _BaseColor;
            
            // 输入结构
            struct VertexInput {
                // 输入顶点的物体空间位置
                float4 vertex : POSITION;
                // 输入法线的物体空间方向
                float3 normal : NORMAL;
            };
            
            // 输出结构
            struct VertexOutput {
                // 输出顶点的裁剪空间位置
                float4 posCS : SV_POSITION;
                // 输出法线的世界空间方向nDir
                float3 nDirWS : TEXCOORD0;
            };
            
            // 顶点shader
            VertexOutput vert (VertexInput v) {
                VertexOutput o = (VertexOutput)0;
                // 计算法线的世界空间方向
                o.nDirWS = UnityObjectToWorldNormal(v.normal);
                // 计算顶点的裁剪空间位置
                o.posCS = UnityObjectToClipPos( v.vertex );
                return o;
            }
            
            // 像素shader
            float4 frag(VertexOutput i) : COLOR {
                // 准备向量
                float3 nDirWS = i.nDirWS;
                float3 lDirWS = normalize(_WorldSpaceLightPos0.xyz);

                // 计算点积
                float nDotl = dot(nDirWS, lDirWS);

                // 光照模型
                float Intensity = max(0.0,nDotl);
                // float Intensity = nDotl * 0.5 + 0.5;    // 半Lambert
                float3 FinalRGB = _BaseColor.rgb * Intensity * _LightColor0.rgb;
                return float4(FinalRGB, 1.0);
            }
            ENDCG
        }
    }
    FallBack "Diffuse"
}

 

本文参考自B站庄懂的技术美术入门课,感谢。

 


坚持你喜欢的,
尊重你不理解的,
是成年人社交的基本礼仪;
少去破坏别人的兴致。

《世界很大,有你刚好》
——鹿满川