2026-04-16 오브젝트 그림자
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
#ifndef BADDOG_AREA_LIGHT_SHADOWS_INCLUDED
|
||||
#define BADDOG_AREA_LIGHT_SHADOWS_INCLUDED
|
||||
|
||||
// URP shadow sampling utilities
|
||||
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Shadows.hlsl"
|
||||
#include "Packages/com.baddog.rendering.arealight/Shaders/Include/HDPCSS.hlsl"
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Area Light Shadow Data
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// Maximum number of area lights that can cast shadows
|
||||
#define MAX_AREA_LIGHT_SHADOW_COUNT 8
|
||||
|
||||
// Area Light Shadow Atlas
|
||||
TEXTURE2D_SHADOW(_AreaLightsShadowmapTexture);
|
||||
|
||||
// Area Light Shadow Atlas Size
|
||||
// x: 1.0 / width (texel size)
|
||||
// y: 1.0 / height (texel size)
|
||||
// z: width (resolution)
|
||||
// w: height (resolution)
|
||||
float4 _AreaLightShadowmapSize;
|
||||
|
||||
// Area Light Shadow Parameters (per area light, indexed by areaLightIndex)
|
||||
// x: shadow strength [0,1]
|
||||
// y: soft shadow (1.0 for soft, 0.0 for hard) - reserved for future use
|
||||
// z: light type identifier (for shader variants) - reserved for future use
|
||||
// w: areaLightIndex (same as array index, or -1 if no shadow)
|
||||
float4 _AreaLightShadowParams[MAX_AREA_LIGHT_SHADOW_COUNT];
|
||||
|
||||
// Area Light World-to-Shadow Matrices (per area light, indexed by areaLightIndex)
|
||||
float4x4 _AreaLightsWorldToShadow[MAX_AREA_LIGHT_SHADOW_COUNT];
|
||||
|
||||
// PCSS Parameters for Area Light Shadows
|
||||
// x: shadowSoftness (world space units)
|
||||
// y: blockerSampleCount
|
||||
// z: filterSampleCount
|
||||
// w: rangeScale (for directional lights, 1.0 for area lights)
|
||||
float4 _PCSSAdditionalLightParams;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// PCSS Support Function (if not provided by URP Shadows.hlsl)
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
real SampleShadowmapFilteredPCSS(TEXTURE2D_SHADOW_PARAM(ShadowMap, sampler_ShadowMap), float4 shadowCoord, ShadowSamplingData samplingData, float4 pcssParams, float3 positionWS, bool isPerspectiveProjection)
|
||||
{
|
||||
// Generate jitter for PCSS using screen pixel coordinates
|
||||
float4 clipPos = TransformWorldToHClip(positionWS);
|
||||
float2 screenPos = ComputeScreenPos(clipPos).xy * _ScreenParams.xy;
|
||||
float sampleJitterAngle = InterleavedGradientNoise(screenPos, 0) * 2.0 * PI;
|
||||
float2 sampleJitter = float2(sin(sampleJitterAngle), cos(sampleJitterAngle));
|
||||
|
||||
// Extract PCSS parameters
|
||||
float shadowSoftness = pcssParams.x;
|
||||
int blockerSampleCount = (int)pcssParams.y;
|
||||
int filterSampleCount = (int)pcssParams.z;
|
||||
float lightRangeScale = pcssParams.w;
|
||||
float minFilterRadius = 2.0;
|
||||
|
||||
// shadowmapSize format: (texelWidth, texelHeight, width, height)
|
||||
// We need to convert shadowSoftness (world units) to UV space
|
||||
// shadowSoftness is in world units, we convert to texels then to UV
|
||||
float lightArea = shadowSoftness * samplingData.shadowmapSize.x; // Convert to UV space using texel size
|
||||
float maxLightArea = 0.04; // 4% of UV space
|
||||
lightArea = min(lightArea, maxLightArea);
|
||||
|
||||
// Shadow map bounds (full texture for non-atlas)
|
||||
float UMin = 0.0, UMax = 1.0, VMin = 0.0, VMax = 1.0;
|
||||
|
||||
// Perform PCSS blocker search
|
||||
real averageBlockerDepth;
|
||||
real numBlockers;
|
||||
bool blockerFound = BlockerSearch(averageBlockerDepth, numBlockers, lightArea, shadowCoord.xyz, UMin, UMax, VMin, VMax, sampleJitter, ShadowMap, sampler_PointClamp, blockerSampleCount);
|
||||
|
||||
// Calculate filter size based on penumbra estimation
|
||||
float filterSize = shadowSoftness * (isPerspectiveProjection ? PenumbraSizePunctual(shadowCoord.z, averageBlockerDepth) : PenumbraSizeDirectional(shadowCoord.z, averageBlockerDepth, lightRangeScale));
|
||||
filterSize = blockerFound ? max(filterSize, minFilterRadius) : minFilterRadius;
|
||||
|
||||
// Convert filter size to UV space (filterSize is in world units, multiply by texel size)
|
||||
filterSize *= samplingData.shadowmapSize.x;
|
||||
|
||||
// Perform PCSS filtering
|
||||
return PCSS(shadowCoord.xyz, UMin, UMax, VMin, VMax, filterSize, sampleJitter, ShadowMap, sampler_ShadowMap, filterSampleCount);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Area Light Shadow Sampling
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// Sample area light shadow for a given area light index
|
||||
// areaLightIndex: Index of the area light (matches _AreaLightDataBuffer index)
|
||||
// positionWS: World space position to sample shadow at
|
||||
// Returns: Shadow attenuation [0,1], where 1 = fully lit, 0 = fully shadowed
|
||||
half SampleAreaLightShadow(uint areaLightIndex, float3 positionWS)
|
||||
{
|
||||
float4 shadowParams = _AreaLightShadowParams[areaLightIndex];
|
||||
|
||||
// Check if this light has shadow (w >= 0)
|
||||
// w contains the areaLightIndex if shadow is enabled, or -1 if disabled
|
||||
if (shadowParams.w < 0.0)
|
||||
return 1.0;
|
||||
|
||||
// Transform position to shadow space
|
||||
float4 shadowCoord = mul(_AreaLightsWorldToShadow[areaLightIndex], float4(positionWS, 1.0));
|
||||
|
||||
// Build sampling data compatible with URP shadow sampling
|
||||
ShadowSamplingData samplingData;
|
||||
samplingData.shadowOffset0 = 0;
|
||||
samplingData.shadowOffset1 = 0;
|
||||
samplingData.shadowmapSize = _AreaLightShadowmapSize;
|
||||
samplingData.softShadowQuality = half(shadowParams.y);
|
||||
|
||||
// Perspective divide and out-of-bounds handling
|
||||
shadowCoord.xyz /= max(shadowCoord.w, 1e-5);
|
||||
if (BEYOND_SHADOW_FAR(shadowCoord))
|
||||
return 1.0;
|
||||
|
||||
real attenuation;
|
||||
|
||||
// Directly call URP shadow filter entry points by BG keywords (no macro remap).
|
||||
#if defined(_BG_AREALIGHT_SHADOWS_PCF2X2)
|
||||
attenuation = SampleShadowmapFilteredLowQuality(TEXTURE2D_SHADOW_ARGS(_AreaLightsShadowmapTexture, sampler_LinearClampCompare), shadowCoord, samplingData);
|
||||
#elif defined(_BG_AREALIGHT_SHADOWS_TENT5X5)
|
||||
attenuation = SampleShadowmapFilteredMediumQuality(TEXTURE2D_SHADOW_ARGS(_AreaLightsShadowmapTexture, sampler_LinearClampCompare), shadowCoord, samplingData);
|
||||
#elif defined(_BG_AREALIGHT_SHADOWS_TENT7X7)
|
||||
attenuation = SampleShadowmapFilteredHighQuality(TEXTURE2D_SHADOW_ARGS(_AreaLightsShadowmapTexture, sampler_LinearClampCompare), shadowCoord, samplingData);
|
||||
#elif defined(_BG_AREALIGHT_SHADOWS_PCSS)
|
||||
// PCSS for area lights: reuse the PCSS path implemented in Shadows.hlsl,
|
||||
// controlled by _PCSSAdditionalLightParams (x: softness, y: blockerSamples, z: filterSamples, w: rangeScale)
|
||||
attenuation = SampleShadowmapFilteredPCSS(
|
||||
TEXTURE2D_SHADOW_ARGS(_AreaLightsShadowmapTexture, sampler_LinearClampCompare),
|
||||
float4(shadowCoord.xyz, 1.0),
|
||||
samplingData,
|
||||
_PCSSAdditionalLightParams,
|
||||
positionWS,
|
||||
/*isPerspectiveProjection*/ true);
|
||||
#else
|
||||
attenuation = real(SAMPLE_TEXTURE2D_SHADOW(_AreaLightsShadowmapTexture, sampler_LinearClampCompare, shadowCoord.xyz));
|
||||
#endif
|
||||
|
||||
// Apply shadow strength
|
||||
attenuation = LerpWhiteTo(attenuation, shadowParams.x);
|
||||
|
||||
return half(attenuation);
|
||||
}
|
||||
|
||||
#endif // BADDOG_AREA_LIGHT_SHADOWS_INCLUDED
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f1c7cba811161e542ae1a802e715bb1d
|
||||
ShaderIncludeImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 346790
|
||||
packageName: Realtime Area Light for URP
|
||||
packageVersion: 1.3.0
|
||||
assetPath: Packages/com.baddog.rendering.arealight/Shaders/Include/BGAreaLightShadows.hlsl
|
||||
uploadId: 884030
|
||||
@@ -0,0 +1,565 @@
|
||||
#ifndef BADDOG_AREA_LIGHTING_INCLUDED
|
||||
#define BADDOG_AREA_LIGHTING_INCLUDED
|
||||
|
||||
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/AreaLighting.hlsl"
|
||||
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"
|
||||
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/CommonLighting.hlsl"
|
||||
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/GlobalSamplers.hlsl"
|
||||
#include "Packages/com.baddog.rendering.arealight/Shaders/Include/PreIntegratedFGD.hlsl"
|
||||
#include "Packages/com.baddog.rendering.arealight/Shaders/Include/LTCAreaLight.hlsl"
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Constants
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#define GPULIGHTTYPE_RECTANGLE (6)
|
||||
#define GPULIGHTTYPE_TUBE (5)
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// PreLightData Structure (Area Light Related)
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// Precomputed lighting data for area light evaluation
|
||||
// Reference: Based on HDRP's PreLightData structure, simplified for area lights only
|
||||
struct BGPreLightData
|
||||
{
|
||||
float NdotV; // Dot product between normal and view direction (could be negative due to normal mapping)
|
||||
|
||||
// Pre-integrated FGD (Fresnel, Geometric, Diffuse) terms
|
||||
float3 specularFGD; // Pre-integrated specular FGD term
|
||||
float diffuseFGD; // Pre-integrated diffuse FGD term
|
||||
float reflectivity; // Pre-integrated reflectivity term (for multiscattering)
|
||||
float energyCompensation; // Energy compensation for multiscattering (1.0 / specularReflectivity - 1.0)
|
||||
|
||||
// Area light LTC (Linearly Transformed Cosines) transforms
|
||||
float3x3 orthoBasisViewNormal; // Right-handed view-dependent orthogonal basis around the normal
|
||||
// Warning: These matrices are transposed! They are designed to transform row vectors via mul(V, M)
|
||||
float3x3 ltcTransformDiffuse; // Inverse LTC transformation matrix for diffuse BRDF (Lambertian or Disney Diffuse)
|
||||
float3x3 ltcTransformSpecular; // Inverse LTC transformation matrix for specular BRDF (GGX)
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Area Light Data Structure
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// Area light data structure for URP
|
||||
// This structure contains the necessary information for evaluating area lights (rectangular and line/tube lights)
|
||||
// IMPORTANT: Must match C# BGAreaLightRenderingData layout exactly
|
||||
// Uses float4 instead of float3+padding to match C# Vector4 layout (avoids alignment issues)
|
||||
struct BGAreaLightData
|
||||
{
|
||||
float4 positionWS; // float4: xyz = position, w = 1 when this entry is an area light (matches C# Vector4 flag)
|
||||
float4 right; // float4: xyz = right vector, w = unused
|
||||
float4 up; // float4: xyz = up vector, w = unused
|
||||
float4 forward; // float4: xyz = forward vector, w = unused
|
||||
float4 size; // float4: xy = size (length, height), zw = unused
|
||||
float4 colorIntensity; // float4: rgb = color, w = intensity
|
||||
float4 rangeParams; // float4: x = range, y = rangeAttenuationScale, z = rangeAttenuationBias, w = isRectLight
|
||||
float4 renderingLayerMask; // float4: x = uint mask bits (reinterpreted from C#), yzw = unused
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Area Light Data Buffer
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// Structured buffer containing area light data for additional lights (per-object order)
|
||||
// Entries without BGAreaLight component will have intensity = 0
|
||||
StructuredBuffer<BGAreaLightData> _AreaLightDataBuffer;
|
||||
|
||||
// Count of entries in the area light data buffer (matches additionalLightsCount)
|
||||
int _AreaLightDataBufferCount;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Area Light Shadow Data
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "Packages/com.baddog.rendering.arealight/Shaders/Include/BGAreaLightShadows.hlsl"
|
||||
|
||||
#if UNITY_VERSION < 60000000
|
||||
// This function assumes that inputs are well-behaved, e.i.
|
||||
// that the line does not pass through the origin and
|
||||
// that the light is (at least partially) above the surface.
|
||||
float I_diffuse_line(float3 C, float3 A, float hl)
|
||||
{
|
||||
// Solve C.z + h * A.z = 0.
|
||||
float h = -C.z * rcp(A.z); // May be Inf, but never NaN
|
||||
|
||||
// Clip the line segment against the z-plane if necessary.
|
||||
float h2 = (A.z >= 0) ? max( hl, h)
|
||||
: min( hl, h); // P2 = C + h2 * A
|
||||
float h1 = (A.z >= 0) ? max(-hl, h)
|
||||
: min(-hl, h); // P1 = C + h1 * A
|
||||
|
||||
// Normalize the tangent.
|
||||
float as = dot(A, A); // |A|^2
|
||||
float ar = rsqrt(as); // 1/|A|
|
||||
float a = as * ar; // |A|
|
||||
float3 T = A * ar; // A/|A|
|
||||
|
||||
// Orthogonal 2D coordinates:
|
||||
// P(n, t) = n * N + t * T.
|
||||
float tc = dot(T, C); // C = n * N + tc * T
|
||||
float3 P0 = C - tc * T; // P(n, 0) = n * N
|
||||
float ns = dot(P0, P0); // |P0|^2
|
||||
|
||||
float nr = rsqrt(ns); // 1/|P0|
|
||||
float n = ns * nr; // |P0|
|
||||
float Nz = P0.z * nr; // N.z = P0.z/|P0|
|
||||
|
||||
// P(n, t) - C = P0 + t * T - P0 - tc * T
|
||||
// = (t - tc) * T = h * A = (h * a) * T.
|
||||
float t2 = tc + h2 * a; // P2.t
|
||||
float t1 = tc + h1 * a; // P1.t
|
||||
float s2 = ns + t2 * t2; // |P2|^2
|
||||
float s1 = ns + t1 * t1; // |P1|^2
|
||||
float mr = rsqrt(s1 * s2); // 1/(|P1|*|P2|)
|
||||
float r2 = s1 * (mr * mr); // 1/|P2|^2
|
||||
float r1 = s2 * (mr * mr); // 1/|P1|^2
|
||||
|
||||
// I = (i1 + i2 + i3) / Pi.
|
||||
// i1 = N.z * (P2.t / |P2|^2 - P1.t / |P1|^2).
|
||||
// i2 = -T.z * (P2.n / |P2|^2 - P1.n / |P1|^2).
|
||||
// i3 = N.z * ArcCos[Dot[P1, P2] / (|P1| * |P2|)] / |P0|.
|
||||
float i12 = (Nz * t2 - (T.z * n)) * r2
|
||||
- (Nz * t1 - (T.z * n)) * r1;
|
||||
// Guard against numerical errors.
|
||||
float dt = min(1, (ns + t1 * t2) * mr);
|
||||
float i3 = acos(dt) * (Nz * nr); // angle * cos(θ) / r^2
|
||||
|
||||
// Guard against numerical errors.
|
||||
return INV_PI * max(0, i12 + i3);
|
||||
}
|
||||
|
||||
// A hack to smoothly limit the influence of the light to the interior of a pillow.
|
||||
// A "pillow" (for the lack of a better name) is formed by sweeping a ball across a rectangle.
|
||||
// This function behaves like CapsuleAttenuation() for a narrow rectangle.
|
||||
// This function behaves like SmoothWindowedDistanceAttenuation() for a small rectangle.
|
||||
// Convention: the surface point is located at the origin of the coordinate system.
|
||||
real PillowWindowing(real3 center, real3 xAxis, real3 yAxis, real halfLength, real halfHeight,
|
||||
real rangeAttenuationScale, real rangeAttenuationBias)
|
||||
{
|
||||
// Conceptually, the idea is very simple: after taking the symmetry
|
||||
// of the pillow into account, it is clear that the problem can be
|
||||
// reduced to finding the closest sphere inside the pillow.
|
||||
// We begin our search at the center of the pillow, and then translate
|
||||
// this point along and across the plane of symmetry until we either
|
||||
// a) find the closest point on the plane, or b) hit an edge of the rectangle.
|
||||
// The problem is simplified by working in the coordinate system of the pillow.
|
||||
real x = dot(center, xAxis); // -x, strictly speaking
|
||||
real dx = max(0, abs(x) - halfLength);
|
||||
real y = dot(center, yAxis); // -y, strictly speaking
|
||||
real dy = max(0, abs(y) - halfHeight);
|
||||
real r2 = dot(center, center); // r^2
|
||||
real z2 = max(0, r2 - x * x - y * y); // z^2
|
||||
real d2 = z2 + dx * dx + dy * dy; // Squared distance to the center of the closest sphere
|
||||
|
||||
return SmoothDistanceWindowing(d2, rangeAttenuationScale, rangeAttenuationBias);
|
||||
}
|
||||
#endif
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Area Light Shadow Sampling (moved to BGAreaLightShadows.hlsl)
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// Get area light data for a specific additional light index (per-object order)
|
||||
BGAreaLightData GetAreaLightData(uint lightIndex)
|
||||
{
|
||||
return _AreaLightDataBuffer[lightIndex];
|
||||
}
|
||||
|
||||
// Legacy helper for visible light indexing (kept for compatibility)
|
||||
BGAreaLightData GetAreaLightDataFromRealIndex(uint realLightIndex)
|
||||
{
|
||||
return _AreaLightDataBuffer[realLightIndex];
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Area Light Evaluation
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// Evaluates area light using LTC approximation
|
||||
// isRectLight: true for rectangular light, false for line/tube light
|
||||
// center: Light center position in local coordinate system (shaded point at origin)
|
||||
// right: Right vector of the light in local coordinate system
|
||||
// up: Up vector of the light in local coordinate system
|
||||
// halfLength: Half-length of the light along the right axis
|
||||
// halfHeight: Half-height of the light along the up axis (0 for line lights)
|
||||
// invM: Inverse LTC transformation matrix (transposed)
|
||||
// perceptualRoughness: Material perceptual roughness [0, 1]
|
||||
// Returns: float4 where rgb = color (1,1,1), a = irradiance
|
||||
float4 EvaluateLTC_Area(bool isRectLight, float3 center, float3 right, float3 up,
|
||||
float halfLength, float halfHeight,
|
||||
float3x3 invM, float perceptualRoughness)
|
||||
{
|
||||
float3 ortho = cross(center, right);
|
||||
float orthoSq = dot(ortho, ortho);
|
||||
|
||||
// Check whether the light is in a vertical orientation
|
||||
bool quit = (orthoSq == 0);
|
||||
|
||||
// Check whether the light is entirely below the surface
|
||||
// We must test twice, since a linear transformation
|
||||
// may bring the light above the surface (a side-effect)
|
||||
quit = quit || (center.z + halfLength * abs(right.z) + halfHeight * abs(up.z) <= 0);
|
||||
|
||||
float4 ltcValue = float4(1, 1, 1, 0);
|
||||
|
||||
if (!quit)
|
||||
{
|
||||
// Perform sparse matrix multiplication
|
||||
float3 C = mul(invM, center);
|
||||
float3 A = mul(invM, right);
|
||||
float3 B = mul(invM, up);
|
||||
|
||||
// Check whether the light is entirely below the surface after transformation
|
||||
if (C.z + halfLength * abs(A.z) + halfHeight * abs(B.z) > 0)
|
||||
{
|
||||
if (isRectLight)
|
||||
{
|
||||
// Transform the rectangular light vertices
|
||||
float4x3 lightVerts;
|
||||
lightVerts[0] = C - halfLength * A - halfHeight * B; // LL
|
||||
lightVerts[1] = lightVerts[0] + (2.0 * halfHeight) * B; // UL
|
||||
lightVerts[2] = lightVerts[1] + (2.0 * halfLength) * A; // UR
|
||||
lightVerts[3] = lightVerts[2] - (2.0 * halfHeight) * B; // LR
|
||||
|
||||
// Compute polygon irradiance in the transformed configuration
|
||||
#if UNITY_VERSION >= 60000000
|
||||
float3 formFactor;
|
||||
ltcValue.a = PolygonIrradiance(lightVerts, formFactor);
|
||||
#else
|
||||
ltcValue.a = PolygonIrradiance(lightVerts);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
// Line light evaluation
|
||||
#if UNITY_VERSION >= 60000000
|
||||
float w = ComputeLineWidthFactor(invM, ortho, orthoSq);
|
||||
#else
|
||||
float w = ComputeLineWidthFactor(invM, ortho);
|
||||
#endif
|
||||
ltcValue.a = I_diffuse_line(C, A, halfLength) * w;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ltcValue;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// PreLightData Initialization
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// Initialize PreLightData for area light evaluation
|
||||
BGPreLightData GetAreaLightPreLightData(
|
||||
float3 V, float3 N,
|
||||
float perceptualRoughness, float3 fresnel0,
|
||||
uint bsdfModelDiffuse, uint bsdfModelSpecular)
|
||||
{
|
||||
BGPreLightData preLightData;
|
||||
|
||||
// Initialize all fields to zero
|
||||
preLightData.NdotV = 0.0;
|
||||
preLightData.specularFGD = float3(0, 0, 0);
|
||||
preLightData.diffuseFGD = 0.0;
|
||||
preLightData.reflectivity = 0.0;
|
||||
preLightData.energyCompensation = 0.0;
|
||||
preLightData.orthoBasisViewNormal = (float3x3)0;
|
||||
preLightData.ltcTransformDiffuse = (float3x3)0;
|
||||
preLightData.ltcTransformSpecular = (float3x3)0;
|
||||
|
||||
// Compute NdotV
|
||||
preLightData.NdotV = dot(N, V);
|
||||
float clampedNdotV = ClampNdotV(preLightData.NdotV);
|
||||
|
||||
// Compute pre-integrated FGD (Fresnel, Geometric, Diffuse) terms
|
||||
float specularReflectivity;
|
||||
#ifdef USE_DIFFUSE_LAMBERT_BRDF
|
||||
// Use Lambertian diffuse FGD (diffuseFGD = 1.0)
|
||||
GetPreIntegratedFGDGGXAndLambert(
|
||||
clampedNdotV,
|
||||
perceptualRoughness,
|
||||
fresnel0,
|
||||
preLightData.specularFGD,
|
||||
preLightData.diffuseFGD,
|
||||
preLightData.reflectivity
|
||||
);
|
||||
specularReflectivity = preLightData.reflectivity;
|
||||
#else
|
||||
// Use Disney Diffuse FGD
|
||||
GetPreIntegratedFGDGGXAndDisneyDiffuse(
|
||||
clampedNdotV,
|
||||
perceptualRoughness,
|
||||
fresnel0,
|
||||
1,
|
||||
preLightData.specularFGD,
|
||||
preLightData.diffuseFGD,
|
||||
preLightData.reflectivity
|
||||
);
|
||||
specularReflectivity = preLightData.reflectivity;
|
||||
#endif
|
||||
|
||||
// Compute energy compensation for multiscattering
|
||||
preLightData.energyCompensation = (specularReflectivity > 0.0) ? (1.0 / specularReflectivity - 1.0) : 0.0;
|
||||
|
||||
// Build orthogonal basis around normal (view-dependent)
|
||||
preLightData.orthoBasisViewNormal = GetOrthoBasisViewNormal(V, N, preLightData.NdotV);
|
||||
|
||||
// Sample LTC matrices for diffuse
|
||||
// HDRP style: Use compile-time macro to choose between Lambertian and other BRDF models
|
||||
#ifdef USE_DIFFUSE_LAMBERT_BRDF
|
||||
// Use identity matrix for Lambertian (no texture sampling)
|
||||
preLightData.ltcTransformDiffuse = k_identity3x3;
|
||||
#else
|
||||
// Sample LTC matrix for the specified BRDF model (e.g., Disney Diffuse)
|
||||
preLightData.ltcTransformDiffuse = SampleLtcMatrix(perceptualRoughness, clampedNdotV, bsdfModelDiffuse);
|
||||
#endif
|
||||
|
||||
// Sample LTC matrix for specular
|
||||
preLightData.ltcTransformSpecular = SampleLtcMatrix(perceptualRoughness, clampedNdotV, bsdfModelSpecular);
|
||||
|
||||
return preLightData;
|
||||
}
|
||||
|
||||
// Area light lighting function compatible with URP's LightingPhysicallyBased style
|
||||
// This function can be used in URP's additional lights loop
|
||||
half3 LightingPhysicallyBasedAreaLight(
|
||||
BGAreaLightData areaLightData,
|
||||
BGPreLightData preLightData,
|
||||
float3 positionWS,
|
||||
half3 normalWS,
|
||||
half3 viewDirectionWS,
|
||||
half3 brdfDiffuse,
|
||||
half3 brdfSpecular,
|
||||
bool specularHighlightsOff)
|
||||
{
|
||||
// Step 1: Calculate light-to-surface vector and half dimensions
|
||||
float3 unL = areaLightData.positionWS.xyz - positionWS;
|
||||
float halfLength = areaLightData.size.x * 0.5;
|
||||
float halfHeight = areaLightData.size.y * 0.5;
|
||||
bool isRectLight = areaLightData.rangeParams.w > 0.5;
|
||||
|
||||
// Step 2: Check if light is front-facing (for rectangular lights only)
|
||||
// Light forward points in the direction the light is facing (light emission direction)
|
||||
// If dot(forward, unL) < 0, the point is behind the light (not illuminated)
|
||||
if (isRectLight && dot(areaLightData.forward.xyz, unL) >= 0)
|
||||
return half3(0, 0, 0);
|
||||
|
||||
// Step 3: Calculate intensity attenuation (matching HDRP implementation)
|
||||
// HDRP uses PillowWindowing for all area lights (rect and line)
|
||||
// For line lights, halfHeight = 0, so PillowWindowing degrades to CapsuleWindowing behavior
|
||||
float intensity = PillowWindowing(unL, areaLightData.right.xyz, areaLightData.up.xyz,
|
||||
halfLength, halfHeight,
|
||||
areaLightData.rangeParams.y, // rangeAttenuationScale
|
||||
areaLightData.rangeParams.z); // rangeAttenuationBias
|
||||
|
||||
// Early exit if light is not visible
|
||||
if (intensity <= 0)
|
||||
return half3(0, 0, 0);
|
||||
|
||||
// Step 4: Transform light vectors into local coordinate system (using pre-computed basis)
|
||||
float3 center = mul(preLightData.orthoBasisViewNormal, unL);
|
||||
float3 right = mul(preLightData.orthoBasisViewNormal, areaLightData.right.xyz);
|
||||
float3 up = mul(preLightData.orthoBasisViewNormal, areaLightData.up.xyz);
|
||||
|
||||
// Step 5: Evaluate diffuse part (using pre-computed LTC matrix)
|
||||
float4 ltcDiffuse = EvaluateLTC_Area(isRectLight,
|
||||
center, right, up,
|
||||
halfLength, halfHeight,
|
||||
transpose(preLightData.ltcTransformDiffuse),
|
||||
1.0); // Roughness = 1 for diffuse
|
||||
|
||||
// Step 6: Evaluate specular part (using pre-computed LTC matrix)
|
||||
half3 lighting = half3(0, 0, 0);
|
||||
|
||||
// Diffuse contribution (FGD from preLightData)
|
||||
lighting += brdfDiffuse * ltcDiffuse.a * preLightData.diffuseFGD;
|
||||
|
||||
// Specular contribution (FGD from preLightData)
|
||||
half3 specularLighting = half3(0, 0, 0);
|
||||
#ifndef _SPECULARHIGHLIGHTS_OFF
|
||||
[branch] if (!specularHighlightsOff)
|
||||
{
|
||||
float4 ltcSpecular = EvaluateLTC_Area(isRectLight,
|
||||
center, right, up,
|
||||
halfLength, halfHeight,
|
||||
transpose(preLightData.ltcTransformSpecular),
|
||||
1.0); // Roughness is handled by LTC matrix
|
||||
|
||||
specularLighting = brdfSpecular * preLightData.specularFGD * ltcSpecular.a;
|
||||
|
||||
// Apply energy compensation for multiscattering (matching HDRP)
|
||||
specularLighting *= (1.0 + preLightData.energyCompensation);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Combine diffuse and specular
|
||||
lighting += specularLighting;
|
||||
|
||||
// Step 7: Apply intensity and light color
|
||||
lighting *= intensity * areaLightData.colorIntensity.rgb * areaLightData.colorIntensity.w;
|
||||
|
||||
return lighting;
|
||||
}
|
||||
|
||||
half3 LightingPhysicallyBasedAreaLight(
|
||||
BGAreaLightData areaLightData,
|
||||
BGPreLightData preLightData,
|
||||
float3 positionWS,
|
||||
half3 normalWS,
|
||||
half3 viewDirectionWS,
|
||||
half3 brdfDiffuse,
|
||||
half3 brdfSpecular)
|
||||
{
|
||||
return LightingPhysicallyBasedAreaLight(areaLightData, preLightData, positionWS,
|
||||
normalWS, viewDirectionWS,
|
||||
brdfDiffuse, brdfSpecular, false);
|
||||
}
|
||||
|
||||
half3 LightingPhysicallyBasedAreaLightWithShadow(
|
||||
uint areaLightIndex,
|
||||
BGAreaLightData areaLightData,
|
||||
BGPreLightData preLightData,
|
||||
float3 positionWS,
|
||||
half3 normalWS,
|
||||
half3 viewDirectionWS,
|
||||
half3 brdfDiffuse,
|
||||
half3 brdfSpecular,
|
||||
bool specularHighlightsOff)
|
||||
{
|
||||
// Calculate base lighting (without shadow)
|
||||
half3 lighting = LightingPhysicallyBasedAreaLight(
|
||||
areaLightData, preLightData, positionWS,
|
||||
normalWS, viewDirectionWS,
|
||||
brdfDiffuse, brdfSpecular, specularHighlightsOff);
|
||||
|
||||
// Apply shadow attenuation
|
||||
half shadowAttenuation = SampleAreaLightShadow(areaLightIndex, positionWS);
|
||||
lighting *= shadowAttenuation;
|
||||
|
||||
return lighting;
|
||||
}
|
||||
|
||||
half3 LightingPhysicallyBasedAreaLightWithShadow(
|
||||
uint areaLightIndex,
|
||||
BGAreaLightData areaLightData,
|
||||
BGPreLightData preLightData,
|
||||
float3 positionWS,
|
||||
half3 normalWS,
|
||||
half3 viewDirectionWS,
|
||||
half3 brdfDiffuse,
|
||||
half3 brdfSpecular)
|
||||
{
|
||||
return LightingPhysicallyBasedAreaLightWithShadow(
|
||||
areaLightIndex, areaLightData, preLightData, positionWS,
|
||||
normalWS, viewDirectionWS,
|
||||
brdfDiffuse, brdfSpecular, false);
|
||||
}
|
||||
|
||||
half3 EvaluateAreaLight(
|
||||
float3 positionWS,
|
||||
half3 normalWS,
|
||||
half3 viewDirectionWS,
|
||||
half3 brdfDiffuse,
|
||||
half3 brdfSpecular,
|
||||
float perceptualRoughness,
|
||||
float3 fresnel0,
|
||||
bool specularHighlightsOff)
|
||||
{
|
||||
half3 totalLighting = half3(0, 0, 0);
|
||||
|
||||
if (_AreaLightDataBufferCount <= 0)
|
||||
{
|
||||
return totalLighting;
|
||||
}
|
||||
|
||||
uint bsdfModelDiffuse = LTCLIGHTINGMODEL_DISNEY_DIFFUSE;
|
||||
uint bsdfModelSpecular = LTCLIGHTINGMODEL_GGX;
|
||||
|
||||
BGPreLightData preLightData = GetAreaLightPreLightData(
|
||||
viewDirectionWS,
|
||||
normalWS,
|
||||
perceptualRoughness,
|
||||
fresnel0,
|
||||
bsdfModelDiffuse,
|
||||
bsdfModelSpecular
|
||||
);
|
||||
|
||||
#ifdef _LIGHT_LAYERS
|
||||
uint meshRenderingLayers = GetMeshRenderingLayer();
|
||||
#endif
|
||||
|
||||
for (uint i = 0; i < (uint)_AreaLightDataBufferCount; ++i)
|
||||
{
|
||||
BGAreaLightData areaLightData = GetAreaLightData(i);
|
||||
|
||||
#ifdef _LIGHT_LAYERS
|
||||
uint lightLayerMask = asuint(areaLightData.renderingLayerMask.x);
|
||||
if (!IsMatchingLightLayer(lightLayerMask, meshRenderingLayers))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
|
||||
half3 lightContribution = LightingPhysicallyBasedAreaLightWithShadow(
|
||||
i, // areaLightIndex
|
||||
areaLightData,
|
||||
preLightData,
|
||||
positionWS,
|
||||
normalWS,
|
||||
viewDirectionWS,
|
||||
brdfDiffuse,
|
||||
brdfSpecular,
|
||||
specularHighlightsOff);
|
||||
|
||||
totalLighting += lightContribution;
|
||||
}
|
||||
|
||||
return totalLighting;
|
||||
}
|
||||
|
||||
half3 EvaluateAreaLight(
|
||||
float3 positionWS,
|
||||
half3 normalWS,
|
||||
half3 viewDirectionWS,
|
||||
half3 brdfDiffuse,
|
||||
half3 brdfSpecular,
|
||||
float perceptualRoughness,
|
||||
float3 fresnel0)
|
||||
{
|
||||
return EvaluateAreaLight(
|
||||
positionWS, normalWS, viewDirectionWS,
|
||||
brdfDiffuse, brdfSpecular,
|
||||
perceptualRoughness, fresnel0,
|
||||
false);
|
||||
}
|
||||
|
||||
half3 EvaluateAreaLight(InputData inputData, SurfaceData surfaceData)
|
||||
{
|
||||
BRDFData brdfData;
|
||||
InitializeBRDFData(surfaceData, brdfData);
|
||||
|
||||
float perceptualRoughness = PerceptualSmoothnessToPerceptualRoughness(surfaceData.smoothness);
|
||||
float3 fresnel0 = brdfData.specular;
|
||||
|
||||
// Check if specular highlights are disabled
|
||||
bool specularHighlightsOff = false;
|
||||
#if defined(_SPECULARHIGHLIGHTS_OFF)
|
||||
specularHighlightsOff = true;
|
||||
#endif
|
||||
|
||||
return EvaluateAreaLight(
|
||||
inputData.positionWS,
|
||||
inputData.normalWS,
|
||||
inputData.viewDirectionWS,
|
||||
brdfData.diffuse,
|
||||
brdfData.specular,
|
||||
perceptualRoughness,
|
||||
fresnel0,
|
||||
specularHighlightsOff);
|
||||
}
|
||||
|
||||
|
||||
#endif // BADDOG_AREA_LIGHTING_INCLUDED
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cdda5adc9fcf03f4185241ccc4e87b7e
|
||||
ShaderIncludeImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 346790
|
||||
packageName: Realtime Area Light for URP
|
||||
packageVersion: 1.3.0
|
||||
assetPath: Packages/com.baddog.rendering.arealight/Shaders/Include/BGAreaLighting.hlsl
|
||||
uploadId: 884030
|
||||
@@ -0,0 +1,293 @@
|
||||
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"
|
||||
|
||||
#define DISK_SAMPLE_COUNT 64
|
||||
// Fibonacci Spiral Disk Sampling Pattern
|
||||
// https://people.irisa.fr/Ricardo.Marques/articles/2013/SF_CGF.pdf
|
||||
//
|
||||
// Normalized direction vector portion of fibonacci spiral can be baked into a LUT, regardless of sampleCount.
|
||||
// This allows us to treat the directions as a progressive sequence, using any sampleCount in range [0, n <= LUT_LENGTH]
|
||||
// the radius portion of spiral construction is coupled to sample count, but is fairly cheap to compute at runtime per sample.
|
||||
// Generated (in javascript) with:
|
||||
// var res = "";
|
||||
// for (var i = 0; i < 64; ++i)
|
||||
// {
|
||||
// var a = Math.PI * (3.0 - Math.sqrt(5.0));
|
||||
// var b = a / (2.0 * Math.PI);
|
||||
// var c = i * b;
|
||||
// var theta = (c - Math.floor(c)) * 2.0 * Math.PI;
|
||||
// res += "float2 (" + Math.cos(theta) + ", " + Math.sin(theta) + "),\n";
|
||||
// }
|
||||
|
||||
static const float2 fibonacciSpiralDirection[DISK_SAMPLE_COUNT] =
|
||||
{
|
||||
float2 (1, 0),
|
||||
float2 (-0.7373688780783197, 0.6754902942615238),
|
||||
float2 (0.08742572471695988, -0.9961710408648278),
|
||||
float2 (0.6084388609788625, 0.793600751291696),
|
||||
float2 (-0.9847134853154288, -0.174181950379311),
|
||||
float2 (0.8437552948123969, -0.5367280526263233),
|
||||
float2 (-0.25960430490148884, 0.9657150743757782),
|
||||
float2 (-0.46090702471337114, -0.8874484292452536),
|
||||
float2 (0.9393212963241182, 0.3430386308741014),
|
||||
float2 (-0.924345556137805, 0.3815564084749356),
|
||||
float2 (0.423845995047909, -0.9057342725556143),
|
||||
float2 (0.29928386444487326, 0.9541641203078969),
|
||||
float2 (-0.8652112097532296, -0.501407581232427),
|
||||
float2 (0.9766757736281757, -0.21471942904125949),
|
||||
float2 (-0.5751294291397363, 0.8180624302199686),
|
||||
float2 (-0.12851068979899202, -0.9917081236973847),
|
||||
float2 (0.764648995456044, 0.6444469828838233),
|
||||
float2 (-0.9991460540072823, 0.04131782619737919),
|
||||
float2 (0.7088294143034162, -0.7053799411794157),
|
||||
float2 (-0.04619144594036213, 0.9989326054954552),
|
||||
float2 (-0.6407091449636957, -0.7677836880006569),
|
||||
float2 (0.9910694127331615, 0.1333469877603031),
|
||||
float2 (-0.8208583369658855, 0.5711318504807807),
|
||||
float2 (0.21948136924637865, -0.9756166914079191),
|
||||
float2 (0.4971808749652937, 0.8676469198750981),
|
||||
float2 (-0.952692777196691, -0.30393498034490235),
|
||||
float2 (0.9077911335843911, -0.4194225289437443),
|
||||
float2 (-0.38606108220444624, 0.9224732195609431),
|
||||
float2 (-0.338452279474802, -0.9409835569861519),
|
||||
float2 (0.8851894374032159, 0.4652307598491077),
|
||||
float2 (-0.9669700052147743, 0.25489019011123065),
|
||||
float2 (0.5408377383579945, -0.8411269468800827),
|
||||
float2 (0.16937617250387435, 0.9855514761735877),
|
||||
float2 (-0.7906231749427578, -0.6123030256690173),
|
||||
float2 (0.9965856744766464, -0.08256508601054027),
|
||||
float2 (-0.6790793464527829, 0.7340648753490806),
|
||||
float2 (0.0048782771634473775, -0.9999881011351668),
|
||||
float2 (0.6718851669348499, 0.7406553331023337),
|
||||
float2 (-0.9957327006438772, -0.09228428288961682),
|
||||
float2 (0.7965594417444921, -0.6045602168251754),
|
||||
float2 (-0.17898358311978044, 0.9838520605119474),
|
||||
float2 (-0.5326055939855515, -0.8463635632843003),
|
||||
float2 (0.9644371617105072, 0.26431224169867934),
|
||||
float2 (-0.8896863018294744, 0.4565723210368687),
|
||||
float2 (0.34761681873279826, -0.9376366819478048),
|
||||
float2 (0.3770426545691533, 0.9261958953890079),
|
||||
float2 (-0.9036558571074695, -0.4282593745796637),
|
||||
float2 (0.9556127564793071, -0.2946256262683552),
|
||||
float2 (-0.50562235513749, 0.8627549095688868),
|
||||
float2 (-0.2099523790012021, -0.9777116131824024),
|
||||
float2 (0.8152470554454873, 0.5791133210240138),
|
||||
float2 (-0.9923232342597708, 0.12367133357503751),
|
||||
float2 (0.6481694844288681, -0.7614961060013474),
|
||||
float2 (0.036443223183926, 0.9993357251114194),
|
||||
float2 (-0.7019136816142636, -0.7122620188966349),
|
||||
float2 (0.998695384655528, 0.05106396643179117),
|
||||
float2 (-0.7709001090366207, 0.6369560596205411),
|
||||
float2 (0.13818011236605823, -0.9904071165669719),
|
||||
float2 (0.5671206801804437, 0.8236347091470047),
|
||||
float2 (-0.9745343917253847, -0.22423808629319533),
|
||||
float2 (0.8700619819701214, -0.49294233692210304),
|
||||
float2 (-0.30857886328244405, 0.9511987621603146),
|
||||
float2 (-0.4149890815356195, -0.9098263912451776),
|
||||
float2 (0.9205789302157817, 0.3905565685566777)
|
||||
};
|
||||
|
||||
real2 ComputeFibonacciSpiralDiskSample(const in int sampleIndex, const in real diskRadius, const in real sampleCountInverse, const in real sampleCountBias)
|
||||
{
|
||||
real sampleRadius = diskRadius * sqrt((real)sampleIndex * sampleCountInverse + sampleCountBias);
|
||||
real2 sampleDirection = fibonacciSpiralDirection[sampleIndex];
|
||||
return sampleDirection * sampleRadius;
|
||||
}
|
||||
|
||||
real PenumbraSizePunctual(real Reciever, real Blocker)
|
||||
{
|
||||
return abs((Reciever - Blocker) / Blocker);
|
||||
}
|
||||
|
||||
real PenumbraSizeDirectional(real Reciever, real Blocker, real rangeScale)
|
||||
{
|
||||
return abs(Reciever - Blocker) * rangeScale;
|
||||
}
|
||||
|
||||
bool BlockerSearch(inout real averageBlockerDepth, inout real numBlockers, real lightArea, real3 coord, real UMin, real UMax, real VMin, real VMax, real2 sampleJitter, Texture2D shadowMap, SamplerState pointSampler, int sampleCount)
|
||||
{
|
||||
real blockerSum = 0.0;
|
||||
real sampleCountInverse = rcp((real)sampleCount);
|
||||
real sampleCountBias = 0.5 * sampleCountInverse;
|
||||
real ditherRotation = sampleJitter.x;
|
||||
|
||||
for (int i = 0; i < sampleCount && i < DISK_SAMPLE_COUNT; ++i)
|
||||
{
|
||||
real2 offset = ComputeFibonacciSpiralDiskSample(i, lightArea, sampleCountInverse, sampleCountBias);
|
||||
offset = real2(offset.x * sampleJitter.y + offset.y * sampleJitter.x,
|
||||
offset.x * -sampleJitter.x + offset.y * sampleJitter.y);
|
||||
|
||||
real U = coord.x + offset.x;
|
||||
real V = coord.y + offset.y;
|
||||
|
||||
//NOTE: We must clamp the sampling within the bounds of the shadow atlas.
|
||||
// Overfiltering will leak results from other shadow lights.
|
||||
if (U < UMin || U > UMax || V < VMin || V > VMax)
|
||||
{
|
||||
// Discard the sample (it is located outside the shadow map, and it may correspond to another cube map face).
|
||||
}
|
||||
else
|
||||
{
|
||||
real shadowMapDepth = SAMPLE_TEXTURE2D_LOD(shadowMap, pointSampler, float2(U, V), 0.0).x;
|
||||
|
||||
if (COMPARE_DEVICE_DEPTH_CLOSER(shadowMapDepth, coord.z))
|
||||
{
|
||||
blockerSum += shadowMapDepth;
|
||||
numBlockers += 1.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return the depth value of the far plane if none of the samples are valid
|
||||
averageBlockerDepth = (numBlockers > 0) ? (blockerSum / numBlockers) : UNITY_RAW_FAR_CLIP_VALUE;
|
||||
|
||||
return numBlockers > 0;
|
||||
}
|
||||
|
||||
real PCSS(real3 coord, real UMin, real UMax, real VMin, real VMax, real filterRadius, real2 sampleJitter, Texture2D shadowMap, SamplerComparisonState compSampler, int sampleCount)
|
||||
{
|
||||
real sum = 0.0;
|
||||
real sampleCountInverse = rcp((real)sampleCount);
|
||||
real sampleCountBias = 0.5 * sampleCountInverse;
|
||||
real ditherRotation = sampleJitter.x;
|
||||
|
||||
real numValidSamples = 0;
|
||||
|
||||
for (int i = 0; i < sampleCount && i < DISK_SAMPLE_COUNT; ++i)
|
||||
{
|
||||
real2 offset = ComputeFibonacciSpiralDiskSample(i, filterRadius, sampleCountInverse, sampleCountBias);
|
||||
offset = real2(offset.x * sampleJitter.y + offset.y * sampleJitter.x,
|
||||
offset.x * -sampleJitter.x + offset.y * sampleJitter.y);
|
||||
|
||||
real U = coord.x + offset.x;
|
||||
real V = coord.y + offset.y;
|
||||
|
||||
//NOTE: We must clamp the sampling within the bounds of the shadow atlas.
|
||||
// Overfiltering will leak results from other shadow lights.
|
||||
if (U < UMin || U > UMax || V < VMin || V > VMax)
|
||||
{
|
||||
// Discard the sample (it is located outside the shadow map, and it may correspond to another cube map face).
|
||||
}
|
||||
else
|
||||
{
|
||||
sum += SAMPLE_TEXTURE2D_SHADOW(shadowMap, compSampler, real3(U, V, coord.z)).r;
|
||||
numValidSamples += 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
// Return the unoccluded (unshadowed) value if none of the samples are valid
|
||||
return (numValidSamples > 0) ? (sum / numValidSamples) : 1.0;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////
|
||||
// PCSS variant for area lights
|
||||
|
||||
// Samples denser near the center - important for blocker search
|
||||
real2 ComputeFibonacciSpiralDiskSampleClumped(const in int sampleIndex, const in real sampleCountInverse, out real sampleDistNorm)
|
||||
{
|
||||
// Samples not biased away from the center - sample 0 at (0, 0) is important for blocker search near shadow contact points.
|
||||
sampleDistNorm = (real)sampleIndex * sampleCountInverse;
|
||||
|
||||
// Third power chosen arbitrarily - center area is really that much more important
|
||||
sampleDistNorm = sampleDistNorm * sampleDistNorm * sampleDistNorm;
|
||||
|
||||
return fibonacciSpiralDirection[sampleIndex] * sampleDistNorm;
|
||||
}
|
||||
|
||||
// Samples uniformly spread across the disk kernel
|
||||
real2 ComputeFibonacciSpiralDiskSampleUniform(const in int sampleIndex, const in real sampleCountInverse, const in real sampleBias, out real sampleDistNorm)
|
||||
{
|
||||
// Samples biased away from the center, so that sample 0 doesn't fall at (0, 0), or it will not be affected by sample jitter and create a visible edge.
|
||||
sampleDistNorm = (real)sampleIndex * sampleCountInverse + sampleBias;
|
||||
|
||||
// sqrt results in uniform distribution
|
||||
sampleDistNorm = sqrt(sampleDistNorm);
|
||||
|
||||
return fibonacciSpiralDirection[sampleIndex] * sampleDistNorm;
|
||||
}
|
||||
|
||||
void FilterScaleOffset(real3 coord, real maxSampleZDistance, real shadowmapSamplingScale, out real2 filterScalePos, out real2 filterScaleNeg, out real2 filterOffset)
|
||||
{
|
||||
real d = shadowmapSamplingScale * maxSampleZDistance / (1 - coord.z);
|
||||
real2 target = (coord.xy + 0.5) * 0.5;
|
||||
|
||||
filterScalePos = (1 - target) * d;
|
||||
filterScaleNeg = target * d;
|
||||
filterOffset = (target - coord.xy) * d;
|
||||
}
|
||||
|
||||
bool BlockerSearch_Area(inout real closestBlocker, real maxSampleZDistance, real2 shadowmapInAtlasScale, real2 posTCAtlas, real3 posTCShadowmap, real2 minCoord, real2 maxCoord, real2 sampleJitter, Texture2D shadowMap, SamplerState pointSampler, int sampleCount)
|
||||
{
|
||||
// The z extent of the filter cone shouldn't go beyond the near plane of the shadow
|
||||
#if UNITY_REVERSED_Z
|
||||
#define NEARPLANE 1
|
||||
maxSampleZDistance = min(1 - posTCShadowmap.z, maxSampleZDistance);
|
||||
#else
|
||||
#define NEARPLANE 0
|
||||
maxSampleZDistance = min(posTCShadowmap.z, maxSampleZDistance);
|
||||
#endif
|
||||
|
||||
real sampleCountInverse = rcp((real)sampleCount);
|
||||
|
||||
real2 filterScalePos, filterScaleNeg;
|
||||
real2 filterOffset;
|
||||
FilterScaleOffset(posTCShadowmap, maxSampleZDistance, shadowmapInAtlasScale.x, filterScalePos, filterScaleNeg, filterOffset);
|
||||
|
||||
closestBlocker = NEARPLANE;
|
||||
for (int i = 0; i < sampleCount && i < DISK_SAMPLE_COUNT; ++i)
|
||||
{
|
||||
real sampleDistNorm;
|
||||
real2 offset = ComputeFibonacciSpiralDiskSampleClumped(i, sampleCountInverse, sampleDistNorm);
|
||||
offset = real2(offset.x * sampleJitter.y + offset.y * sampleJitter.x,
|
||||
offset.x * -sampleJitter.x + offset.y * sampleJitter.y);
|
||||
|
||||
offset = offset * real2(offset.x > 0 ? filterScalePos.x : filterScaleNeg.x, offset.y > 0 ? filterScalePos.y : filterScaleNeg.y) + filterOffset * sampleDistNorm;
|
||||
real zoffset = maxSampleZDistance * sampleDistNorm;
|
||||
|
||||
real2 pos = posTCAtlas + offset;
|
||||
|
||||
real blocker = SAMPLE_TEXTURE2D_LOD(shadowMap, pointSampler, pos, 0.0).x;
|
||||
|
||||
if (!(any(pos < minCoord) || any(pos > maxCoord)) &&
|
||||
COMPARE_DEVICE_DEPTH_CLOSER(blocker, posTCShadowmap.z + zoffset) &&
|
||||
COMPARE_DEVICE_DEPTH_CLOSER(closestBlocker, blocker))
|
||||
{
|
||||
closestBlocker = blocker;
|
||||
}
|
||||
}
|
||||
|
||||
return COMPARE_DEVICE_DEPTH_CLOSER(NEARPLANE, closestBlocker);
|
||||
}
|
||||
|
||||
real PCSS_Area(real2 posTCAtlas, real3 posTCShadowmap, real maxSampleZDistance, real2 shadowmapInAtlasScale, real2 shadowmapInAtlasOffset, real2 minCoord, real2 maxCoord, real2 sampleJitter, Texture2D shadowMap, SamplerComparisonState compSampler, int sampleCount)
|
||||
{
|
||||
real biasFactor = 1;
|
||||
real sampleCountInverse = rcp((real)sampleCount + biasFactor);
|
||||
real sampleBias = biasFactor * sampleCountInverse;
|
||||
|
||||
real2 filterScalePos, filterScaleNeg;
|
||||
real2 filterOffset;
|
||||
FilterScaleOffset(posTCShadowmap, maxSampleZDistance, shadowmapInAtlasScale.x, filterScalePos, filterScaleNeg, filterOffset);
|
||||
|
||||
real sum = 0.0;
|
||||
for (int i = 0; i < sampleCount && i < DISK_SAMPLE_COUNT; ++i)
|
||||
{
|
||||
real sampleDistNorm;
|
||||
real2 offset = ComputeFibonacciSpiralDiskSampleUniform(i, sampleCountInverse, sampleBias, sampleDistNorm);
|
||||
offset = real2(offset.x * sampleJitter.y + offset.y * sampleJitter.x,
|
||||
offset.x * -sampleJitter.x + offset.y * sampleJitter.y);
|
||||
|
||||
offset = offset * real2(offset.x > 0 ? filterScalePos.x : filterScaleNeg.x, offset.y > 0 ? filterScalePos.y : filterScaleNeg.y) + filterOffset * sampleDistNorm;
|
||||
real zoffset = maxSampleZDistance * sampleDistNorm;
|
||||
|
||||
real3 pos = 0;
|
||||
pos.xy = posTCAtlas + offset;
|
||||
pos.z = posTCShadowmap.z + zoffset;
|
||||
|
||||
sum += (any(pos.xy < minCoord) || any(pos.xy > maxCoord)) ? 1 : SAMPLE_TEXTURE2D_SHADOW(shadowMap, compSampler, pos).r;
|
||||
}
|
||||
|
||||
return sum / sampleCount;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0f71b54072966be4f94964b8cd3f65ca
|
||||
ShaderIncludeImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 346790
|
||||
packageName: Realtime Area Light for URP
|
||||
packageVersion: 1.3.0
|
||||
assetPath: Packages/com.baddog.rendering.arealight/Shaders/Include/HDPCSS.hlsl
|
||||
uploadId: 884030
|
||||
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// This file was automatically generated. Please don't edit by hand. Execute Editor command [ Edit > Rendering > Generate Shader Includes ] instead
|
||||
//
|
||||
|
||||
#ifndef LTCAREALIGHT_CS_HLSL
|
||||
#define LTCAREALIGHT_CS_HLSL
|
||||
//
|
||||
// UnityEngine.Rendering.HighDefinition.LTCLightingModel: static fields
|
||||
//
|
||||
#define LTCLIGHTINGMODEL_GGX (0)
|
||||
#define LTCLIGHTINGMODEL_DISNEY_DIFFUSE (1)
|
||||
#define LTCLIGHTINGMODEL_CHARLIE (2)
|
||||
#define LTCLIGHTINGMODEL_KAJIYA_KAY_SPECULAR (3)
|
||||
#define LTCLIGHTINGMODEL_MARSCHNER (4)
|
||||
#define LTCLIGHTINGMODEL_COOK_TORRANCE (5)
|
||||
#define LTCLIGHTINGMODEL_WARD (6)
|
||||
#define LTCLIGHTINGMODEL_OREN_NAYAR (7)
|
||||
#define LTCLIGHTINGMODEL_COUNT (8)
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c092a617a3180384d9e8377e5bba8351
|
||||
ShaderIncludeImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 346790
|
||||
packageName: Realtime Area Light for URP
|
||||
packageVersion: 1.3.0
|
||||
assetPath: Packages/com.baddog.rendering.arealight/Shaders/Include/LTCAreaLight.cs.hlsl
|
||||
uploadId: 884030
|
||||
@@ -0,0 +1,75 @@
|
||||
// Area light textures
|
||||
TEXTURE2D_ARRAY(_LtcData); // We pack all Ltc data inside one texture array to limit the number of resource used
|
||||
|
||||
#include "Packages/com.baddog.rendering.arealight/Shaders/Include/LTCAreaLight.cs.hlsl"
|
||||
|
||||
#define LTC_LUT_SIZE (64)
|
||||
|
||||
// Approximate fit of BRDF with power for NdotL coefficient
|
||||
void ModifyLambertLTCTransformForDiffusePower(inout float3x3 ltcTransformDiffuse, float diffusePower)
|
||||
{
|
||||
// reminder: value is remapped to have 0 as neutral
|
||||
diffusePower = diffusePower + 1;
|
||||
|
||||
// To do this fitting, the ltcTransformDiffuse were outputed from C# by
|
||||
// - modifying the code in BRDF_Disney.cs to handle diffuse power
|
||||
// - uncommenting MenuItem in LTCTableGeneratorEditor.cs
|
||||
// - generating a few tables for various diffuse power values
|
||||
// Then each column of the table is fitted with respect to the diffuse power
|
||||
|
||||
float fitted = 0.26564f;
|
||||
float w = diffusePower - 1;
|
||||
ltcTransformDiffuse._m00 += w * fitted;
|
||||
ltcTransformDiffuse._m11 += w * fitted;
|
||||
}
|
||||
|
||||
// Approximate fit of BRDF with power for NdotL coefficient
|
||||
void ModifyDisneyLTCTransformForDiffusePower(inout float3x3 ltcTransformDiffuse, float diffusePower, float perceptualRoughness, float clampedNdotV)
|
||||
{
|
||||
// reminder: value is remapped to have 0 as neutral
|
||||
diffusePower = diffusePower + 1;
|
||||
|
||||
// To do this fitting, the ltcTransformDiffuse were outputed from C# by
|
||||
// - modifying the code in BRDF_Disney.cs to handle diffuse power
|
||||
// - uncommenting MenuItem in LTCTableGeneratorEditor.cs
|
||||
// - generating a few tables for various diffuse power values
|
||||
// Then each column of the table is fitted with respect to the diffuse power
|
||||
|
||||
// TODO: should revisit, can probably be made cheaper and more precise
|
||||
// When diffusePower > 3.5, fitting is not great but value is limited to range [1, 3] in the UI
|
||||
float w = sqrt(abs(diffusePower)) - 1;
|
||||
float w2 = sqrt(abs(diffusePower - 1));
|
||||
float x = perceptualRoughness;
|
||||
float y2 = 1 - clampedNdotV;
|
||||
float y = sqrt(y2);
|
||||
float y4 = y2 * y2;
|
||||
|
||||
float fitted = lerp(0.6039, 0.6588, x);
|
||||
|
||||
float c = lerp(0.0043359, 0.024585, x);
|
||||
float d = lerp(0.0, 0.012516, x);
|
||||
float fitted2 = y4*y4 * c + d * y;
|
||||
|
||||
float c2 = lerp(0.0039, 0.02, x);
|
||||
float d2 = lerp(0.0, 0.00705, x);
|
||||
float fitted3 = y4*y4 * c2 + d2 * y;
|
||||
|
||||
ltcTransformDiffuse._m00 += w * fitted;
|
||||
ltcTransformDiffuse._m02 -= w2 * fitted2;
|
||||
ltcTransformDiffuse._m11 += w * fitted;
|
||||
ltcTransformDiffuse._m20 += w2 * fitted3;
|
||||
}
|
||||
|
||||
// Fetches the transposed M^(-1) matrix need for runtime LTC evaluation.
|
||||
float3x3 SampleLtcMatrix(float perceptualRoughness, float clampedNdotV, uint bsdfIndex)
|
||||
{
|
||||
// sqrt(1 - cos(theta)) results in an approximately linear parametrization
|
||||
// that replaces an expensive acos() function with a simple sqrt().
|
||||
float2 uv = Remap01ToHalfTexelCoord(float2(perceptualRoughness, sqrt(1 - clampedNdotV)), LTC_LUT_SIZE);
|
||||
|
||||
float3x3 invM = 0;
|
||||
invM._m22 = 1;
|
||||
invM._m00_m02_m11_m20 = SAMPLE_TEXTURE2D_ARRAY_LOD(_LtcData, sampler_LinearClamp, uv, bsdfIndex, 0);
|
||||
|
||||
return invM;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 461a3082fb16a954da111509b2c670f5
|
||||
ShaderIncludeImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 346790
|
||||
packageName: Realtime Area Light for URP
|
||||
packageVersion: 1.3.0
|
||||
assetPath: Packages/com.baddog.rendering.arealight/Shaders/Include/LTCAreaLight.hlsl
|
||||
uploadId: 884030
|
||||
@@ -0,0 +1,81 @@
|
||||
#ifndef BADDOG_PREINTEGRATED_FGD_INCLUDED
|
||||
#define BADDOG_PREINTEGRATED_FGD_INCLUDED
|
||||
|
||||
#include "Packages/com.baddog.rendering.arealight/Shaders/PreIntegratedFGD.cs.hlsl"
|
||||
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"
|
||||
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/GlobalSamplers.hlsl"
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Pre-Integrated FGD Texture Declarations
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// Pre-integrated FGD (Fresnel, Geometric, Diffuse) textures
|
||||
// These textures store pre-computed BSDF integrals for efficient IBL and area light evaluation
|
||||
TEXTURE2D(_PreIntegratedFGD_GGXDisneyDiffuse);
|
||||
TEXTURE2D(_PreIntegratedFGD_CharlieAndFabric);
|
||||
TEXTURE2D(_PreIntegratedFGD_Marschner);
|
||||
|
||||
// Use URP's standard linear clamp sampler from GlobalSamplers.hlsl
|
||||
// sampler_LinearClamp is defined in GlobalSamplers.hlsl and can be used directly
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Pre-Integrated FGD Functions (GGX + Disney Diffuse)
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// For image based lighting, a part of the BSDF is pre-integrated.
|
||||
// This is done both for specular GGX height-correlated and DisneyDiffuse
|
||||
// reflectivity is Integral{(BSDF_GGX / F)} - use for multiscattering
|
||||
void GetPreIntegratedFGDGGXAndDisneyDiffuse(float NdotV, float perceptualRoughness, float3 fresnel0, float F90, out float3 specularFGD, out float diffuseFGD, out float reflectivity)
|
||||
{
|
||||
// We want the LUT to contain the entire [0, 1] range, without losing half a texel at each side.
|
||||
float2 coordLUT = Remap01ToHalfTexelCoord(float2(sqrt(NdotV), perceptualRoughness), FGDTEXTURE_RESOLUTION);
|
||||
|
||||
float3 preFGD = SAMPLE_TEXTURE2D_LOD(_PreIntegratedFGD_GGXDisneyDiffuse, sampler_LinearClamp, coordLUT, 0).xyz;
|
||||
|
||||
// Pre-integrate GGX FGD
|
||||
// Integral{BSDF * <N,L> dw} =
|
||||
// Integral{(F0 + (F90 - F0) * (1 - <V,H>)^5) * (BSDF / F) * <N,L> dw} =
|
||||
// (F90 - F0) * Integral{(1 - <V,H>)^5 * (BSDF / F) * <N,L> dw} + F0 * Integral{(BSDF / F) * <N,L> dw}=
|
||||
// (F90 - F0) * x + F0 * y
|
||||
specularFGD = (F90 - fresnel0) * preFGD.xxx + fresnel0 * preFGD.yyy;
|
||||
|
||||
// Pre integrate DisneyDiffuse FGD:
|
||||
// z = DisneyDiffuse
|
||||
// Remap from the [0, 1] to the [0.5, 1.5] range.
|
||||
diffuseFGD = preFGD.z + 0.5;
|
||||
|
||||
reflectivity = preFGD.y;
|
||||
}
|
||||
|
||||
// Overload without F90 parameter (defaults to 1.0)
|
||||
void GetPreIntegratedFGDGGXAndDisneyDiffuse(float NdotV, float perceptualRoughness, float3 fresnel0, out float3 specularFGD, out float diffuseFGD, out float reflectivity)
|
||||
{
|
||||
GetPreIntegratedFGDGGXAndDisneyDiffuse(NdotV, perceptualRoughness, fresnel0, 1.0, specularFGD, diffuseFGD, reflectivity);
|
||||
}
|
||||
|
||||
// GGX + Lambert variant (for USE_DIFFUSE_LAMBERT_BRDF)
|
||||
void GetPreIntegratedFGDGGXAndLambert(float NdotV, float perceptualRoughness, float3 fresnel0, out float3 specularFGD, out float diffuseFGD, out float reflectivity)
|
||||
{
|
||||
GetPreIntegratedFGDGGXAndDisneyDiffuse(NdotV, perceptualRoughness, fresnel0, specularFGD, diffuseFGD, reflectivity);
|
||||
diffuseFGD = 1.0;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Pre-Integrated FGD Functions (Charlie + Fabric Lambert)
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void GetPreIntegratedFGDCharlieAndFabricLambert(float NdotV, float perceptualRoughness, float3 fresnel0, out float3 specularFGD, out float diffuseFGD, out float reflectivity)
|
||||
{
|
||||
// Read the texture
|
||||
float3 preFGD = SAMPLE_TEXTURE2D_LOD(_PreIntegratedFGD_CharlieAndFabric, sampler_LinearClamp, float2(NdotV, perceptualRoughness), 0).xyz;
|
||||
|
||||
specularFGD = lerp(preFGD.xxx, preFGD.yyy, fresnel0) * 2.0 * PI;
|
||||
|
||||
// z = FabricLambert
|
||||
diffuseFGD = preFGD.z;
|
||||
|
||||
reflectivity = preFGD.y;
|
||||
}
|
||||
|
||||
#endif // BADDOG_PREINTEGRATED_FGD_INCLUDED
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5f3c66b7998a30f4398aa55c231750eb
|
||||
ShaderIncludeImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 346790
|
||||
packageName: Realtime Area Light for URP
|
||||
packageVersion: 1.3.0
|
||||
assetPath: Packages/com.baddog.rendering.arealight/Shaders/Include/PreIntegratedFGD.hlsl
|
||||
uploadId: 884030
|
||||
Reference in New Issue
Block a user