《Unity Shader入门精要》 第六章 Unity中的基础光照 笔记

Unity 中的基础光照

渲染的基础问题就是,我们如何决定一个像素的颜色?
宏观上来看,渲染包括两个部分:决定一个像素的可见性,决定这个像素上的光照计算。光照模型就是用于决定在一个像素上进行怎样的光照计算。

我们是如何看这个世界的

当我们说一个物体是红色的时候,意味着这个物体会反射更多的红光波,吸收其他的光波。而一个物体看起来是黑色的则是吸收了大部分的光波。
模拟真实的光照环境来生成一张图片时,我们需要考虑3种物理现象:

  • 首相,光从光源(light source)中发射出来
  • 然后,光线和场景中的一些物体相交:一些光线被吸收了,而另一些光线被散射到其他方向。
  • 最后,摄像机吸收了一些光,产生一张图像。

光源

在实时渲染中,我们通常把光源当成一个没有体积的点,用l来表示它的方向。在光学中,我们使用辐照度(irradiance)来量化光。对于平行光,它的辐照度可以通过计算垂直于l的单位面积上单位时间内穿过的能量来得到。在计算光照模型时,我们需要知道一个物体表面的辐照度,而物体表面通常与l不垂直,我们可以使用光源方向l和表面法线之间的夹角余弦值来得到。
《Unity Shader入门精要》 第六章 Unity中的基础光照 笔记
左图中,光线垂直照射到物体表面,因此光线之间距离不变;右图中,光斜照到物体表面,物体表面光线之间的距离是d/cosθ,因此单位面积上接受的光线数目少于左图。
因为辐照度与物体表面光线之间距离d/cosθ成反比,所以辐照度与cosθ成正比。cosθ可以由光线方向与物体表面法线方向的点积得到(需归一化)。

吸收和散射

通常光线与物体表面相交后有两个结果,吸收和散射。

  • 散射只改变光线的方向,但不改变光线的密度和颜色。
  • 吸收只改变光线的密度和颜色,但不改变光线的方向。

散射

光线在物体表面经过散射后,有两种方向:一种是散射到物体内部,这种现象被称为折射透射 ;另一种会散射到外部,这种现象被称为反射
对于不透明物体,折射进入物体内部的光线还会继续与内部的颗粒进行相交,其中一些光线最后会重新发射出物体表面,另一些则被物体吸收。重新从物体发射出的光线与射入光线有不同的方向分布和颜色。

  • 高光反射用来表示物体表面如何进行反射。
  • 漫反射表示有多少光线被折射、吸收和散射出表面。
  • 根据入射光线的数量和方向,我们可以计算出射光线的数量和方向,通常使用出射度来描述它。
    辐照度和出射度之间是满足线性关系的,而他们之间的比值就是材质的漫反射和高光反射。
    ps:本章中,我们先假设漫反射无方向,即均匀分布到各个方向;同时也只考虑某一个特定方向的高光反射。

着色

  • 着色指,根据材质属性(如漫反射等)、光源信息(如光源方向、辐照度)等,使用一个等式去计算沿某个观察方向的出射度的过程。我们也把这个等式称为光照模型。不同的光照模型有不同的用途。例如,一些用于描述粗糙的物体表面,一些用于描述金属表面。

BRDF光照模型

  • 在图形学中BRDF大多使用一个数学公式来表示,并且提供了一些参数来调整材质属性。通俗来讲,给定入射光线的光线和辐照度后,BEDF就可以给出在某个出射方向上的光照能量分布。
  • 本章涉及的BRDF都是对真实场景进行理想化和简化后的模型。意味着这些模型任然不能模拟真实光照,这些光照模型被称为经验模型。尽管如此,这些模型任在实时渲染领域被应用了多年。在邓恩的著作《3D数学基础:图形与游戏开发》中提到一句名言:
    计算机图形学的第一定律:如果它看起来是对的,那么它就是对的。

