Shader学习笔记(四)

UnityShader 中级篇(一)

更复杂的光照

学习Unity shader的第四天,过完初级篇,后续将只展示关键代码进行解释。

Unity 的渲染路径

在Unity里,**渲染路径(Rendering Path)**决定了光照是如何应用到Unity Shader 中的。因此若要和光源打交道,需为每个Pass指定它使用的渲染路径,只有为Shader正确地选择和设置了需要的渲染路径,该Shader的光照计算才能被正确执行。
Unity支持多种类型的渲染路径。主要有3种:前向渲染路径(Forward Rendering Path)延迟渲染路径(Deferred Rendering Path)顶点照明渲染路径(Vertex Lit Rendering Path)
大多数情况下,一个项目只使用一种渲染路径,默认情况是选择前向渲染路径,也可多个摄像机使用多个渲染路径。每个摄像机的渲染路径设置中设置该摄像机使用的渲染路径,以覆盖Project Settings 中的设置。
在这里插入图片描述
在这里插入图片描述
如果目标平台的显卡不能处理所选择的硬件,那么Unity会自动过渡至低保真档次Rendering Path去。比如有的GPU不能处理Deferred Shading,那就会自动采用Forward Rendering。

前向渲染路径的原理

每进行一次完整的前向渲染,需要渲染该对象的渲染图元,并计算两个缓冲区的信息:一个是颜色缓冲区,一个是深度缓冲区。利用深度缓冲来决定一个片元是否可见,若可见就更新颜色缓冲区中的颜色值。

对于每个逐像素光源,都需要进行上面一次完整的渲染流程。如果一个物体在多个逐像素光源的影响区域内,那么该物体就需要执行多个Pass,每个Pass计算一个逐像素光源的光照结果,然后在帧缓冲中把这些光照结果混合起来得到最终的颜色值。假设场景中有N个物体,每个物体受M个光源的影响,那么要渲染整个场景一共需要N*M个Pass。可看出,若有大量逐像素光照,那么需执行的Pass数目也会很大。因此,渲染引擎通常会限制每个物体的逐像素光照的数目。

Unity中的前向渲染

事实上,一个Pass不仅仅可以用来计算逐像素光照,它也可用来计算逐顶点等其他光照。这取决于光照计算所处流水线阶段以及计算时使用的数学模型。当渲染一个物体时,Unity会计算哪些光源照亮了它,以及这些光源照亮该物体的方式。
在Unity中,前向渲染路径有3种处理光照(即照亮物体)的方式:逐顶点处理、逐像素处理,球谐函数(Spherical Harmonics,SH)处理。而决定一个光源使用哪种处理模式取决于它的类型和渲染模式。光源类型指的是该光源是平行光还是其他类型的光源,而光源的渲染模式指的是该光源是否是重要的(Important)
Unity会根据场景中各个光源的设置以及这些光源对物体的影响程度(例如,距离该物体的远近、光源强度等)对这些光源进行一个重要度排序。其中,一定数目的光源会按逐像素的方式处理,然后最多有4个光源按逐顶点的方式处理,剩下的光源可以按SH方式处理。Unity使用的判断规则如下。

场景中最亮的平行光总要按逐像素处理的。
渲染模式被设置成Not Important 的光源,会按逐顶点或者SH处理
渲染模式被设置成Important的光源,会按逐像素处理
若根据以上规则得到的逐像素光源数量小于Quality Setting 中的逐像素光源数量(Pixel Light Count),会有更多的光源以逐像素的方式进行渲染。

那么,在哪里进行光照计算呢?当然是在Pass里。前面提到过,前向渲染有两种Pass:
Base PassAdditional Pass。通过来说,这两种Pass进行的标签和渲染设置以及常规光照计算如图所示。
在这里插入图片描述
具体解释可参考 Unity前向渲染路径简单概括笔记

Unity的光源类型

Unity Shader 中处理更复杂的光源类型以及数目更多的光源。Unity一共支持4种光源类型:平行光(directional light)、点光源(point light)、聚光灯(spot light)面光源(area light)
最常使用的光源属性有光源的位置方向(更具体说就是,到某点的方向)、颜色强度以及衰减(更具体说就是,到某点的衰减,与该点到光源的距离有关)这5个属性。而这些属性和它们的几何定义息息相关。
1. 平行光
通常作为太阳,没有衰减强度不变,只有方向属性
在这里插入图片描述
2. 点光源
球体,半径、位置、方向属性,光照强度球体边为0中心为1
在这里插入图片描述
3. 聚光灯
聚光灯是这3种光源类型中最复杂的一种。锥形,半径、张开角度、位置、方向属性,光照强度顶点最强边界为0。
在这里插入图片描述
一些属性

1.在Range属性上向右或向左拖动以增加或减少光在场景中的传播距离;
2.在Intensity属性上向右或向左拖动以增加或减少其设置范围内的灯光强度;将Intensity属性设置为200到600之间更实际
3. Indirect Multiplier属性会影响 此光源提供的间接光的强度(被传感器接收前多次反弹的光);
如果你设置它:
低于 1时,间接光每次从物体反弹时都会变暗。这是真实光照的行为方式,但您可能希望覆盖该行为以实现特定的光照效果。
高于 1 ,间接光会随着每次反弹而变亮。这并不自然,但如果您试图照亮场景中的黑暗封闭空间,它会非常有用。
4. 选择color属性框打开颜色选择器窗口并调整灯光的颜色。

