浏览代码

Merge pull request #331 from EvgeniiG/merge_proj_lights

Merge projector lights into punctual lights
/RenderPassXR_Sandbox
GitHub 7 年前
当前提交
9e3bb7df
共有 10 个文件被更改,包括 521 次插入680 次删除
  1. 2
      Assets/ScriptableRenderPipeline/Fptl/LightDefinitions.cs.hlsl
  2. 5
      Assets/ScriptableRenderPipeline/HDRenderPipeline/AdditionalData/HDAdditionalLightData.cs
  3. 64
      Assets/ScriptableRenderPipeline/HDRenderPipeline/Lighting/LightDefinition.cs
  4. 176
      Assets/ScriptableRenderPipeline/HDRenderPipeline/Lighting/LightDefinition.cs.hlsl
  5. 266
      Assets/ScriptableRenderPipeline/HDRenderPipeline/Lighting/TilePass/TilePass.cs
  6. 10
      Assets/ScriptableRenderPipeline/HDRenderPipeline/Lighting/TilePass/TilePass.cs.hlsl
  7. 2
      Assets/ScriptableRenderPipeline/HDRenderPipeline/Lighting/TilePass/TilePass.hlsl
  8. 43
      Assets/ScriptableRenderPipeline/HDRenderPipeline/Lighting/TilePass/TilePassLoop.hlsl
  9. 229
      Assets/ScriptableRenderPipeline/HDRenderPipeline/Material/Lit/Lit.hlsl
  10. 404
      Assets/Textures/Batman.png

2
Assets/ScriptableRenderPipeline/Fptl/LightDefinitions.cs.hlsl


//
// This file was automatically generated from Assets/ScriptableRenderPipeline/fptl/LightDefinitions.cs. Please don't edit by hand.
// This file was automatically generated from Assets/ScriptableRenderPipeline/Fptl/LightDefinitions.cs. Please don't edit by hand.
//
#ifndef LIGHTDEFINITIONS_CS_HLSL

5
Assets/ScriptableRenderPipeline/HDRenderPipeline/AdditionalData/HDAdditionalLightData.cs


