Lights

Single-Pass Forward Rendering

  • 实现 diffuse shading.
  • 支持 directional(方向光), point(点光源), and spotlights(聚光灯).
  • 每帧可允许最多16个可见光参与渲染
  • 每个物体可以最多由4个像素光和4个顶点光参与计算光照。

这是本系列教程的第三篇,在这一篇中,我们将实现每个物体由8个光源进行shading且仅消耗一个draw call。

1. Shading With a Light

为了渲染光照,我们得为我们的渲染管线加入一个最基础的lit shader。光照渲染可以非常简单,比如只包括光的漫反射,也可以非常非常复杂,比如基于物理的渲染(PBS)。我们现在先从最基础的开始,只计算方向光的漫反射,不考虑阴影。

1.1 Lit Shader

复制Unlit.hlsl并重命名为Lit.hlsl. 在文件中,用lit代替unlit,尤其是定义vertex和fragment 函数的名字。

同样复制Unlit.shader并重命名为Lit.shader. 在文件中,用lit代替unlit。

现在我们可以通过新建的lit shader创建material了,虽然目前渲染的效果和unlit一样(没写呢还当然一样)

1.2 Normal Vectors

为了计算方向光,我们需要知道表面的法线。我们为vertext函数的输入和输出结构体添加法线信息。

我们假设物体使用统一的scale,因此用3X3 模型矩阵简化法线的坐标变换,如果不是统一的scale,我们需要用world to object的转置矩阵进行计算(具体原理可以搜索其他资料,法线的坐标变换)。坐标变换后在fragment函数中进行归一化。

为了证明我们获得了正确的法线信息,我们在fragment函数中输出法线看看效果

1.3 Diffuse Light

漫反射由光照与表面法线的角度夹角决定,目前我们先硬编码,将光照的方向设置为 (0 ,1,0)。

2. Visible Lights

为了使用场景中的光源,我们的渲染管线需要将光源数据传输到GPU中,场景中可能存在多光源,所以我们也需要支持多光源的渲染。Unity中默认的渲染管线会为每个物体每个光源分配一个pass进行渲染(M X N 即M个物体N个光源需要M X N个Pass)。LWRP渲染管线则对每个物体只使用一个Pass渲染所有光源。HDRP则使用Deferred rendering,先渲染所有物体的表面信息,再对每个光源使用一个pass进行渲染。

在本文里,我们使用和LWRP相同的策略,对每个物体用一个pass渲染所有光源,所以要求我们将当前可见的所有光源信息传输到GPU,那些虽然在场景中,但是对物体没有产生任何影响的光源将被忽略不参与计算。

2.1 Light Buffer

在一个Pass中渲染所有光源意味着所有光源的信息必须在同时都准备好,我们目前暂且将所有光源类型限制为方向光,这意味着我们需要知道每个光源的颜色和方向信息。为了支持多光源,我们采用数组来存储。我们用一个单独的buffer存储光源的信息,给这个buffer命名为_LightBuffer.

然而我们并不能够在定义数组时不指定数组大小,我们声明一个宏来定义最大可见光源数量,用它来指定数组大小

加入一个DIffuseLight函数,它用传入进来的光源信息计算Diffuse光照

在LitPassFragment函数中加入for循环来支持多光源的渲染

2.2 Filling the Buffer

现在我们渲染出来的东西还是一片漆黑,这是因为我们还没把光源数据传进GPU来,我们需要在我们的渲染管线MyPipeline中声明同样大小的数组,再使用Shader.PropertyToID方法获取shader中相关属性的引用,

通过函数SetGlobalVectorArray操作command buffer,可以将数组数据传入到GPU中。

2.3 Configuring the Lights

我们现在是可以将光源数据每帧传输到GPU中了,但是现在确依然显示漆黑,这是因为我们还得先设置数据,我们声明个ConfigureLights函数来完成这项工作。

在culling剪裁中,Unity同时指出了哪些光源是可见的。这一信息可以从cull结果中获得,这一信息以visibleLights名字的list变量存储在cull结果中。

finalColor字段存储了光源的颜色,该颜色数据是由光源的color属性和intensity属性相乘后的结果,并经过了颜色空间的校正,所以我们可以直接使用该信息将其赋值给visibleLightColors数组。

然后,unity默认的渲染管线中,intensity定义在gamma空间,我们工作中线性空间,所以通过GraphicsSettings.lightsUseLinearIntensity 属性我们将其设置为线性空间。