在前向渲染中处理不同的光源类型

参照前面的Blinn-Phong光照模型,并为前向渲染定义了Base PassAdditional Pass来处理多个光源,这里只给出关键代码如下:

  1. 定义第一个Pass–Base Pass,需设置Pass的渲染路径:
    在这里插入图片描述
  2. 在Base Pass的片元着色器重,先计算了场景中的环境光,在后面的Additional Pass中不会再计算,与之类似,还有物体的自发光。可使用_WorldSpaceLightPos0直接得到平行光的方向。使用_LightColor0来得到它的颜色和强度
    在这里插入图片描述
    至此Base Pass 的工作完成了
  3. 接下来为场景中其他逐像素光源定义 Additional Pass。需先设置Pass的渲染路径:
    在这里插入图片描述
  4. 片元着色器
    在这里插入图片描述

最终效果

在这里插入图片描述

实践:Base Pass 和 Additional Pass 的调用

场景中放置1个平行光 + 4个红色点光源
在这里插入图片描述

打开帧调试器(Frame Debugger) 工具来查看场景的绘制过程,Unity一共进行了6个渲染事件,本例只包含一个物体,因此这6个渲染事件几乎都是用于渲染该物体的光照结果。依次单击帧调试器中的渲染工作,

  1. 在第一个渲染事件中,Unity首先清除颜色、深度和模板缓冲,为后面的渲染做准备
    在这里插入图片描述
  2. 在第二个渲染事件中,Unity利用ForwardRendering的第一个Pass,即Base Pass,将平行光渲染到帧缓存中;
    在这里插入图片描述
  3. 在后面的4个渲染事件中,Unity使用ForwardRendering的第二个Pass,即Additional Pass,依次将4个点光源的光照应用到物体上,得到最后的渲染结果。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    可注意到,若把距离最近点的点光源强度设为0.2,相比较上面的情况,帧调试器的绘制顺序会发生变化。最近的点光源会在最后被渲染。

在这里插入图片描述在这里插入图片描述
在这里插入图片描述
修改点光源的 RenderModeNot Important,不希望把该光源当作逐像素处理
在这里插入图片描述
在这里插入图片描述

Unity 的光照衰减

在片元着色器中计算逐像素光照的衰减。好处在于,计算衰减不依赖于数学公式的复杂性,只要使用一个参数值去纹理中采样即可,但使用纹理查找来计算衰减也有一些弊端。

1.需要预处理得到采样纹理,而且纹理的大小也会影响衰减的精度。
2.不直观,同时也不方便,因此一旦把数据存储到查找表中,就无法使用其他数学公式来计算衰减。

但由于这种方法可在一定程度上提升性能,得到的效果在不大部分情况下是良好的,因此Unity默认是使用这种纹理查找的方式来计算逐像素的点光源和聚光灯的衰减的。

用于光照衰减的纹理

Unity在内部使用一张名为_LightTexture0 的纹理来计算光源衰减。通常只关心_ LightTexture0 对角线上的纹理颜色值,这些值表明了在光源空间中不同位置的点的衰减值。例如,(0, 0)点表明了与光源位置重合的点的衰减值,而(1, 1)点表明了在光源空间中所关心的距离最远的点的衰减。

为了对_LightTexture0纹理采样得到给定点到该光源的衰减值,首先需要得到该点在光源空间中的位置,这是通过LightMatrix0 变换矩阵得到的。_ LightMatrix0可以把顶点从世界空间变换到光源空间。因此,只需要把_LightMatrix0 和世界空间中的顶点坐标相乘即可得到光源空间中的相应位置:

float3 lightCoord = mul(_ LightMatrix0, float4 (i.worldPosition, 1)) .xyz;

然后,可以使用这个坐标的模的平方对衰减纹理进行采样,得到衰减值:

fixed atten = tex2D(_ LightTexture0, dot (lightCoord, lightCoord) . rr) .UNITY_ ATTEN CHANNEL;

可以发现,在上面的代码中,使用了光源空间中顶点距离的平方(通过dot函数来得到)来对纹理采样,之所以没有使用距离值来采样是因为这种方法可以避免开方操作。然后,使用宏UNITY_ ATTEN_CHANNEL来得到衰减纹理中衰减值所在的分量,以得到最终的衰减值。

使用数学公式计算衰减

float distance = length(_ WorldSpaceLightPos.xyz -i . worldPosition.xyz) ;
atten = 1.0 / distance; // linear attenuation

Unity 的阴影

Unity的阴影概述

不透明物体的阴影

本例中,选择软阴影(Soft Shadows)

让物体投射阴影