namespace UnityEngine.Experimental.Rendering
{
public enum LightArchetype { Punctual, Area, Projector };
public enum LightArchetype { Punctual, Area };
public enum SpotLightShape { Cone, Pyramid, Box };
//@TODO: We should continuously move these values
// into the engine when we can see them being generally useful

public bool affectSpecular = true;
public LightArchetype archetype = LightArchetype.Punctual;
public SpotLightShape spotLightShape = SpotLightShape.Cone;
[Range(0.0f, 20.0f)]
public float lightLength = 0.0f; // Area & projector lights

64
Assets/ScriptableRenderPipeline/HDRenderPipeline/Lighting/LightDefinition.cs


public enum GPULightType
{
Directional,
ProjectorBox,
ProjectorOrtho,
ProjectorPyramid,
// AreaLight

// These structures share between C# and hlsl need to be align on float4, so we pad them.
[GenerateHLSL]
public struct LightData
public struct DirectionalLightData
public float invSqrAttenuationRadius;
public float unused;
public float angleScale; // Spot light
public int shadowIndex; // -1 if unused
public float angleOffset; // Spot light
public Vector3 up;
public float diffuseScale;
public int cookieIndex; // -1 if unused
public Vector3 right;
public Vector3 right; // Rescaled by (2 / lightLength)
public float shadowDimmer;
// index are -1 if not used
public int shadowIndex;
public int IESIndex;
public int cookieIndex;
public Vector2 size; // Used by area, projector and spot lights; x = cot(outerHalfAngle) for spot lights
public GPULightType lightType;
public float unused;
public Vector3 up; // Rescaled by (2 / lightWidth)
public float diffuseScale;
public struct DirectionalLightData
public struct LightData
public Vector3 forward;
public float diffuseScale;
public Vector3 up;
public float invScaleY;
public Vector3 right;
public float invScaleX;
// DirectionalLightData >>>
public bool tileCookie;
public float invSqrAttenuationRadius;
public float specularScale;
// Sun disc size
public float cosAngle; // Distance to the disk
public float sinAngle; // Disk radius
public Vector3 forward;
public Vector3 right; // If spot: rescaled by cot(outerHalfAngle); if projector: rescaled by (2 / lightLength)
public float specularScale;
public Vector3 up; // If spot: rescaled by cot(outerHalfAngle); if projector: rescaled by * (2 / lightWidth)
public float diffuseScale;
// <<< DirectionalLightData
public float angleScale; // Spot light
public float angleOffset; // Spot light
public float shadowDimmer;
public int IESIndex; // -1 if unused
public Vector2 size; // Used by area, frustum projector and spot lights (x = cot(outerHalfAngle))
public GPULightType lightType;
public float unused;
[GenerateHLSL]

176
Assets/ScriptableRenderPipeline/HDRenderPipeline/Lighting/LightDefinition.cs.hlsl


// UnityEngine.Experimental.Rendering.HDPipeline.GPULightType: static fields
//
#define GPULIGHTTYPE_DIRECTIONAL (0)
#define GPULIGHTTYPE_SPOT (1)
#define GPULIGHTTYPE_POINT (2)
#define GPULIGHTTYPE_PROJECTOR_ORTHO (3)
#define GPULIGHTTYPE_PROJECTOR_BOX (1)
#define GPULIGHTTYPE_SPOT (2)
#define GPULIGHTTYPE_POINT (3)
#define GPULIGHTTYPE_PROJECTOR_PYRAMID (4)
#define GPULIGHTTYPE_RECTANGLE (5)
#define GPULIGHTTYPE_LINE (6)

#define STENCILLIGHTINGUSAGE_SPLIT_LIGHTING (1)
#define STENCILLIGHTINGUSAGE_REGULAR_LIGHTING (2)
// Generated from UnityEngine.Experimental.Rendering.HDPipeline.LightData
// Generated from UnityEngine.Experimental.Rendering.HDPipeline.DirectionalLightData
struct LightData
struct DirectionalLightData
float invSqrAttenuationRadius;
float unused;
float angleScale;
int shadowIndex;
float angleOffset;
float3 up;
float diffuseScale;
int cookieIndex;
float shadowDimmer;
int shadowIndex;
int IESIndex;
int cookieIndex;
float2 size;
int lightType;
float unused;
float3 up;
float diffuseScale;
// Generated from UnityEngine.Experimental.Rendering.HDPipeline.DirectionalLightData
// Generated from UnityEngine.Experimental.Rendering.HDPipeline.LightData
struct DirectionalLightData
struct LightData
float3 forward;
float diffuseScale;
float3 up;
float invScaleY;
float3 right;
float invScaleX;
bool tileCookie;
float invSqrAttenuationRadius;
float specularScale;
float cosAngle;
float sinAngle;
float3 forward;
float3 right;
float specularScale;
float3 up;
float diffuseScale;
float angleScale;
float angleOffset;
float shadowDimmer;
int IESIndex;
float2 size;
int lightType;
float unused;
};
// Generated from UnityEngine.Experimental.Rendering.HDPipeline.EnvLightData

};
//
// Accessors for UnityEngine.Experimental.Rendering.HDPipeline.LightData
// Accessors for UnityEngine.Experimental.Rendering.HDPipeline.DirectionalLightData
float3 GetPositionWS(LightData value)
float3 GetPositionWS(DirectionalLightData value)
float GetInvSqrAttenuationRadius(LightData value)
float GetUnused(DirectionalLightData value)
return value.invSqrAttenuationRadius;
return value.unused;
float3 GetColor(LightData value)
float3 GetColor(DirectionalLightData value)
float GetAngleScale(LightData value)
int GetShadowIndex(DirectionalLightData value)
return value.angleScale;
return value.shadowIndex;
float3 GetForward(LightData value)
float3 GetForward(DirectionalLightData value)
float GetAngleOffset(LightData value)
{
return value.angleOffset;
}
float3 GetUp(LightData value)
{
return value.up;
}
float GetDiffuseScale(LightData value)
int GetCookieIndex(DirectionalLightData value)
return value.diffuseScale;
return value.cookieIndex;
float3 GetRight(LightData value)
float3 GetRight(DirectionalLightData value)
float GetSpecularScale(LightData value)
float GetSpecularScale(DirectionalLightData value)
float GetShadowDimmer(LightData value)
{
return value.shadowDimmer;
}
int GetShadowIndex(LightData value)
float3 GetUp(DirectionalLightData value)
return value.shadowIndex;
return value.up;
int GetIESIndex(LightData value)
float GetDiffuseScale(DirectionalLightData value)
return value.IESIndex;
return value.diffuseScale;
int GetCookieIndex(LightData value)
//
// Accessors for UnityEngine.Experimental.Rendering.HDPipeline.LightData
//
float3 GetPositionWS(LightData value)
return value.cookieIndex;
return value.positionWS;
float2 GetSize(LightData value)
float GetInvSqrAttenuationRadius(LightData value)
return value.size;
return value.invSqrAttenuationRadius;
int GetLightType(LightData value)
float3 GetColor(LightData value)
return value.lightType;
return value.color;
float GetUnused(LightData value)
int GetShadowIndex(LightData value)
return value.unused;
return value.shadowIndex;
//
// Accessors for UnityEngine.Experimental.Rendering.HDPipeline.DirectionalLightData
//
float3 GetForward(DirectionalLightData value)
float3 GetForward(LightData value)
float GetDiffuseScale(DirectionalLightData value)
int GetCookieIndex(LightData value)
return value.diffuseScale;
}
float3 GetUp(DirectionalLightData value)
{
return value.up;
return value.cookieIndex;
float GetInvScaleY(DirectionalLightData value)
float3 GetRight(LightData value)
return value.invScaleY;
return value.right;
float3 GetRight(DirectionalLightData value)
float GetSpecularScale(LightData value)
return value.right;
return value.specularScale;
float GetInvScaleX(DirectionalLightData value)
float3 GetUp(LightData value)
return value.invScaleX;
return value.up;
float3 GetPositionWS(DirectionalLightData value)
float GetDiffuseScale(LightData value)
return value.positionWS;
return value.diffuseScale;
bool GetTileCookie(DirectionalLightData value)
float GetAngleScale(LightData value)
return value.tileCookie;
return value.angleScale;
float3 GetColor(DirectionalLightData value)
float GetAngleOffset(LightData value)
return value.color;
return value.angleOffset;
float GetSpecularScale(DirectionalLightData value)
float GetShadowDimmer(LightData value)
return value.specularScale;
return value.shadowDimmer;
float GetCosAngle(DirectionalLightData value)
int GetIESIndex(LightData value)
return value.cosAngle;
return value.IESIndex;
float GetSinAngle(DirectionalLightData value)
float2 GetSize(LightData value)
return value.sinAngle;
return value.size;
int GetShadowIndex(DirectionalLightData value)
int GetLightType(LightData value)
return value.shadowIndex;
return value.lightType;
int GetCookieIndex(DirectionalLightData value)
float GetUnused(LightData value)
return value.cookieIndex;
return value.unused;
}
//

266
Assets/ScriptableRenderPipeline/HDRenderPipeline/Lighting/TilePass/TilePass.cs


{
Punctual,
Area,
Projector,
Env,
Count
}

Punctual = 1 << 0,
Area = 1 << 1,
Directional = 1 << 2,
Projector = 1 << 3,
Env = 1 << 4,
Sky = 1 << 5
Env = 1 << 3,
Sky = 1 << 4
}
[GenerateHLSL]

public const int k_MaxDirectionalLightsOnScreen = 4;
public const int k_MaxPunctualLightsOnScreen = 512;
public const int k_MaxAreaLightsOnScreen = 64;
public const int k_MaxProjectorLightsOnScreen = 64;
public const int k_MaxLightsOnScreen = k_MaxDirectionalLightsOnScreen + k_MaxPunctualLightsOnScreen + k_MaxAreaLightsOnScreen + k_MaxProjectorLightsOnScreen;
public const int k_MaxLightsOnScreen = k_MaxDirectionalLightsOnScreen + k_MaxPunctualLightsOnScreen + k_MaxAreaLightsOnScreen;
public const int k_MaxEnvLightsOnScreen = 64;
public const int k_MaxShadowOnScreen = 16;
public const int k_MaxCascadeCount = 4; //Should be not less than m_Settings.directionalLightCascadeCount;

LightList m_lightList;
int m_punctualLightCount = 0;
int m_areaLightCount = 0;
int m_projectorLightCount = 0;
int m_lightCount = 0;
private ComputeShader buildScreenAABBShader { get { return m_Resources.buildScreenAABBShader; } }

