聚光灯有许多用途,他们可以用来模拟路灯, 壁灯,或许多创意用法,例如模拟手电筒,因为投射区域能精确的控制,因此很适合用来模拟打在角色身上的光或是模拟舞台灯光效果等等
在项目中我们自己实现了一套聚光灯的方法(没有体积光的计算),整体思路是比较像素点与光照原点的位置得到的向量和光照方向的向量点乘,再跟spotAngle的一半的cos做比较。如果大于spot的一半的cos则说明超过了限制,是不属于聚光灯下的。然后再结合点光源的距离计算得到长度的判断和衰减。https://blog.csdn.net/llsansun/article/details/103216534
和
比较。
其中在管线中传递就好,不需要再gpu来做cos运算。
最终的聚光灯shader运算为
half3 LightingPointAttenuation(half3 finalColor, half3 posWorld, half3 lightPos)
{
half distance = length(posWorld - lightPos);
return finalColor / (max(1.0h, (distance * (1.0h / _MainLightRange))));
}
half3 LightingSpotAttenuation(half3 finalColor, half3 posWorld, half3 lightPos)
{
//顶点和光照原点的向量
half3 vertexDir = normalize(posWorld - _MainLightPosition);
//光照方向
half3 centerPointDir = (_MainLightSpotDir.xyz);
//顶点方向和光照方向的点乘
half dotPoint = min(1, max(0, (-dot(centerPointDir, vertexDir) - _MainLightAttenuation.w) + 0.8));
//获取点光源范围的光照衰减
half3 distanceColor = LightingPointAttenuation(finalColor, posWorld, lightPos);
half3 circleColor = distanceColor * dotPoint;
half distance = length(posWorld - lightPos);
//超过距离不显示
if ((-dot(centerPointDir, vertexDir) - _MainLightAttenuation.w) < 0 || _MainLightRange - distance < 0)
{
return circleColor * DarkColorPercent;
}
return circleColor;
}
half3 LightingAttenuation(half3 finalColor, half3 posWorld, half3 lightPos)
{
//光照类型选择
return (_MainLightType == 1) ? finalColor : (_MainLightType == 2) ? LightingPointAttenuation(finalColor, posWorld, lightPos) : LightingSpotAttenuation(finalColor, posWorld, lightPos);
}
最终应用的shader改为
//最终颜色做一次衰减运算
finalColor.rgb = LightingAttenuation(c, i.posWorld, mainLight.dir);
return half4(finalColor, s.alpha);
当然还需要修改管线
using System;
using System.Collections.Generic;
using UnityEngine.Experimental.GlobalIllumination;
using UnityEngine.Rendering;
namespace UnityEngine.Experimental.Rendering.LightweightPipeline
{
/// <summary>
/// Configure the shader constants needed by the render pipeline
///
/// This pass configures constants that LWRP uses when rendering.
/// For example, you can execute this pass before you render opaque
/// objects, to make sure that lights are configured correctly.
/// </summary>
public class SetupLightweightConstanstPass : ScriptableRenderPass
{
static class LightConstantBuffer
{
public static int _MainLightPosition;
public static int _MainLightColor;
public static int _MainLightRange;
public static int _MainLightType;
//多给了两个变量,一个是光线衰减,一个是光照方向及范围
public static int _MainLightAttenuation;
public static int _MainLightSpotDir;
public static int _AdditionalLightsCount;
public static int _AdditionalLightsPosition;
public static int _AdditionalLightsColor;
public static int _AdditionalLightsAttenuation;
public static int _AdditionalLightsSpotDir;
public static int _AdditionalLightsBuffer;
}
const string k_SetupLightConstants = "Setup Light Constants";
MixedLightingSetup m_MixedLightingSetup;
Vector4 k_DefaultLightPosition = new Vector4(0.0f, 0.0f, 1.0f, 1.0f);
Vector4 k_DefaultLightColor = Color.black;
Vector4 k_DefaultLightAttenuation = new Vector4(1.0f, 0.0f, 0.0f, 1.0f);
Vector4 k_DefaultLightSpotDirection = new Vector4(0.0f, 0.0f, 1.0f, 0.0f);
float k_DefaultLightRange = 10.0f;
Vector4[] m_AdditionalLightPositions;
Vector4[] m_AdditionalLightColors;
Vector4[] m_AdditionalLightAttenuations;
Vector4[] m_AdditionalLightSpotDirections;
private int maxVisibleAdditionalLights { get; set; }
private ComputeBuffer perObjectLightIndices { get; set; }
/// <summary>
/// Create the pass
/// </summary>
public SetupLightweightConstanstPass()
{
LightConstantBuffer._MainLightPosition = Shader.PropertyToID("_MainLightPosition");
LightConstantBuffer._MainLightColor = Shader.PropertyToID("_MainLightColor");
LightConstantBuffer._MainLightRange = Shader.PropertyToID("_MainLightRange");
LightConstantBuffer._MainLightType = Shader.PropertyToID("_MainLightType");
LightConstantBuffer._MainLightAttenuation = Shader.PropertyToID("_MainLightAttenuation");
LightConstantBuffer._MainLightSpotDir = Shader.PropertyToID("_MainLightSpotDir");
LightConstantBuffer._AdditionalLightsCount = Shader.PropertyToID("_AdditionalLightsCount");
LightConstantBuffer._AdditionalLightsPosition = Shader.PropertyToID("_AdditionalLightsPosition");
LightConstantBuffer._AdditionalLightsColor = Shader.PropertyToID("_AdditionalLightsColor");
LightConstantBuffer._AdditionalLightsAttenuation = Shader.PropertyToID("_AdditionalLightsAttenuation");
LightConstantBuffer._AdditionalLightsSpotDir = Shader.PropertyToID("_AdditionalLightsSpotDir");
LightConstantBuffer._AdditionalLightsBuffer = Shader.PropertyToID("_AdditionalLightsBuffer");
m_AdditionalLightPositions = new Vector4[0];
m_AdditionalLightColors = new Vector4[0];
m_AdditionalLightAttenuations = new Vector4[0];
m_AdditionalLightSpotDirections = new Vector4[0];
}
/// <summary>
/// Configure the pass
/// </summary>
/// <param name="maxVisibleAdditionalLights">Maximum number of visible additional lights</param>
/// <param name="perObjectLightIndices">Buffer holding per object light indicies</param>
public void Setup(int maxVisibleAdditionalLights, ComputeBuffer perObjectLightIndices)
{
this.maxVisibleAdditionalLights = maxVisibleAdditionalLights;
this.perObjectLightIndices = perObjectLightIndices;
if (m_AdditionalLightColors.Length != maxVisibleAdditionalLights)
{
m_AdditionalLightPositions = new Vector4[maxVisibleAdditionalLights];
m_AdditionalLightColors = new Vector4[maxVisibleAdditionalLights];
m_AdditionalLightAttenuations = new Vector4[maxVisibleAdditionalLights];
m_AdditionalLightSpotDirections = new Vector4[maxVisibleAdditionalLights];
}
}
void InitializeLightConstants(List<VisibleLight> lights, int lightIndex, out Vector4 lightPos, out Vector4 lightColor, out Vector4 lightAttenuation, out Vector4 lightSpotDir, out float lightRange, out int lightType)
{
lightPos = k_DefaultLightPosition;
lightColor = k_DefaultLightColor;
lightAttenuation = k_DefaultLightAttenuation;
lightSpotDir = k_DefaultLightSpotDirection;
lightRange = k_DefaultLightRange;
lightType = 1;
// When no lights are visible, main light will be set to -1.
// In this case we initialize it to default values and return
if (lightIndex < 0)
return;
VisibleLight lightData = lights[lightIndex];
if (lightData.lightType == LightType.Directional)
{
Vector4 dir = -lightData.localToWorld.GetColumn(2);
lightPos = new Vector4(dir.x, dir.y, dir.z, k_DefaultLightAttenuation.w);
}
else
{
Vector4 pos = lightData.localToWorld.GetColumn(3);
lightPos = new Vector4(pos.x, pos.y, pos.z, k_DefaultLightAttenuation.w);
lightRange = lightData.range;
}
lightType = (int)lightData.lightType;
// VisibleLight.finalColor already returns color in active color space
lightColor = lightData.finalColor;
// Directional Light attenuation is initialize so distance attenuation always be 1.0
if (lightData.lightType != LightType.Directional)
{
// Light attenuation in lightweight matches the unity vanilla one.
// attenuation = 1.0 / distanceToLightSqr
// We offer two different smoothing factors.
// The smoothing factors make sure that the light intensity is zero at the light range limit.
// The first smoothing factor is a linear fade starting at 80 % of the light range.
// smoothFactor = (lightRangeSqr - distanceToLightSqr) / (lightRangeSqr - fadeStartDistanceSqr)
// We rewrite smoothFactor to be able to pre compute the constant terms below and apply the smooth factor
// with one MAD instruction
// smoothFactor = distanceSqr * (1.0 / (fadeDistanceSqr - lightRangeSqr)) + (-lightRangeSqr / (fadeDistanceSqr - lightRangeSqr)
// distanceSqr * oneOverFadeRangeSqr + lightRangeSqrOverFadeRangeSqr
// The other smoothing factor matches the one used in the Unity lightmapper but is slower than the linear one.
// smoothFactor = (1.0 - saturate((distanceSqr * 1.0 / lightrangeSqr)^2))^2
float lightRangeSqr = lightData.range * lightData.range;
float fadeStartDistanceSqr = 0.8f * 0.8f * lightRangeSqr;
float fadeRangeSqr = (fadeStartDistanceSqr - lightRangeSqr);
float oneOverFadeRangeSqr = 1.0f / fadeRangeSqr;
float lightRangeSqrOverFadeRangeSqr = -lightRangeSqr / fadeRangeSqr;
float oneOverLightRangeSqr = 1.0f / Mathf.Max(0.0001f, lightData.range * lightData.range);
// On mobile: Use the faster linear smoothing factor.
// On other devices: Use the smoothing factor that matches the GI.
lightAttenuation.x = Application.isMobilePlatform ? oneOverFadeRangeSqr : oneOverLightRangeSqr;
lightAttenuation.y = lightRangeSqrOverFadeRangeSqr;
}
else
{
lightRange = 99999;
}
if (lightData.lightType == LightType.Spot)
{
Vector4 dir = lightData.localToWorld.GetColumn(2);
lightSpotDir = new Vector4(-dir.x, -dir.y, -dir.z, 0.0f);
// Spot Attenuation with a linear falloff can be defined as
// (SdotL - cosOuterAngle) / (cosInnerAngle - cosOuterAngle)
// This can be rewritten as
// invAngleRange = 1.0 / (cosInnerAngle - cosOuterAngle)
// SdotL * invAngleRange + (-cosOuterAngle * invAngleRange)
// If we precompute the terms in a MAD instruction
float cosOuterAngle = Mathf.Cos(Mathf.Deg2Rad * lightData.spotAngle * 0.5f);
// We neeed to do a null check for particle lights
// This should be changed in the future
// Particle lights will use an inline function
float cosInnerAngle;
if (lightData.light != null)
cosInnerAngle = Mathf.Cos(LightmapperUtils.ExtractInnerCone(lightData.light) * 0.5f);
else
cosInnerAngle = Mathf.Cos((2.0f * Mathf.Atan(Mathf.Tan(lightData.spotAngle * 0.5f * Mathf.Deg2Rad) * (64.0f - 18.0f) / 64.0f)) * 0.5f);
float smoothAngleRange = Mathf.Max(0.001f, cosInnerAngle - cosOuterAngle);
float invAngleRange = 1.0f / smoothAngleRange;
float add = -cosOuterAngle * invAngleRange;
lightAttenuation.z = invAngleRange;
lightAttenuation.w = cosOuterAngle;//add;
}
Light light = lightData.light;
// TODO: Add support to shadow mask
if (light != null && light.bakingOutput.mixedLightingMode == MixedLightingMode.Subtractive && light.bakingOutput.lightmapBakeType == LightmapBakeType.Mixed)
{
if (m_MixedLightingSetup == MixedLightingSetup.None && lightData.light.shadows != LightShadows.None)
{
m_MixedLightingSetup = MixedLightingSetup.Subtractive;
// In subtractive light mode, main light direct contribution is baked on lightmap
// In this case we setup light position w component as 0.0f so we can remove it's contribution
// from realtime light computation
if (lightData.lightType == LightType.Directional)
lightPos.w = 0.0f;
}
}
}
void SetupShaderLightConstants(CommandBuffer cmd, ref LightData lightData)
{
float lightRange = 0;
int lightType = 1;
// Clear to default all light constant data
for (int i = 0; i < maxVisibleAdditionalLights; ++i)
InitializeLightConstants(lightData.visibleLights, -1, out m_AdditionalLightPositions[i],
out m_AdditionalLightColors[i],
out m_AdditionalLightAttenuations[i],
out m_AdditionalLightSpotDirections[i],
out lightRange,
out lightType);
m_MixedLightingSetup = MixedLightingSetup.None;
// Main light has an optimized shader path for main light. This will benefit games that only care about a single light.
// Lightweight pipeline also supports only a single shadow light, if available it will be the main light.
SetupMainLightConstants(cmd, ref lightData);
SetupAdditionalLightConstants(cmd, ref lightData);
}
private int currentLightType;
void SetupMainLightConstants(CommandBuffer cmd, ref LightData lightData)
{
Vector4 lightPos, lightColor, lightAttenuation, lightSpotDir;
float lightRange = 0.0f;
int lightType = 1;
InitializeLightConstants(lightData.visibleLights, lightData.mainLightIndex, out lightPos, out lightColor, out lightAttenuation, out lightSpotDir,
out lightRange, out lightType);
var lights = Light.GetLights(LightType.Spot, 0);
if (lightData.visibleLights.Count == 0 && lights.Length != 0)
{
cmd.SetGlobalVector(LightConstantBuffer._MainLightColor, lights[0].color);
cmd.SetGlobalInt(LightConstantBuffer._MainLightType, 0);
}
else
{
cmd.SetGlobalVector(LightConstantBuffer._MainLightColor, lightColor);
cmd.SetGlobalInt(LightConstantBuffer._MainLightType, lightType);
}
cmd.SetGlobalVector(LightConstantBuffer._MainLightPosition, lightPos);
cmd.SetGlobalFloat(LightConstantBuffer._MainLightRange, lightRange);
cmd.SetGlobalVector(LightConstantBuffer._MainLightAttenuation, lightAttenuation);
cmd.SetGlobalVector(LightConstantBuffer._MainLightSpotDir, lightSpotDir);
}
void SetupAdditionalLightConstants(CommandBuffer cmd, ref LightData lightData)
{
List<VisibleLight> lights = lightData.visibleLights;
if (lightData.additionalLightsCount > 0)
{
int additionalLightsCount = 0;
for (int i = 0; i < lights.Count && additionalLightsCount < maxVisibleAdditionalLights; ++i)
{
VisibleLight light = lights[i];
if (light.lightType != LightType.Directional)
{
float lightRange = 0.0f;
int lightType = 1;
InitializeLightConstants(lights, i, out m_AdditionalLightPositions[additionalLightsCount],
out m_AdditionalLightColors[additionalLightsCount],
out m_AdditionalLightAttenuations[additionalLightsCount],
out m_AdditionalLightSpotDirections[additionalLightsCount],
out lightRange,
out lightType);
additionalLightsCount++;
}
}
cmd.SetGlobalVector(LightConstantBuffer._AdditionalLightsCount, new Vector4(lightData.maxPerObjectAdditionalLightsCount,
0.0f, 0.0f, 0.0f));
// if not using a compute buffer, engine will set indices in 2 vec4 constants
// unity_4LightIndices0 and unity_4LightIndices1
if (perObjectLightIndices != null)
cmd.SetGlobalBuffer(LightConstantBuffer._AdditionalLightsBuffer, perObjectLightIndices);
}
else
{
cmd.SetGlobalVector(LightConstantBuffer._AdditionalLightsCount, Vector4.zero);
}
cmd.SetGlobalVectorArray(LightConstantBuffer._AdditionalLightsPosition, m_AdditionalLightPositions);
cmd.SetGlobalVectorArray(LightConstantBuffer._AdditionalLightsColor, m_AdditionalLightColors);
cmd.SetGlobalVectorArray(LightConstantBuffer._AdditionalLightsAttenuation, m_AdditionalLightAttenuations);
cmd.SetGlobalVectorArray(LightConstantBuffer._AdditionalLightsSpotDir, m_AdditionalLightSpotDirections);
}
/// <inheritdoc/>
public override void Execute(ScriptableRenderer renderer, ScriptableRenderContext context, ref RenderingData renderingData)
{
if (renderer == null)
throw new ArgumentNullException("renderer");
int additionalLightsCount = renderingData.lightData.additionalLightsCount;
bool additionalLightsPerVertex = renderingData.lightData.shadeAdditionalLightsPerVertex;
CommandBuffer cmd = CommandBufferPool.Get(k_SetupLightConstants);
SetupShaderLightConstants(cmd, ref renderingData.lightData);
CoreUtils.SetKeyword(cmd, ShaderKeywordStrings.AdditionalLightsVertex, additionalLightsCount > 0 && additionalLightsPerVertex);
CoreUtils.SetKeyword(cmd, ShaderKeywordStrings.AdditionalLightsPixel, additionalLightsCount > 0 && !additionalLightsPerVertex);
CoreUtils.SetKeyword(cmd, ShaderKeywordStrings.MixedLightingSubtractive, renderingData.lightData.supportsMixedLighting && m_MixedLightingSetup == MixedLightingSetup.Subtractive);
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
}
}
}
这里要注意的是lightAttenuation.w,这里改为了只接收cosOuterAngle,因为我们只判断他来决定聚光灯横向范围。
另外就是
var lights = Light.GetLights(LightType.Spot, 0);
if (lightData.visibleLights.Count == 0 && lights.Length != 0)
{
cmd.SetGlobalVector(LightConstantBuffer._MainLightColor, lights[0].color);
cmd.SetGlobalInt(LightConstantBuffer._MainLightType, 0);
}
else
{
cmd.SetGlobalVector(LightConstantBuffer._MainLightColor, lightColor);
cmd.SetGlobalInt(LightConstantBuffer._MainLightType, lightType);
}
这段,因为管线拿的是可见光,如果不可见那么我们的光照类型拿不到就会渲染不正确,所以要给默认类型。
当然可以看到这里看到如果能加上一些体积光会更好。但是要渲染比较好的体积光也需要大量的运算。我这次会忽略他。然后用一个聚光的特效代替,只需要把他放到我们的光源位置就好了,想做得比较好看比较细只需要改变这个特效就好。但要注意的是这个特效的渲染队列要放到前面,不能低于里面要显示的角色
如果要做一个渐变的效果也是可以的,只需要根据cos的插值做渐变就好
#define DarkColorPercent 0.4
half3 LightingPointAttenuation(half3 finalColor, half3 posWorld, half3 lightPos)
{
half distance = length(posWorld - lightPos);
return finalColor / (max(1.0h, (distance * (1.0h / _MainLightRange))));
}
half3 LightingSpotAttenuation(half3 finalColor, half3 posWorld, half3 lightPos)
{
half3 vertexDir = normalize(posWorld - _MainLightPosition);
half3 centerPointDir = (_MainLightSpotDir.xyz);
half cDotv = -dot(centerPointDir, vertexDir);
half cosIntenve = cDotv - _MainLightAttenuation.w;
half dotPoint = min(1, max(DarkColorPercent, cosIntenve + 0.8));
//half3 distanceColor = LightingPointAttenuation(finalColor, posWorld, lightPos);
half3 circleColor = finalColor * dotPoint;
half distance = length(posWorld - lightPos);
half lightRange = 45;
half lightBlurRate = 0.01;
return circleColor * ((cosIntenve < 0 || _MainLightRange - distance < 0) ? max(DarkColorPercent, ((cosIntenve + lightBlurRate) * lightRange)) : min(1, (cosIntenve + lightBlurRate) * lightRange));
}
half3 LightingAttenuation(half3 finalColor, half3 posWorld, half3 lightPos)
{
return (_MainLightType >= 0 && _MainLightType < 1) ? LightingSpotAttenuation(finalColor, posWorld, lightPos) : (_MainLightType >= 1 && _MainLightType < 2) ? finalColor : LightingPointAttenuation(finalColor, posWorld, lightPos);
}
补充:
另外如果想要多个聚光灯的话,其实也可以一次消耗多个聚光灯效果。主要就是管线传递多个光照的位置,颜色等信息。然后再采样然后叠加。(只是gpu需要多计算一个光,但是drawcall不会增加)
using System;
using System.Collections.Generic;
using UnityEngine.Experimental.GlobalIllumination;
using UnityEngine.Rendering;
namespace UnityEngine.Experimental.Rendering.LightweightPipeline
{
/// <summary>
/// Configure the shader constants needed by the render pipeline
///
/// This pass configures constants that LWRP uses when rendering.
/// For example, you can execute this pass before you render opaque
/// objects, to make sure that lights are configured correctly.
/// </summary>
public class SetupLightweightConstanstPass : ScriptableRenderPass
{
static class LightConstantBuffer
{
public static int _MainLightPosition;
public static int _MainLightColor;
public static int _MainLightRange;
public static int _MainLightType;
public static int _MainLightAttenuation;
public static int _MainLightSpotDir;
public static int _LightCount;
//不建立数组,避免堆内存
public static int _MainLightColor2;
public static int _MainLightType2;
public static int _MainLightPosition2;
public static int _MainLightRange2;
public static int _MainLightAttenuation2;
public static int _MainLightSpotDir2;
public static int _MainLightColor3;
public static int _MainLightType3;
public static int _MainLightPosition3;
public static int _MainLightRange3;
public static int _MainLightAttenuation3;
public static int _MainLightSpotDir3;
public static int _MainLightColor4;
public static int _MainLightType4;
public static int _MainLightPosition4;
public static int _MainLightRange4;
public static int _MainLightAttenuation4;
public static int _MainLightSpotDir4;
public static int _AdditionalLightsCount;
public static int _AdditionalLightsPosition;
public static int _AdditionalLightsColor;
public static int _AdditionalLightsAttenuation;
public static int _AdditionalLightsSpotDir;
public static int _AdditionalLightsBuffer;
}
const string k_SetupLightConstants = "Setup Light Constants";
MixedLightingSetup m_MixedLightingSetup;
Vector4 k_DefaultLightPosition = new Vector4(0.0f, 0.0f, 1.0f, 1.0f);
Vector4 k_DefaultLightColor = Color.black;
Vector4 k_DefaultLightAttenuation = new Vector4(1.0f, 0.0f, 0.0f, 1.0f);
Vector4 k_DefaultLightSpotDirection = new Vector4(0.0f, 0.0f, 1.0f, 0.0f);
const float k_DefaultLightRange = 10.0f;
Vector4[] m_AdditionalLightPositions;
Vector4[] m_AdditionalLightColors;
Vector4[] m_AdditionalLightAttenuations;
Vector4[] m_AdditionalLightSpotDirections;
private int maxVisibleAdditionalLights { get; set; }
private ComputeBuffer perObjectLightIndices { get; set; }
/// <summary>
/// Create the pass
/// </summary>
public SetupLightweightConstanstPass()
{
LightConstantBuffer._LightCount = Shader.PropertyToID("_LightCount");
LightConstantBuffer._MainLightPosition = Shader.PropertyToID("_MainLightPosition");
LightConstantBuffer._MainLightColor = Shader.PropertyToID("_MainLightColor");
LightConstantBuffer._MainLightRange = Shader.PropertyToID("_MainLightRange");
LightConstantBuffer._MainLightType = Shader.PropertyToID("_MainLightType");
LightConstantBuffer._MainLightColor2 = Shader.PropertyToID("_MainLightColor2");
LightConstantBuffer._MainLightType2 = Shader.PropertyToID("_MainLightType2");
LightConstantBuffer._MainLightPosition2 = Shader.PropertyToID("_MainLightPosition2");
LightConstantBuffer._MainLightAttenuation2 = Shader.PropertyToID("_MainLightAttenuation2");
LightConstantBuffer._MainLightSpotDir2 = Shader.PropertyToID("_MainLightSpotDir2");
LightConstantBuffer._MainLightRange2 = Shader.PropertyToID("_MainLightRange2");
LightConstantBuffer._MainLightColor3 = Shader.PropertyToID("_MainLightColor3");
LightConstantBuffer._MainLightType3 = Shader.PropertyToID("_MainLightType3");
LightConstantBuffer._MainLightPosition3 = Shader.PropertyToID("_MainLightPosition3");
LightConstantBuffer._MainLightRange3 = Shader.PropertyToID("_MainLightRange3");
LightConstantBuffer._MainLightAttenuation3 = Shader.PropertyToID("_MainLightAttenuation3");
LightConstantBuffer._MainLightSpotDir3 = Shader.PropertyToID("_MainLightSpotDir3");
LightConstantBuffer._MainLightColor4 = Shader.PropertyToID("_MainLightColor4");
LightConstantBuffer._MainLightType4 = Shader.PropertyToID("_MainLightType4");
LightConstantBuffer._MainLightPosition4 = Shader.PropertyToID("_MainLightPosition4");
LightConstantBuffer._MainLightRange4 = Shader.PropertyToID("_MainLightRange4");
LightConstantBuffer._MainLightAttenuation4 = Shader.PropertyToID("_MainLightAttenuation4");
LightConstantBuffer._MainLightSpotDir4 = Shader.PropertyToID("_MainLightSpotDir4");
LightConstantBuffer._MainLightAttenuation = Shader.PropertyToID("_MainLightAttenuation");
LightConstantBuffer._MainLightSpotDir = Shader.PropertyToID("_MainLightSpotDir");
LightConstantBuffer._AdditionalLightsCount = Shader.PropertyToID("_AdditionalLightsCount");
LightConstantBuffer._AdditionalLightsPosition = Shader.PropertyToID("_AdditionalLightsPosition");
LightConstantBuffer._AdditionalLightsColor = Shader.PropertyToID("_AdditionalLightsColor");
LightConstantBuffer._AdditionalLightsAttenuation = Shader.PropertyToID("_AdditionalLightsAttenuation");
LightConstantBuffer._AdditionalLightsSpotDir = Shader.PropertyToID("_AdditionalLightsSpotDir");
LightConstantBuffer._AdditionalLightsBuffer = Shader.PropertyToID("_AdditionalLightsBuffer");
m_AdditionalLightPositions = new Vector4[0];
m_AdditionalLightColors = new Vector4[0];
m_AdditionalLightAttenuations = new Vector4[0];
m_AdditionalLightSpotDirections = new Vector4[0];
}
/// <summary>
/// Configure the pass
/// </summary>
/// <param name="maxVisibleAdditionalLights">Maximum number of visible additional lights</param>
/// <param name="perObjectLightIndices">Buffer holding per object light indicies</param>
public void Setup(int maxVisibleAdditionalLights, ComputeBuffer perObjectLightIndices)
{
this.maxVisibleAdditionalLights = maxVisibleAdditionalLights;
this.perObjectLightIndices = perObjectLightIndices;
if (m_AdditionalLightColors.Length != maxVisibleAdditionalLights)
{
m_AdditionalLightPositions = new Vector4[maxVisibleAdditionalLights];
m_AdditionalLightColors = new Vector4[maxVisibleAdditionalLights];
m_AdditionalLightAttenuations = new Vector4[maxVisibleAdditionalLights];
m_AdditionalLightSpotDirections = new Vector4[maxVisibleAdditionalLights];
}
}
void InitializeLightConstants(List<VisibleLight> lights, int lightIndex, out Vector4 lightPos, out Vector4 lightColor, out Vector4 lightAttenuation, out Vector4 lightSpotDir, out float lightRange, out int lightType)
{
lightPos = k_DefaultLightPosition;
lightColor = k_DefaultLightColor;
lightAttenuation = k_DefaultLightAttenuation;
lightSpotDir = k_DefaultLightSpotDirection;
lightRange = k_DefaultLightRange;
lightType = 1;
// When no lights are visible, main light will be set to -1.
// In this case we initialize it to default values and return
if (lightIndex < 0)
return;
VisibleLight lightData = lights[lightIndex];
if (lightData.lightType == LightType.Directional)
{
Vector4 dir = -lightData.localToWorld.GetColumn(2);
lightPos = new Vector4(dir.x, dir.y, dir.z, k_DefaultLightAttenuation.w);
}
else
{
Vector4 pos = lightData.localToWorld.GetColumn(3);
lightPos = new Vector4(pos.x, pos.y, pos.z, k_DefaultLightAttenuation.w);
lightRange = lightData.range;
}
lightType = (int)lightData.lightType;
// VisibleLight.finalColor already returns color in active color space
lightColor = lightData.finalColor;
// Directional Light attenuation is initialize so distance attenuation always be 1.0
if (lightData.lightType != LightType.Directional)
{
// Light attenuation in lightweight matches the unity vanilla one.
// attenuation = 1.0 / distanceToLightSqr
// We offer two different smoothing factors.
// The smoothing factors make sure that the light intensity is zero at the light range limit.
// The first smoothing factor is a linear fade starting at 80 % of the light range.
// smoothFactor = (lightRangeSqr - distanceToLightSqr) / (lightRangeSqr - fadeStartDistanceSqr)
// We rewrite smoothFactor to be able to pre compute the constant terms below and apply the smooth factor
// with one MAD instruction
// smoothFactor = distanceSqr * (1.0 / (fadeDistanceSqr - lightRangeSqr)) + (-lightRangeSqr / (fadeDistanceSqr - lightRangeSqr)
// distanceSqr * oneOverFadeRangeSqr + lightRangeSqrOverFadeRangeSqr
// The other smoothing factor matches the one used in the Unity lightmapper but is slower than the linear one.
// smoothFactor = (1.0 - saturate((distanceSqr * 1.0 / lightrangeSqr)^2))^2
float lightRangeSqr = lightData.range * lightData.range;
float fadeStartDistanceSqr = 0.8f * 0.8f * lightRangeSqr;
float fadeRangeSqr = (fadeStartDistanceSqr - lightRangeSqr);
float oneOverFadeRangeSqr = 1.0f / fadeRangeSqr;
float lightRangeSqrOverFadeRangeSqr = -lightRangeSqr / fadeRangeSqr;
float oneOverLightRangeSqr = 1.0f / Mathf.Max(0.0001f, lightData.range * lightData.range);
// On mobile: Use the faster linear smoothing factor.
// On other devices: Use the smoothing factor that matches the GI.
lightAttenuation.x = Application.isMobilePlatform ? oneOverFadeRangeSqr : oneOverLightRangeSqr;
lightAttenuation.y = lightRangeSqrOverFadeRangeSqr;
}
else
{
lightRange = 99999;
}
if (lightData.lightType == LightType.Spot)
{
Vector4 dir = lightData.localToWorld.GetColumn(2);
lightSpotDir = new Vector4(-dir.x, -dir.y, -dir.z, 0.0f);
// Spot Attenuation with a linear falloff can be defined as
// (SdotL - cosOuterAngle) / (cosInnerAngle - cosOuterAngle)
// This can be rewritten as
// invAngleRange = 1.0 / (cosInnerAngle - cosOuterAngle)
// SdotL * invAngleRange + (-cosOuterAngle * invAngleRange)
// If we precompute the terms in a MAD instruction
float cosOuterAngle = Mathf.Cos(Mathf.Deg2Rad * lightData.spotAngle * 0.5f);
// We neeed to do a null check for particle lights
// This should be changed in the future
// Particle lights will use an inline function
float cosInnerAngle;
if (lightData.light != null)
cosInnerAngle = Mathf.Cos(LightmapperUtils.ExtractInnerCone(lightData.light) * 0.5f);
else
cosInnerAngle = Mathf.Cos((2.0f * Mathf.Atan(Mathf.Tan(lightData.spotAngle * 0.5f * Mathf.Deg2Rad) * (64.0f - 18.0f) / 64.0f)) * 0.5f);
float smoothAngleRange = Mathf.Max(0.001f, cosInnerAngle - cosOuterAngle);
float invAngleRange = 1.0f / smoothAngleRange;
float add = -cosOuterAngle * invAngleRange;
lightAttenuation.z = invAngleRange;
lightAttenuation.w = cosOuterAngle;//add;
}
Light light = lightData.light;
// TODO: Add support to shadow mask
if (light != null && light.bakingOutput.mixedLightingMode == MixedLightingMode.Subtractive && light.bakingOutput.lightmapBakeType == LightmapBakeType.Mixed)
{
if (m_MixedLightingSetup == MixedLightingSetup.None && lightData.light.shadows != LightShadows.None)
{
m_MixedLightingSetup = MixedLightingSetup.Subtractive;
// In subtractive light mode, main light direct contribution is baked on lightmap
// In this case we setup light position w component as 0.0f so we can remove it's contribution
// from realtime light computation
if (lightData.lightType == LightType.Directional)
lightPos.w = 0.0f;
}
}
}
void SetupShaderLightConstants(CommandBuffer cmd, ref LightData lightData)
{
float lightRange = 0;
int lightType = 1;
// Clear to default all light constant data
for (int i = 0; i < maxVisibleAdditionalLights; ++i)
InitializeLightConstants(lightData.visibleLights, -1, out m_AdditionalLightPositions[i],
out m_AdditionalLightColors[i],
out m_AdditionalLightAttenuations[i],
out m_AdditionalLightSpotDirections[i],
out lightRange,
out lightType);
m_MixedLightingSetup = MixedLightingSetup.None;
// Main light has an optimized shader path for main light. This will benefit games that only care about a single light.
// Lightweight pipeline also supports only a single shadow light, if available it will be the main light.
SetupMainLightConstants(cmd, ref lightData);
SetupAdditionalLightConstants(cmd, ref lightData);
}
void SetupMainLightConstants(CommandBuffer cmd, ref LightData lightData)
{
Vector4 lightPos, lightColor, lightAttenuation, lightSpotDir;
float lightRange = 0.0f;
int lightType = 1;
InitializeLightConstants(lightData.visibleLights, lightData.mainLightIndex, out lightPos, out lightColor, out lightAttenuation, out lightSpotDir,
out lightRange, out lightType);
if (lightData.visibleLights.Count == 0)
{
var lights = Light.GetLights(LightType.Spot, 0);
if (lights.Length != 0)
{
cmd.SetGlobalVector(LightConstantBuffer._MainLightColor, lights[0].color);
cmd.SetGlobalInt(LightConstantBuffer._MainLightType, 0);
cmd.SetGlobalVector(LightConstantBuffer._MainLightPosition, lightPos);
cmd.SetGlobalFloat(LightConstantBuffer._MainLightRange, lightRange);
cmd.SetGlobalVector(LightConstantBuffer._MainLightAttenuation, lightAttenuation);
cmd.SetGlobalVector(LightConstantBuffer._MainLightSpotDir, lightSpotDir);
}
}
else
{
//不存在的灯要设置为默认值
for (int i = lightData.visibleLights.Count; i <= 3; i++)
{
SetLightPro(cmd, i);
}
//if (lightData.visibleLights.Count >= 2)
{
//多光源处理
SetLightDataToRealLight(cmd, lightData);
}
//else
//{
// //只是主光源
// SetLightPro(cmd, 0, lightColor,lightType,lightPos,lightRange,lightAttenuation,lightSpotDir);
//}
}
cmd.SetGlobalInt(LightConstantBuffer._LightCount, lightData.visibleLights.Count);
}
void SetLightDataToRealLight(CommandBuffer cmd, LightData lightData)
{
int directionalType = 0;
for (int i = 0; i < lightData.visibleLights.Count; i++)
{
Vector4 pos = lightData.visibleLights[i].localToWorld.GetColumn(3);
Vector4 lightPos = new Vector4(pos.x, pos.y, pos.z, 1);
Vector4 attenuation = k_DefaultLightAttenuation;
float cosOuterAngle = Mathf.Cos(Mathf.Deg2Rad * lightData.visibleLights[i].spotAngle * 0.5f);
attenuation.w = cosOuterAngle;
Vector4 spotDir = k_DefaultLightSpotDirection;
Vector4 dir = lightData.visibleLights[i].localToWorld.GetColumn(2);
spotDir.x = -dir.x;
spotDir.y = -dir.y;
spotDir.z = -dir.z;
spotDir.w = 0;
//最多只支持一盏平行光
if (lightData.visibleLights[i].lightType == LightType.Directional)
{
++directionalType;
if (directionalType > 1)
{
continue;
}
}
SetLightPro(cmd, i, lightData.visibleLights[i].finalColor,
(int)lightData.visibleLights[i].lightType,
lightPos,
lightData.visibleLights[i].range,
attenuation, spotDir);
}
}
Vector4 zeroVector4 = new Vector4(0, 0, 0, 0);
void SetLightPro(CommandBuffer cmd, int index, Vector4 lightColor = default(Vector4), int lightType = 0,
Vector4 lightPos = default(Vector4), float lightRange = 10.0f,Vector4 lightAttenuation = default(Vector4),
Vector4 lightSpotDir = default(Vector4))
{
if (lightColor == default(Vector4))
lightColor = k_DefaultLightColor;
lightType = lightType;
if (lightPos == default(Vector4))
lightPos = k_DefaultLightPosition;
lightRange = k_DefaultLightRange;
if (lightAttenuation == default(Vector4))
lightAttenuation = k_DefaultLightAttenuation;
if (lightSpotDir == default(Vector4))
lightSpotDir = k_DefaultLightSpotDirection;
int lightColorPro = LightConstantBuffer._MainLightColor;
int lightTypePro = LightConstantBuffer._MainLightType;
int lightPosPro = LightConstantBuffer._MainLightPosition;
int lightRangePro = LightConstantBuffer._MainLightRange;
int lightAttenuationPro = LightConstantBuffer._MainLightAttenuation;
int lightSpotDirPro = LightConstantBuffer._MainLightSpotDir;
if (index == 1)
{
lightColorPro = LightConstantBuffer._MainLightColor2;
lightTypePro = LightConstantBuffer._MainLightType2;
lightPosPro = LightConstantBuffer._MainLightPosition2;
lightRangePro = LightConstantBuffer._MainLightRange2;
lightAttenuationPro = LightConstantBuffer._MainLightAttenuation2;
lightSpotDirPro = LightConstantBuffer._MainLightSpotDir2;
}
else if (index == 2)
{
lightColorPro = LightConstantBuffer._MainLightColor3;
lightTypePro = LightConstantBuffer._MainLightType3;
lightPosPro = LightConstantBuffer._MainLightPosition3;
lightRangePro = LightConstantBuffer._MainLightRange3;
lightAttenuationPro = LightConstantBuffer._MainLightAttenuation3;
lightSpotDirPro = LightConstantBuffer._MainLightSpotDir3;
}
else if (index == 3)
{
lightColorPro = LightConstantBuffer._MainLightColor4;
lightTypePro = LightConstantBuffer._MainLightType4;
lightPosPro = LightConstantBuffer._MainLightPosition4;
lightRangePro = LightConstantBuffer._MainLightRange4;
lightAttenuationPro = LightConstantBuffer._MainLightAttenuation4;
lightSpotDirPro = LightConstantBuffer._MainLightSpotDir4;
}
cmd.SetGlobalVector(lightColorPro, lightColor);
cmd.SetGlobalInt(lightTypePro, lightType);
cmd.SetGlobalVector(lightPosPro, lightPos);
cmd.SetGlobalFloat(lightRangePro, lightRange);
cmd.SetGlobalVector(lightAttenuationPro, lightAttenuation);
cmd.SetGlobalVector(lightSpotDirPro, lightSpotDir);
}
void SetupAdditionalLightConstants(CommandBuffer cmd, ref LightData lightData)
{
List<VisibleLight> lights = lightData.visibleLights;
if (lightData.additionalLightsCount > 0)
{
int additionalLightsCount = 0;
for (int i = 0; i < lights.Count && additionalLightsCount < maxVisibleAdditionalLights; ++i)
{
VisibleLight light = lights[i];
if (light.lightType != LightType.Directional)
{
float lightRange = 0.0f;
int lightType = 1;
InitializeLightConstants(lights, i, out m_AdditionalLightPositions[additionalLightsCount],
out m_AdditionalLightColors[additionalLightsCount],
out m_AdditionalLightAttenuations[additionalLightsCount],
out m_AdditionalLightSpotDirections[additionalLightsCount],
out lightRange,
out lightType);
additionalLightsCount++;
}
}
cmd.SetGlobalVector(LightConstantBuffer._AdditionalLightsCount, new Vector4(lightData.maxPerObjectAdditionalLightsCount,
0.0f, 0.0f, 0.0f));
// if not using a compute buffer, engine will set indices in 2 vec4 constants
// unity_4LightIndices0 and unity_4LightIndices1
if (perObjectLightIndices != null)
cmd.SetGlobalBuffer(LightConstantBuffer._AdditionalLightsBuffer, perObjectLightIndices);
}
else
{
cmd.SetGlobalVector(LightConstantBuffer._AdditionalLightsCount, Vector4.zero);
}
cmd.SetGlobalVectorArray(LightConstantBuffer._AdditionalLightsPosition, m_AdditionalLightPositions);
cmd.SetGlobalVectorArray(LightConstantBuffer._AdditionalLightsColor, m_AdditionalLightColors);
cmd.SetGlobalVectorArray(LightConstantBuffer._AdditionalLightsAttenuation, m_AdditionalLightAttenuations);
cmd.SetGlobalVectorArray(LightConstantBuffer._AdditionalLightsSpotDir, m_AdditionalLightSpotDirections);
}
/// <inheritdoc/>
public override void Execute(ScriptableRenderer renderer, ScriptableRenderContext context, ref RenderingData renderingData)
{
if (renderer == null)
throw new ArgumentNullException("renderer");
int additionalLightsCount = renderingData.lightData.additionalLightsCount;
bool additionalLightsPerVertex = renderingData.lightData.shadeAdditionalLightsPerVertex;
CommandBuffer cmd = CommandBufferPool.Get(k_SetupLightConstants);
SetupShaderLightConstants(cmd, ref renderingData.lightData);
CoreUtils.SetKeyword(cmd, ShaderKeywordStrings.AdditionalLightsVertex, additionalLightsCount > 0 && additionalLightsPerVertex);
CoreUtils.SetKeyword(cmd, ShaderKeywordStrings.AdditionalLightsPixel, additionalLightsCount > 0 && !additionalLightsPerVertex);
CoreUtils.SetKeyword(cmd, ShaderKeywordStrings.MixedLightingSubtractive, renderingData.lightData.supportsMixedLighting && m_MixedLightingSetup == MixedLightingSetup.Subtractive);
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
}
}
}
其中
//多光源处理
SetLightDataToRealLight(cmd, lightData);
这里是对多个光照传递的数据处理。
还得做平衡光限制
//最多只支持一盏平行光
if (lightData.visibleLights[i].lightType == LightType.Directional)
{
++directionalType;
if (directionalType > 1)
{
continue;
}
}
half _LightCount;
half4 _MainLightColor2;
half _MainLightType2;
half4 _MainLightPosition2;
half _MainLightRange2;
half4 _MainLightAttenuation2;
half4 _MainLightSpotDir2;
half4 _MainLightColor3;
half _MainLightType3;
half4 _MainLightPosition3;
half _MainLightRange3;
half4 _MainLightAttenuation3;
half4 _MainLightSpotDir3;
half4 _MainLightColor4;
half _MainLightType4;
half4 _MainLightPosition4;
half _MainLightRange4;
half4 _MainLightAttenuation4;
half4 _MainLightSpotDir4;
half DarkColorPercentHandle(half num)
{
return 0.5 - (num * 0.08);
}
half3 LightingPointAttenuation(half3 finalColor, half3 posWorld, half3 lightPos)
{
half distance = length(posWorld - lightPos);
return finalColor / (max(1.0h, (distance * (1.0h / _MainLightRange))));
}
half3 LightingSpotAttenuation(half3 finalColor, half4 lightColor, half3 posWorld, half3 lightPos, half3 spotDir, half4 attenuation, half range)
{
half3 vertexDir = normalize(posWorld - lightPos);
half3 centerPointDir = (spotDir.xyz);
half cDotv = -dot(centerPointDir, vertexDir);
half cosIntenve = cDotv - attenuation.w;
half DarkColorPercent = DarkColorPercentHandle(_LightCount);
half dotPoint = min(1, max(DarkColorPercent, cosIntenve + 0.8));
//half3 distanceColor = LightingPointAttenuation(finalColor, posWorld, lightPos);
half3 circleColor = finalColor * dotPoint * lightColor.rgb;
half distance = length(posWorld - lightPos);
half lightRange = 45;
half lightBlurRate = 0.01;
return circleColor * ((cosIntenve < 0 || range - distance < 0) ? max(DarkColorPercent, ((cosIntenve + lightBlurRate) * lightRange)) : min(1, (cosIntenve + lightBlurRate) * lightRange));
}
half3 LightingAttenuationHandle(half type, half4 lightColor, half3 finalColor, half3 posWorld, half3 lightPos, half3 spotDir, half4 attenuation, half range)
{
//(lightColor.r == 0 && lightColor.g == 0 && lightColor.b == 0)这个是因为如果是黑色就不需要运算了,直接返回
return (lightColor.r == 0 && lightColor.g == 0 && lightColor.b == 0) ? 0 : (type >= 0 && type < 1) ? LightingSpotAttenuation(finalColor, lightColor, posWorld, lightPos, spotDir, attenuation, range) : (type >= 1 && type < 2) ? finalColor : LightingPointAttenuation(finalColor, posWorld, lightPos);
}
half3 LightingAttenuation(half3 finalColor, half3 posWorld, half3 lightPos)
{
return LightingAttenuationHandle(_MainLightType, _MainLightColor, finalColor, posWorld, _MainLightPosition.xyz, _MainLightSpotDir, _MainLightAttenuation, _MainLightRange) +
max(0, _LightCount - 1) * LightingAttenuationHandle(_MainLightType2, _MainLightColor2, finalColor, posWorld, _MainLightPosition2.xyz, _MainLightSpotDir2, _MainLightAttenuation2, _MainLightRange2) +
max(0, _LightCount - 2) * LightingAttenuationHandle(_MainLightType3, _MainLightColor3, finalColor, posWorld, _MainLightPosition3.xyz, _MainLightSpotDir3, _MainLightAttenuation3, _MainLightRange3) +
max(0, _LightCount - 3) * LightingAttenuationHandle(_MainLightType4, _MainLightColor4, finalColor, posWorld, _MainLightPosition4.xyz, _MainLightSpotDir4, _MainLightAttenuation4, _MainLightRange4);
}
shader部分主要是下面这个把两个光源叠加
return LightingAttenuationHandle(_MainLightType, _MainLightColor, finalColor, posWorld, _MainLightPosition.xyz, _MainLightSpotDir, _MainLightAttenuation, _MainLightRange) +
max(0, _LightCount - 1) * LightingAttenuationHandle(_MainLightType2, _MainLightColor2, finalColor, posWorld, _MainLightPosition2.xyz, _MainLightSpotDir2, _MainLightAttenuation2, _MainLightRange2) +
max(0, _LightCount - 2) * LightingAttenuationHandle(_MainLightType3, _MainLightColor3, finalColor, posWorld, _MainLightPosition3.xyz, _MainLightSpotDir3, _MainLightAttenuation3, _MainLightRange3) +
max(0, _LightCount - 3) * LightingAttenuationHandle(_MainLightType4, _MainLightColor4, finalColor, posWorld, _MainLightPosition4.xyz, _MainLightSpotDir4, _MainLightAttenuation4, _MainLightRange4);