通过设置物体的MeshRenderer组件中的 Cast ShadowsReceive Shadows 属性来实现的。
Cast Shadows 设On(该物体加入到光源的阴影映射纹理的计算中)或Off。
在这里插入图片描述
在这里插入图片描述
Receive Shadows 可选择是否让物体接收来自其他物体的阴影。若没开启,当调用Unity内置宏和变量计算阴影时,这些宏通过判断该物体没有开启接收阴影的功能,就不会在内部计算阴影。
在这里插入图片描述
在这里插入图片描述
把右侧平面的Cast Shadows设置为Two Sided 来允许对物体的所有面都计算阴影信息
在这里插入图片描述
在这里插入图片描述

让物体接收阴影

  1. 在Base Pass 中包含进一个新的内置文件:
    在这里插入图片描述

  2. 顶点着色器的输出结构体v2f中添加了一个内置宏SHADOW_COORDS:
    在这里插入图片描述

  3. 在顶点着色器返回之前添加另一内置宏TRAMSFER_SHADOW:
    在这里插入图片描述

  4. 接着,在片元着色器中计算阴影值,这同样使用了一个内置宏SHADOW_ATTENUATION:
    在这里插入图片描述
    SHADOW_COORDSSHADOW_ATTENUATIONTRAMSFER_SHADOW计算阴影时的"三剑客"。这些宏帮助在必要时计算光源的阴影。
    在前向渲染中,宏SHADOW_COORDS实际上就是声明了一个名为_ShadowCoord的阴影纹理坐标变量。而TRAMSFER_SHADOW的实现会根据平台不同而有所差异。若当前平台可使用屏幕空间的阴影映射技术,TRAMSFER_SHADOW会调用内置的ComputeScreenPos 函数来计算_ShadowCoord;若该平台不支持屏幕空间的阴影映射技术,就会使用传统的阴影映射技术,TRAMSFER_SHADOW会把顶点坐标从模型空间变换到光源空间后存储到_ShadowCoord中。然后,SHADOW_ATTENUATION负责使用_ShadowCoord对相关的纹理进行采样,得到阴影信息。
    当关闭了阴影后,SHADOW_COORDSTRAMSFER_SHADOW实际没有任何作用,而SHADOW_ATTENUATION会直接等同于数值1。

使用帧调试器查看阴影绘制过程

其中RenderShadomap,即渲染得到平行光的阴影映射纹理;以下是4个渲染事件
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

统一管理光照衰减和阴影

在Base Pass 中,平行光的衰减因子总等于1,而在Additional Pass 中,需判断该Pass处理的光源类型,再使用内置变量和宏计算衰减因子。实际上,光照衰减和阴影对物体最终的渲染结果的影响本质上是相同的——都是把光照衰减因子和阴影值及光照结果相乘得到最终的渲染结果。有一个方法可同时计算两个信息,主要通过内置的UNITY_LIGHT_ATTENUATION宏来实现的。

  1. 与上面不同的是,在Base Pass 和 Additional Pass中,片元着色器中使用内置宏UNITY_LIGHT_ATTENUATION来计算光照衰减和阴影:
    在这里插入图片描述

透明度物体的阴影

参考之前的AlphaTest的Shader,在此基础上补充

  1. 设置通道及先包含头文件
    在这里插入图片描述

  2. 使用内置宏SHADOW_COORDS声明阴影纹理坐标:
    在这里插入图片描述

  3. 使用内置宏TRANSFER_SHADOW计算阴影纹理坐标后传递给片元着色器:
    在这里插入图片描述

  4. 在片元着色器中,使用内置宏UNITY_LIGHT_ATTENUATION计算阴影和光照衰减:
    在这里插入图片描述

  5. 这次,更改它的Fallback,使用VertexLit作为它的回调Shader:
    在这里插入图片描述

最终效果

在这里插入图片描述
6. 为了使用透明度测试的物体得到正确的阴影效果,只需在Unity Shader中更改一行,即把Fallback设置为Transparent/Cutout/VertexLit。但需要注意的是,由于Transparent/Cutout/VertexLit是计算透明度测试时,使用了名为_Cutoff 的属性来进行透明度测试,因此,要求Shader也必须提供名为_Cutoff的属性。否则,同样无法得到正确的阴影效果

最终效果

在这里插入图片描述

半透明度物体的阴影

透明度混合中几乎完全相同的代码,只是添加了关于阴影的计算
在这里插入图片描述
处理半透明物体,由于透明度混合需要关闭深度写入,由此带来的问题也影响了阴影的生成。总体来说,想为这些半透明物体产生正确的阴影,需要在每个光源空间下仍然严格按照从后往前的顺序进行渲染,这让阴影处理变得非常复杂,而且也会影响性能。因此,在Unity中所有内置的半透明Shader是不会产生任何阴影效果的。当然,可使用一些dirty trick 来强制为半透明物体生成阴影,这可通过把它们的Fallback找到一个阴影投射的Pass。然后,通过物体的Mesh Renderer组件上的Cast ShadowsReceive Shadows选项来控制是否需要向其他物体投射或接收阴影。如图显示了把Fallback设为VertexLit开启阴影投射和接收阴影后的半透明物体的渲染效果。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_42050609/article/details/124870805