本文是自定义可渲染管线系列比较重要的章节,我们将实现自定义可编程渲染管线对灯光照明的支持。

如果我们想创建一个更加逼真的场景,那么我们必须要模拟物体表面的光照现象。这需要提供更加复杂的shader才能实现。

LitShader

复制UnlitPass.hlsl文件并将其重命名为LitPass,然后修改引用保护以及顶点、片元函数的名字。我们将在后面添加灯光计算。

#ifndef CUSTOM_LIT_PASS_INCLUDED
#define CUSTOM_LIT_PASS_INCLUDED…Varyings LitPassVertex (Attributes input) { … }float4 LitPassFragment (Varyings input) : SV_TARGET { … }#endif

同时复制Unlit shader文件,并重命名为Lit。修改它的菜单名,引用文件和所使用的函数。让我们也将默认颜色更改为灰色,因为在光线充足的场景中全白的表面可能显得非常明亮。 默认情况下,unity的通用管道也使用灰色。

Shader "Custom RP/Lit" {Properties {_BaseMap("Texture", 2D) = "white" {}_BaseColor("Color", Color) = (0.5, 0.5, 0.5, 1.0)…}SubShader {Pass {…#pragma vertex LitPassVertex#pragma fragment LitPassFragment#include "LitPass.hlsl"ENDHLSL}}
}

我们将使用一种自定义的照明方法,通过将着色器的照明模式设置为CustomLit。在Pass模块中添加一个Tag标签,其中包括”LightMode”=”CustomLit”。

Pass {Tags {"LightMode" = "CustomLit"}…
}

要渲染使用此通道的对象,我们必须将其包含在CameraRenderer中。 首先为其添加一个着色器标签标识符。

static ShaderTagId
unlitShaderTagId = new ShaderTagId("SRPDefaultUnlit"),
litShaderTagId = new ShaderTagId("CustomLit");

然后将其添加到要在DrawVisibleGeometry中渲染的过程中,就像在DrawUnsupportedShaders中所做的那样。

      var drawingSettings = new DrawingSettings(unlitShaderTagId, sortingSettings) {enableDynamicBatching = useDynamicBatching,enableInstancing = useGPUInstancing};drawingSettings.SetShaderPassName(1, litShaderTagId);

现在我们可以创建一个新的不透明材质了,虽然现在它的结果跟无光照材质一样。

法向量

一个物体的光照结果取决于很多因素,比如物体表面与光线的相对角度。要想知道物体表面的方向,我们必须要知道物体表面的法向,它是一个单位长度的指向表面正向的向量。这个向量是顶点数据的一部分,在物体局部空间中定义,就像位置坐标一样。所以将它添加到LitPass中的属性中。

struct Attributes {float3 positionOS : POSITION;float3 normalOS : NORMAL;float2 baseUV : TEXCOORD0;UNITY_VERTEX_INPUT_INSTANCE_ID
};

光照是根据每个片段计算的,所以我们必须将法向量也添加到Varyings中。我们将在世界空间中执行计算,因此将其命名为normalWS。

struct Varyings {float4 positionCS : SV_POSITION;float3 normalWS : VAR_NORMAL;float2 baseUV : VAR_BASE_UV;UNITY_VERTEX_INPUT_INSTANCE_ID
};

我们可以使用SpaceTransforms库中的TransformObjectToWorldNormal把法向量从局部坐标转换到世界空间。

   output.positionWS = TransformObjectToWorld(input.positionOS);output.positionCS = TransformWorldToHClip(positionWS);output.normalWS = TransformObjectToWorldNormal(input.normalOS);

为了验证我们是否在LitPassFragment中得到了一个正确的法向量,我们可以使用它作为一种颜色。

   base.rgb = input.normalWS;return base;

负值是无法显示的,所以它们被限制为0。

法线插值

虽然法向量在顶点程序中是单位长度的,但三角形间的线性插值会影响它们的长度。我们可以通过渲染向量长度与1之间的差值来可视化误差,并将其放大10倍以使其更加明显。

base.rgb = abs(length(input.normalWS) - 1.0) * 10.0;

我们可以通过对LitPassFragment中的法向量进行归一化来平滑插值失真。当只观察法向量时,这种差异并不是很明显,但当用于照明时,这种差异就更明显了。

base.rgb = normalize(input.normalWS);

表面参数

着色器中的照明是实质模拟光线到物体表面的相互作用,这意味着我们必须跟踪表面的属性。现在我们有一个法向量和一个底色。我们可以将后者分为两部分:RGB颜色和alpha值。我们将在几个地方使用这些数据,所以让我们定义一个方便的表面结构来包含所有相关数据。把它放在一个单独的surface.hlsl文件中,并保存在ShaderLibrary文件夹下。

#ifndef CUSTOM_SURFACE_INCLUDED
#define CUSTOM_SURFACE_INCLUDEDstruct Surface {float3 normal;float3 color;float alpha;
};#endif

然后在LitPass中,我们在Common之后将Surface.hlsl文件引入进来。这样我们可以让LitPass保持简短。

#include "../ShaderLibrary/Common.hlsl"
#include "../ShaderLibrary/Surface.hlsl"

在LitPassFragment中定义一个Surface变量并填充它。最后的结果就是表面的颜色和alpha值。

 Surface surface;surface.normal = normalize(input.normalWS);surface.color = base.rgb;surface.alpha = base.a;return float4(surface.color, surface.alpha);

光照计算

为了计算实际的照明,我们将创建一个带有表面属性参数的GetLighting函数。首先让它返回曲面法线的Y分量。由于这是照明功能,我们将把它放在一个单独的Lighting.HLSL文件中。

#ifndef CUSTOM_LIGHTING_INCLUDED
#define CUSTOM_LIGHTING_INCLUDEDfloat3 GetLighting (Surface surface) {return surface.normal.y;
}#endif

