unity 角度限制_Unity自定义可编程渲染管线(SRP)(九)——灯光照明
本文是自定義可渲染管線系列比較重要的章節(jié),我們將實(shí)現(xiàn)自定義可編程渲染管線對燈光照明的支持。
如果我們想創(chuàng)建一個(gè)更加逼真的場景,那么我們必須要模擬物體表面的光照現(xiàn)象。這需要提供更加復(fù)雜的shader才能實(shí)現(xiàn)。
LitShader
復(fù)制UnlitPass.hlsl文件并將其重命名為LitPass,然后修改引用保護(hù)以及頂點(diǎn)、片元函數(shù)的名字。我們將在后面添加燈光計(jì)算。
#ifndef CUSTOM_LIT_PASS_INCLUDED #define CUSTOM_LIT_PASS_INCLUDED…Varyings LitPassVertex (Attributes input) { … }float4 LitPassFragment (Varyings input) : SV_TARGET { … }#endif同時(shí)復(fù)制Unlit shader文件,并重命名為Lit。修改它的菜單名,引用文件和所使用的函數(shù)。讓我們也將默認(rèn)顏色更改為灰色,因?yàn)樵诠饩€充足的場景中全白的表面可能顯得非常明亮。 默認(rèn)情況下,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}} }我們將使用一種自定義的照明方法,通過將著色器的照明模式設(shè)置為CustomLit。在Pass模塊中添加一個(gè)Tag標(biāo)簽,其中包括”LightMode”=”CustomLit”。
Pass {Tags {"LightMode" = "CustomLit"}… }要渲染使用此通道的對象,我們必須將其包含在CameraRenderer中。 首先為其添加一個(gè)著色器標(biāo)簽標(biāo)識(shí)符。
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);現(xiàn)在我們可以創(chuàng)建一個(gè)新的不透明材質(zhì)了,雖然現(xiàn)在它的結(jié)果跟無光照材質(zhì)一樣。
法向量
一個(gè)物體的光照結(jié)果取決于很多因素,比如物體表面與光線的相對角度。要想知道物體表面的方向,我們必須要知道物體表面的法向,它是一個(gè)單位長度的指向表面正向的向量。這個(gè)向量是頂點(diǎn)數(shù)據(jù)的一部分,在物體局部空間中定義,就像位置坐標(biāo)一樣。所以將它添加到LitPass中的屬性中。
struct Attributes {float3 positionOS : POSITION;float3 normalOS : NORMAL;float2 baseUV : TEXCOORD0;UNITY_VERTEX_INPUT_INSTANCE_ID };光照是根據(jù)每個(gè)片段計(jì)算的,所以我們必須將法向量也添加到Varyings中。我們將在世界空間中執(zhí)行計(jì)算,因此將其命名為normalWS。
struct Varyings {float4 positionCS : SV_POSITION;float3 normalWS : VAR_NORMAL;float2 baseUV : VAR_BASE_UV;UNITY_VERTEX_INPUT_INSTANCE_ID };我們可以使用SpaceTransforms庫中的TransformObjectToWorldNormal把法向量從局部坐標(biāo)轉(zhuǎn)換到世界空間。
output.positionWS = TransformObjectToWorld(input.positionOS);output.positionCS = TransformWorldToHClip(positionWS);output.normalWS = TransformObjectToWorldNormal(input.normalOS);為了驗(yàn)證我們是否在LitPassFragment中得到了一個(gè)正確的法向量,我們可以使用它作為一種顏色。
base.rgb = input.normalWS;return base;負(fù)值是無法顯示的,所以它們被限制為0。
法線插值
雖然法向量在頂點(diǎn)程序中是單位長度的,但三角形間的線性插值會(huì)影響它們的長度。我們可以通過渲染向量長度與1之間的差值來可視化誤差,并將其放大10倍以使其更加明顯。
base.rgb = abs(length(input.normalWS) - 1.0) * 10.0;我們可以通過對LitPassFragment中的法向量進(jìn)行歸一化來平滑插值失真。當(dāng)只觀察法向量時(shí),這種差異并不是很明顯,但當(dāng)用于照明時(shí),這種差異就更明顯了。
base.rgb = normalize(input.normalWS);表面參數(shù)
著色器中的照明是實(shí)質(zhì)模擬光線到物體表面的相互作用,這意味著我們必須跟蹤表面的屬性?,F(xiàn)在我們有一個(gè)法向量和一個(gè)底色。我們可以將后者分為兩部分:RGB顏色和alpha值。我們將在幾個(gè)地方使用這些數(shù)據(jù),所以讓我們定義一個(gè)方便的表面結(jié)構(gòu)來包含所有相關(guān)數(shù)據(jù)。把它放在一個(gè)單獨(dú)的surface.hlsl文件中,并保存在ShaderLibrary文件夾下。
#ifndef CUSTOM_SURFACE_INCLUDED #define CUSTOM_SURFACE_INCLUDEDstruct Surface {float3 normal;float3 color;float alpha; };#endif然后在LitPass中,我們在Common之后將Surface.hlsl文件引入進(jìn)來。這樣我們可以讓LitPass保持簡短。
#include "../ShaderLibrary/Common.hlsl" #include "../ShaderLibrary/Surface.hlsl"在LitPassFragment中定義一個(gè)Surface變量并填充它。最后的結(jié)果就是表面的顏色和alpha值。
Surface surface;surface.normal = normalize(input.normalWS);surface.color = base.rgb;surface.alpha = base.a;return float4(surface.color, surface.alpha);光照計(jì)算
為了計(jì)算實(shí)際的照明,我們將創(chuàng)建一個(gè)帶有表面屬性參數(shù)的GetLighting函數(shù)。首先讓它返回曲面法線的Y分量。由于這是照明功能,我們將把它放在一個(gè)單獨(dú)的Lighting.HLSL文件中。
#ifndef CUSTOM_LIGHTING_INCLUDED #define CUSTOM_LIGHTING_INCLUDEDfloat3 GetLighting (Surface surface) {return surface.normal.y; }#endif在LitPass中,我們在引用 surface后引用它,因?yàn)楣庹找蕾囉谒?/p>#include "../ShaderLibrary/Surface.hlsl" #include "../ShaderLibrary/Lighting.hlsl"
現(xiàn)在我們可以在LitPassFragment中獲取照明,并將其用于fragment的RGB部分。
float3 color = GetLighting(surface);return float4(color, surface.alpha);光源
要進(jìn)行光照計(jì)算,我們還需要知道光的特性。在本教程中,我們將只討論平行光。平行光表示光源離得很遠(yuǎn),所以它的位置無關(guān)緊要,只與它的方向有關(guān)。它可以模擬地球上的太陽光和其它單向入射光。
這里我們將使用一個(gè)結(jié)構(gòu)體來存儲(chǔ)光源數(shù)據(jù),現(xiàn)在我們只需要一個(gè)顏色和一個(gè)方向就足夠了。把它放在一個(gè)單獨(dú)的light.hlsl文件。還定義一個(gè)GetDirectionalLight函數(shù),該函數(shù)返回配置的方向燈。光源默認(rèn)顏色是白色和方向是向上,與我們當(dāng)前使用的光線數(shù)據(jù)相匹配。請注意,光的方向這里被定義為光射入的方向,而不是光射出的方向。
#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之前引用這個(gè)文件
#include "../ShaderLibrary/Light.hlsl" #include "../ShaderLibrary/Lighting.hlsl"光照函數(shù)
為lighting添加一個(gè)GetIncomingLight函數(shù),計(jì)算給定表面和入射光線的反射光。對于任意方向光的反射結(jié)果,我們需要取表面法線和方向的點(diǎn)積再乘以光的顏色。
float3 GetIncomingLight (Surface surface, Light light) {return dot(surface.normal, light.direction) * light.color; }但這個(gè)結(jié)果只在表面朝向光源的時(shí)候是正確的。當(dāng)點(diǎn)積是負(fù)數(shù)時(shí),我們必須使它等于零,這可以通過saturate函數(shù)來實(shí)現(xiàn)。
float3 IncomingLight (Surface surface, Light light) {return saturate(dot(surface.normal, light.direction)) * light.color; }向GPU發(fā)送光源數(shù)據(jù)
我們應(yīng)該使用當(dāng)前場景的燈光,而不是總是使用來自我們設(shè)置的默認(rèn)光。默認(rèn)場景有一個(gè)方向燈,代表太陽,顏色稍微偏黃——fff4d6十六進(jìn)制——繞X軸旋轉(zhuǎn)50°,繞Y軸旋轉(zhuǎn)30°。如果這樣的光源不存在則創(chuàng)造一個(gè)。
為了使光源數(shù)據(jù)在著色器中可訪問,我們必須為它創(chuàng)建統(tǒng)一值,就像著色器屬性一樣。在本例中,我們將定義兩個(gè)float3類型的向量:_DirectionalLightColor和_DirectionalLightDirection。將它們放到定義在Light頂部的_CustomLight緩沖區(qū)中。
CBUFFER_START(_CustomLight)float3 _DirectionalLightColor;float3 _DirectionalLightDirection; CBUFFER_END在GetDirectionalLight中使用這些值而不是常量。
Light GetDirectionalLight () {Light light;light.color = _DirectionalLightColor;light.direction = _DirectionalLightDirection;return light; }現(xiàn)在,我們的RP必須將光源數(shù)據(jù)發(fā)送到GPU。 我們將為此創(chuàng)建一個(gè)新的Lighting類。 它的工作方式與CameraRenderer相似,但適用于燈光照明。給它提供一個(gè)帶有context參數(shù)的公共Setup方法,在該方法中它調(diào)用一個(gè)單獨(dú)的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 () {} }追蹤兩個(gè)著色器屬性的標(biāo)識(shí)符。
static int dirLightColorId = Shader.PropertyToID("_DirectionalLightColor"),dirLightDirectionId = Shader.PropertyToID("_DirectionalLightDirection");我們可以通過RenderSettings.sun訪問場景的主光源。 默認(rèn)情況下,這使我們得到主光源,還可以通過Window /Rendering /Lighting Settings顯式配置它。 使用CommandBuffer.SetGlobalVector將光源數(shù)據(jù)發(fā)送到GPU。 顏色是光源在線性空間中的顏色,而方向是光源的正向向量取反。
void SetupDirectionalLight () {Light light = RenderSettings.sun;buffer.SetGlobalVector(dirLightColorId, light.color.linear);buffer.SetGlobalVector(dirLightDirectionId, -light.transform.forward); }光源的顏色屬性是其配置的顏色,但是光源也具有單獨(dú)的強(qiáng)度因子。 最終的顏色是兩者的乘積。
buffer.SetGlobalVector(dirLightColorId, light.color.linear * light.intensity );為CameraRenderer提供一個(gè)Lighting實(shí)例,并在繪制幾何圖形之前使用它來設(shè)置光源信息。
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();}在進(jìn)行剔除時(shí),Unity還會(huì)找出哪些光源會(huì)影響相機(jī)可見的空間。 我們可以依靠這些信息而不是全局的sun光源。 為此,Lighting需要訪問剔除結(jié)果,我們?yōu)镾etup添加一個(gè)剔除結(jié)果的輸入?yún)?shù),并將其存儲(chǔ)在字段中以方便使用。然后,我們可以支持多個(gè)光源,讓我們使用新的SetupLights方法來替換SetupDirectionalLight。
CullingResults cullingResults;public void Setup (ScriptableRenderContext context, CullingResults cullingResults) {this.cullingResults = cullingResults;buffer.BeginSample(bufferName);//SetupDirectionalLight();SetupLights();…}void SetupLights () {}在CameraRenderer.Render中調(diào)用Setup時(shí),將剔除結(jié)果添加為參數(shù)。
lighting.Setup(context, cullingResults);現(xiàn)在Lighting.SetupLights可以通過剔除結(jié)果的visibleLights屬性檢索所需的數(shù)據(jù)。 它以的Unity.Collections.NativeArray的形式存在。
using Unity.Collections; using UnityEngine; using UnityEngine.Rendering;public class Lighting {…void SetupLights () {NativeArray<VisibleLight> visibleLights = cullingResults.visibleLights;}… }多光源照射
使用可見光數(shù)據(jù)可以支持多個(gè)定向光,但是我們必須將所有這些光的數(shù)據(jù)發(fā)送到GPU。 因此,我們將使用兩個(gè)Vector4數(shù)組,并用這兩個(gè)數(shù)組來存儲(chǔ)光源信息。讓我們將最大值設(shè)置為四個(gè),這對于大多數(shù)場景來說應(yīng)該足夠了。
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參數(shù)傳遞給SetupDirectionalLight。 用提供的索引設(shè)置顏色和光照方向。在這種情況下,最終顏色是通過VisibleLight.finalColor屬性提供的。 可以通過VisibleLight.localToWorldMatrix屬性找到前向矢量。 它是矩陣的第三列,必須再次取反。
void SetupDirectionalLight (int index, VisibleLight visibleLight) {dirLightColors[index] = visibleLight.finalColor;dirLightDirections[index] = -visibleLight.localToWorldMatrix.GetColumn(2);}最終顏色已經(jīng)應(yīng)用了光源的強(qiáng)度,但是默認(rèn)情況下Unity不會(huì)將其轉(zhuǎn)換為線性空間。 我們必須將GraphicsSettings.lightsUseLinearIntensity設(shè)置為true,這可以在CustomRenderPipeline的構(gòu)造函數(shù)中執(zhí)行一次。
public CustomRenderPipeline (bool useDynamicBatching, bool useGPUInstancing, bool useSRPBatcher) {this.useDynamicBatching = useDynamicBatching;this.useGPUInstancing = useGPUInstancing;GraphicsSettings.useScriptableRenderPipelineBatching = useSRPBatcher;GraphicsSettings.lightsUseLinearIntensity = true;}接下來,遍歷Lighting.SetupLights中的所有可見光,并為每個(gè)元素調(diào)用SetupDirectionalLight。 然后在緩沖區(qū)上調(diào)用SetGlobalInt和SetGlobalVectorArray以將數(shù)據(jù)發(fā)送到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);但是我們最多只支持四個(gè)定向燈,因此當(dāng)達(dá)到最大值時(shí),我們應(yīng)該中止循環(huán)。 讓我們添加一個(gè)與循環(huán)的迭代器分開的索引。
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);因?yàn)槲覀儍H支持定向光源,所以我們應(yīng)該忽略其他光源類型。 我們可以通過檢查可見光的lightType屬性是否等于LightType.Directional來做到這一點(diǎn)。
VisibleLight visibleLight = visibleLights[i]; if (visibleLight.lightType == LightType.Directional) {SetupDirectionalLight(dirLightCount++, visibleLight);if (dirLightCount >= maxDirLightCount) {break;} }這樣雖然可行,但是VisibleLight結(jié)構(gòu)相當(dāng)大。 理想情況下,我們只從本地?cái)?shù)組中檢索一次,并且也不要將其作為常規(guī)參數(shù)傳遞給SetupDirectionalLight,因?yàn)槟菢訒?huì)對其進(jìn)行復(fù)制。 我們可以通過引用傳遞參數(shù)。
SetupDirectionalLight(dirLightCount++, ref visibleLight);這要求我們也將參數(shù)定義為引用。
void SetupDirectionalLight (int index, ref VisibleLight visibleLight) { … }Shader循環(huán)
在Light中調(diào)整_CustomLight緩沖區(qū),使其與我們的新數(shù)據(jù)格式匹配。 在這種情況下,我們將顯式使用float4作為數(shù)組類型。 著色器中的數(shù)組大小固定,無法調(diào)整大小。 確保使用與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添加一個(gè)函數(shù)以獲取定向光照計(jì)數(shù)并調(diào)整GetDirectionalLight,以便它檢索特定光照索引的數(shù)據(jù)。
int GetDirectionalLightCount () {return _DirectionalLightCount; }Light GetDirectionalLight (int index) {Light light;light.color = _DirectionalLightColors[index].rgb;light.direction = _DirectionalLightDirections[index].xyz;return light; }然后調(diào)整曲面的GetLight,使其使用for循環(huán)來累積所有定向光的貢獻(xiàn)。
float3 GetLighting (Surface surface) {float3 color = 0.0;for (int i = 0; i < GetDirectionalLightCount(); i++) {color += GetLighting(surface, GetDirectionalLight(i));}return color; }現(xiàn)在,我們的著色器最多支持四個(gè)平行光。 通常只需要一個(gè)平行光來表示太陽或月球,但是也可能存在行星上有多個(gè)太陽的場景。 定向燈也可以用于近似多個(gè)大型照明設(shè)備,例如大型體育場的照明設(shè)備。
如果游戲始終只有一個(gè)平行光,那么你可以擺脫循環(huán),或者制作多個(gè)著色器變體。 但是對于本教程,我們?yōu)榱撕唵螆?jiān)持一個(gè)通用循環(huán)。 最好的性能總是通過剔除不需要的內(nèi)容來實(shí)現(xiàn)的。
Shader的目標(biāo)等級(jí)
著色器曾經(jīng)遇到過長度可變的循環(huán)問題,但是現(xiàn)在的GPU可以毫無問題地處理它們,尤其是當(dāng)繪制的所有片段以相同的方式遍歷同一數(shù)據(jù)時(shí)。但是,默認(rèn)情況下,OpenGL ES 2.0和WebGL 1.0圖形API不能處理此類循環(huán)。我們可以通過合并硬編碼的最大值來使其工作,例如,使GetDirectionalLight返回min(_DirectionalLightCount,MAX_DIRECTIONAL_LIGHT_COUNT)。這樣就可以展開循環(huán),將其變成一系列條件代碼塊。不幸的是,生成的著色器代碼一團(tuán)糟,性能下降得很快。在非常老式的硬件上,所有代碼塊都將始終執(zhí)行,它們的貢獻(xiàn)可通過條件分配來控制。盡管我們可以進(jìn)行這項(xiàng)工作,但它會(huì)使代碼更加復(fù)雜,因?yàn)槲覀冞€必須進(jìn)行其他調(diào)整。因此,為了簡化起見,我選擇忽略這些限制并在構(gòu)建中關(guān)閉WebGL 1.0和OpenGL ES 2.0支持。他們不支持線性照明。我們還可以通過#pragma target 3.5指令將著色器傳遞的目標(biāo)級(jí)別提高到3.5,從而避免為它們編譯OpenGL ES 2.0著色器變體。讓我們保持一致,并為兩個(gè)著色器執(zhí)行此操作。
HLSLPROGRAM#pragma target 3.5…ENDHLSL總結(jié)
以上是生活随笔為你收集整理的unity 角度限制_Unity自定义可编程渲染管线(SRP)(九)——灯光照明的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: inovance变频器说明书参数设置_变
- 下一篇: 扑克牌排序_巧用扑克牌搞定孩子的数学思维