m_lightList.Allocate();
s_DirectionalLightDatas = new ComputeBuffer(k_MaxDirectionalLightsOnScreen, System.Runtime.InteropServices.Marshal.SizeOf(typeof(DirectionalLightData)));
s_LightDatas = new ComputeBuffer(k_MaxPunctualLightsOnScreen + k_MaxAreaLightsOnScreen + k_MaxProjectorLightsOnScreen, System.Runtime.InteropServices.Marshal.SizeOf(typeof(LightData)));
s_LightDatas = new ComputeBuffer(k_MaxPunctualLightsOnScreen + k_MaxAreaLightsOnScreen, System.Runtime.InteropServices.Marshal.SizeOf(typeof(LightData)));
s_EnvLightDatas = new ComputeBuffer(k_MaxEnvLightsOnScreen, System.Runtime.InteropServices.Marshal.SizeOf(typeof(EnvLightData)));
s_shadowDatas = new ComputeBuffer(k_MaxCascadeCount + k_MaxShadowOnScreen, System.Runtime.InteropServices.Marshal.SizeOf(typeof(ShadowData)));

// Light direction for directional is opposite to the forward direction
directionalLightData.forward = light.light.transform.forward;
directionalLightData.up = light.light.transform.up;
directionalLightData.right = light.light.transform.right;
// Rescale for cookies and windowing.
directionalLightData.up = light.light.transform.up * 2 / additionalData.lightWidth;
directionalLightData.right = light.light.transform.right * 2 / additionalData.lightLength;
directionalLightData.invScaleX = 1.0f / light.light.transform.localScale.x;
directionalLightData.invScaleY = 1.0f / light.light.transform.localScale.y;
directionalLightData.cosAngle = 0.0f;
directionalLightData.sinAngle = 0.0f;
directionalLightData.shadowIndex = -1;
directionalLightData.cookieIndex = -1;
directionalLightData.shadowIndex = directionalLightData.cookieIndex = -1;
directionalLightData.tileCookie = (light.light.cookie.wrapMode == TextureWrapMode.Repeat);
directionalLightData.cookieIndex = m_CookieTexArray.FetchSlice(light.light.cookie);
}
// fix up shadow information

lightData.forward = light.light.transform.forward; // Note: Light direction is oriented backward (-Z)
lightData.up = light.light.transform.up;
lightData.right = light.light.transform.right;
lightData.size = new Vector2(additionalLightData.lightLength, additionalLightData.lightWidth);
if (lightData.lightType == GPULightType.ProjectorBox || lightData.lightType == GPULightType.ProjectorPyramid)
{
// Rescale for cookies and windowing.
lightData.right *= 2 / additionalLightData.lightLength;
lightData.up *= 2 / additionalLightData.lightWidth;
}
if (lightData.lightType == GPULightType.Spot)
{

lightData.angleScale = 1.0f / val;
lightData.angleOffset = -cosSpotOuterHalfAngle * lightData.angleScale;
// TODO: Currently the spot cookie code use the cotangent, either we fix the spot cookie code to not use cotangent
// or we clean the name here, store it in size.x for now
lightData.size.x = cosSpotOuterHalfAngle / sinSpotOuterHalfAngle;
// Rescale for cookies and windowing.
float cotOuterHalfAngle = cosSpotOuterHalfAngle / sinSpotOuterHalfAngle;
lightData.up *= cotOuterHalfAngle;
lightData.right *= cotOuterHalfAngle;
// 1.0f, 2.0f are neutral value allowing GetAngleAnttenuation in shader code to return 1.0
lightData.angleScale = 1.0f;
lightData.angleOffset = 2.0f;
// These are the neutral values allowing GetAngleAnttenuation in shader code to return 1.0
lightData.angleScale = 0.0f;
lightData.angleOffset = 1.0f;
}
float distanceToCamera = (lightData.positionWS - camera.transform.position).magnitude;

lightData.cookieIndex = m_CubeCookieTexArray.FetchSlice(light.light.cookie);
break;
}
if (additionalLightData.archetype == LightArchetype.Projector)
{
lightData.cookieIndex = m_CookieTexArray.FetchSlice(light.light.cookie);
}
}
else if (light.lightType == LightType.Spot && additionalLightData.spotLightShape != SpotLightShape.Cone)
{
// Projectors lights must always have a cookie texture.
lightData.cookieIndex = m_CookieTexArray.FetchSlice(Texture2D.whiteTexture);
}
if (additionalshadowData)

{
lightData.shadowIndex = shadowIdx;
}
if (additionalLightData.archetype != LightArchetype.Punctual)
{
lightData.size = new Vector2(additionalLightData.lightLength, additionalLightData.lightWidth);
}
m_lightList.lights.Add(lightData);

// Then Culling side
var range = light.range;
var lightToWorld = light.localToWorld;
Vector3 lightPos = lightData.positionWS;
Vector3 positionWS = lightData.positionWS;
Vector3 positionVS = worldToView.MultiplyPoint(positionWS);
Matrix4x4 lightToView = worldToView * lightToWorld;
Vector3 xAxisVS = lightToView.GetColumn(0);
Vector3 yAxisVS = lightToView.GetColumn(1);
Vector3 zAxisVS = lightToView.GetColumn(2);
// Fill bounds
var bound = new SFiniteLightBound();