方向光的光源方向信息可以通过光源的旋转信息获得,光源的方向是它的z轴方向。我们可以通过VisibleLight.localtoWorld矩阵获取在世界坐标系中的该信息。这个矩阵的第三列定义了光源的本地Z轴方向。

在shader中我们使用从物体朝向光源的向量方向进行计算,所以将获得的光源方向进行取反操作。

我们的shader目前将会计算四个光源,即使场景中没有四个光源,也将会计算四次。在场景中加入四个光源后,渲染的效果如下。

在frame debugger中可以查看到传入GPU的light data。

2.4 Varying the Number of Lights

当可见光的数量大于我们设定的maxVisibleLights时,会产生越界的错误,所以我们要对边界条件进行处理,当可见光数量大于maxVisibleLights时,忽略掉多出的那些光源(Unity光源排序的规则可以参考其他资料,简单来讲是通过光源的重要程度排序)

我们还要处理的一种情形是当光源数目由多变少,这时候需要清理重置光源的信息,确保下一帧的正确渲染。

3. Point Lights

这一节我们将实现渲染管线中的点光源。

3.1 Light Position

和方向光不同,点光源不关心光的方向而关心光源的位置。我们不另外开辟新数组存储位置信息,而是使用之前声明用于存储方向光方向信息的数组来存储点光源的位置数据。在Mypipeline中重新命名该数组

使用VisibleLight.lightType来判断当前光源的类型,当是方向光时存入方向信息,当是点光源时存入位置信息。

在shader函数中,使用该数据信息获取光源位置信息,并传入worldPos,两者相减即可获得光线的方向。

当是方向光时,w是0,当是点光源时w是1,我们利用该性质将worldPos 与 w分量相乘,这样就可以用同一个公式计算点光源和方向光的信息。

为了获取片段的位置信息,我们需要在shader中进行处理,由vertex函数输出到fragement函数。

至此,我们就可以看到点光源的效果了。

3.2 Distance Attenuation

和方向光不同,点光源要考虑光源强度随着距离而衰减。这里的衰减关系是距离平方的倒数。为了避免除数是0出现错误,因此加入一个极小的值0.00001

3.3 Light Range

点光源还有个属性是光照范围。 在范围外的物体将不会受该光源的影响,虽然在事实上它们可能会被物体照亮,但是用范围这个属性,我们可以更好的规定哪些物体受到该光源到影响,没有这个范围属性限制,所有的光源都会被认为是可见的。

范围属性不是突变的而是平滑渐变的,其公式为:

范围属性是场景中的数据,所以我们也需要将其传入GPU,这回我们将使用一个新的数组来存储它。

像之前做的一样,把数据用command buffer输入到GPU中

填充数据时,我们计算好,将结果存入数组后传入GPU,这样可以减少GPU的工作。

在shader中计算范围的影响,进行着色

Light fades out based on range

4. Spotlights

接下来我们添加聚光灯光源.聚光灯和点光源很像,但是有方向的限制

4.1 pot Direction

像方向光源,聚光灯也是沿着它的z方向发射光,但是是一个圆锥形范围,它也有个位置属性,所以我们得新添加个数组来支持聚光灯。

判断光源类型,如果是聚光灯,将方向信息填入新的数组中。

在shader中添加方向数据。

4.2 Angle Falloff

聚光灯类型光源也是渐变的衰减,这个范围可以被定义为一个内层的角度和一个外层的角度,从内层的角度开始衰减,直到外层衰减到0.

Unity LWRP中,spot light类型光源只允许我们控制其外层角度,其衰减的方法被假定为与外层的角度有一个固定算法。

为了得到fallof,先把spot 光源的角度的一半由角度转换成弧度,并计算其cosine值。

根据外层的角度计算内层的角度的公式以及衰减函数的公式和计算如下所示:

其中衰减函数可以进行简化:

最后在shader中用计算出来的光照进行着色

为了保证不同类型的光照计算的一致性(用同样的shader代码),将w分量设置为1

5.  Lights Per Object

目前我们支持了对一个物体用四个光源进行光照,实际上,无论有几个光源,目前每个物体都将计算4次,但其实很多时候是不必要的。不如如下的例子。9乘9的方格,共有81个球体,场景中有4个光源在四个角,当光源的范围并不是很大时,大多数球体只受到一个光源的影响,甚至有的球体不受到任何影响。

目前81个球体在开启GPU Instaing的时候将只会消耗一个draw call,但是球体的每个fragment将在fragment shader中计算4次光照,我们应该改进成只计算影响该fragment的光源。

5.1 Light Indices