在LitPass中,我们在引用 surface后引用它,因为光照依赖于它。

#include "../ShaderLibrary/Surface.hlsl"
#include "../ShaderLibrary/Lighting.hlsl"

现在我们可以在LitPassFragment中获取照明,并将其用于fragment的RGB部分。

    float3 color = GetLighting(surface);return float4(color, surface.alpha);

光源

要进行光照计算,我们还需要知道光的特性。在本教程中,我们将只讨论平行光。平行光表示光源离得很远,所以它的位置无关紧要,只与它的方向有关。它可以模拟地球上的太阳光和其它单向入射光。

这里我们将使用一个结构体来存储光源数据,现在我们只需要一个颜色和一个方向就足够了。把它放在一个单独的light.hlsl文件。还定义一个GetDirectionalLight函数,该函数返回配置的方向灯。光源默认颜色是白色和方向是向上,与我们当前使用的光线数据相匹配。请注意,光的方向这里被定义为光射入的方向,而不是光射出的方向。

#ifndef CUSTOM_LIGHT_INCLUDED
#define CUSTOM_LIGHT_INCLUDEDstruct Light {float3 color;float3 direction;
};Light GetDirectionalLight () {Light light;light.color = 1.0;light.direction = float3(0.0, 1.0, 0.0);return light;
}#endif

在litpass中引用Lighting之前引用这个文件

#include "../ShaderLibrary/Light.hlsl"
#include "../ShaderLibrary/Lighting.hlsl"

光照函数

为lighting添加一个GetIncomingLight函数,计算给定表面和入射光线的反射光。对于任意方向光的反射结果,我们需要取表面法线和方向的点积再乘以光的颜色。

float3 GetIncomingLight (Surface surface, Light light) {return dot(surface.normal, light.direction) * light.color;
}

但这个结果只在表面朝向光源的时候是正确的。当点积是负数时,我们必须使它等于零,这可以通过saturate函数来实现。

float3 IncomingLight (Surface surface, Light light) {return saturate(dot(surface.normal, light.direction)) * light.color;
}

向GPU发送光源数据