{
Vector3 lightDir = lightToWorld.GetColumn(2);
// represents a left hand coordinate system in world space
Vector3 vx = lightToWorld.GetColumn(0); // X axis in world space
Vector3 vy = lightToWorld.GetColumn(1); // Y axis in world space
Vector3 vz = lightDir; // Z axis in world space
// transform to camera space (becomes a left hand coordinate frame in Unity since Determinant(worldToView)<0)
vx = worldToView.MultiplyVector(vx);
vy = worldToView.MultiplyVector(vy);
vz = worldToView.MultiplyVector(vz);
// represents a left hand coordinate system in world space since det(worldToView)<0
Vector3 vx = xAxisVS;
Vector3 vy = yAxisVS;
Vector3 vz = zAxisVS;
const float pi = 3.1415926535897932384626433832795f;
const float degToRad = (float)(pi / 180.0);

// apply nonuniform scale to OBB of spot light
var squeeze = true;//sa < 0.7f * 90.0f; // arb heuristic
var fS = squeeze ? ta : si;
bound.center = worldToView.MultiplyPoint(lightPos + ((0.5f * range) * lightDir)); // use mid point of the spot as the center of the bounding volume for building screen-space AABB for tiled lighting.
bound.center = worldToView.MultiplyPoint(positionWS + ((0.5f * range) * lightDir)); // use mid point of the spot as the center of the bounding volume for building screen-space AABB for tiled lighting.
// scale axis to match box or base of pyramid
bound.boxAxisX = (fS * range) * vx;

lightVolumeData.lightAxisX = vx;
lightVolumeData.lightAxisY = vy;
lightVolumeData.lightAxisZ = vz;
lightVolumeData.lightPos = worldToView.MultiplyPoint(lightPos);
lightVolumeData.lightPos = positionVS;
lightVolumeData.featureFlags = (gpuLightType == GPULightType.Spot) ? (uint)LightFeatureFlags.Punctual
: (uint)LightFeatureFlags.Projector;
lightVolumeData.featureFlags = (uint)LightFeatureFlags.Punctual;
bound.center = worldToView.MultiplyPoint(lightPos);
bound.center = positionVS;
bound.boxAxisX.Set(range, 0, 0);
bound.boxAxisY.Set(0, range, 0);
bound.boxAxisZ.Set(0, 0, isNegDeterminant ? (-range) : range); // transform to camera space (becomes a left hand coordinate frame in Unity since Determinant(worldToView)<0)

// represents a left hand coordinate system in world space since det(worldToView)<0
var lightToView = worldToView * lightToWorld;
Vector3 vx = lightToView.GetColumn(0);
Vector3 vy = lightToView.GetColumn(1);
Vector3 vz = lightToView.GetColumn(2);
Vector3 vx = xAxisVS;
Vector3 vy = yAxisVS;
Vector3 vz = zAxisVS;
// fill up ldata
lightVolumeData.lightAxisX = vx;

lightVolumeData.radiusSq = range * range;
lightVolumeData.featureFlags = (uint)LightFeatureFlags.Punctual;
}
else if (gpuLightType == GPULightType.Rectangle)
else if (gpuLightType == GPULightType.Line)
Vector3 centerVS = worldToView.MultiplyPoint(lightData.positionWS);
Vector3 xAxisVS = worldToView.MultiplyVector(lightData.right);
Vector3 yAxisVS = worldToView.MultiplyVector(lightData.up);
Vector3 zAxisVS = worldToView.MultiplyVector(lightData.forward);
float radius = 1.0f / Mathf.Sqrt(lightData.invSqrAttenuationRadius);
Vector3 dimensions = new Vector3(lightData.size.x + 2 * range, 2 * range, 2 * range); // Omni-directional
Vector3 extents = 0.5f * dimensions;
Vector3 dimensions = new Vector3(lightData.size.x * 0.5f + radius, lightData.size.y * 0.5f + radius, radius);
dimensions.z *= 0.5f;
centerVS += zAxisVS * radius * 0.5f;
bound.center = centerVS;
bound.boxAxisX = dimensions.x * xAxisVS;
bound.boxAxisY = dimensions.y * yAxisVS;
bound.boxAxisZ = dimensions.z * zAxisVS;
bound.center = positionVS;
bound.boxAxisX = extents.x * xAxisVS;
bound.boxAxisY = extents.y * yAxisVS;
bound.boxAxisZ = extents.z * zAxisVS;
bound.radius = dimensions.magnitude;
bound.radius = extents.magnitude;
lightVolumeData.lightPos = centerVS;
lightVolumeData.lightPos = positionVS;
lightVolumeData.boxInnerDist = dimensions;
lightVolumeData.boxInvRange.Set(1e5f, 1e5f, 1e5f);
lightVolumeData.boxInnerDist = new Vector3(lightData.size.x, 0, 0);
lightVolumeData.boxInvRange.Set(1.0f / range, 1.0f / range, 1.0f / range);
else if (gpuLightType == GPULightType.Line)
else if (gpuLightType == GPULightType.Rectangle)
Vector3 centerVS = worldToView.MultiplyPoint(lightData.positionWS);
Vector3 xAxisVS = worldToView.MultiplyVector(lightData.right);
Vector3 yAxisVS = worldToView.MultiplyVector(lightData.up);
Vector3 zAxisVS = worldToView.MultiplyVector(lightData.forward);
float radius = 1.0f / Mathf.Sqrt(lightData.invSqrAttenuationRadius);
Vector3 dimensions = new Vector3(lightData.size.x * 0.5f + radius, radius, radius);
Vector3 dimensions = new Vector3(lightData.size.x + 2 * range, lightData.size.y + 2 * range, range); // One-sided
Vector3 extents = 0.5f * dimensions;
Vector3 centerVS = positionVS + extents.z * zAxisVS;
bound.boxAxisX = dimensions.x * xAxisVS;
bound.boxAxisY = dimensions.y * yAxisVS;
bound.boxAxisZ = dimensions.z * zAxisVS;
bound.boxAxisX = extents.x * xAxisVS;
bound.boxAxisY = extents.y * yAxisVS;
bound.boxAxisZ = extents.z * zAxisVS;
bound.radius = dimensions.magnitude;
bound.radius = extents.magnitude;
lightVolumeData.lightPos = centerVS;
lightVolumeData.lightAxisX = xAxisVS;
lightVolumeData.lightAxisY = yAxisVS;
lightVolumeData.lightAxisZ = zAxisVS;
lightVolumeData.boxInnerDist = new Vector3(lightData.size.x * 0.5f, 0.01f, 0.01f);
lightVolumeData.boxInvRange.Set(1.0f / radius, 1.0f / radius, 1.0f / radius);
lightVolumeData.lightPos = centerVS;
lightVolumeData.lightAxisX = xAxisVS;
lightVolumeData.lightAxisY = yAxisVS;
lightVolumeData.lightAxisZ = zAxisVS;
lightVolumeData.boxInnerDist = extents;
lightVolumeData.boxInvRange.Set(Mathf.Infinity, Mathf.Infinity, Mathf.Infinity);
else if (gpuLightType == GPULightType.ProjectorOrtho)
else if (gpuLightType == GPULightType.ProjectorBox)
Vector3 posVS = worldToView.MultiplyPoint(lightData.positionWS);
Vector3 xAxisVS = worldToView.MultiplyVector(lightData.right);
Vector3 yAxisVS = worldToView.MultiplyVector(lightData.up);
Vector3 zAxisVS = worldToView.MultiplyVector(lightData.forward);
Vector3 dimensions = new Vector3(lightData.size.x, lightData.size.y, range); // One-sided
Vector3 extents = 0.5f * dimensions;
Vector3 centerVS = positionVS + extents.z * zAxisVS;
// Projector lights point forwards (along Z). The projection window is aligned with the XY plane.
Vector3 boxDims = new Vector3(lightData.size.x, lightData.size.y, 1000000.0f);
Vector3 halfDims = 0.5f * boxDims;
bound.center = posVS;
bound.boxAxisX = halfDims.x * xAxisVS; // Should this be halved or not?
bound.boxAxisY = halfDims.y * yAxisVS; // Should this be halved or not?
bound.boxAxisZ = halfDims.z * zAxisVS; // Should this be halved or not?
bound.radius = halfDims.magnitude; // Radius of a circumscribed sphere?
bound.center = centerVS;
bound.boxAxisX = extents.x * xAxisVS;
bound.boxAxisY = extents.y * yAxisVS;
bound.boxAxisZ = extents.z * zAxisVS;
bound.radius = extents.magnitude;
lightVolumeData.lightPos = posVS; // Is this the center of the volume?
lightVolumeData.lightPos = centerVS;
lightVolumeData.boxInnerDist = halfDims; // No idea what this is. Document your code
lightVolumeData.boxInvRange.Set(1.0f / halfDims.x, 1.0f / halfDims.y, 1.0f / halfDims.z); // No idea what this is. Document your code
lightVolumeData.featureFlags = (uint)LightFeatureFlags.Projector;
lightVolumeData.boxInnerDist = extents;
lightVolumeData.boxInvRange.Set(Mathf.Infinity, Mathf.Infinity, Mathf.Infinity);
lightVolumeData.featureFlags = (uint)LightFeatureFlags.Punctual;
}
else
{

int directionalLightcount = 0;
int punctualLightcount = 0;
int areaLightCount = 0;
int projectorLightCount = 0;
int lightCount = Math.Min(cullResults.visibleLights.Count, k_MaxLightsOnScreen);
var sortKeys = new uint[lightCount];

// Note: LightType.Area is offline only, use for baking, no need to test it
if (additionalData.archetype == LightArchetype.Punctual)
{
lightCategory = LightCategory.Punctual;
lightCategory = LightCategory.Punctual;
gpuLightType = GPULightType.Point;
lightVolumeType = LightVolumeType.Sphere;
break;

continue;
lightCategory = LightCategory.Punctual;
gpuLightType = GPULightType.Spot;
lightVolumeType = LightVolumeType.Cone;
switch (additionalData.spotLightShape)
{
case SpotLightShape.Cone:
gpuLightType = GPULightType.Spot;
lightVolumeType = LightVolumeType.Cone;
break;
case SpotLightShape.Pyramid:
gpuLightType = GPULightType.ProjectorPyramid;
lightVolumeType = LightVolumeType.Cone;
break;
case SpotLightShape.Box:
gpuLightType = GPULightType.ProjectorBox;
lightVolumeType = LightVolumeType.Box;
break;
default:
Debug.Assert(false, "Encountered an unknown SpotLightShape.");
break;
}
lightCategory = LightCategory.Punctual;
gpuLightType = GPULightType.Directional;
// No need to add volume, always visible
lightVolumeType = LightVolumeType.Count; // Count is none

Debug.Assert(false, "TODO: encountered an unknown LightType.");
Debug.Assert(false, "Encountered an unknown LightType.");
else
else // LightArchetype.Area
switch (additionalData.archetype)
{
case LightArchetype.Area:
if (areaLightCount >= k_MaxAreaLightsOnScreen) { continue; }
lightCategory = LightCategory.Area;
gpuLightType = (additionalData.lightWidth > 0) ? GPULightType.Rectangle : GPULightType.Line;
lightVolumeType = LightVolumeType.Box;
break;
case LightArchetype.Projector:
if (projectorLightCount >= k_MaxProjectorLightsOnScreen) { continue; }
lightCategory = LightCategory.Projector;
switch (light.lightType)
{
case LightType.Directional:
gpuLightType = GPULightType.ProjectorOrtho;
lightVolumeType = LightVolumeType.Box;
break;
case LightType.Spot:
gpuLightType = GPULightType.ProjectorPyramid;
lightVolumeType = LightVolumeType.Cone;
break;
default:
Debug.Assert(false, "Projectors can only be Spot or Directional lights.");
break;
}
break;
default:
Debug.Assert(false, "TODO: encountered an unknown LightArchetype.");
break;
}
if (areaLightCount >= k_MaxAreaLightsOnScreen) { continue; }
lightCategory = LightCategory.Area;
gpuLightType = (additionalData.lightWidth > 0) ? GPULightType.Rectangle : GPULightType.Line;
lightVolumeType = LightVolumeType.Box;
}
uint shadow = m_ShadowIndices.ContainsKey(lightIndex) ? 1u : 0;

}
continue;
}
// Punctual, area, projector lights - the rendering side.
if (GetLightData(shadowSettings, camera, gpuLightType, light, additionalLightData, additionalShadowData, lightIndex))
{

break;
case LightCategory.Area:
areaLightCount++;
break;
case LightCategory.Projector:
projectorLightCount++;
break;
default:
Debug.Assert(false, "TODO: encountered an unknown LightCategory.");

// Sanity check
Debug.Assert(m_lightList.directionalLights.Count == directionalLightcount);
Debug.Assert(m_lightList.lights.Count == areaLightCount + punctualLightcount + projectorLightCount);
Debug.Assert(m_lightList.lights.Count == areaLightCount + punctualLightcount);
m_punctualLightCount = punctualLightcount;
m_areaLightCount = areaLightCount;
m_projectorLightCount = projectorLightCount;
m_punctualLightCount = punctualLightcount;
m_areaLightCount = areaLightCount;
// Redo everything but this time with envLights
int envLightCount = 0;

SetGlobalBuffer("_LightDatas", s_LightDatas);
SetGlobalInt("_PunctualLightCount", m_punctualLightCount);
SetGlobalInt("_AreaLightCount", m_areaLightCount);
SetGlobalInt("_ProjectorLightCount", m_projectorLightCount);
SetGlobalBuffer("_EnvLightDatas", s_EnvLightDatas);
SetGlobalInt("_EnvLightCount", m_lightList.envLights.Count);
SetGlobalBuffer("_ShadowDatas", s_shadowDatas);

10
Assets/ScriptableRenderPipeline/HDRenderPipeline/Lighting/TilePass/TilePass.cs.hlsl


//
#define LIGHTCATEGORY_PUNCTUAL (0)
#define LIGHTCATEGORY_AREA (1)
#define LIGHTCATEGORY_PROJECTOR (2)
#define LIGHTCATEGORY_ENV (3)
#define LIGHTCATEGORY_COUNT (4)
#define LIGHTCATEGORY_ENV (2)
#define LIGHTCATEGORY_COUNT (3)
//
// UnityEngine.Experimental.Rendering.HDPipeline.TilePass.LightFeatureFlags: static fields

#define LIGHTFEATUREFLAGS_DIRECTIONAL (4)
#define LIGHTFEATUREFLAGS_PROJECTOR (8)
#define LIGHTFEATUREFLAGS_ENV (16)
#define LIGHTFEATUREFLAGS_SKY (32)
#define LIGHTFEATUREFLAGS_ENV (8)
#define LIGHTFEATUREFLAGS_SKY (16)
//
// UnityEngine.Experimental.Rendering.HDPipeline.TilePass.LightDefinitions: static fields

2
Assets/ScriptableRenderPipeline/HDRenderPipeline/Lighting/TilePass/TilePass.hlsl


#define PROCESS_DIRECTIONAL_LIGHT
#define PROCESS_PUNCTUAL_LIGHT
#define PROCESS_AREA_LIGHT
#define PROCESS_PROJECTOR_LIGHT
#endif
#if defined (LIGHTLOOP_TILE_INDIRECT) || defined(LIGHTLOOP_TILE_ALL)

uint _DirectionalLightCount;
uint _PunctualLightCount;
uint _AreaLightCount;
uint _ProjectorLightCount;
uint _EnvLightCount;
float4 _DirShadowSplitSpheres[4]; // TODO: share this max between C# and hlsl

43
Assets/ScriptableRenderPipeline/HDRenderPipeline/Lighting/TilePass/TilePassLoop.hlsl


{
float3 localDiffuseLighting, localSpecularLighting;
EvaluateBSDF_Directional( context, V, posInput, prelightData, _DirectionalLightDatas[i], bsdfData,
localDiffuseLighting, localSpecularLighting);
EvaluateBSDF_Directional(context, V, posInput, prelightData,
GPULIGHTTYPE_DIRECTIONAL, _DirectionalLightDatas[i], bsdfData,
localDiffuseLighting, localSpecularLighting);
diffuseLighting += localDiffuseLighting;
specularLighting += localSpecularLighting;

}
#endif
#ifdef PROCESS_PROJECTOR_LIGHT
if(featureFlags & LIGHTFEATUREFLAGS_PROJECTOR)
{
// TODO: Convert the for loop below to a while on each type as we know we are sorted!
uint projectorLightStart;
uint projectorLightCount;
GetCountAndStart(posInput, LIGHTCATEGORY_PROJECTOR, projectorLightStart, projectorLightCount);
for(i = 0; i < projectorLightCount; ++i)
{
float3 localDiffuseLighting, localSpecularLighting;
uint projectorIndex = FetchIndex(projectorLightStart, i);
EvaluateBSDF_Projector(context, V, posInput, prelightData, _LightDatas[projectorIndex], bsdfData,
localDiffuseLighting, localSpecularLighting);
diffuseLighting += localDiffuseLighting;
specularLighting += localSpecularLighting;
}
}
#endif
#ifdef PROCESS_ENV_LIGHT
float3 iblDiffuseLighting = float3(0.0, 0.0, 0.0);
float3 iblSpecularLighting = float3(0.0, 0.0, 0.0);

{
float3 localDiffuseLighting, localSpecularLighting;
EvaluateBSDF_Directional( context, V, posInput, prelightData, _DirectionalLightDatas[i], bsdfData,
localDiffuseLighting, localSpecularLighting);
EvaluateBSDF_Directional(context, V, posInput, prelightData,
GPULIGHTTYPE_DIRECTIONAL, _DirectionalLightDatas[i], bsdfData,
localDiffuseLighting, localSpecularLighting);
diffuseLighting += localDiffuseLighting;
specularLighting += localSpecularLighting;

EvaluateBSDF_Area( context, V, posInput, prelightData, _LightDatas[i], bsdfData,
localDiffuseLighting, localSpecularLighting);
}
diffuseLighting += localDiffuseLighting;
specularLighting += localSpecularLighting;
}
for (; i < _PunctualLightCount + _AreaLightCount + _ProjectorLightCount; ++i)
{
float3 localDiffuseLighting, localSpecularLighting;
EvaluateBSDF_Projector( context, V, posInput, prelightData, _LightDatas[i], bsdfData,
localDiffuseLighting, localSpecularLighting);
diffuseLighting += localDiffuseLighting;
specularLighting += localSpecularLighting;

229
Assets/ScriptableRenderPipeline/HDRenderPipeline/Material/Lit/Lit.hlsl


}
//-----------------------------------------------------------------------------
// EvaluateBSDF_Directional
// EvaluateBSDF_Directional (supports directional and box projector lights)
void EvaluateBSDF_Directional( LightLoopContext lightLoopContext,
float3 V, PositionInputs posInput, PreLightData preLightData, DirectionalLightData lightData, BSDFData bsdfData,
out float3 diffuseLighting,
out float3 specularLighting)
void EvaluateBSDF_Directional(LightLoopContext lightLoopContext,
float3 V, PositionInputs posInput, PreLightData preLightData,
int lightType, DirectionalLightData lightData, BSDFData bsdfData,
out float3 diffuseLighting,
out float3 specularLighting)
// Compute the NDC position (in [-1, 1]^2) by projecting 'positionWS' onto the near plane.
// 'lightData.right' and 'lightData.up' are pre-scaled on CPU.
float3 lightToSurface = positionWS - lightData.positionWS;
float3x3 lightToWorld = float3x3(lightData.right, lightData.up, lightData.forward);
float3 positionLS = mul(lightToSurface, transpose(lightToWorld));
float2 positionNDC = positionLS.xy;
// Clip only box projector lights.
float clipFactor = 1;
// Static branch.
if (lightType == GPULIGHTTYPE_PROJECTOR_BOX)
{
// bool isInBounds = max(abs(positionNDC.x), abs(positionNDC.y)) <= 1 && positionLS.z >= 0;
// float clipFactor = isInBounds ? 1 : 0;
// This version is slightly faster (trade 1x VALU for 1x SALU):
float clipFactor = saturate(FLT_MAX - FLT_MAX * max(abs(positionNDC.x), abs(positionNDC.y))) * (positionLS.z >= 0 ? 1 : 0);
}
float attenuation = clipFactor;
float illuminance = saturate(NdotL);
float illuminance = saturate(NdotL * attenuation);
diffuseLighting = float3(0.0, 0.0, 0.0);
specularLighting = float3(0.0, 0.0, 0.0);
float4 cookie = float4(1.0, 1.0, 1.0, 1.0);
diffuseLighting = float3(0, 0, 0); // TODO: check whether using 'out' instead of 'inout' increases the VGPR pressure
specularLighting = float3(0, 0, 0); // TODO: check whether using 'out' instead of 'inout' increases the VGPR pressure
float3 cookie = float3(1, 1, 1);
float shadow = 1;
[branch] if (lightData.shadowIndex >= 0)

