网站排名突然掉没了,做 专而精 的网站,如何设计一款软件,做减肥餐的网站Lights
Single-Pass Forward Rendering
实现 diffuse shading.支持 directional#xff08;方向光#xff09;, point#xff08;点光源#xff09;, and spotlights#xff08;聚光灯#xff09;.每帧可允许最多16个可见光参与渲染每个物体可以最多由4个像素光和4个顶…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 10。 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 range4. 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