Unity SRP自定义渲染管线 -- 3.Lights

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:

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/246245.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Faceware 面部捕捉在Unity中的应用

官网软件下载:https://www.facewaretech.com/ 官网教程:http://support.facewaretech.com/ 官网素材下载:https://www.facewaretech.com/learn/free-assets Faceware Analyzer Faceware Retargeter(Maya) Unity 真…

Tone mapping进化论

转载自 https://zhuanlan.zhihu.com/p/21983679 这几年,随着拍摄设备、渲染方法和显示设备的发展,HDR慢慢会成为标配。照相机和摄像机可以捕捉到HDR的影响,渲染过程中可以产生HDR的画面。这些内容如果需要显示到LDR的设备上,就需…

坐标变换过程(vertex transformation)

原文:https://blog.csdn.net/wangdingqiaoit/article/details/51594408 在上面的图中,注意,OpenGL只定义了裁剪坐标系、规范化设备坐标系和屏幕坐标系,而局部坐标系(模型坐标系)、世界坐标系和照相机坐标系都是为了方便用户设计而…

三灯布光法

原文:https://zhuanlan.zhihu.com/p/62307736?utm_sourcewechat_session&utm_mediumsocial&utm_oi919394520523739136 如果将视频影像比喻成一幅画,光线就是画笔,光影造就了影像画面的立体感。本期圈圈就给大家简单介绍一下视频影像…

齐次坐标

本文是一些关于齐次坐标知识的整合。 https://www.sohu.com/a/258317807_100007727 http://www.songho.ca/math/homogeneous/homogeneous.html https://blog.csdn.net/VenoBling/article/details/87794400 https://www.cnblogs.com/csyisong/archive/2008/12/09/1351372.ht…

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

英文原文:https://catlikecoding.com/unity/tutorials/scriptable-render-pipeline/spotlight-shadows/ 渲染并且读取纹理从光空间(光源角度)渲染为阴影投射(shadow casters)添加一个着色器pass采样阴影贴图支持软阴影…

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

原文:https://catlikecoding.com/unity/tutorials/scriptable-render-pipeline/directional-shadows/ 支持多个方向光阴影控制阴影距离定义独立的主光源渲染和采样级联阴影(cascaded shadow map)使用球形剔除1. Shadows for Directional Lig…

浅析Unity中的Enlighten与混合光照

原文https://www.cnblogs.com/murongxiaopifu/p/8553367.html 0x00 前言 在Unity的5.6版本之前的5.x中,主要使用了Geomerics公司的Enlighten【1】来提供实时全局照明以及烘焙全局照明,在5.6之后Unity引入了新的Lightmapper——Progressive来提供烘焙全…

聊聊LightProbe原理实现以及对LightProbe数据的修改

原文链接https://www.cnblogs.com/murongxiaopifu/p/8997720.html 0x00 前言 最近工作比较忙,所以文章已经很久没有更新了。这篇小文的主题也是在出差的高铁上想到,因为最近和一些朋友聊天,发现他们中很多人的项目中都使用了多个实时光源。…

3D游戏的照明设计理论,第3部分:三点照明法的异端与误区

https://zhuanlan.zhihu.com/p/87997570 这是有关如何处理游戏照明的系列文章的一部分。第一部分是关于灯具,第二部分是关于光的形式材料。 在第一部分中,我们首先从文化角度考虑了灯光-灯光在整个历史上对不同的人意味着不同的事物,并且在照…

3D游戏的照明设计理论,第4部分:如何在游戏引擎中照亮游戏世界

从更一般和更概念的角度来看,这是有关我如何处理游戏照明的系列文章的一部分。我在Unity中构建了大部分示例,但这通常适用于任何3D游戏引擎,其中大多数具有类似的照明工具。 我们开始思考了有关光照的文化和概念,在第一部分。在第…

unity shader 变种(多重编译 multi_compile)

一、定义 在unity中我们可以通过使用#pragma multi_compile或#pragma shader_feature指令来为shader创建多个稍微有点区别的shader变体。这个Shader被称为宏着色器(mega shader)或者超着色器(uber shader)。实现原理:…

AndroidStudio导出aar文件给Unity使用

AndroidStudio导出aar文件给Unity使用 本文参考 :http://www.devacg.com/?post548 Demo地址:https://github.com/JulyNine/AndroidToUnity 一、用Android Studio创建个空工程 注意:包名要与Unity中工程的包名不一致,不然打包时…

Unity C# Job System介绍(四) 并行化Job和故障排除(完结)

并行化job ParallelFor jobs​docs.unity3d.com 当调度Jobs时,只能有一个job来进行一项任务。在游戏中,非常常见的情况是在一个庞大数量的对象上执行一个相同的操作。这里有一个独立的job类型叫做IJobParallelFor来处理此类问题。ParallelFor jobs当调…

C# Job System

概述 设计目的:简单安全地使用多线程,随便就能写出高性能代码 收益:FPS更高,电池消耗更低(Burst编译器) 并行性:C# Job System和Unity Native Job System共享工作线程worker threads&#xf…

Unity游戏开发——C#特性Attribute与自动化

这篇文章主要讲一下C#里面Attribute的使用方法及其可能的应用场景。 比如你把玩家的血量、攻击、防御等属性写到枚举里面。然后界面可能有很多地方要根据这个枚举获取属性的描述文本。 比如你做网络框架的时候,一个协议号对应一个类的处理或者一个方法。 比如你做…

Unity c#中Attribute用法详解

举两个例子,在变量上使用[SerializeFiled]属性,可以强制让变量进行序列化,可以在Unity的Editor上进行赋值。 在Class上使用[RequireComponent]属性,就会在Class的GameObject上自动追加所需的Component。 以下是Unity官网文档中找…

走进LWRP(Universal RP)的世界

走进LWRP(Universal RP)的世界 原文:https://connect.unity.com/p/zou-jin-lwrp-universal-rp-de-shi-jie LWRP自Unity2018发布以来,进入大家视野已经有一段时间了,不过对于广大Unity开发者来说,依然相对…

Unity 2017 Game Optimization 读书笔记(1)Scripting Strategies Part 1

1.Obtain Components using the fastest method Unity有多种Getcomponet的方法&#xff1a; GetComponent(string), GetComponent<T>() GetComponent(typeof(T)) 哪种效率最高会跟随Unity版本的变化而变化&#xff0c;对于Unity 2017&#xff0c;本书作者的测试是Ge…

C# 多态相关的文章

一 C# 多态的实现 封装、继承、多态&#xff0c;面向对象的三大特性&#xff0c;前两项理解相对容易&#xff0c;但要理解多态&#xff0c;特别是深入的了解&#xff0c;对于初学者而言可能就会有一定困难了。我一直认为学习OO的最好方法就是结合实践&#xff0c;封装、继承在…