Add soft PCSS shadows on Ultra quality
Add new `SourceAngle` property to Directional light that controls PCSS penumbra size
This commit is contained in:
@@ -39,7 +39,7 @@ public:
|
|||||||
API_FIELD() static Quality VolumetricFogQuality;
|
API_FIELD() static Quality VolumetricFogQuality;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The shadows quality.
|
/// The shadows filtering quality (sampling).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
API_FIELD() static Quality ShadowsQuality;
|
API_FIELD() static Quality ShadowsQuality;
|
||||||
|
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ void DirectionalLight::Draw(RenderContext& renderContext)
|
|||||||
data.Cascade3Spacing = Cascade3Spacing;
|
data.Cascade3Spacing = Cascade3Spacing;
|
||||||
data.Cascade4Spacing = Cascade4Spacing;
|
data.Cascade4Spacing = Cascade4Spacing;
|
||||||
data.CascadeBlendSize = CascadeBlendSize;
|
data.CascadeBlendSize = CascadeBlendSize;
|
||||||
|
data.SourceAngle = Math::Tan(SourceAngle * DegreesToRadians * 0.5f);
|
||||||
data.PartitionMode = PartitionMode;
|
data.PartitionMode = PartitionMode;
|
||||||
data.ContactShadowsLength = ContactShadowsLength;
|
data.ContactShadowsLength = ContactShadowsLength;
|
||||||
data.StaticFlags = GetStaticFlags();
|
data.StaticFlags = GetStaticFlags();
|
||||||
@@ -65,6 +66,7 @@ void DirectionalLight::Serialize(SerializeStream& stream, const void* otherObj)
|
|||||||
|
|
||||||
SERIALIZE_GET_OTHER_OBJ(DirectionalLight);
|
SERIALIZE_GET_OTHER_OBJ(DirectionalLight);
|
||||||
|
|
||||||
|
SERIALIZE(SourceAngle);
|
||||||
SERIALIZE(CascadeCount);
|
SERIALIZE(CascadeCount);
|
||||||
SERIALIZE(Cascade1Spacing);
|
SERIALIZE(Cascade1Spacing);
|
||||||
SERIALIZE(Cascade2Spacing);
|
SERIALIZE(Cascade2Spacing);
|
||||||
@@ -79,6 +81,7 @@ void DirectionalLight::Deserialize(DeserializeStream& stream, ISerializeModifier
|
|||||||
// Base
|
// Base
|
||||||
LightWithShadow::Deserialize(stream, modifier);
|
LightWithShadow::Deserialize(stream, modifier);
|
||||||
|
|
||||||
|
DESERIALIZE(SourceAngle);
|
||||||
DESERIALIZE(CascadeCount);
|
DESERIALIZE(CascadeCount);
|
||||||
DESERIALIZE(Cascade1Spacing);
|
DESERIALIZE(Cascade1Spacing);
|
||||||
DESERIALIZE(Cascade2Spacing);
|
DESERIALIZE(Cascade2Spacing);
|
||||||
|
|||||||
@@ -12,6 +12,12 @@ class FLAXENGINE_API DirectionalLight : public LightWithShadow
|
|||||||
{
|
{
|
||||||
DECLARE_SCENE_OBJECT(DirectionalLight);
|
DECLARE_SCENE_OBJECT(DirectionalLight);
|
||||||
public:
|
public:
|
||||||
|
/// <summary>
|
||||||
|
/// Light source angle (in degrees) that defines its angular diameter. Higher values produce softer shadows. The default value is 0.5357 degrees, which is the angular diameter of the Sun as seen from Earth. Set this to 0 to produce hard shadows with sharp edges.
|
||||||
|
/// </summary>
|
||||||
|
API_FIELD(Attributes = "EditorOrder(41), EditorDisplay(\"Light\"), Limit(0, 4, 0.001f), ValueCategory(Utils.ValueCategory.Angle)")
|
||||||
|
float SourceAngle = 0.5332f;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The partitioning mode for the shadow cascades.
|
/// The partitioning mode for the shadow cascades.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ void RenderDirectionalLightData::SetShaderData(ShaderLightData& data, bool useSh
|
|||||||
{
|
{
|
||||||
data.SpotAngles.X = -2.0f;
|
data.SpotAngles.X = -2.0f;
|
||||||
data.SpotAngles.Y = 1.0f;
|
data.SpotAngles.Y = 1.0f;
|
||||||
data.SourceRadius = 0;
|
data.SourceRadius = SourceAngle;
|
||||||
data.SourceLength = 0;
|
data.SourceLength = 0;
|
||||||
data.Color = Color;
|
data.Color = Color;
|
||||||
data.MinRoughness = Math::Max(MinRoughness, MIN_ROUGHNESS);
|
data.MinRoughness = Math::Max(MinRoughness, MIN_ROUGHNESS);
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ struct RenderDirectionalLightData : RenderLightData
|
|||||||
PartitionMode PartitionMode;
|
PartitionMode PartitionMode;
|
||||||
int32 CascadeCount;
|
int32 CascadeCount;
|
||||||
float CascadeBlendSize;
|
float CascadeBlendSize;
|
||||||
|
float SourceAngle;
|
||||||
|
|
||||||
RenderDirectionalLightData()
|
RenderDirectionalLightData()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -56,12 +56,16 @@ float2 PerlinNoiseFade(float2 t)
|
|||||||
|
|
||||||
// "Next Generation Post Processing in Call of Duty: Advanced Warfare"
|
// "Next Generation Post Processing in Call of Duty: Advanced Warfare"
|
||||||
// http://advances.realtimerendering.com/s2014/index.html
|
// http://advances.realtimerendering.com/s2014/index.html
|
||||||
|
float InterleavedGradientNoise(float2 uv)
|
||||||
|
{
|
||||||
|
float3 magic = float3(0.06711056f, 0.00583715f, 52.9829189f);
|
||||||
|
return frac(magic.z * frac(dot(uv, magic.xy)));
|
||||||
|
}
|
||||||
float InterleavedGradientNoise(float2 uv, uint frameCount)
|
float InterleavedGradientNoise(float2 uv, uint frameCount)
|
||||||
{
|
{
|
||||||
const float2 magicFrameScale = float2(47, 17) * 0.695;
|
const float2 magicFrameScale = float2(47, 17) * 0.695;
|
||||||
uv += frameCount * magicFrameScale;
|
uv += frameCount * magicFrameScale;
|
||||||
const float3 magic = float3(0.06711056, 0.00583715, 52.9829189);
|
return InterleavedGradientNoise(uv);
|
||||||
return frac(magic.z * frac(dot(uv, magic.xy)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Removes error from the color to properly store it in lower precision formats (error = 2^(-mantissaBits))
|
// Removes error from the color to properly store it in lower precision formats (error = 2^(-mantissaBits))
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ float RandN1(float n)
|
|||||||
{
|
{
|
||||||
return frac(sin(n) * 43758.5453123);
|
return frac(sin(n) * 43758.5453123);
|
||||||
}
|
}
|
||||||
|
float RandN1(float2 n)
|
||||||
|
{
|
||||||
|
return RandN1(dot(n, float2(12.9898, 78.233)));
|
||||||
|
}
|
||||||
|
|
||||||
// Generic noise (2-components)
|
// Generic noise (2-components)
|
||||||
float2 RandN2(float2 n)
|
float2 RandN2(float2 n)
|
||||||
|
|||||||
@@ -9,11 +9,14 @@
|
|||||||
#ifndef SHADOWS_CSM_DITHERING
|
#ifndef SHADOWS_CSM_DITHERING
|
||||||
#define SHADOWS_CSM_DITHERING 0
|
#define SHADOWS_CSM_DITHERING 0
|
||||||
#endif
|
#endif
|
||||||
|
#ifndef SHADOWS_PCSS
|
||||||
|
#define SHADOWS_PCSS (SHADOWS_QUALITY >= 3)
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "./Flax/ShadowsCommon.hlsl"
|
#include "./Flax/ShadowsCommon.hlsl"
|
||||||
#include "./Flax/GBufferCommon.hlsl"
|
#include "./Flax/GBufferCommon.hlsl"
|
||||||
#include "./Flax/LightingCommon.hlsl"
|
#include "./Flax/LightingCommon.hlsl"
|
||||||
#if SHADOWS_CSM_DITHERING
|
#if SHADOWS_CSM_DITHERING || SHADOWS_PCSS
|
||||||
#include "./Flax/Random.hlsl"
|
#include "./Flax/Random.hlsl"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -207,6 +210,148 @@ float SampleShadowMapOptimizedPCF(Texture2D<float> shadowMap, float2 shadowMapUV
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if SHADOWS_PCSS
|
||||||
|
|
||||||
|
// "Vogel disk" sampling pattern: https://github.com/corporateshark/poisson-disk-generator
|
||||||
|
#define SHADOWS_PCSS_SAMPLES 32
|
||||||
|
static const half2 VogelPoints[SHADOWS_PCSS_SAMPLES] = {
|
||||||
|
#if SHADOWS_PCSS_SAMPLES == 4
|
||||||
|
// 4 samples
|
||||||
|
half2(0.353553, 0.000000),
|
||||||
|
half2(-0.451560, 0.413635),
|
||||||
|
half2(0.069174, -0.787537),
|
||||||
|
half2(0.569060, 0.742409),
|
||||||
|
#elif SHADOWS_PCSS_SAMPLES == 8
|
||||||
|
// 8 samples
|
||||||
|
half2(0.25000000, 0.00000000),
|
||||||
|
half2(-0.31930089, 0.29248416),
|
||||||
|
half2(0.04891348, -0.55687296),
|
||||||
|
half2(0.40238643, 0.52496207),
|
||||||
|
half2(-0.73851585, -0.13074535),
|
||||||
|
half2(0.69968677, -0.44490278),
|
||||||
|
half2(-0.23419666, 0.87043202),
|
||||||
|
half2(-0.44604915, -0.85938364),
|
||||||
|
#elif SHADOWS_PCSS_SAMPLES == 16
|
||||||
|
// 16 samples
|
||||||
|
half2(0.17677665, 0.00000000),
|
||||||
|
half2(-0.22577983, 0.20681751),
|
||||||
|
half2(0.03458714, -0.39376867),
|
||||||
|
half2(0.28453016, 0.37120426),
|
||||||
|
half2(-0.52220953, -0.09245092),
|
||||||
|
half2(0.49475324, -0.31459379),
|
||||||
|
half2(-0.16560209, 0.61548841),
|
||||||
|
half2(-0.31540442, -0.60767603),
|
||||||
|
half2(0.68456841, 0.25023210),
|
||||||
|
half2(-0.71235347, 0.29377294),
|
||||||
|
half2(0.34362423, -0.73360229),
|
||||||
|
half2(0.25340176, 0.80903494),
|
||||||
|
half2(-0.76454973, -0.44352412),
|
||||||
|
half2(0.89722824, -0.19680285),
|
||||||
|
half2(-0.54790950, 0.77848911),
|
||||||
|
half2(-0.12594837, -0.97615927),
|
||||||
|
#elif SHADOWS_PCSS_SAMPLES == 32
|
||||||
|
// 32 samples
|
||||||
|
half2(0.12500000, 0.00000000),
|
||||||
|
half2(-0.15965044, 0.14624202),
|
||||||
|
half2(0.02445674, -0.27843648),
|
||||||
|
half2(0.20119321, 0.26248097),
|
||||||
|
half2(-0.36925793, -0.06537271),
|
||||||
|
half2(0.34984338, -0.22245139),
|
||||||
|
half2(-0.11709833, 0.43521595),
|
||||||
|
half2(-0.22302461, -0.42969179),
|
||||||
|
half2(0.48406291, 0.17694080),
|
||||||
|
half2(-0.50370997, 0.20772886),
|
||||||
|
half2(0.24297905, -0.51873517),
|
||||||
|
half2(0.17918217, 0.57207417),
|
||||||
|
half2(-0.54061830, -0.31361890),
|
||||||
|
half2(0.63443625, -0.13916063),
|
||||||
|
half2(-0.38743055, 0.55047488),
|
||||||
|
half2(-0.08905894, -0.69024885),
|
||||||
|
half2(0.54879880, 0.46308208),
|
||||||
|
half2(-0.73889750, 0.03009081),
|
||||||
|
half2(0.53931046, -0.53597510),
|
||||||
|
half2(-0.03660476, 0.77976608),
|
||||||
|
half2(-0.51236552, -0.61490381),
|
||||||
|
half2(0.81227481, 0.10993028),
|
||||||
|
half2(-0.68869931, 0.47834957),
|
||||||
|
half2(0.18879366, -0.83590192),
|
||||||
|
half2(0.43436146, 0.75957572),
|
||||||
|
half2(-0.85019755, -0.27210116),
|
||||||
|
half2(0.82646751, -0.38088906),
|
||||||
|
half2(-0.35873961, 0.85479879),
|
||||||
|
half2(-0.31848884, -0.88836360),
|
||||||
|
half2(0.84942913, 0.44759941),
|
||||||
|
half2(-0.94430852, 0.24780297),
|
||||||
|
half2(0.53754807, -0.83391672),
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
float2 SampleShadowPCSSRotate(float2 pos, float2 rotation)
|
||||||
|
{
|
||||||
|
return float2(pos.x * rotation.x - pos.y * rotation.y, pos.y * rotation.x + pos.x * rotation.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
// [Percentage-Closer Soft Shadows, Randima Fernando, NVIDIA]
|
||||||
|
float SampleShadowMapPCSS(Texture2D<float> shadowMap, float2 shadowMapUV, float sceneDepth, float4 shadowToAtlas, float sourceAngle)
|
||||||
|
{
|
||||||
|
// Scale samples to shadow map tile
|
||||||
|
float2 shadowMapSize;
|
||||||
|
shadowMap.GetDimensions(shadowMapSize.x, shadowMapSize.y);
|
||||||
|
float resolution = shadowMapSize.x * shadowToAtlas.x;
|
||||||
|
float minRadius = 0.3f / resolution;
|
||||||
|
float2 uvMin = shadowToAtlas.zw;
|
||||||
|
float2 uvMax = shadowToAtlas.xy + shadowToAtlas.zw;
|
||||||
|
|
||||||
|
// Fix penumbra size to be consistent across different shadow map resolutions
|
||||||
|
sourceAngle *= resolution / 1024.0;
|
||||||
|
|
||||||
|
// Rotate the sampling pattern based on the pixel position to reduce banding artifacts (use shadow-space position for stability)
|
||||||
|
float rotationAngle = RandN1(shadowMapUV) * PI;
|
||||||
|
float2 rotation;
|
||||||
|
sincos(rotationAngle, rotation.x, rotation.y);
|
||||||
|
|
||||||
|
// Search blockers
|
||||||
|
float searchRadius = sourceAngle * saturate(sceneDepth - 0.02f) / sceneDepth;
|
||||||
|
searchRadius = max(searchRadius, minRadius);
|
||||||
|
uint blockers = 0;
|
||||||
|
float avgBlockerDistance = 0.0f;
|
||||||
|
for (uint i = 0; i < SHADOWS_PCSS_SAMPLES; i++)
|
||||||
|
{
|
||||||
|
float2 offset = VogelPoints[i] * searchRadius;
|
||||||
|
offset = SampleShadowPCSSRotate(offset, rotation);
|
||||||
|
offset = shadowMapUV + offset;
|
||||||
|
offset = clamp(offset, uvMin, uvMax);
|
||||||
|
float shadowMapDepth = LOAD_SHADOW_MAP(shadowMap, offset);
|
||||||
|
if (shadowMapDepth < sceneDepth)
|
||||||
|
{
|
||||||
|
blockers++;
|
||||||
|
avgBlockerDistance += shadowMapDepth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (blockers < 1)
|
||||||
|
return 1; // No blockers, fully lit
|
||||||
|
avgBlockerDistance /= blockers;
|
||||||
|
|
||||||
|
// Calculate penumbra size
|
||||||
|
float penumbra = max(sceneDepth - avgBlockerDistance, 0.0);
|
||||||
|
float filterRadius = penumbra * sourceAngle;
|
||||||
|
filterRadius = max(filterRadius, minRadius); // Don't use too small filter near blockers to avoid jagged edges
|
||||||
|
|
||||||
|
// Filter shadowmap
|
||||||
|
float shadow = 0.0f;
|
||||||
|
for (uint i = 0; i < SHADOWS_PCSS_SAMPLES; i++)
|
||||||
|
{
|
||||||
|
float2 offset = VogelPoints[i] * filterRadius;
|
||||||
|
offset = SampleShadowPCSSRotate(offset, rotation);
|
||||||
|
offset = shadowMapUV + offset;
|
||||||
|
offset = clamp(offset, uvMin, uvMax);
|
||||||
|
shadow += LOAD_SHADOW_MAP(shadowMap, offset) > sceneDepth;
|
||||||
|
}
|
||||||
|
return shadow / (float)SHADOWS_PCSS_SAMPLES;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
// Samples the shadow cascade for the given directional light on the material surface (supports subsurface shadowing)
|
// Samples the shadow cascade for the given directional light on the material surface (supports subsurface shadowing)
|
||||||
ShadowSample SampleDirectionalLightShadowCascade(LightData light, Buffer<float4> shadowsBuffer, Texture2D<float> shadowMap, GBufferSample gBuffer, ShadowData shadow, float3 samplePosition, uint cascadeIndex)
|
ShadowSample SampleDirectionalLightShadowCascade(LightData light, Buffer<float4> shadowsBuffer, Texture2D<float> shadowMap, GBufferSample gBuffer, ShadowData shadow, float3 samplePosition, uint cascadeIndex)
|
||||||
{
|
{
|
||||||
@@ -218,7 +363,12 @@ ShadowSample SampleDirectionalLightShadowCascade(LightData light, Buffer<float4>
|
|||||||
float2 shadowMapUV = GetLightShadowAtlasUV(shadow, shadowTile, samplePosition, shadowPosition);
|
float2 shadowMapUV = GetLightShadowAtlasUV(shadow, shadowTile, samplePosition, shadowPosition);
|
||||||
|
|
||||||
// Sample shadow map
|
// Sample shadow map
|
||||||
|
#if SHADOWS_PCSS
|
||||||
|
float sourceAngle = light.SourceRadius; // tan(SourceAngle * DegreesToRadians * 0.5);
|
||||||
|
result.SurfaceShadow = SampleShadowMapPCSS(shadowMap, shadowMapUV, shadowPosition.z, shadowTile.ShadowToAtlas, sourceAngle);
|
||||||
|
#else
|
||||||
result.SurfaceShadow = SampleShadowMapOptimizedPCF(shadowMap, shadowMapUV, shadowPosition.z);
|
result.SurfaceShadow = SampleShadowMapOptimizedPCF(shadowMap, shadowMapUV, shadowPosition.z);
|
||||||
|
#endif
|
||||||
|
|
||||||
// Increase the sharpness for higher cascades to match the filter radius
|
// Increase the sharpness for higher cascades to match the filter radius
|
||||||
const float SharpnessScale[MaxNumCascades] = { 1.0f, 1.5f, 3.0f, 3.5f };
|
const float SharpnessScale[MaxNumCascades] = { 1.0f, 1.5f, 3.0f, 3.5f };
|
||||||
@@ -322,7 +472,7 @@ ShadowSample SampleDirectionalLightShadow(LightData light, Buffer<float4> shadow
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Samples the shadow for the given local light on the material surface (supports subsurface shadowing)
|
// Samples the shadow for the given local light on the material surface (supports subsurface shadowing)
|
||||||
ShadowSample SampleLocalLightShadow(LightData light, Buffer<float4> shadowsBuffer, Texture2D<float> shadowMap, GBufferSample gBuffer, float3 L, float toLightLength, uint tileIndex)
|
ShadowSample SampleLocalLightShadow(LightData light, Buffer<float4> shadowsBuffer, Texture2D<float> shadowMap, GBufferSample gBuffer, float3 L, float toLightLength, uint tileIndex, float sourceAngle = 0)
|
||||||
{
|
{
|
||||||
#if !LIGHTING_NO_DIRECTIONAL
|
#if !LIGHTING_NO_DIRECTIONAL
|
||||||
// Skip if surface is in a full shadow
|
// Skip if surface is in a full shadow
|
||||||
@@ -362,7 +512,11 @@ ShadowSample SampleLocalLightShadow(LightData light, Buffer<float4> shadowsBuffe
|
|||||||
float2 shadowMapUV = GetLightShadowAtlasUV(shadow, shadowTile, samplePosition, shadowPosition);
|
float2 shadowMapUV = GetLightShadowAtlasUV(shadow, shadowTile, samplePosition, shadowPosition);
|
||||||
|
|
||||||
// Sample shadow map
|
// Sample shadow map
|
||||||
|
#if SHADOWS_PCSS
|
||||||
|
result.SurfaceShadow = SampleShadowMapPCSS(shadowMap, shadowMapUV, shadowPosition.z, shadowTile.ShadowToAtlas, sourceAngle);
|
||||||
|
#else
|
||||||
result.SurfaceShadow = SampleShadowMapOptimizedPCF(shadowMap, shadowMapUV, shadowPosition.z);
|
result.SurfaceShadow = SampleShadowMapOptimizedPCF(shadowMap, shadowMapUV, shadowPosition.z);
|
||||||
|
#endif
|
||||||
|
|
||||||
#if defined(USE_GBUFFER_CUSTOM_DATA)
|
#if defined(USE_GBUFFER_CUSTOM_DATA)
|
||||||
// Subsurface shadowing
|
// Subsurface shadowing
|
||||||
@@ -387,7 +541,11 @@ ShadowSample SampleSpotLightShadow(LightData light, Buffer<float4> shadowsBuffer
|
|||||||
float3 toLight = light.Position - gBuffer.WorldPos;
|
float3 toLight = light.Position - gBuffer.WorldPos;
|
||||||
float toLightLength = length(toLight);
|
float toLightLength = length(toLight);
|
||||||
float3 L = toLight / toLightLength;
|
float3 L = toLight / toLightLength;
|
||||||
return SampleLocalLightShadow(light, shadowsBuffer, shadowMap, gBuffer, L, toLightLength, 0);
|
|
||||||
|
// TODO: make it physical-based (need to compare with path tracing)
|
||||||
|
float sourceAngle = 0.02 * light.SourceRadius * dot(-light.Direction, light.Position - gBuffer.WorldPos) / light.Radius;
|
||||||
|
|
||||||
|
return SampleLocalLightShadow(light, shadowsBuffer, shadowMap, gBuffer, L, toLightLength, 0, sourceAngle);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Samples the shadow for the given point light on the material surface (supports subsurface shadowing)
|
// Samples the shadow for the given point light on the material surface (supports subsurface shadowing)
|
||||||
@@ -399,8 +557,11 @@ ShadowSample SamplePointLightShadow(LightData light, Buffer<float4> shadowsBuffe
|
|||||||
|
|
||||||
// Figure out which cube face we're sampling from
|
// Figure out which cube face we're sampling from
|
||||||
uint cubeFaceIndex = GetCubeFaceIndex(-L);
|
uint cubeFaceIndex = GetCubeFaceIndex(-L);
|
||||||
|
|
||||||
|
// TODO: make it physical-based (need to compare with path tracing)
|
||||||
|
float sourceAngle = 0.02 * light.SourceRadius * length(light.Position - gBuffer.WorldPos) / light.Radius;
|
||||||
|
|
||||||
return SampleLocalLightShadow(light, shadowsBuffer, shadowMap, gBuffer, L, toLightLength, cubeFaceIndex);
|
return SampleLocalLightShadow(light, shadowsBuffer, shadowMap, gBuffer, L, toLightLength, cubeFaceIndex, sourceAngle);
|
||||||
}
|
}
|
||||||
|
|
||||||
GBufferSample GetDummyGBufferSample(float3 worldPosition)
|
GBufferSample GetDummyGBufferSample(float3 worldPosition)
|
||||||
|
|||||||
Reference in New Issue
Block a user