[branch] if (lightData.cookieIndex >= 0)
{
float3 lightToSurface = positionWS - lightData.positionWS;
// Remap the texture coordinates from [-1, 1]^2 to [0, 1]^2.
float2 coord = positionNDC * 0.5 + 0.5;
// Project 'lightToSurface' onto the light's axes.
float2 coord = float2(dot(lightToSurface, lightData.right), dot(lightToSurface, lightData.up));
// Compute the NDC coordinates (in [-1, 1]^2).
coord.x *= lightData.invScaleX;
coord.y *= lightData.invScaleY;
// We let the sampler handle tiling or clamping to border.
// Note: tiling (the repeat mode) is not currently supported.
float4 c = SampleCookie2D(lightLoopContext, coord, lightData.cookieIndex);
if (lightData.tileCookie || (abs(coord.x) <= 1 && abs(coord.y) <= 1))
{
// Remap the texture coordinates from [-1, 1]^2 to [0, 1]^2.
coord = coord * 0.5 + 0.5;
// Tile the texture if the 'repeat' wrap mode is enabled.
if (lightData.tileCookie) { coord = frac(coord); }
cookie = SampleCookie2D(lightLoopContext, coord, lightData.cookieIndex);
}
else
{
cookie = float4(0, 0, 0, 0);
}
illuminance *= cookie.a;
// Use premultiplied alpha to save 1x VGPR.
cookie = c.rgb * c.a;
}
[branch] if (illuminance > 0.0)