标准光照模型

  • 它在BDRF理论提出前就已经被广泛使用了。它只关心直接光照,也就是那些直接从光源发射出来照射到物体表面后经过一次反射直接进入摄像机的光线。
  • 它的基本方法把进入摄像机的光线分为4个部分,每个部分使用一种方法来计算它的贡献度:
    自发光之后使用 Cmissive 来表示。用于描述给定一个方向后,一个表面会向这个方向发射多少辐射量。如果没有使用全局光照技术,这些自发光的表面并不会真的照亮周围的物体
    高光反射之后使用 Cspecular 来表示。用于描述当光线从光源照射到模型表面时,该表面会在完全镜面反射方向散射多少辐射量。
    漫反射之后使用 Cdiffuse 来表示。用于描述当光线从光源照射到模型表面是,该表面会向每个方向散射多少辐射量。
    环境光之后使用 Cambient 来表示。用于描述其他所有间接光。

环境光

标准光照模型重点在于描述直接光照,但现实世界中,物体也会被间接光照所照亮。间接光照指,光线进入摄像机之前,在多个物体间反射,经过了不止一次物体反射。
标准光照模型中,环境光通常使用一个全局变量,场景中所有物体都使用这个环境光。
Cambient=gambient

自发光

光线也可以直接由光源发射进入摄像机,而不需经过任何物体的反射。标准光照模型使用自发光来计算这个部分的贡献度。它的计算也很简单,直接使用这个物体自发光的颜色。
Cemissive=memissive
通常在实时光照中,自发光不会照亮其他表面,也就是这个物体不会被当成一个光源。Unity5中引入的全新的全局光照系统可以模拟这类自发光对周围物体的影响,在18章中我们会看到。(好遥远啊,怎么才第六章)

漫反射

漫反射光是对物体表面随机散射到各个方向的辐射度进行建模的。在漫反射中,视角的位置不重要,因为方向完全随机,我们可以假设在任何方向上的分布都是一样的。但是入射光线的角度很重要。
满反射符合兰伯特定律:反射光线的强度与表面法线和光源之间的夹角余弦值成正比。
Cdiffuse=(Clight·mdiffuse)max(0,n·I)
n:表面法线
I:指向光源的单位矢量
mdiffuse:材质的漫反射颜色
Clight:光源颜色
我们要防止法线和光源方向点乘结果为负值,所以使用max函数,最低取0,防止表面被来自后面的光照亮。

高光反射

这里的高光反射为经验模型。它可以用于计算那些沿着完全镜面反射方向被反射的光线,这可以让物体看起来有光泽,例如金属材质。
高光反射的计算需要知道的信息较多,表面法线、视角方向、光源方向、反射方向等。本节中假设这些都是单位矢量。
四个矢量中我们只需要知道3个就足够了,反射方向可以通过其他3个矢量求出。
《Unity Shader入门精要》 第六章 Unity中的基础光照 笔记

  • 利用Pong模型计算高光反射:
    《Unity Shader入门精要》 第六章 Unity中的基础光照 笔记
    《Unity Shader入门精要》 第六章 Unity中的基础光照 笔记
    其中mgloss为材质的光泽度,也被称为反光度。它用于控制高光区域的“亮点”有多宽mgloss越大,亮点越小。
    mspecular是材质的高光反射颜色,它用于控制该材质对于高光反射的强度和颜色。
    v是指向摄像机方向的向量。
  • 利用Blinn模型计算高光反射:
    《Unity Shader入门精要》 第六章 Unity中的基础光照 笔记
    《Unity Shader入门精要》 第六章 Unity中的基础光照 笔记
    与Pong模型相比,Blinn模型提出了一个简单的方法修改来得到类似的效果。这个方法避免了对反射方向的计算,引入了一个新的矢量h,通过对I和v取平均后归一化得到的。
    《Unity Shader入门精要》 第六章 Unity中的基础光照 笔记
    然后使用n和h计算。
    (我个人的理解是,Pong模型中r和v的夹角与n和I有关,r是I相对于n的镜像,Blinn中的h计算也与v和I有关,当光线入射角变大时,这两个模型中的夹角都变大,虽然增长比例不同但都是线性增长,表现出来的效果类似,相当于是对前文邓恩的那句话的体现了)
    摄像机和光源距离表面较近时,使用blinn模型更快些(v和I近似为定值,则h便为一个常量),反之Pong要快些。两者都是经验模型,没有谁更准确的说法。

逐像素还是逐顶点

对于上面给出的基本光照模型的公式的应用,我们有两种选择:

  • 在片元着色器中计算,被称为逐像素光照
  • 在顶点着色器中计算,被称为逐顶点光照

逐像素光照

