https://catlikecoding.com/unity/tutorials/custom-srp/point-and-spot-lights/

Lights with Limited Influence

1 Point Lights

1.1 Other Light Data (Point )

同方向光一样,我们支持有限数量的 Other Light.尽管场景中可能有很多 Other Lights,可能有超过光源上限的光源时可见的,但是超过支持上限的将被忽略掉,我们将只处理64个. Untiy 会根据“重要性”为光源排序,我们就根据这个排序来排除超过上限的光源.

光源重要性是相对稳定的.但是在场景有变化时,比如摄像机移动,会导致重要性更新,这时上一帧还在渲染的光源,这一帧由于重要性下降,就不渲染了,这回造成光照效果的突然改变,十分显眼.因此我们把光源上限设置的大一点:64.

首先我们要把 Other Lights Data 收集并上传到GPU.点光源需要颜色和位置.同时为了计算范围衰减,将光源范围平方的倒数,即 1/r^2 存储到 w 中,基于

/////////////// Lighting.cspublic class Lighting
{...// Point/Spot 光源的最大数量const int maxOtherLightCount = 64;// 当前收集到了多少 Point/Spot Lightsint otherLightCount = 0;// Point/Spot 光源颜色Vector4[] otherLightColors = new Vector4[maxOtherLightCount];// xyz: Point/Spot 光源位置// w: 衰减系数Vector4[] otherLightPositions = new Vector4[maxOtherLightCount];// Point/Spot 光源 shader 常量IDint otherLightCountID = Shader.PropertyToID("_OtherLightCount");int otherLightColorsID = Shader.PropertyToID("_OtherLightColors");int otherLightPositionsID = Shader.PropertyToID("_OtherLightPositions");...public void SetupLights(){dirLightCount = 0;otherLightCount = 0;NativeArray<VisibleLight> visibleLights = cullingResults.visibleLights;for(int i = 0; i < visibleLights.Length; i++){VisibleLight light = visibleLights[i];switch(light.lightType){// 方向光case LightType.Directional:if(dirLightCount < maxDirLightCount)SetupDirectionalLight(dirLightCount++, ref light);break;// 点光源case LightType.Point:if(otherLightCount < maxOtherLightCount)SetupPointLight(otherLightCount++, ref light);break;}}buffer.BeginSample(bufferName);// 上传方向光数据buffer.SetGlobalInt(dirLightCountID, dirLightCount);if(dirLightCount > 0){buffer.SetGlobalVectorArray(dirLightColorID, dirLightColors);buffer.SetGlobalVectorArray(dirLightDirectionID, dirLightDirections);buffer.SetGlobalVectorArray(dirLightShadowDataID, dirLightShadowData);}// 上传Point/Spot光源数据buffer.SetGlobalInt(otherLightCountID, otherLightCount);if(otherLightCount > 0){buffer.SetGlobalVectorArray(otherLightColorsID, otherLightColors);buffer.SetGlobalVectorArray(otherLightPositionsID, otherLightPositions);}buffer.EndSample(bufferName);context.ExecuteCommandBuffer(buffer);buffer.Clear();}...// 收集 Point 光源数据private void SetupPointLight(int index, ref VisibleLight light){otherLightColors[index] = light.finalColor;otherLightPositions[index] = light.localToWorldMatrix.GetColumn(3);// 点光源衰减系数otherLightPositions[index].w = 1.0f / Mathf.Max(light.range * light.range, 0.000001f);}...
}

在 shader 中,定义相应的常量,并计算累加点光源光照

/////////////// Light.hlsl#define MAX_DIR_LIGHT_COUNT 4
#define MAX_OTHER_LIGHT_COUNT 64CBUFFER_START(_Lights)
...
// Point/Spot 光源数量
int _OtherLightCount;
float4 _OtherLightColors[MAX_OTHER_LIGHT_COUNT];
float4 _OtherLightPositions[MAX_OTHER_LIGHT_COUNT];
CBUFFER_ENDint GetOtherLightCount()
{return _OtherLightCount;
}Light GetOtherLight(int index, Surface surfaceWS, ShadowData shadowData)
{Light light;light.color = _OtherLightColors[index].rgb;float3 ray = _OtherLightPositions[index].xyz - surfaceWS.position;// 计算范围衰减float distSqr = max(dot(ray, ray), 0.000001f);float rangeAttenuation = Square(saturate(1.0 - Square(distSqr*_OtherLightPositions[index].w)));light.attenuation = rangeAttenuation / distSqr;light.direction = normalize(ray);return light;
}/////////////// Lighting.hlslfloat3 GetLighting(Surface surfaceWS, BRDF brdf, GI gi)
{...for(int i = 0; i < GetOtherLightCount(); ++i){Light light = GetOtherLight(i, surfaceWS, shadowData);color += GetLighting(surfaceWS, brdf, light);}return color;
}

