【UnityShader】Unity中的光照(一)概念

该文主要来源于《Unity Shader入门精要》,做了一些修改和加入了一些补充信息

光的一些基本原理

光线从光源发射出来,与一些物体相交,有一部分光线被物体吸收,另一部分则被散射到其他方向。最后,摄像机吸收了一些光,产生了一张图像。

光的属性

假设光源方向为L,表面法线为N,光线照射到表面之前的距离d

辐照度(irradiance):垂直于L的单位面积上单位时间穿过的能量,我们可以利用L和N的夹角余弦值来得到。(物体表面的光线之间的距离等于d/cos(L,N),辐照度与这个值成反比,也就是说辐照度与cos(L,N)成正比)

高光反射(specular):表示物体表面如何反射光线

漫反射(diffuse):表示有多少光线会被吸收,散射出表面

出射度(exitance):出射光线的数量和方向,辐照度和出射度符合线性关系,而他们的比值就是材质的漫反射和高光反射属性

 

着色

根据物体的材质属性(如漫反射属性)、光源信息(如光源方向、辐照度),使用一个等式去计算沿某个观察方向的出射度的过程。

这个等式也被称为光照模型(Ligthing Model)

 

标准光照模型 Blinn-Phong模型

标准光照模型只关心直接光照,也就是哪些从光源发射出来照射到物体表面只经过物体表面的一次反射直接进入摄像机的光线。

它把进入到摄像机内的光线分为四部分

  • 自发光(emissive),给定一个方向时,一个表面本身会向该方向发射多少辐射量。需要注意的是,如果没有使用全局光照技术,这些自发光表面并不会真的照亮周围的物体,只是本身看起来更亮了而已。
  • 高光反射(specular),当光线从光源照射到表面时,该表面会在完全镜面反射方向散射多少辐射量。高光反射需要4个参数:入射光线的颜色和强度,材质高光反射系数,视角方向,反射方向,反射方向可以由光源方向L,表面法线N计算得出,CG提供了计算反射方向的函数reflect(L,N)。
  • Phong高光反射等式:
  • Blinn-Phong高光反射等式:
  • 没有使用反射方向而是引入新的矢量h,通过对视角方向和光照方向的归一化得到。
  • 漫反射(diffuse),当光线从光源照射到表面时,该表面会向每个方向散射多少辐射量。符合lambert定律,也就是说反射光线的强度与表面法线和光源方向之间的余弦值成正比。因此漫反射部分计算max(0,dot(L,N));,截取到0是为了避免背面光强度成为负数,再被后面的光源照亮时依然不大于0,没有光,也可以用saturate(dot(L,N))截取0-1。半lambert着色,不使用max来避免为负数,而是*0.5再+0.5,将-1,1的值映射到0,1,并没有物理依据,只是一个视觉增强技术。
  • 漫反射等式:
  • 环境光(ambient),描述所有其他间接光照,通常是一个全局变量,场景中所有物体都是用这个环境光。

可以用来辅助的内置函数: (使用前记得归一化)
内置函数float3 WorldSpaceLightDir (float 4 v)    仅可用于前向渲染。输入一个模型空间中的顶点位置,返回世界空间中从该点到光源的光照方向,没有被归一化。
UnityWorldSpaceLightDir 仅可用于前向渲染。输入一个世界空间中的顶点位置,返回世界空间中从该点到光源的光照方向,没有被归一化。

ObjSpaceLightDir 仅可用于前向渲染。输入一个模型空间中的顶点位置,返回模型空间中从该点到光源的光照方向,没有被归一化。

reflect(L,N)计算反射方向,注意这里使用上面的光照方向时需要取反,-dir,因为这里要的光照方向输入是指从光源到该点的方向
 

如何着色:

逐像素光照,phong着色,对每个像素进行光照计算,法线由顶点法线插值或是法线纹理得到。

逐顶点光照,高洛德着色,每个顶点进行光照计算,再在渲染图元内部进行线性插入,输出像素颜色。这会导致渲染图元内部颜色总暗于顶点处的最高颜色值,这在某些情况下会产生棱角现象。

标准光照模型局限性

各项同性,当我们固定视角和光源方向,旋转这个表面,反射不会发生改变,但有些表面时各向异性的 ,例如毛发,拉丝金属。

很多重要物理现象也无法使用标准光照模型,如菲涅尔反射。

 

BRDF光照模型(Bidirectional Reflectance Distribution Function)

通俗来讲,给定一个点,BRDF就可以给出某个出射方向上的光照能量分布

(没有详细了解,后续看完修改)更详细的内容https://zhuanlan.zhihu.com/p/81959654

https://learnopengl-cn.github.io/07%20PBR/01%20Theory/

 

渲染路径

渲染路径决定了光照在 shader 中是如何应用的,所以在计算光源时,需要在每个 Pass 块内指定它的渲染路径,Unity 才会为我们提供正确的光照信息。