我们应该使用当前场景的灯光,而不是总是使用来自我们设置的默认光。默认场景有一个方向灯,代表太阳,颜色稍微偏黄——fff4d6十六进制——绕X轴旋转50°,绕Y轴旋转30°。如果这样的光源不存在则创造一个。

为了使光源数据在着色器中可访问,我们必须为它创建统一值,就像着色器属性一样。在本例中,我们将定义两个float3类型的向量:_DirectionalLightColor和_DirectionalLightDirection。将它们放到定义在Light顶部的_CustomLight缓冲区中。

CBUFFER_START(_CustomLight)float3 _DirectionalLightColor;float3 _DirectionalLightDirection;
CBUFFER_END

在GetDirectionalLight中使用这些值而不是常量。

Light GetDirectionalLight () {Light light;light.color = _DirectionalLightColor;light.direction = _DirectionalLightDirection;return light;
}

现在,我们的RP必须将光源数据发送到GPU。 我们将为此创建一个新的Lighting类。 它的工作方式与CameraRenderer相似,但适用于灯光照明。给它提供一个带有context参数的公共Setup方法,在该方法中它调用一个单独的SetupDirectionalLight方法。

using UnityEngine.Rendering;public class Lighting {const string bufferName = "Lighting";CommandBuffer buffer = new CommandBuffer {name = bufferName};public void Setup (ScriptableRenderContext context) {buffer.BeginSample(bufferName);SetupDirectionalLight();buffer.EndSample(bufferName);context.ExecuteCommandBuffer(buffer);buffer.Clear();}void SetupDirectionalLight () {}
}

追踪两个着色器属性的标识符。

static int dirLightColorId = Shader.PropertyToID("_DirectionalLightColor"),dirLightDirectionId = Shader.PropertyToID("_DirectionalLightDirection");

我们可以通过RenderSettings.sun访问场景的主光源。 默认情况下,这使我们得到主光源,还可以通过Window /Rendering /Lighting Settings显式配置它。 使用CommandBuffer.SetGlobalVector将光源数据发送到GPU。 颜色是光源在线性空间中的颜色,而方向是光源的正向向量取反。

void SetupDirectionalLight () {Light light = RenderSettings.sun;buffer.SetGlobalVector(dirLightColorId, light.color.linear);buffer.SetGlobalVector(dirLightDirectionId, -light.transform.forward);
}

光源的颜色属性是其配置的颜色,但是光源也具有单独的强度因子。 最终的颜色是两者的乘积。

buffer.SetGlobalVector(dirLightColorId, light.color.linear * light.intensity
);

为CameraRenderer提供一个Lighting实例,并在绘制几何图形之前使用它来设置光源信息。

  Lighting lighting = new Lighting();public void Render (ScriptableRenderContext context, Camera camera,bool useDynamicBatching, bool useGPUInstancing) {…Setup();lighting.Setup(context);DrawVisibleGeometry(useDynamicBatching, useGPUInstancing);DrawUnsupportedShaders();DrawGizmos();Submit();}