在Culling期间,Unity也会计算出哪些光是可见的,每个物体受哪些光源的影响的信息可以以光照索引list的形式传输到GPU

Unity目前支持两种形式的光源索引,第一种是对每个物体,将其受影响的光源存入两个float4类型变量中。第二种是将所有物体受光源影响的信息以list形式一起存入单独的buffer中。然而目前Unity 2018.3版本只支持第一种,因此我们采用第一种。

设置rendererConfiguration字段为RendererConfiguration.PerObjectLightIndices8来开启光源的索引功能。

Unity现在需要为每个物体设置额外的数据以提供给GPU,这将会影响到GPU instancing。相较于根据受影响的光源分组,Unity更倾向于根据距离分组,另外光源的重要性也会影响到索引的排序,这些都会影响到合批。在我们的这个例子中,会由30个draw call,远大于1,当然也远小于81.

索引通过unity_4LightIndices0 and unity_4LightIndices1引通变量可以获得,它们应该存在UnityPerDraw Buffer中。另外

unity_LightIndicesOffsetAndCount变量中的Y分量存有当前物体受多少光源影响的数量。

现在我们可以限制调用DiffuseLight着色的次数为实际需要的了,但是我们还需要取出正确的索引来使用。我们目前限制灯光数量最多为4个,所以只需从unity_4LightIndices0变量中获取。

限制GPU的开销变小了,我们只需要计算真正影响到物体的光源,通过frame debugger我们可以查看传入的光源的数量以及索引。

现在不在需要使用固定的数值来循环计算了,也不需要再去每次清除data。

5.2 More Visible Lights

现在可以支持更多可见的光源,让我们把场景中最大的可见光源数量提升到16,但是大部分物体只会受少量光源的影响。修改变量值为16:

对于unity_4LightIndices0变量,最多只能存储4个值,所以我们要注意不要越界:

但是我们可以不必限制单个物体最多受4个光源的影响,因为我们还可以用unity_4LightIndices1变量。但是我们不能超过8个,这已经是对当个物体来讲,目前能够支持最多数量的光源了:

光源的索引是按照重要程度排序的,对于大多数物体,后四个光源的影响其实很小,关掉前四个光源的效果,可以查看后四个光源的效果:

5.3 Vertex Lights

由于后四个光源其实并没有那么重要,我们可以将其计算从fragment函数中移到vertex函数中,也就是从逐像素光照改为逐顶点光照,这样虽然着色的精度会损失一些,但是可以减少GPU的消耗。现在,意味着我们支持4个逐像素光照,4个逐顶点光照,注逐顶点光照的结果要传入到fragment函数中,作为初始值参与光照的计算,和逐像素光照相加后输出:

5.4 Too Many Visible Lights

尽管目前我们已经支持到场景中最多16个光源,但是依然无法避免有可能会存在更多光源的情况。当超出时,我们需要告诉Unity需要将一些光源舍弃以避免数组的越界。

我们可以通过GetLightIndexMap函数获得光源索引的list,修改该list后再通过SetLightIndexMap函数存回去。Unity将对索引数组中为-1的值进行忽略,所以我们可以将超出的光源的索引改为-1:

进一步优化,我们可以只需要当数量确实超出时进行该操作:

5.5 Zero Visible Lights

另一个可能性是场景中没有一个光源,这时为了避免错误崩溃,我们需要先判断场景中的光源数量大于0再设置drawSettings.rendererConfiguration变量,同时只有在场景中光源数量大于0的情况下才设置光源数据:

不设置光源数据的一个副作用是这些数据将一直保持最后一个物体的数据,为了避免这个问题,我们需要手动将unity_LightIndicesOffsetAndCount设置为0:

Unity SRP自定义渲染管线 -- 3.Lights相关推荐

  1. Unity SRP自定义渲染管线 -- 2.Custom Shaders

    本章将接着上一篇文章,在初步实现一个渲染管线后来创建自定义的shader.上一篇文章的链接 https://blog.csdn.net/yinfourever/article/details/9051 ...

  2. Unity SRP自定义渲染管线 -- 1.Custom Pipeline

    该篇是对Catlike Coding这篇文章的概要总结,本人能力有限,如果有不正确的地方欢迎指正  https://catlikecoding.com/unity/tutorials/scriptab ...

  3. Unity SRP自定义渲染管线学习2.2: 合批(Batching) SRP Batcher

    接下来我们要来学习下自定义渲染管线中的合批,这一节主要学习SRP Batcher 每一次的Draw Call都需要CPU和GPU之间的通信,如果有大量的数据需要从CPU发送到GPU中,那GPU就可能因 ...

  4. Unity SRP自定义渲染管线 -- 4.Spotlight Shadows

    英文原文:https://catlikecoding.com/unity/tutorials/scriptable-render-pipeline/spotlight-shadows/ 渲染并且读取纹 ...

  5. Unity SRP自定义渲染管线 -- 5.Directional Shadows

    原文:https://catlikecoding.com/unity/tutorials/scriptable-render-pipeline/directional-shadows/ 支持多个方向光 ...

  6. Unity可编程渲染管线系列教程(1):自定义渲染管线

    前言     Jasper Flick<Unity可编程渲染管线>系列教程之:自定义渲染管线.该教程分享了用户如何在Unity引擎从头构建简易的渲染管线.原文链接可见该博客末尾. 目录 创 ...

  7. [游戏开发]Unity SRP 学习(一)

    前言 现在的手游开发团队都会首选URP管线开发,而SRP是URP的根基,所以学好SRP可以更高自由度的控制渲染流程. 正文 首先,Graphics设置如果不指定Render Pipeline Asse ...

  8. Unity可编程渲染管线系列(六)透明度(裁剪与淡化 Clipping and Fading)

    目录 1 Alpha裁剪 1.1 Alpha贴图 1.2 纹理化 1.3 丢弃片段 1.4 裁剪阴影 1.5 双面渲染 1.6 给背面翻转法线 1.7 可选的裁剪 1.8 Alpha-Test渲染队列 ...

  9. Unity可编程渲染管线系列(四)聚光灯阴影(阴影贴图)

    目录 1 一个带有阴影的聚光灯 1.1 阴影贴图 1.2 阴影命令缓冲区 1.3 设置 渲染目标 1.4 配置视图和投影矩阵 1.5 渲染阴影投射器 2 阴影投射器通道 2.1 阴影包含文件 2.2 ...

最新文章

  1. 自动驾驶技术之——虚拟场景数据库研究
  2. 零基础自学python教程-零基础学Python不迷茫——基本学习路线及教程
  3. VMM2012应用指南之2- 准备VMM2012虚拟机
  4. javascript焦点图(根据图片下方的小框自动播放)
  5. mybatis动态SQL语句
  6. matlab能流图,有会用MATLAB写海流图的程序吗
  7. java 基础知识学习 内存泄露(memory leak) VS 内存溢出(out of memory)以及内存管理...
  8. codeforces 690D2 D2. The Wall (medium)(组合数学)
  9. 吉米多维奇数学分析习题集每日一题--泰勒公式习题1376
  10. 突出的就是一个「性价比」— 小新 Air 14 2020 评测
  11. 用差分法求解burger方程 matlab,偏微分方程数值解上机实验.doc
  12. Java实现剪切MP3格式的文件_java_java实现酷狗音乐临时缓存文件转换为MP3文件的方法,本文实例讲述了java实现酷狗音 - phpStudy...
  13. 表格里加横线一分为二_Word2010表格分割线一分为二斜线
  14. 使用nodejs pkg创建exe文件后更改图标
  15. TYUT太原理工大学2022需求工程考试填空题
  16. 在上海奋斗的五年---从月薪3500到700万 (一个西北真汉子的人生)
  17. 基于准反射学习的哈里斯鹰优化算法
  18. 解决IDEA输入ctrl+空格,和输入法冲突的问题
  19. 基于linkboy+W801编程实现彩灯带的多种发光算法
  20. SQl语句查询重复数据 只显示其中一条

热门文章

  1. c语言代码大全_从学生到专家,C语言开发必读的8本书
  2. php获取dropzone上传的文件,php - 如何上传文件,使用php中的dropzone将文件详细信息保存到mysql数据库 - SO中文参考 - www.soinside.com...
  3. 【计算机组成原理】定点数的表示和运算
  4. access denied for_abm怎么样?ACCESS集团携8大国际品牌在进博会首秀,展示abmr 硬核实力!...
  5. codeblocks怎么用已封装的类_mitoq 在新西兰用着怎么样?已入手mitoq,我的感受
  6. 带权并查集--hdu3047 ZJnu stadium
  7. 第K短路+严格第K短路
  8. TensorFlow 2.0快速上手指南12条:“Keras之父”亲授 | 高赞热贴
  9. 同域下iframe操作时,js访问document出现拒绝访问的问题原因
  10. 随机森林算法的随机性_理解随机森林算法的图形指南