前向渲染

在前向渲染中,影响每个物体的一些最亮的光是以完全逐像素呈现的。然后,计算最多四个的逐顶点光照。其他的光被计算成球谐波(SH),这很快,但只是一个近似。光是否是每像素的光取决于以下情况:

  • 场景中最亮的平行光总是逐像素处理的。
  • Render Mode被设置成Not Important的光源,会按逐顶点或者球谐函数处理。第一点中的平行光不受这点的约束。
  • Render Mode被设置成Important的光源,会按逐像素处理。
  • 按以上规则得到的逐像素光源数目少于 Quality 中设置的数目时,会有更多的光源以逐像素的方式渲染

Base Pass 

只渲染一个平行光(逐像素),所有的顶点光/SH光,光照纹理不会接收SH光源,只有主平行光可以拥有阴影,使用“OnlyDirectional” pass flag那么这个 Base Pass 块只会计算主平行光,环境光、光照探针和光照纹理,而 SH 和 逐顶点光源则不包含在内。

渲染设置 Tags { "LightMode"="ForwardBase" } Unity会提供一些内置光照变量让我们可以访问一些属性,如上文出现的UnityWorldSpaceLightDir

编译指令 #pragma multi_compile_fwdbase

 

Additional Pass

只处理其他逐像素光,每个逐像素光源都会调用一次Additional Pass

默认情况下,这些pass中的灯没有阴影,除非使用 multi_compile_fwdadd_fullshadows指令

 

前向渲染中的阴影

对于Forward Rendering来说,只有Bass Pass中处理的第一个平行光可以有阴影效果。也就是说,错过了这里就不会得到阴影信息了。程序中模拟阴影主要是依靠一张Shadow Map,里面记录了从光源出发距离它最近的深度信息。Unity提供了这样的一张纹理(_ShadowMapTexture)。

由于阴影和光照衰减都是对纹理进行采样,然后将结果乘以颜色值,因此Unity把这两步合并到一个宏中,让我们通过一个宏调用就可以解决这两个问题。既然是对纹理采样,那么首先就要知道顶点对应的纹理坐标,Unity同样是通过宏来辅助我们完成的,我们只需要在v2f(vertexOutput)中添加关于宏LIGHTING_COORDS即可。然后,为了计算顶点对应的两张纹理上的坐标,需要在vert函数里面调用一个新的宏:TRANSFER_VERTEX_TO_FRAGMENT。

 

与阴影的实现类似,Unity还提供了一张纹理(_LightTexture0),这张纹理包含了光照衰减(attenuation)。

Additional Passe

注意:想要Additional Passes是叠加在Bass Pass上的话(一般人的目的都是这个),请确保你给Pass添加了合适的混合模式。例如:

 Tags { "LightMode"="ForwardAdd"}	
ZWrite Off Blend One One Fog { Color (0,0,0,0) }

Additional Passes只能处理逐像素光,如果你想要其他光照效果,都需要在Bass Pass中处理。

 

对于逐像素光照,我们最长使用的变量和函数如下:

来自UnityShaderVariables.cginc:

uniform float4 _WorldSpaceLightPos0;
uniform float3 _WorldSpaceCameraPos;

来自Lighting.cginc:

fixed4 _LightColor0;

来自UnityCG.cginc(文档说明):

// Computes world space light direction
inline float3 WorldSpaceLightDir( in float4 v );
// Computes object space light direction
inline float3 ObjSpaceLightDir( in float4 v );
// Computes world space view direction
inline float3 WorldSpaceViewDir( in float4 v );
// Computes object space view direction
inline float3 ObjSpaceViewDir( in float4 v );

 

延迟渲染路径

包含大量实时光源时,前向渲染性能会急速下降,延迟渲染可以解决这个问题。

他除了使用前向渲染中的颜色缓冲和深度缓冲,还会使用额外的G缓冲区,存储我们所关心的表面(通常是离摄像机最近的表面)的其他信息,例如该表面的法线,位置,用于光照计算的材质属性等。

在第二个 Pass 中计算光照时,默认仅可以使用 Unity 内置的 Standard 光照模型

 

延迟渲染原理

延迟渲染主要包含两个pass,第一个pass中不进行任何光照计算,仅仅计算哪些片元是可见的,通过深度缓冲,如果是可见的,将它的相关信息存储到G缓冲区中,然后,在第二个pass中,利用G缓冲区的各个片元信息,例如表面法线、视角方向、漫反射系数等,来进行真正的光照计算

 

效率依赖于屏幕空间大小

只使用两个pass,与光源数目无关,它的效率不依赖于场景复杂度,而是与屏幕空间大小有关。因为我们所需信息都存储在缓冲区中,这些缓冲区可以理解成是一张张2D图像,我们的计算实际上就是在这些图像空间内进行。

 

缺点

不支持抗锯齿

不能处理半透明