在进行剔除时,Unity还会找出哪些光源会影响相机可见的空间。 我们可以依靠这些信息而不是全局的sun光源。 为此,Lighting需要访问剔除结果,我们为Setup添加一个剔除结果的输入参数,并将其存储在字段中以方便使用。然后,我们可以支持多个光源,让我们使用新的SetupLights方法来替换SetupDirectionalLight。

  CullingResults cullingResults;public void Setup (ScriptableRenderContext context, CullingResults cullingResults) {this.cullingResults = cullingResults;buffer.BeginSample(bufferName);//SetupDirectionalLight();SetupLights();…}void SetupLights () {}

在CameraRenderer.Render中调用Setup时,将剔除结果添加为参数。

lighting.Setup(context, cullingResults);

现在Lighting.SetupLights可以通过剔除结果的visibleLights属性检索所需的数据。 它以的Unity.Collections.NativeArray的形式存在。

using Unity.Collections;
using UnityEngine;
using UnityEngine.Rendering;public class Lighting {…void SetupLights () {NativeArray<VisibleLight> visibleLights = cullingResults.visibleLights;}…
}

多光源照射

使用可见光数据可以支持多个定向光,但是我们必须将所有这些光的数据发送到GPU。 因此,我们将使用两个Vector4数组,并用这两个数组来存储光源信息。让我们将最大值设置为四个,这对于大多数场景来说应该足够了。

const int maxDirLightCount = 4;static int //dirLightColorId = Shader.PropertyToID("_DirectionalLightColor"),//dirLightDirectionId = Shader.PropertyToID("_DirectionalLightDirection");dirLightCountId = Shader.PropertyToID("_DirectionalLightCount"),dirLightColorsId = Shader.PropertyToID("_DirectionalLightColors"),dirLightDirectionsId = Shader.PropertyToID("_DirectionalLightDirections");static Vector4[] dirLightColors = new Vector4[maxDirLightCount],dirLightDirections = new Vector4[maxDirLightCount];

将索引和VisibleLight参数传递给SetupDirectionalLight。 用提供的索引设置颜色和光照方向。在这种情况下,最终颜色是通过VisibleLight.finalColor属性提供的。 可以通过VisibleLight.localToWorldMatrix属性找到前向矢量。 它是矩阵的第三列,必须再次取反。

   void SetupDirectionalLight (int index, VisibleLight visibleLight) {dirLightColors[index] = visibleLight.finalColor;dirLightDirections[index] = -visibleLight.localToWorldMatrix.GetColumn(2);}

最终颜色已经应用了光源的强度,但是默认情况下Unity不会将其转换为线性空间。 我们必须将GraphicsSettings.lightsUseLinearIntensity设置为true,这可以在CustomRenderPipeline的构造函数中执行一次。

   public CustomRenderPipeline (bool useDynamicBatching, bool useGPUInstancing, bool useSRPBatcher) {this.useDynamicBatching = useDynamicBatching;this.useGPUInstancing = useGPUInstancing;GraphicsSettings.useScriptableRenderPipelineBatching = useSRPBatcher;GraphicsSettings.lightsUseLinearIntensity = true;}

接下来,遍历Lighting.SetupLights中的所有可见光,并为每个元素调用SetupDirectionalLight。 然后在缓冲区上调用SetGlobalInt和SetGlobalVectorArray以将数据发送到GPU。

NativeArray<VisibleLight> visibleLights = cullingResults.visibleLights;
for (int i = 0; i < visibleLights.Length; i++) {VisibleLight visibleLight = visibleLights[i];SetupDirectionalLight(i, visibleLight);
}buffer.SetGlobalInt(dirLightCountId, visibleLights.Length);
buffer.SetGlobalVectorArray(dirLightColorsId, dirLightColors);
buffer.SetGlobalVectorArray(dirLightDirectionsId, dirLightDirections);

但是我们最多只支持四个定向灯,因此当达到最大值时,我们应该中止循环。 让我们添加一个与循环的迭代器分开的索引。

int dirLightCount = 0;
for (int i = 0; i < visibleLights.Length; i++) {VisibleLight visibleLight = visibleLights[i];SetupDirectionalLight(dirLightCount++, visibleLight);if (dirLightCount >= maxDirLightCount) {break;}
}
buffer.SetGlobalInt(dirLightCountId, dirLightCount);

因为我们仅支持定向光源,所以我们应该忽略其他光源类型。 我们可以通过检查可见光的lightType属性是否等于LightType.Directional来做到这一点。

VisibleLight visibleLight = visibleLights[i];
if (visibleLight.lightType == LightType.Directional) {SetupDirectionalLight(dirLightCount++, visibleLight);if (dirLightCount >= maxDirLightCount) {break;}
}

这样虽然可行,但是VisibleLight结构相当大。 理想情况下,我们只从本地数组中检索一次,并且也不要将其作为常规参数传递给SetupDirectionalLight,因为那样会对其进行复制。 我们可以通过引用传递参数。

SetupDirectionalLight(dirLightCount++, ref visibleLight);

这要求我们也将参数定义为引用。

void SetupDirectionalLight (int index, ref VisibleLight visibleLight) { … }

Shader循环

在Light中调整_CustomLight缓冲区,使其与我们的新数据格式匹配。 在这种情况下,我们将显式使用float4作为数组类型。 着色器中的数组大小固定,无法调整大小。 确保使用与Lighting中定义的最大值相同。

#define MAX_DIRECTIONAL_LIGHT_COUNT 4CBUFFER_START(_CustomLight)//float4 _DirectionalLightColor;//float4 _DirectionalLightDirection;int _DirectionalLightCount;float4 _DirectionalLightColors[MAX_DIRECTIONAL_LIGHT_COUNT];float4 _DirectionalLightDirections[MAX_DIRECTIONAL_LIGHT_COUNT];
CBUFFER_END

添加一个函数以获取定向光照计数并调整GetDirectionalLight,以便它检索特定光照索引的数据。

int GetDirectionalLightCount () {return _DirectionalLightCount;
}Light GetDirectionalLight (int index) {Light light;light.color = _DirectionalLightColors[index].rgb;light.direction = _DirectionalLightDirections[index].xyz;return light;
}

然后调整曲面的GetLight,使其使用for循环来累积所有定向光的贡献。

float3 GetLighting (Surface surface) {float3 color = 0.0;for (int i = 0; i < GetDirectionalLightCount(); i++) {color += GetLighting(surface, GetDirectionalLight(i));}return color;
}

现在,我们的着色器最多支持四个平行光。 通常只需要一个平行光来表示太阳或月球,但是也可能存在行星上有多个太阳的场景。 定向灯也可以用于近似多个大型照明设备,例如大型体育场的照明设备。

如果游戏始终只有一个平行光,那么你可以摆脱循环,或者制作多个着色器变体。 但是对于本教程,我们为了简单坚持一个通用循环。 最好的性能总是通过剔除不需要的内容来实现的。

Shader的目标等级

着色器曾经遇到过长度可变的循环问题,但是现在的GPU可以毫无问题地处理它们,尤其是当绘制的所有片段以相同的方式遍历同一数据时。但是,默认情况下,OpenGL ES 2.0和WebGL 1.0图形API不能处理此类循环。我们可以通过合并硬编码的最大值来使其工作,例如,使GetDirectionalLight返回min(_DirectionalLightCount,MAX_DIRECTIONAL_LIGHT_COUNT)。这样就可以展开循环,将其变成一系列条件代码块。不幸的是,生成的着色器代码一团糟,性能下降得很快。在非常老式的硬件上,所有代码块都将始终执行,它们的贡献可通过条件分配来控制。尽管我们可以进行这项工作,但它会使代码更加复杂,因为我们还必须进行其他调整。因此,为了简化起见,我选择忽略这些限制并在构建中关闭WebGL 1.0和OpenGL ES 2.0支持。他们不支持线性照明。我们还可以通过#pragma target 3.5指令将着色器传递的目标级别提高到3.5,从而避免为它们编译OpenGL ES 2.0着色器变体。让我们保持一致,并为两个着色器执行此操作。

            HLSLPROGRAM#pragma target 3.5…ENDHLSL

unity 角度限制_Unity自定义可编程渲染管线(SRP)(九)——灯光照明相关推荐

  1. unity 天空盒_Unity自定义可编程渲染管线(SRP)(二)——编写第一个自定义SRP

    一句话描述,我们可以把SRP分解成两个部分,分别是SRP Asset,SRP Instance. SRP Asset SRP Asset是一个Unity Asset文件,用来存储渲染管线的特定配置信息 ...

  2. unity 角度限制_Unity实现角度限制

    在Unity(C#)中实现角度限制,有一个坑点(角度换算),相信大多数人都遇到过,本文会对其探究,同时补充点相机角度控制的算法. 1. 通过角度换算来实现角度限制 假设世界空间中存在一游戏物体Obje ...

  3. Unity 升级项目到Urp(通用渲染管线)以及画面后处理

    前言: Urp全称为Universal Render Pipeline,即通用渲染管线 在开始学习Urp之前,需要了解一下,什么是Render PipeLine(渲染管线),渲染管线也称为渲染流水线或 ...

  4. unity hub是什么东西_Unity可编程渲染管线(SRP)教程:一、自定义管线

    本文翻译自Catlike Coding,原作者:Jasper Flick. 本文经原作者授权,转载请说明出处. 原文链接在下: https://catlikecoding.com/unity/tuto ...

  5. unity烘培单个物体_Unity可编程渲染管线(SRP)教程:二、自定义着色器

    本文翻译自Catlike Coding,原作者:Jasper Flick. 本文经原作者授权,转载请说明出处. 原文链接在下: https://catlikecoding.com/unity/tuto ...

  6. unity绘制管道_Unity可编程渲染管线(SRP)教程:一、自定义管线

    控制渲染 创建pipeline asset 和 instance. 剔除.过滤.排序.渲染. 保持内存清洁. 提供良好的编辑体验. 这是Unity scriptable render pipeline ...

  7. micro hdmi引脚定义义_Unity可编程渲染管线(SRP)教程:一、自定义管线

    本文翻译自Catlike Coding,原作者:Jasper Flick. 本文经原作者授权,转载请说明出处. 原文链接在下: https://catlikecoding.com/unity/tuto ...

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

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

  9. 在unity中设置多种怪物数据_Unity可编程渲染管线(SRP)系列(三)——光照(单通道 正向渲染)...

    本文重点: 1.漫反射着色 2.支持方向光.点光源和聚光灯 3.每帧允许16个可见光源 4.每个对象最多计算四个像素光和四个顶点光 这是涵盖Unity可编写脚本的渲染管线的教程系列的第三部分.这次,我 ...

最新文章

  1. java 今天 昨天_js获取当前时间(昨天、今天、明天)
  2. Java 8状态更新
  3. 修改Oracle最大连接数
  4. f4 stm32 神经网络_STM32神经网络开发工具箱将AI技术引入边缘和节点嵌入式设备...
  5. 计算机类专业权威解读,09计算机考研统考大纲权威解读之操作系统
  6. 自考的那些事儿(二):第二次自考完了???
  7. java基础:13.2 集合框架 - LinkedList、Queue
  8. 系统提示 由于系统缓冲区空间不足或队列已满,不能执行套接字上的操作。
  9. HDU 5273 Dylans loves sequence【 树状数组 】
  10. android 模拟器测试之旅
  11. mysql判断叠字_. 请在以下作品中选出皆使用了“叠字”手法的作品。( ___ )
  12. python修改散点图中点的颜色_更改matplotlib中散点图点的颜色
  13. pwn libc找偏移的在线网站
  14. 看完这篇java单利模式文章,面试的时候再也不怕了
  15. 饥荒联机版服务器搭建教程-WeGame
  16. 蕴含命题遇到的疑惑和解答
  17. Android Activity的隐式调用(跨进程)★
  18. 74hc165三片级联
  19. 长沙市明德华兴中学2015班在湖南省智慧教育装备展示体验中心开展寒假社会实践活动
  20. 八万红客将国旗插到美国白宫网站,聊聊三个世界著名黑客的事迹

热门文章

  1. 信息检索及信息过滤方法概述
  2. python小实验(1):字符串处理
  3. c++图的创建_使用 Amazon Neptune 构建基于图数据库的应用
  4. Python编程基础:第二十九节 异常Exception
  5. 【分布式计算】DFS BigTable
  6. java中最容易犯错的特殊字符
  7. Docker源码分析(十一):镜像存储
  8. schedule() 和 scheduleAtFixedRate() 的区别--转载
  9. forward 和redirect
  10. 【机器学习】什么是机器学习?(上)