diffuseLighting *= (cookie.rgb * lightData.color) * (illuminance * lightData.diffuseScale);
specularLighting *= (cookie.rgb * lightData.color) * (illuminance * lightData.specularScale);
diffuseLighting *= (cookie * lightData.color) * (illuminance * lightData.diffuseScale);
specularLighting *= (cookie * lightData.color) * (illuminance * lightData.specularScale);
}
[branch] if (bsdfData.enableTransmission)

// (we reuse the illumination) with the reversed normal of the current sample.
// We apply wrapped lighting instead of the regular Lambertian diffuse
// to compensate for these approximations.
illuminance = ComputeWrappedDiffuseLighting(NdotL, SSS_WRAP_LIGHT);
illuminance = ComputeWrappedDiffuseLighting(NdotL, SSS_WRAP_LIGHT) * attenuation;
illuminance *= shadow * cookie.a;
illuminance *= shadow;
float3 backLight = (cookie.rgb * lightData.color) * (Lambert() * illuminance * lightData.diffuseScale);
float3 backLight = (cookie * lightData.color) * (Lambert() * illuminance * lightData.diffuseScale);
// TODO: multiplication by 'diffuseColor' and 'transmittance' is the same for each light.
float3 transmittedLight = backLight * (bsdfData.diffuseColor * bsdfData.transmittance);

}
//-----------------------------------------------------------------------------
// EvaluateBSDF_Punctual
// EvaluateBSDF_Punctual (supports spot, point and projector lights)
//-----------------------------------------------------------------------------
void EvaluateBSDF_Punctual( LightLoopContext lightLoopContext,

{
float3 positionWS = posInput.positionWS;
int lightType = lightData.lightType;
// All punctual light type in the same formula, attenuation is neutral depends on light type.
// light.positionWS is the normalize light direction in case of directional light and invSqrAttenuationRadius is 0

float3 unL = lightData.positionWS - positionWS;
float3 L = normalize(unL);
float3 lightToSurface = positionWS - lightData.positionWS;
float3 unL = -lightToSurface;
float3 L = (lightType != GPULIGHTTYPE_PROJECTOR_BOX) ? normalize(unL) : -lightData.forward;
float attenuation = GetDistanceAttenuation(unL, lightData.invSqrAttenuationRadius);
// Reminder: lights are ortiented backward (-Z)
float attenuation = (lightType != GPULIGHTTYPE_PROJECTOR_BOX) ? GetDistanceAttenuation(unL, lightData.invSqrAttenuationRadius) : 1;
// Reminder: lights are oriented backward (-Z)
diffuseLighting = float3(0.0, 0.0, 0.0);
specularLighting = float3(0.0, 0.0, 0.0);
float4 cookie = float4(1.0, 1.0, 1.0, 1.0);
diffuseLighting = float3(0, 0, 0); // TODO: check whether using 'out' instead of 'inout' increases the VGPR pressure
specularLighting = float3(0, 0, 0); // TODO: check whether using 'out' instead of 'inout' increases the VGPR pressure
float3 cookie = float3(1, 1, 1);
float shadow = 1;
// TODO: measure impact of having all these dynamic branch here and the gain (or not) of testing illuminace > 0

[branch] if (lightData.shadowIndex >= 0)
{
// TODO: make projector lights cast shadows.
float3 offset = float3(0.0, 0.0, 0.0); // GetShadowPosOffset(nDotL, normal);
shadow = GetPunctualShadowAttenuation(lightLoopContext.shadowContext, positionWS + offset, bsdfData.normalWS, lightData.shadowIndex, L, posInput.unPositionSS);
shadow = lerp(1.0, shadow, lightData.shadowDimmer);

// Projector lights always have a cookie.
// Translate and rotate 'positionWS' into the light space.
// 'lightData.right' and 'lightData.up' are pre-scaled on CPU.
float3 positionLS = mul(lightToSurface, transpose(lightToWorld));
// Rotate 'L' into the light space.
// We perform the negation because lights are oriented backwards (-Z).
float3 coord = mul(-L, transpose(lightToWorld));
[branch] if (lightType == GPULIGHTTYPE_POINT)
{
float4 c = SampleCookieCube(lightLoopContext, positionLS, lightData.cookieIndex);
[branch] if (lightData.lightType == GPULIGHTTYPE_SPOT)
// Use premultiplied alpha to save 1x VGPR.
cookie = c.rgb * c.a;
}
else
// Perform the perspective projection of the hemisphere onto the disk.
coord.xy /= coord.z;
// Compute the NDC position (in [-1, 1]^2) by projecting 'positionWS' onto the plane at 1m distance.
// Box projector lights require no perspective division.
float perspectiveZ = (lightType != GPULIGHTTYPE_PROJECTOR_BOX) ? positionLS.z : 1;
float2 positionNDC = positionLS.xy / perspectiveZ;
// bool isInBounds = max(abs(positionNDC.x), abs(positionNDC.y)) <= 1 && positionLS.z >= 0;
// float clipFactor = isInBounds ? 1 : 0;
// This version is slightly faster (trade 1x VALU for 1x SALU):
float clipFactor = saturate(FLT_MAX - FLT_MAX * max(abs(positionNDC.x), abs(positionNDC.y))) * (positionLS.z >= 0 ? 1 : 0);
// Rescale the projective coordinates to fit into the [-1, 1]^2 range.
float cotOuterHalfAngle = lightData.size.x;
coord.xy *= cotOuterHalfAngle;
// Remap the texture coordinates from [-1, 1]^2 to [0, 1]^2.
float2 coord = positionNDC * 0.5 + 0.5;
// Remap the texture coordinates from [-1, 1]^2 to [0, 1]^2.
coord.xy = coord.xy * 0.5 + 0.5;
// We let the sampler handle clamping to border.
float4 c = SampleCookie2D(lightLoopContext, coord, lightData.cookieIndex);
cookie = SampleCookie2D(lightLoopContext, coord.xy, lightData.cookieIndex);
// Use premultiplied alpha to save 1x VGPR.
cookie = c.rgb * (c.a * clipFactor);
else // GPULIGHTTYPE_POINT
{
cookie = SampleCookieCube(lightLoopContext, coord, lightData.cookieIndex);
}
illuminance *= cookie.a;
}
[branch] if (illuminance > 0.0)

// For low thickness, we can reuse the shadowing status for the back of the object.
shadow = bsdfData.useThinObjectMode ? shadow : 1;
illuminance *= shadow * cookie.a;
float3 backLight = (cookie.rgb * lightData.color) * (Lambert() * illuminance * lightData.diffuseScale);
// TODO: multiplication by 'diffuseColor' and 'transmittance' is the same for each light.
float3 transmittedLight = backLight * (bsdfData.diffuseColor * bsdfData.transmittance);
// We use diffuse lighting for accumulation since it is going to be blurred during the SSS pass.
diffuseLighting += transmittedLight;
}
}
//-----------------------------------------------------------------------------
// EvaluateBSDF_Projector
//-----------------------------------------------------------------------------
void EvaluateBSDF_Projector(LightLoopContext lightLoopContext,
float3 V, PositionInputs posInput, PreLightData preLightData, LightData lightData, BSDFData bsdfData,
out float3 diffuseLighting,
out float3 specularLighting)
{
float3 positionWS = posInput.positionWS;
// Translate and rotate 'positionWS' into the light space.
float3 positionLS = mul(positionWS - lightData.positionWS,
transpose(float3x3(lightData.right, lightData.up, lightData.forward)));
if (lightData.lightType == GPULIGHTTYPE_PROJECTOR_PYRAMID)
{
// Perform perspective division.
positionLS *= rcp(positionLS.z);
}
else
{
// For orthographic projection, the Z coordinate plays no role.
positionLS.z = 0;
}
// Compute the NDC position (in [-1, 1]^2). TODO: precompute the inverse?
float2 positionNDC = positionLS.xy * rcp(0.5 * lightData.size);
// Perform clipping.
float clipFactor = ((positionLS.z >= 0) && (abs(positionNDC.x) <= 1 && abs(positionNDC.y) <= 1)) ? 1 : 0;
float3 L = -lightData.forward; // Lights are pointing backward in Unity
float NdotL = dot(bsdfData.normalWS, L);
float illuminance = saturate(NdotL * clipFactor);
diffuseLighting = float3(0.0, 0.0, 0.0);
specularLighting = float3(0.0, 0.0, 0.0);
float4 cookie = float4(1.0, 1.0, 1.0, 1.0);
float shadow = 1;
[branch] if (lightData.shadowIndex >= 0)
{
shadow = GetDirectionalShadowAttenuation(lightLoopContext.shadowContext, positionWS, bsdfData.normalWS, lightData.shadowIndex, L, posInput.unPositionSS);
}
[branch] if (lightData.cookieIndex >= 0)
{
// Compute the texture coordinates in [0, 1]^2.
float2 coord = positionNDC * 0.5 + 0.5;
cookie = SampleCookie2D(lightLoopContext, coord, lightData.cookieIndex);
illuminance *= cookie.a;
}
[branch] if (illuminance > 0.0)
{
BSDF(V, L, positionWS, preLightData, bsdfData, diffuseLighting, specularLighting);
diffuseLighting *= (cookie.rgb * lightData.color) * (illuminance * lightData.diffuseScale);
specularLighting *= (cookie.rgb * lightData.color) * (illuminance * lightData.specularScale);
}
[branch] if (bsdfData.enableTransmission)
{
// Currently, we only model diffuse transmission. Specular transmission is not yet supported.
// We assume that the back side of the object is a uniformly illuminated infinite plane
// (we reuse the illumination) with the reversed normal of the current sample.
// We apply wrapped lighting instead of the regular Lambertian diffuse
// to compensate for these approximations.
illuminance = ComputeWrappedDiffuseLighting(NdotL, SSS_WRAP_LIGHT) * clipFactor;
// For low thickness, we can reuse the shadowing status for the back of the object.
shadow = bsdfData.useThinObjectMode ? shadow : 1;
illuminance *= shadow * cookie.a;
float3 backLight = (cookie.rgb * lightData.color) * (Lambert() * illuminance * lightData.diffuseScale);
// TODO: multiplication by 'diffuseColor' and 'transmittance' is the same for each light.

404
Assets/Textures/Batman.png

之前 之后
宽度: 512  |  高度: 512  |  大小: 49 KiB
正在加载...
取消
保存