如下图,场景中只有点光源,并被点光源照亮

2 Spot Lights

与 Point Light 相比, Spot Light 需要额外的数据:

  • 方向

  • 内外角衰减系数 内外角是相对于方向的角度*2.光线强度从内角开始衰减,到外角衰减为0

内外角衰减用下面公式计算:

首先,定义并收集数据,上传到 shader

public class Lighting
{// Point/Spot 光源的最大数量const int maxOtherLightCount = 64;// 当前收集到了多少 Point/Spot Lightsint otherLightCount = 0;// Point/Spot 光源颜色Vector4[] otherLightColors = new Vector4[maxOtherLightCount];// xyz: Point/Spot 光源位置// w: 衰减系数Vector4[] otherLightPositions = new Vector4[maxOtherLightCount];Vector4[] otherLightDirections = new Vector4[maxOtherLightCount];Vector4[] otherLightSpotAngles = new Vector4[maxOtherLightCount];// Point/Spot 光源 shader 常量IDint otherLightCountID = Shader.PropertyToID("_OtherLightCount");int otherLightColorsID = Shader.PropertyToID("_OtherLightColors");int otherLightPositionsID = Shader.PropertyToID("_OtherLightPositions");int otherLightDirectionsID = Shader.PropertyToID("_OtherLightDirections");int otherLightSpotAnglesID = Shader.PropertyToID("_OtherLightSpotAngles");public void SetupLights(){dirLightCount = 0;otherLightCount = 0;NativeArray<VisibleLight> visibleLights = cullingResults.visibleLights;for(int i = 0; i < visibleLights.Length; i++){VisibleLight light = visibleLights[i];switch(light.lightType){// 方向光case LightType.Directional:if(dirLightCount < maxDirLightCount)SetupDirectionalLight(dirLightCount++, ref light);break;// 点光源case LightType.Point:if(otherLightCount < maxOtherLightCount)SetupPointLight(otherLightCount++, ref light);break;// 聚光灯case LightType.Spot:if(otherLightCount < maxOtherLightCount)SetupSpotLight(otherLightCount++, ref light);break;}}buffer.BeginSample(bufferName);...// 上传Point/Spot光源数据buffer.SetGlobalInt(otherLightCountID, otherLightCount);if(otherLightCount > 0){buffer.SetGlobalVectorArray(otherLightColorsID, otherLightColors);buffer.SetGlobalVectorArray(otherLightPositionsID, otherLightPositions);buffer.SetGlobalVectorArray(otherLightDirectionsID, otherLightDirections);buffer.SetGlobalVectorArray(otherLightSpotAnglesID, otherLightSpotAngles);}buffer.EndSample(bufferName);context.ExecuteCommandBuffer(buffer);buffer.Clear();}// 收集 Spot 光源数据private void SetupSpotLight(int index, ref VisibleLight light){otherLightColors[index] = light.finalColor;otherLightPositions[index] = light.localToWorldMatrix.GetColumn(3);otherLightPositions[index].w = 1.0f / Mathf.Max(light.range * light.range, 0.000001f);otherLightDirections[index] = -light.localToWorldMatrix.GetColumn(2);// 内外角衰减系数float innerCos = Mathf.Cos(Mathf.Deg2Rad * 0.5f * light.light.innerSpotAngle);float outerCos = Mathf.Cos(Mathf.Deg2Rad * 0.5f * light.spotAngle);float angleRangeInv = 1f / Mathf.Max(innerCos - outerCos, 0.001f);otherLightSpotAngles[index] = new Vector4(angleRangeInv, -outerCos * angleRangeInv);}
}

在 shader 侧的 Light.hlsl 中,定义 shader 常量接收数据,并计算 spot 光源数据