逐像素光照中,我们会以每个像素为基础,得到它的法线(对顶点法线插值得到或者从法线纹理中采样得到),进行光照模型计算。这种面片之间对顶点插值的技术成为Pong着色,也成为Pong插值或法线插值,不同于Pong光照模型。

逐顶点光照

也被称为高罗德着色(Gouraud shading)。在逐顶点光照中,我们在每个顶点上计算光照,然后会在渲染图元内部进行插值,最后输出成像素颜色。顶点数目永远小于像素数目,所以计算次数比逐像素光照要少。但是逐顶点光照过于依赖线性插值来的到像素光照,光照模型中有非线性的计算时(例如计算高光反射时),逐顶点光照就会出问题。因为是线性插值,顶点处颜色最深,在某些情况下会产生明显的棱角现象。

Unity中的环境光和自发光

在标准光照模型中,环境光和自发光的计算是最简单的。
在shader中,我们只需要通过Unity的内置变量UNITY_LIGHTMODEL_AMBIENT就可以得到环境光的颜色和强度信息。
而大多数物体是没有自发光特性的,因此在书中绝大部分的shader中都不计算自发光。

在Unity Shader中实现漫反射光照模型

Cdiffuse=(Clight·mdiffuse)max(0,n·I)
根据公式可知,我们需要知道4个参数:入射光线的颜色和强度Clight,材质的漫反射系数mdiffuse,表面法线n以及光源方向I。
为防止点积结果为负数,我们需要max操作,而CG提供了这样一个函数。

  • 函数:saturate(x)
  • 参数:x:为用于操作的标量或者矢量,可以使float、float2、float3等类型。
  • 描述:把x截取在[0,1]范围内,如果x是一个矢量,那么会对它的每一个分量进行这样的操作。
    (由于是个人笔记,里面的范例我就不记录了,范例可以在书中6.4.1和6.4.2节看到)

在Unity Shader中实现高光反射光照模型

《Unity Shader入门精要》 第六章 Unity中的基础光照 笔记
从公式可以看出,计算高光反射需要知道4个参数:入射光线的颜色和强度、材质的高光反射系数、视角方向和反射方向。其中,反射方向可以由表面法线和光源方向计算而得。
《Unity Shader入门精要》 第六章 Unity中的基础光照 笔记
CG提供了计算反射方向的函数reflect。

  • 函数:reflect(i,n)
  • 参数:i,入射方向;n,法线方向。可以是float、float2、float3等类型。
  • 描述:当给定入射方向i和法线方向n时,reflect函数可以返回反射方向。
    (由于是个人笔记,里面的范例我就不记录了,范例可以在书中6.5.1和6.5.2节看到)

UnityCG.cginc中一些常用的帮助函数

函数名 描述
float3 WorldSpaceViewDir(float4 v) 输入一个模型空间中的顶点位置,返回世界空间中从该点到摄像机的观察方向。(内部实现使用了UnityWorldSpaceViewDir函数)
float3 UnityWorldSpaceViewDir(float4 v) 输入一个世界空间中的顶点位置,返回世界空间中从该点到摄像机的观察方向。(内部实现使用了UnityWorldSpaceViewDir函数
float3 ObjSpaceViewDir(float4 v) 输入一个模型空间中的顶点位置,返回模型空间中从该点到摄像机的观察方向
float3 WorldSpaceLightDir(float4 v) 仅可用于前向渲染。输入一个模型空间中的顶点位置,返回世界空间中从该点到光源的光照方向。没有被归一化。(内部实现使用了UnityWorldSpaceViewDir函数)
float3 UnityWorldSpaceViewDir(float4 v) 仅可用于前向渲染。输入一个世界空间中的顶点位置,返回世界空间中从该点到光源的光照方向。没有被归一化。
float3 ObjSpaceLightDir(float4 v) 仅可用于前向渲染中。输入一个模型空间中的顶点位置,返回模型空间中从该点到光源的光照方向。没有被归一化。
float3 UnityObjectToWorldNormal(float3 norm) 把法线方向从模型空间转换到世界空间中
float3 UnityObjectToWorldDir(in float3 dir) 把矢量方向从模型空间变换到世界空间中
float3 UnityWorldToObjectDit(float3 dir) 把矢量方向从世界空间变换到模型空间中

ps:注意这些函数都没有进行归一化,我们在使用前需要进行归一化处理。