显卡需要支持 MRT 、Shader Mode 3.0 以上、深度渲染纹理。

在移动端,需要硬件支持 OpenGL ES 3.0 以上

需要注意的是:如果摄像机的 Projection 设置为了 Orthographi(正交),那么摄像机会回退到前向渲染。因为延迟渲染不支持正交投影。

 

默认G缓冲区包含以下几个渲染纹理这里是2018.2的数据,不同版本可能不同,需要使用的话到官方文档查找自己版本的数据

RT0,ARGB32格式:漫反射颜色(RGB),遮罩(A)
RT1,ARGB32格式:镜面反射颜色(RGB),粗糙度(A)
RT2,ARGB2101010格式:世界空间标准(RGB),未使用(A)
RT3,ARGB2101010(非HDR)或ARGBHalf(HDR)格式:Emission + lighting + lightmaps + reflection probes
深度+模板缓冲区
 

Unity中的光源

Unity 中的光源有 4 种:平行光、点光源、聚光灯和面光源(仅在烘焙时使用),我们来简单学习一下前3种光源。

平行光是最简单的光源:

  • 照亮范围没有限制,位置不唯一,几何属性只有方向,相当于 “太阳” 或 “月亮”。
  • 到场景中任何一点的方向都是一样的,光照强度不会随着距离而改变,即没有光照衰减的概念

点光源

  • 点光源位于空间中的一个点,并在所有方向上均匀地发出光,有空间限制
  • 撞击表面的光的方向是从接触点返回到光对象的中心的线,强度随着距光的距离而减小,在指定范围内达到零
  • 光强度与距离光源的距离的平方成反比。这被称为“平方反比定律”,类似于光在现实世界中的表现

聚光灯是这 3 种光源中最复杂的一种。

  • 由空间中的一块锥形区域定义,有空间范围限制
  • 光照衰减随物体逐渐远离点光源而逐渐减小,顶点处光照强度最强。边界处强度为 0

常用的光源属性有:光源位置、方向(光源到某一点的方向)、颜色、强度和衰减(到某一点的衰减,除了平行光,均与该点到光源的距离有关)。

 

光源衰减

点光源和聚光灯求衰减比较复杂,要通过复杂的数学表达式计算得到。因为这种计算操作较大,所以 Unity 选择使用一张纹理作为查找表(LUT),我们首先得到光源空间下的坐标,然后用这个坐标对衰减纹理进行采样得到衰减值。

光照衰减纹理
Unity 内使用 _LightTexture0 的纹理来计算光照衰减,在之前的代码中,我们已经使用过了。通常情况下,我们只关心 _LightTexture0 对角线上的纹理颜色值,其代表了在光源空间下不同位置的点的衰减值。(0,0)表示与光源重合的点的衰减值,(1,1)表示距离最远的点的光照衰减值。

光照纹理采样

需要用一个点对纹理采样,那么就要先知道该点在光源空间下位置信息。同样是空间转换,我们在这里需要用到的转换矩阵为unity_WorldToLight 

float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz;

然后使用这个坐标的摸的平方进行采样。当然,如果用距离值来计算就需要开方操作了,为了,避免这个繁琐的步骤,我们使用顶点距离的平方来采样

fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;

 

阴影

在 Unity 的实时渲染中,我们采用的是 Shadow Map 技术。关于 Shadow Map,在 Unity 中,就是先把摄像机的位置与光源重合,然后摄像机看不到的区域就是阴影。

例如:在前向渲染中,如果平行光开启了阴影(要注意需要手动开启,创建了一个新光源,默认是没有阴影的

Unity 就会为这个平行光计算阴影映射纹理。这张阴影映射纹理实质就是一张深度纹理,记录着从光源出发,距离光源最近的表面信息

Unity 选择使用另外一个特别的 Pass 来管理光源的映射纹理。这个 Pass 就是 LightMode 标签中设置为 Shadow Caster 的那个 Pass

这个 Pass 的渲染目标是深度纹理。所以当一个光源开启了阴影效果之后,引擎就会在当前渲染物体的 shader 中寻找这个 Pass ,如果找不到,就去 Fallback 里面找;还找不到,就去 Fallback 的 Fallback 里面找。如果这样都找不到,那么该物体就无法向其它物体投射阴影,但是可以接收来自其它物体的阴影。

总结
如果想要一个物体接收其它的物体的阴影,就要在 shader 中对阴影映射纹理进行采样,把采样结果和光照结果相乘得到阴影效果。
如果想要一个物体向其它物体投射阴影,就要把该物体加入到阴影映射纹理之中,这一步骤是在 Shadow Pass 中实现的。
如果想要一个光源产生阴影效果,则需要手动选择阴影类型:No Shadows , Hard Shadows , Soft Shadows。Hard Shadows 相对于 Soft Shadows 计算量少一些,能满足大部分场景,边缘不平滑,锯齿明显。
 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值