CBUFFER_START(_Lights)
...
// Point/Spot 光源数量
int _OtherLightCount;
float4 _OtherLightColors[MAX_OTHER_LIGHT_COUNT];
float4 _OtherLightPositions[MAX_OTHER_LIGHT_COUNT];
float4 _OtherLightDirections[MAX_OTHER_LIGHT_COUNT];
float4 _OtherLightSpotAngles[MAX_OTHER_LIGHT_COUNT];
CBUFFER_ENDLight GetOtherLight(int index, Surface surfaceWS, ShadowData shadowData)
{Light light;light.color = _OtherLightColors[index].rgb;float3 ray = _OtherLightPositions[index].xyz - surfaceWS.position;// 计算光源范围衰减float distSqr = max(dot(ray, ray), 0.000001f);light.direction = normalize(ray);float rangeAttenuation = Square(saturate(1.0 - Square(distSqr*_OtherLightPositions[index].w)));// 计算聚光灯的内外角度衰减 (saturate(da+b))^2 float4 spotAngles = _OtherLightSpotAngles[index];float dotProduct = dot(_OtherLightDirections[index].xyz, light.direction);float spotAttenuation = Square(saturate(dotProduct * spotAngles.x +spotAngles.y));// 总衰减light.attenuation = spotAttenuation * rangeAttenuation / distSqr;return light;
}

默认情况下, Spot Light 的内角是无法编辑的,但是我们可以通过扩展编辑器来实现编辑功能

/// <summary>
/// 扩展 Light 组件面板
/// </summary>
// 允许选中多个对象进行编辑
[CanEditMultipleObjects]
// 声明该类为 CustomRenderPipelineAsset 管线的 Light 类型对象的编辑器控制类
[CustomEditorForRenderPipeline(typeof(Light), typeof(CustomRenderPipelineAsset))]
public class CustomLightEditor : LightEditor
{public override void OnInspectorGUI(){// 依然用默认方法绘制 Light 编辑面板base.OnInspectorGUI();// 判断选中的光源,全都是 spot 类型// 选中的 Light 的属性会被序列化缓存,settings 提供了访问缓存属性的接口if (!settings.lightType.hasMultipleDifferentValues&& (LightType)settings.lightType.enumValueIndex == LightType.Spot){// 绘制 inner / outer 角编辑控件settings.DrawInnerAndOuterSpotAngle();// 应用修改后的数据settings.ApplyModifiedProperties();}}
}

如下图,是不同的 inner/outer angle 的效果

3 Baked Light and Shadows

这篇教程不会涉及到point/spot光源的实时阴影,仅会介绍烘焙阴影,包括烘焙光照

  • 首先将光源的 Mode 改为 Baked
  • Shadow Type 默认是 None,如果需要烘焙阴影,则改为其它选项

场景中只有一个点光源和一个聚光灯,可以看到烘焙后到效果

下面是实时光照效果

通过对比,可以发现,烘焙的效果,其亮度明显高于实时效果.这是因为 unity 为了兼容旧管线,使用了错误的衰减算法.

3.1 Light Delegate

Unity 允许我们指定衰减算法,需要通过下面的编辑器扩展来完成.核心是指定一个委托,完成构建烘焙用的光源数据的的逻辑,并在该逻辑中,指定光源烘焙的一下参数/配置,其中就包括衰减算法.

将 CustomRenderPipeline 定义为 partial 类,在同目录下定义新的 CustomRenderPipeline.Editor.cs 文件,以实现委托注册

using Unity.Collections;
using UnityEngine;
using UnityEngine.Experimental.GlobalIllumination;
using LightType = UnityEngine.LightType;    // 与 Experimental 下的 LightType 类型冲突,因此需要显式声明用哪个public partial class CustomRenderPipeline 
{partial void InitializeForEditor();#if UNITY_EDITORpartial void InitializeForEditor(){// 设置委托Lightmapping.SetDelegate(requestLightsDelegate);}protected override void Dispose(bool disposing){base.Dispose(disposing);// 清理委托Lightmapping.ResetDelegate();}private static Lightmapping.RequestLightsDelegate requestLightsDelegate =(Light[] lights, NativeArray<LightDataGI> output) =>{var lightData = new LightDataGI();for (int i = 0; i < lights.Length; i++){Light light = lights[i];switch (light.type){case LightType.Directional:var dirLight = new DirectionalLight();// 从 light 提取数据LightmapperUtils.Extract(light, ref dirLight);lightData.Init(ref dirLight);break;case LightType.Point:var pointLight = new PointLight();LightmapperUtils.Extract(light, ref pointLight);lightData.Init(ref pointLight);break;case LightType.Spot:var spotLight = new SpotLight();LightmapperUtils.Extract(light, ref spotLight);// 填充角度信息spotLight.innerConeAngle = light.innerSpotAngle * Mathf.Rad2Deg;spotLight.angularFalloff = AngularFalloffType.AnalyticAndInnerAngle;lightData.Init(ref spotLight);break;case LightType.Area:var areaLight = new RectangleLight();LightmapperUtils.Extract(light, ref areaLight);areaLight.mode = LightMode.Baked;   // 仅支持烘焙,不支持实时lightData.Init(ref areaLight);break;// 默认分支,不参与烘焙default:lightData.InitNoBake(light.GetInstanceID());break;}// 关键点:指定以平方的反比进行衰减lightData.falloff = FalloffType.InverseSquared;output[i] = lightData;}};
#endif
}

然后在 CustomRenderPipeline.cs 中的构造函数中,调用初始化函数,完成注册

public CustomRenderPipeline(bool useSRPBatcher, bool useDynamicBatching, bool useGPUInstancing, ShadowSettings shadows)
{...// 在 partial for editor 中定义的方法InitializeForEditor();
}

如下图,烘焙结果没有那么亮了.但是可以看到,由于没有阴影,光源会“穿过”墙壁.

3.2 Shadow Mask

Point/Spot Lights 也可以烘焙 shadow mask,只需要将它们的 mode 改为 mixed 即可.

然后,我们需要将光源的 shadow mask 参数: 阴影强度, 所在通道 上传到 GPU.在 shader 侧,获取参数,并计算衰减.

这里要想看到明显的效果,把 range intensity 设置的大一些

////////////////// shadow.cs 
// 首先在 shadow.cs 中,加入收集 point/spot 光源数据的接口
public Vector4 ReserveOtherShadows(Light light, int visibleLightIndex)
{if (light.shadows != LightShadows.None &&light.shadowStrength > 0f){LightBakingOutput lightBaking =  light.bakingOutput;if (lightBaking.lightmapBakeType == LightmapBakeType.Mixed&& lightBaking.mixedLightingMode == MixedLightingMode.Shadowmask){useShadowMask = true;// 返回阴影强度,shadow mask 通道return new Vector4(light.shadowStrength, 0f, 0f, lightBaking.occlusionMaskChannel);}}return new Vector4(0f, 0f, 0f, -1f);
}////////////////// lighting.cs
// 定义相关 shader id 和 buffer,收集数据...
Vector4[] otherLightSpotAngles = new Vector4[maxOtherLightCount];
Vector4[] otherLightShadowData = new Vector4[maxOtherLightCount];
...
int otherLightSpotAnglesID = Shader.PropertyToID("_OtherLightSpotAngles");
int otherLightShadowDataID = Shader.PropertyToID("_OtherLightShadowData");public void SetupLights()
{dirLightCount = 0;otherLightCount = 0;NativeArray<VisibleLight> visibleLights = cullingResults.visibleLights;for(int i = 0; i < visibleLights.Length; i++){VisibleLight light = visibleLights[i];switch(light.lightType){// 方向光case LightType.Directional:if(dirLightCount < maxDirLightCount)SetupDirectionalLight(dirLightCount++, ref light);break;// 点光源case LightType.Point:if(otherLightCount < maxOtherLightCount)SetupPointLight(otherLightCount++, ref light);break;// 聚光灯case LightType.Spot:if(otherLightCount < maxOtherLightCount)SetupSpotLight(otherLightCount++, ref light);break;}}buffer.BeginSample(bufferName);// 上传方向光数据...// 上传Point/Spot光源数据buffer.SetGlobalInt(otherLightCountID, otherLightCount);if(otherLightCount > 0){buffer.SetGlobalVectorArray(otherLightColorsID, otherLightColors);buffer.SetGlobalVectorArray(otherLightPositionsID, otherLightPositions);buffer.SetGlobalVectorArray(otherLightDirectionsID, otherLightDirections);buffer.SetGlobalVectorArray(otherLightSpotAnglesID, otherLightSpotAngles);buffer.SetGlobalVectorArray(otherLightShadowDataID, otherLightShadowData);}buffer.EndSample(bufferName);context.ExecuteCommandBuffer(buffer);buffer.Clear();}// 收集 Point 光源数据
private void SetupPointLight(int index, ref VisibleLight light)
{otherLightColors[index] = light.finalColor;otherLightPositions[index] = light.localToWorldMatrix.GetColumn(3);// 光源距离衰减系数otherLightPositions[index].w = 1.0f / Mathf.Max(light.range * light.range, 0.000001f);// 传入下面的数值,以避免 Point Light 受到 Spot Light 算法的影响(共用算法)otherLightSpotAngles[index] = new Vector4(0, 1);otherLightShadowData[index] = shadows.ReserveOtherShadows(light.light, index);
}// 收集 Spot 光源数据
private void SetupSpotLight(int index, ref VisibleLight light)
{otherLightColors[index] = light.finalColor;otherLightPositions[index] = light.localToWorldMatrix.GetColumn(3);otherLightPositions[index].w = 1.0f / Mathf.Max(light.range * light.range, 0.000001f);otherLightDirections[index] = -light.localToWorldMatrix.GetColumn(2);// 内外角衰减系数float innerCos = Mathf.Cos(Mathf.Deg2Rad * 0.5f * light.light.innerSpotAngle);float outerCos = Mathf.Cos(Mathf.Deg2Rad * 0.5f * light.spotAngle);float angleRangeInv = 1f / Mathf.Max(innerCos - outerCos, 0.001f);otherLightSpotAngles[index] = new Vector4(angleRangeInv, -outerCos * angleRangeInv);otherLightShadowData[index] = shadows.ReserveOtherShadows(light.light, index);
}

在 shader 侧,接收常量缓冲,采样 shadow mask

/////////////// shadow.hlsl// point / spot 光源 shadow 数据
struct OtherShadowData
{float strength;int shadowMaskChannel;
};// 获取 point/spot 阴影衰减
float GetOtherShadowAttenuation(OtherShadowData otherShadowData, ShadowData global, Surface surfaceWS)
{// 材质不接收阴影
#if !defined(_RECEIVE_SHADOWS)return 1.0f;
#endif// 采样 shadow maskif(otherShadowData.strength > 0.0f)return GetBakedShadow(global.shadowMask, otherShadowData.strength, otherShadowData.shadowMaskChannel);return 1.0f;
}/////////////// Light.hlsl
CBUFFER_START(_Lights)
...
float4 _OtherLightSpotAngles[MAX_OTHER_LIGHT_COUNT];
float4 _OtherLightShadowData[MAX_OTHER_LIGHT_COUNT];
CBUFFER_ENDOtherShadowData GetOtherLightShadowData(int index)
{OtherShadowData otherShadowData;otherShadowData.strength = _OtherLightShadowData[index].x;otherShadowData.shadowMaskChannel = _OtherLightShadowData[index].w;return otherShadowData;
}Light GetOtherLight(int index, Surface surfaceWS, ShadowData shadowData)
{Light light;light.color = _OtherLightColors[index].rgb;float3 ray = _OtherLightPositions[index].xyz - surfaceWS.position;// 计算光源范围衰减float distSqr = max(dot(ray, ray), 0.000001f);light.direction = normalize(ray);float rangeAttenuation = Square(saturate(1.0 - Square(distSqr*_OtherLightPositions[index].w)));// 计算聚光灯的内外角度衰减 (saturate(da+b))^2 float4 spotAngles = _OtherLightSpotAngles[index];float dotProduct = dot(_OtherLightDirections[index].xyz, light.direction);float spotAttenuation = Square(saturate(dotProduct * spotAngles.x +spotAngles.y));// 获取 shadow maskOtherShadowData otherShadowData = GetOtherLightShadowData(index); float shadowMaskAttenuation = GetOtherShadowAttenuation(otherShadowData, shadowData, surfaceWS);   // 总衰减light.attenuation = shadowMaskAttenuation * spotAttenuation * rangeAttenuation / distSqr;return light;
}

最后得到如下效果(关闭了间接光照)

4 Lights Per Object

现在,场景中的所有光源,每帧都会被渲染.对于方向光来说没问题.但是对于点光和聚光灯来说,那些距离很远,对当前画面没有贡献的光,也被收集,参与计算,但是没有效果,完全是浪费算力.为了支持更多的光源,并保证性能,需要降低每帧处理的光源的数量.有多种方法可以实现,这里使用 unity 的 per-object indices (逐对象索引).

思想是针对每个对象,仅将影响该对象的光源送到 GPU 进行计算.这种方式对于小的对象效果很好.但是对于那些很大的对象,一个光源本来只影响该对象的一部分,但是由于各种限制,忽略了同时影响该对象的其它光源,导致光照看起来不太正常.

由于该方案有时效果不好,因此将该特性作为一个选项,可以根据需要开启或关闭.

4.1 Per-Object Light Data

是否使用(上传) PerObject Light Data,依然是由 DrawingSettings 决定的,因此完善该方法,加入是否开启 PerObjectLight 的参数.

///////////////////// CameraRenderer.csvoid DrawVisibleGeometry(bool useDynamicBatching, bool useGPUInstancing, bool useLightsPerObject){PerObjectData lightsPerObjectFlags = useLightsPerObject ? PerObjectData.LightData | PerObjectData.Indices : PerObjectData.None;// 渲染不透明物体var sortingSettings = new SortingSettings(camera){ criteria = SortingCriteria.CommonOpaque };var drawingSettings = new DrawingSettings(unlitShaderTagId, sortingSettings){ enableDynamicBatching = useDynamicBatching, enableInstancing = useGPUInstancing};// 索引是 1,因为索引为 0 的通过构造函数将 unlitShaderTagId 设置了drawingSettings.SetShaderPassName(1, litShaderTagId);drawingSettings.perObjectData = PerObjectData.Lightmaps | PerObjectData.LightProbe| PerObjectData.LightProbeProxyVolume| PerObjectData.ShadowMask| PerObjectData.OcclusionProbe| PerObjectData.OcclusionProbeProxyVolume| PerObjectData.ReflectionProbes| lightsPerObjectFlags;...}

在 CustomRenderPipelineAsset 中定义参数,并一路传递到 DrawVisibleGeometry 调用.

4.2 Sanitizing Light Indices 整理索引

Unity 只是简单地收集所有光源,并按照重要顺序进行排序,然后以该顺序作为每个光源的索引.当上传每个对象的光源列表时,就使用该索引.但是我们上面在提交光 OtherLight 数据时,剔除掉了方向光,同时我们只上传了64个其它光源,因此需要对索引进行调整.逻辑实现在 Lighting.SetupLights 函数中,调用该函数的相关逻辑也需要做调整.

////////////////// Lighting.cspublic void SetupLights(bool usePerObjectLights){dirLightCount = 0;otherLightCount = 0;// 如果开启了 usePerObjectLights 则获取索引表,根据我们自己收集的光源进行重新映射索引NativeArray<int> indexMap = usePerObjectLights ? cullingResults.GetLightIndexMap(Allocator.Temp) : default;NativeArray<VisibleLight> visibleLights = cullingResults.visibleLights;int i = 0;for(i = 0; i < visibleLights.Length; i++){int newIndex = -1;VisibleLight light = visibleLights[i];switch(light.lightType){// 方向光case LightType.Directional:if(dirLightCount < maxDirLightCount)SetupDirectionalLight(dirLightCount++, ref light);break;// 点光源case LightType.Point:if(otherLightCount < maxOtherLightCount){newIndex = otherLightCount;SetupPointLight(otherLightCount++, ref light);}break;// 聚光灯case LightType.Spot:if(otherLightCount < maxOtherLightCount){newIndex = otherLightCount;SetupSpotLight(otherLightCount++, ref light);}break;}// 重新映射索引if(usePerObjectLights)indexMap[i] = newIndex;}if(usePerObjectLights){// 不可见光,索引设置成 -1for(; i < indexMap.Length; ++i)indexMap[i] = -1;// 设置更新后的索引cullingResults.SetLightIndexMap(indexMap);indexMap.Dispose();Shader.EnableKeyword(lightsPerObjectKeyword);}else{Shader.DisableKeyword(lightsPerObjectKeyword);}....}

在 shader 侧,需要定义对应的 multi_compile keyword,并跟 per-object lights 传进来的索引,引用正确光源进行光照

////////////////// Lit.shader
#pragma multi_compile_instancing
#pragma multi_compile _ _LIGHTS_PER_OBJECT////////////////// UnityInput.hlsl
CBUFFER_START(UnityPerDraw)
real4 unity_WorldTransformParams;
// per object lights 数据
// y 是影响该对象的光源数量
real4 unity_LightData;
// 存储光源索引,每个通道一个,最多8个. index = unity_LightIndices[i/4][i%4]
real4 unity_LightIndices[2];
...
CBUFFER_END////////////////// Lighting.hlsl
float3 GetLighting(Surface surfaceWS, BRDF brdf, GI gi)
{ShadowData shadowData = GetShadowData(surfaceWS);shadowData.shadowMask = gi.shadowMask;// 临时返回以查看数据//return gi.shadowMask.shadows.rgb;//return float4(gi.specular, 1.0f);float3 color = IndirectBRDF(surfaceWS, brdf, gi.diffuse, gi.specular);//color = 0;   // 显示去掉间接光照的效果for(int i = 0; i < GetDirectionalLightCount(); ++i){Light light = GetDirectionalLight(i, surfaceWS, shadowData);color += GetLighting(surfaceWS, brdf, light);}#if defined(_LIGHTS_PER_OBJECT)// 每个对象定义了影响的光源// y 可能大于8,而我们最多支持8个,因此用 min 确保for(int i = 0; i < min(8,unity_LightData.y); ++i){int index = unity_LightIndices[(uint)i/4][(uint)i%4];Light light = GetOtherLight(index, surfaceWS, shadowData);color += GetLighting(surfaceWS, brdf, light);}
#else// 没有每个对象光源的数据,因此处理所有for(int i = 0; i < GetOtherLightCount(); ++i){Light light = GetOtherLight(i, surfaceWS, shadowData);color += GetLighting(surfaceWS, brdf, light);}
#endifreturn color;
}

需要注意的是, Per-Object Lights 会降低 GPU Instancing 的效率,因为只有受相同光源影响的对象,才能合批(增加了条件).

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

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

相关文章

hive数据仓库的搭建

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录前言一、内嵌模式二、本地模式三、远程模式前言 HIVE是基于HDFS的数据仓库&#xff0c;要首先搭建好HADOOP的集群才可以正常使用HIVE&#xff0c;HADOOP集运搭建详见…

域名SSL证书免费申请lcjmSSL

.-.lcjmSSL&#xff08;又名“来此加密”&#xff09;是一个提供免费SSL证书申请的一站式平台。它支持单域名、多域名以及泛域名证书申请&#xff0c;且单张证书最高可覆盖100个域名&#xff0c;让您轻松实现全站HTTPS加密。为什么您的网站必须安装SSL证书&#xff1f;数据加密…

“能量逆流泵”:一种基于电容阵与开关矩阵的超高效大功率降压架构

摘要本文提出并验证了一种面向大功率降压应用的革命性电源架构——"能量逆流泵"&#xff08;Energy Inversion Pump, EIP&#xff09;。该架构摒弃了传统Buck转换器中的电感元件&#xff0c;通过高速开关矩阵控制的电容阵列&#xff0c;将高压侧能量以"分时、分…

打造精简高效的 uni-app 网络请求工具

在 uni-app 开发中&#xff0c;网络请求是连接前端与后端的核心桥梁。一个设计良好的请求工具能够显著提升开发效率&#xff0c;减少重复代码。本文将分享一个精简版的 uni-app 网络请求工具实现&#xff0c;它保留了核心功能同时保持了足够的灵活性。设计思路一个优秀的网络请…

【面试场景题】交易流水表高qps写入会有锁等待或死锁问题吗

文章目录一、先明确交易流水表的核心特性二、InnoDB的锁机制在流水表写入场景的表现1. 行锁&#xff08;Record Lock&#xff09;&#xff1a;基本不涉及2. 间隙锁&#xff08;Gap Lock&#xff09;与Next-Key Lock&#xff1a;几乎不触发3. 表锁&#xff1a;仅在极端场景出现三…

项目部署——LAMP、LNMP和LTMJ

前情提要问&#xff1a;如何通过nginx的反向代理&#xff0c;代理多台虚拟主机&#xff08;一台apache服务器上的虚拟主机&#xff09;&#xff1f;1.在nginx的配置文件中&#xff0c;将基于域名的访问改为基于端口的访问&#xff08;nginx.conf&#xff09;upstream daili{ser…

晨曦中,它已劳作:一台有温度的机器人如何重塑我们的洁净日常

清晨六点&#xff0c;城市的轮廓在微光中逐渐清晰。某高端小区的路面上&#xff0c;一台灰色机身、线条流畅的机器正在安静地工作。它绕过停靠的车辆&#xff0c;精准地沿着路缘石前进&#xff0c;吸走落叶与尘土&#xff0c;遇到突然窜出的流浪猫时轻巧避让&#xff0c;仿佛有…

【最新高级版】酷柚易汛生产管理系统v1.2.8 +uniapp全开源+文档教程

酷柚易汛生产管理系统是基于FastAdminThinkPHPLayuiuniapp开发的生产管理系统&#xff0c;帮助企业数字化转型&#xff0c;打造智能工厂&#xff0c;专业为生产企业量身开发的一套完整的生产管理系统。主要包含以下模块&#xff1a;购货模块、生产模块、仓库模块、资料模块&…

40分钟的Docker实战攻略

一&#xff1a;什么是Docker &#xff08;1&#xff09;基本概念 Docker 是一种开源的 容器化平台&#xff0c;用于快速构建、部署和运行应用程序。它通过将应用程序及其依赖项打包到轻量级的、可移植的容器中&#xff0c;实现了环境一致性&#xff0c;解决了“在我机器上能运…

qt使用camke时,采用vcpkg工具链设置OSG的qt模块osgQOpenGLWidget

【免费】osgQOpenGLWidget嵌入qt模块,VS2022使用cmake的方式,工具链vcpkg资源-CSDN下载 CMake中设置 1.查找osg相关的库,同时也会设置对应include的路径 # 检查是否找到 osg find_package(OpenSceneGraph 3.6.5REQUIRED COMPONENTS osgosgUtilosgGAosgViewerosgDBosgAnimatio…

洛谷 P2245 星际导航(kruskal 重构树 + 倍增优化求路径最大边权)

题目链接 题目难度 洛谷上是蓝题&#xff0c;我觉得这道题挺简单的&#xff0c;一眼就看穿了&#xff0c;应该是绿题。 题目解法概括 kruskal 重构树 倍增优化求路径最大边权。 代码 #include <iostream> #include <vector> #include <algorithm> #in…

STM32H743-ARM例程1-IDE环境搭建与调试下载

目录实验平台环境搭建一、Keil MDK集成开发环境1.MDK简介2.MDK5安装3.程序下载与调试二、STM32CubeMX1.STM32CubeMX简介2.JAVA JRE安装3.STM32CubeMX安装4.STM32CubeH7库安装实验平台 硬件&#xff1a;银杏科技GT7000双核心开发板-ARM-STM32H743XIH6&#xff0c;银杏科技iTool…

FPGA学习篇——Verilog学习MUX的实现

PS&#xff1a;目前手上仍然没有板子&#xff0c;按照野火视频的讲解&#xff0c;目前我们只能做到前面六步&#xff08;其实第一步设计规划也是需要看板子的硬件的&#xff0c;但是现在没有板子就完全与野火传授的板子一致来看&#xff09; 首先我们以最简单的2路选择器MUX2_1…

OpenStack 学习笔记

OpenStack 1. 什么是 OpenStack 1.1 OpenStack 发展史 2006 年亚马逊推出 AWS&#xff0c;正式开启云计算的新纪元 2010 年 7 月美国国家航空航天局&#xff08;NASA&#xff09;与 Rackspace 合作&#xff0c;共同宣布 OpenStack 开放源码计划&#xff0c;由此开启了属于 Open…

mysql小数取整

1 向下取整 SELECT FLOOR(123.456); -- 结果: 1232 向上取整 SELECT CEIL(123.001); -- 结果: 1243 四舍五入 SELECT ROUND(123.456); -- 结果: 123 SELECT ROUND(123.556); -- 结果: 1244 截断&#xff08;不四舍五入&#xff0c;直接截断小数位&#xff09; SELECT …

Day43 PHP(mysql不同注入类型、mysql不同注入点、mysql传输不同数据类型 )

一、不同注入类型实际&#xff1a;我们未知sql是哪种类型&#xff0c;只能靠试/使用sql工具原理&#xff1a;闭合程序员写的sql语句&#xff0c;并且执行我们所需要的sql语句&#xff0c;最后将闭合后多余的 用-- 或者#注释掉。 总结一下就是先闭合&#xff0c;后注释。共四种…

Linux应用开发(君正T23):三网智能切换及配网功能

前段时间接手了一个监控项目&#xff0c;其中甲方对于设备的要求有一条就是实现网口eth、WiFi、4G三种手段的联网方式并且当某一个网络不好的时候就去切换到下一个能用的网络&#xff0c;让监控设备持续不断的有网络&#xff0c;保证监控数据的上传。这个部分的功能就交由我来实…

IvorySQL 4.6:DocumentDB+FerretDB 实现 MongoDB 兼容部署指南

背景 MongoDB 诞生之初&#xff0c;便以出色的易用性与详尽的驱动程序文档脱颖而出&#xff0c;堪称对传统关系型数据库的一次重要革新&#xff0c;也正因如此&#xff0c;它迅速成为开发者社区的热门之选。 然而&#xff0c;随着其许可模式从开源转向 SSPL 许可证&#xff0…

论文阅读:arixv 2025 One Token to Fool LLM-as-a-Judge

总目录 大模型相关研究&#xff1a;https://blog.csdn.net/WhiffeYF/article/details/142132328 https://arxiv.org/pdf/2507.08794 https://www.doubao.com/chat/20698287584991234 速览 这篇文档主要讲了一个关于“大语言模型当裁判”的重要发现——很多我们以为靠谱的AI裁…

webrtc弱网-AlrDetector类源码分析与算法原理

AlrDetector&#xff08;应用受限区域检测器&#xff09;是WebRTC中用于检测发送端是否处于应用层限速状态的核心组件。它通过维护一个基于时间间隔的预算系统&#xff0c;监控实际发送数据量与网络容量之间的关系。当发送速率持续低于网络容量的设定比例&#xff08;如65%&…