Skip to content

Commit 705b919

Browse files
fix: PCF implementation
1 parent 9b43060 commit 705b919

11 files changed

Lines changed: 180 additions & 39 deletions

editor/app.c

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -473,13 +473,15 @@ pl_app_load(plApiRegistryI* ptApiRegistry, plAppData* ptAppData)
473473
plEntity tDirectionLight = gptRenderer->create_directional_light(ptAppData->ptComponentLibrary, "direction light", pl_create_vec3(0.425f, -1.0f, -0.384f), &ptLight);
474474
ptLight->uCascadeCount = 4;
475475
ptLight->fIntensity = 18.0f;
476+
ptLight->fShadowLambda = 0.6f;
476477
ptLight->uShadowResolution = 1024 * 2;
477478
ptLight->afCascadeSplits[0] = 0.005f;
478-
ptLight->afCascadeSplits[1] = 0.020f;
479-
ptLight->afCascadeSplits[2] = 0.10f;
480-
ptLight->afCascadeSplits[3] = 1.00f;
479+
ptLight->afCascadeSplits[1] = 0.05f;
480+
ptLight->afCascadeSplits[2] = 0.1f;
481+
ptLight->afCascadeSplits[3] = 0.15f;
481482
ptLight->tFlags |= PL_LIGHT_FLAG_CAST_SHADOW | PL_LIGHT_FLAG_VISUALIZER;
482483

484+
483485
plEntity tPointLight = gptRenderer->create_point_light(ptAppData->ptComponentLibrary, "point light", pl_create_vec3(fXOffset + 9.316f, fYOffset + 1.497f, fZOffset-1.042f), &ptLight);
484486
ptLight->uShadowResolution = 512;
485487
ptLight->tColor = (plVec3){0.0f, 1.0f, 0.0f};
@@ -1065,6 +1067,9 @@ pl__show_editor_window(plAppData* ptAppData)
10651067

10661068
gptUI->input_float("Depth Bias", &ptRuntimeOptions->fShadowConstantDepthBias, NULL, 0);
10671069
gptUI->input_float("Slope Depth Bias", &ptRuntimeOptions->fShadowSlopeDepthBias, NULL, 0);
1070+
gptUI->input_float("Terrain Depth Bias", &ptRuntimeOptions->fTerrainShadowConstantDepthBias, NULL, 0);
1071+
gptUI->input_float("Terrain Slope Depth Bias", &ptRuntimeOptions->fTerrainShadowSlopeDepthBias, NULL, 0);
1072+
gptUI->slider_float("Max Shadow Range", &ptRuntimeOptions->fMaxShadowRange, 100.0f, 1000.0f, 0);
10681073
gptUI->slider_uint("Outline Width", &ptRuntimeOptions->uOutlineWidth, 2, 50, 0);
10691074

10701075
if(ptAppData->ptScene)

extensions/pl_ecs_tools_ext.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -807,6 +807,8 @@ pl_show_ecs_window(plComponentLibrary* ptLibrary, plEntity* ptSelectedEntity, pl
807807
{
808808
gptUI->slider_uint("Cascades", &ptLightComp->uCascadeCount, 1, 4, 0);
809809
gptUI->input_float4("Cascade Splits", ptLightComp->afCascadeSplits, "%0.6f", 0);
810+
gptUI->input_float("Shadow Lambda", &ptLightComp->fShadowLambda, "%0.2f", 0);
811+
gptUI->slider_float("Shadow Lambda#2", &ptLightComp->fShadowLambda, 0.0f, 1.0f, 0);
810812
}
811813
gptUI->end_collapsing_header();
812814
}

extensions/pl_renderer_ext.c

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,9 +239,13 @@ pl_renderer_initialize(plRendererSettings tSettings)
239239
gptData->tRuntimeOptions.bShowSelectedBoundingBox = true;
240240
gptData->tRuntimeOptions.bImageBasedLighting = true;
241241
gptData->tRuntimeOptions.bPunctualLighting = true;
242+
gptData->tRuntimeOptions.bPcfShadows = true;
242243
gptData->tRuntimeOptions.bNormalMapping = true;
244+
gptData->tRuntimeOptions.fMaxShadowRange = 150.0f;
245+
gptData->tRuntimeOptions.fTerrainShadowConstantDepthBias = -100.0f;
246+
gptData->tRuntimeOptions.fTerrainShadowSlopeDepthBias = -10.0f;
243247
gptData->tRuntimeOptions.fShadowConstantDepthBias = -1.25f;
244-
gptData->tRuntimeOptions.fShadowSlopeDepthBias = -10.75f;
248+
gptData->tRuntimeOptions.fShadowSlopeDepthBias = -1.75f;
245249
gptData->tRuntimeOptions.fExposure = 1.0f;
246250
gptData->tRuntimeOptions.fBrightness = 0.0f;
247251
gptData->tRuntimeOptions.fContrast = 1.0f;

extensions/pl_renderer_ext.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,8 +239,11 @@ typedef struct _plRendererRuntimeOptions
239239
bool bNormalMapping;
240240
bool bPunctualLighting;
241241
bool bPcfShadows;
242+
float fMaxShadowRange;
242243
float fShadowConstantDepthBias;
243244
float fShadowSlopeDepthBias;
245+
float fTerrainShadowConstantDepthBias;
246+
float fTerrainShadowSlopeDepthBias;
244247
uint32_t uOutlineWidth;
245248
plShaderDebugMode tShaderDebugMode;
246249

@@ -349,6 +352,7 @@ typedef struct _plLightComponent
349352
uint32_t uShadowResolution; // 0 -> automatic
350353
float afCascadeSplits[PL_MAX_SHADOW_CASCADES];
351354
uint32_t uCascadeCount;
355+
float fShadowLambda;
352356
} plLightComponent;
353357

354358
#endif // PL_RENDERER_EXT_H

extensions/pl_renderer_internal.c

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1351,6 +1351,8 @@ pl__renderer_generate_cascaded_shadow_map(plRenderEncoder* ptEncoder, plCommandB
13511351

13521352
// TODO: we shouldn't have to check all rects, optimize this
13531353

1354+
float fShadowFarZ = pl_min(ptSceneCamera->fFarZ, gptData->tRuntimeOptions.fMaxShadowRange);
1355+
13541356
const uint32_t uAtlasRectCount = pl_sb_size(ptScene->sbtShadowRects);
13551357
for(uint32_t uRectIndex = 0; uRectIndex < uAtlasRectCount; uRectIndex++)
13561358
{
@@ -1363,7 +1365,7 @@ pl__renderer_generate_cascaded_shadow_map(plRenderEncoder* ptEncoder, plCommandB
13631365
{
13641366
continue;
13651367
}
1366-
const plLightComponent* ptLight = gptECS->get_component(ptScene->ptComponentLibrary, gptData->tLightComponentType, ptScene->sbtDirectionLights[ptData->uLightIndex].tEntity);
1368+
plLightComponent* ptLight = gptECS->get_component(ptScene->ptComponentLibrary, gptData->tLightComponentType, ptScene->sbtDirectionLights[ptData->uLightIndex].tEntity);
13671369

13681370

13691371
int iShadowIndex = ptScene->sbtDirectionLightData[ptData->uLightIndex].iShadowIndex;
@@ -1381,13 +1383,34 @@ pl__renderer_generate_cascaded_shadow_map(plRenderEncoder* ptEncoder, plCommandB
13811383
const plVec3 tDirection = pl_norm_vec3(ptLight->tDirection);
13821384
const uint32_t uCascadeCount = tInfo.bAltMode ? 1 : ptLight->uCascadeCount; // probe only needs single cascade
13831385

1384-
const float afCascadeSplits[4] = {
1386+
float afCascadeSplits[4] = {
13851387
tInfo.bAltMode ? 1.0f : ptLight->afCascadeSplits[0], // use whole frustum for environment probes
13861388
ptLight->afCascadeSplits[1],
13871389
ptLight->afCascadeSplits[2],
13881390
ptLight->afCascadeSplits[3]
13891391
};
13901392

1393+
if(!tInfo.bAltMode && ptLight->fShadowLambda > 0.0f)
1394+
{
1395+
for(uint32_t i = 1; i <= uCascadeCount; ++i)
1396+
{
1397+
float p = (float)i / (float)uCascadeCount;
1398+
1399+
float logSplit =
1400+
ptSceneCamera->fNearZ * powf(fShadowFarZ / ptSceneCamera->fNearZ, p);
1401+
1402+
float linSplit =
1403+
ptSceneCamera->fNearZ + (fShadowFarZ - ptSceneCamera->fNearZ) * p;
1404+
1405+
float di =
1406+
ptLight->fShadowLambda * logSplit +
1407+
(1.0f - ptLight->fShadowLambda) * linSplit;
1408+
1409+
afCascadeSplits[i - 1] = (di - ptSceneCamera->fNearZ)/(ptSceneCamera->fFarZ - ptSceneCamera->fNearZ);
1410+
ptLight->afCascadeSplits[i - 1] = afCascadeSplits[i - 1];
1411+
}
1412+
}
1413+
13911414
//-------------------------------------------------------------------------
13921415
// stable light basis from direction only
13931416
//-------------------------------------------------------------------------
@@ -1513,7 +1536,7 @@ pl__renderer_generate_cascaded_shadow_map(plRenderEncoder* ptEncoder, plCommandB
15131536
const float fCenterZ = 0.5f * (fZMin + fZMax);
15141537

15151538
// optional z padding for off-frustum casters
1516-
const float fDepthPadding = 200.0f; // TODO: make option
1539+
const float fDepthPadding = 100.0f; // TODO: make option
15171540
const float fNearZ = fZMax + fDepthPadding;
15181541
const float fFarZ = fZMin - fDepthPadding;
15191542

@@ -1580,7 +1603,7 @@ pl__renderer_generate_cascaded_shadow_map(plRenderEncoder* ptEncoder, plCommandB
15801603
};
15811604
gptGfx->set_viewport(ptEncoder, &tViewport);
15821605
gptGfx->set_scissor_region(ptEncoder, &tScissor);
1583-
gptGfx->set_depth_bias(ptEncoder, gptData->tRuntimeOptions.fShadowConstantDepthBias, 0.0f, gptData->tRuntimeOptions.fShadowSlopeDepthBias);
1606+
gptGfx->set_depth_bias(ptEncoder, gptData->tRuntimeOptions.fTerrainShadowConstantDepthBias, 0.0f, gptData->tRuntimeOptions.fTerrainShadowSlopeDepthBias);
15841607
gptGfx->bind_shader(ptEncoder, ptScene->tTerrainShadowShader);
15851608
gptGfx->bind_vertex_buffer(ptEncoder, ptScene->ptTerrain->tVertexBuffer);
15861609
plBindGroupHandle atBindGroups[] = {ptScene->atSceneBindGroups[uFrameIdx], tInfo.tBindGroup};

shaders/pl_deferred_lighting_directional.frag

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ void main()
125125
abiasMat[1][1] *= tDirectionShadowData.atData[iShadowIndex].fFactor;
126126
abiasMat[3][0] *= tDirectionShadowData.atData[iShadowIndex].fFactor;
127127
abiasMat[3][1] *= tDirectionShadowData.atData[iShadowIndex].fFactor;
128-
shadow = 0.0;
128+
shadow = 1.0;
129129

130130
// Get cascade index for the current fragment's view position
131131

@@ -134,7 +134,7 @@ void main()
134134
vec4 tWorldPos2 = vec4(tWorldPosition.xyz, 1.0);
135135
if(tObjectInfo.tData.iProbe == 0)
136136
{
137-
for(int j = 0; j < tLightData.iCascadeCount - 1; j++)
137+
for(int j = 0; j < tLightData.iCascadeCount; j++)
138138
{
139139
if(viewDepth > tLightData.afCascadeSplits[j])
140140
cascadeIndex = j + 1;
@@ -143,13 +143,17 @@ void main()
143143
// vec4 rawshadowCoord = biasMat * tDirectionShadowData.atData[iShadowIndex].viewProjMat[cascadeIndex] * tWorldPos2;
144144

145145
// if(abs(rawshadowCoord.x - pl_saturate(rawshadowCoord.x)) < 0.00001 && abs(rawshadowCoord.y - pl_saturate(rawshadowCoord.y)) < 0.00001 && abs(rawshadowCoord.z - pl_saturate(rawshadowCoord.z)) < 0.00001)
146+
if(cascadeIndex < 4)
146147
{
147148
vec4 shadowCoord = (abiasMat * tDirectionShadowData.atData[iShadowIndex].viewProjMat[cascadeIndex]) * tWorldPos2;
148149
// cascadeIndex = j;
149150

150151
if(bool(iRenderingFlags & PL_RENDERING_FLAG_PCF_SHADOWS))
151152
{
152-
shadow = filterPCF(shadowCoord, vec2(tDirectionShadowData.atData[iShadowIndex].fXOffset, tDirectionShadowData.atData[iShadowIndex].fYOffset) + vec2(cascadeIndex * tDirectionShadowData.atData[iShadowIndex].fFactor, 0), tDirectionShadowData.atData[iShadowIndex].iShadowMapTexIdx);
153+
shadow = filterPCF(
154+
shadowCoord,
155+
vec2(tDirectionShadowData.atData[iShadowIndex].fXOffset, tDirectionShadowData.atData[iShadowIndex].fYOffset) + vec2(cascadeIndex * tDirectionShadowData.atData[iShadowIndex].fFactor, 0),
156+
tDirectionShadowData.atData[iShadowIndex].iShadowMapTexIdx, cascadeIndex);
153157
}
154158
else
155159
{
@@ -164,7 +168,7 @@ void main()
164168
float splitEnd = tLightData.afCascadeSplits[cascadeIndex];
165169

166170
// width of fade region (10% of cascade)
167-
float fadeRange = (splitEnd - splitStart) * 0.25;
171+
float fadeRange = (splitEnd - splitStart) * 0.1;
168172

169173
// distance to end of cascade
170174
float distToEnd = splitEnd - viewDepth;
@@ -176,7 +180,10 @@ void main()
176180
{
177181

178182
shadowCoord = (abiasMat * tDirectionShadowData.atData[iShadowIndex].viewProjMat[cascadeIndex + 1]) * tWorldPos2;
179-
float shadowfallback = filterPCF(shadowCoord, vec2(tDirectionShadowData.atData[iShadowIndex].fXOffset, tDirectionShadowData.atData[iShadowIndex].fYOffset) + vec2((cascadeIndex + 1.0) * tDirectionShadowData.atData[iShadowIndex].fFactor, 0), tDirectionShadowData.atData[iShadowIndex].iShadowMapTexIdx);
183+
float shadowfallback = filterPCF(
184+
shadowCoord,
185+
vec2(tDirectionShadowData.atData[iShadowIndex].fXOffset, tDirectionShadowData.atData[iShadowIndex].fYOffset) + vec2((cascadeIndex + 1.0) * tDirectionShadowData.atData[iShadowIndex].fFactor, 0),
186+
tDirectionShadowData.atData[iShadowIndex].iShadowMapTexIdx, cascadeIndex + 1);
180187

181188
shadow = mix(shadow, shadowfallback, cascade_fade);
182189
// shadow = 100.0;

shaders/pl_deferred_lighting_point.frag

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ void main()
137137
shadowCoord.xy = result.xy;
138138
if(bool(iRenderingFlags & PL_RENDERING_FLAG_PCF_SHADOWS))
139139
{
140-
shadow = filterPCF(shadowCoord, vec2(tShadowData.fXOffset, tShadowData.fYOffset) + faceoffsets[int(result.z)] * tShadowData.fFactor, tShadowData.iShadowMapTexIdx);
140+
shadow = filterPCFSimple(shadowCoord, vec2(tShadowData.fXOffset, tShadowData.fYOffset) + faceoffsets[int(result.z)] * tShadowData.fFactor, tShadowData.iShadowMapTexIdx);
141141
}
142142
else
143143
{

shaders/pl_deferred_lighting_spot.frag

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ void main()
128128

129129
if(bool(iRenderingFlags & PL_RENDERING_FLAG_PCF_SHADOWS))
130130
{
131-
shadow = filterPCF(shadowCoord, vec2(tShadowData.fXOffset, tShadowData.fYOffset), tShadowData.iShadowMapTexIdx);
131+
shadow = filterPCFSimple(shadowCoord, vec2(tShadowData.fXOffset, tShadowData.fYOffset), tShadowData.iShadowMapTexIdx);
132132
}
133133
else
134134
{

shaders/pl_forward.frag

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -516,7 +516,7 @@ void main()
516516
shadowCoord.xy = result.xy;
517517
if(bool(iRenderingFlags & PL_RENDERING_FLAG_PCF_SHADOWS))
518518
{
519-
shadow = filterPCF(shadowCoord, vec2(tShadowData.fXOffset, tShadowData.fYOffset) + faceoffsets[int(result.z)] * tShadowData.fFactor, tShadowData.iShadowMapTexIdx);
519+
shadow = filterPCFSimple(shadowCoord, vec2(tShadowData.fXOffset, tShadowData.fYOffset) + faceoffsets[int(result.z)] * tShadowData.fFactor, tShadowData.iShadowMapTexIdx);
520520
}
521521
else
522522
{
@@ -654,7 +654,7 @@ void main()
654654

655655
if(bool(iRenderingFlags & PL_RENDERING_FLAG_PCF_SHADOWS))
656656
{
657-
shadow = filterPCF(shadowCoord, vec2(tShadowData.fXOffset, tShadowData.fYOffset), tShadowData.iShadowMapTexIdx);
657+
shadow = filterPCFSimple(shadowCoord, vec2(tShadowData.fXOffset, tShadowData.fYOffset), tShadowData.iShadowMapTexIdx);
658658
}
659659
else
660660
{
@@ -805,13 +805,18 @@ void main()
805805
}
806806
}
807807

808+
if(cascadeIndex < 4)
808809
{
809810
vec4 shadowCoord = (abiasMat * tDirectionShadowData.atData[iShadowIndex].viewProjMat[cascadeIndex]) * tWorldPos2;
810811
// cascadeIndex = j;
811812

812813
if(bool(iRenderingFlags & PL_RENDERING_FLAG_PCF_SHADOWS))
813814
{
814-
shadow = filterPCF(shadowCoord, vec2(tDirectionShadowData.atData[iShadowIndex].fXOffset, tDirectionShadowData.atData[iShadowIndex].fYOffset) + vec2(cascadeIndex * tDirectionShadowData.atData[iShadowIndex].fFactor, 0), tDirectionShadowData.atData[iShadowIndex].iShadowMapTexIdx);
815+
shadow = filterPCF(
816+
shadowCoord,
817+
vec2(tDirectionShadowData.atData[iShadowIndex].fXOffset, tDirectionShadowData.atData[iShadowIndex].fYOffset) + vec2(cascadeIndex * tDirectionShadowData.atData[iShadowIndex].fFactor, 0),
818+
tDirectionShadowData.atData[iShadowIndex].iShadowMapTexIdx,
819+
cascadeIndex);
815820
}
816821
else
817822
{
@@ -826,7 +831,7 @@ void main()
826831
float splitEnd = tLightData.afCascadeSplits[cascadeIndex];
827832

828833
// width of fade region (10% of cascade)
829-
float fadeRange = (splitEnd - splitStart) * 0.25;
834+
float fadeRange = (splitEnd - splitStart) * 0.1;
830835

831836
// distance to end of cascade
832837
float distToEnd = splitEnd - viewDepth;
@@ -838,7 +843,10 @@ void main()
838843
{
839844

840845
shadowCoord = (abiasMat * tDirectionShadowData.atData[iShadowIndex].viewProjMat[cascadeIndex + 1]) * tWorldPos2;
841-
float shadowfallback = filterPCF(shadowCoord, vec2(tDirectionShadowData.atData[iShadowIndex].fXOffset, tDirectionShadowData.atData[iShadowIndex].fYOffset) + vec2((cascadeIndex + 1.0) * tDirectionShadowData.atData[iShadowIndex].fFactor, 0), tDirectionShadowData.atData[iShadowIndex].iShadowMapTexIdx);
846+
float shadowfallback = filterPCF(
847+
shadowCoord,
848+
vec2(tDirectionShadowData.atData[iShadowIndex].fXOffset, tDirectionShadowData.atData[iShadowIndex].fYOffset) + vec2((cascadeIndex + 1.0) * tDirectionShadowData.atData[iShadowIndex].fFactor, 0),
849+
tDirectionShadowData.atData[iShadowIndex].iShadowMapTexIdx, cascadeIndex + 1);
842850

843851
shadow = mix(shadow, shadowfallback, cascade_fade);
844852
// shadow = 100.0;

shaders/pl_lighting.glsl

Lines changed: 90 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -322,12 +322,43 @@ textureProj(vec4 shadowCoord, vec2 offset, int textureIndex)
322322
}
323323

324324
float
325-
filterPCF(vec4 sc, vec2 offset, int textureIndex)
325+
textureProj2(vec4 shadowCoord, vec2 offset, int textureIndex)
326+
{
327+
328+
// Perspective divide
329+
vec3 proj = shadowCoord.xyz / shadowCoord.w;
330+
331+
// Early out: outside light frustum → lit
332+
if (proj.z < 0.0 || proj.z > 1.0)
333+
return 1.0;
334+
335+
// Apply atlas offset in UV space
336+
vec2 uv = proj.xy + offset;
337+
338+
float dist =
339+
texture(
340+
sampler2D(
341+
at2DTextures[nonuniformEXT(textureIndex)],
342+
tSamplerNearestClamp),
343+
uv).r;
344+
return (dist <= proj.z) ? 1.0 : 0.0;
345+
}
346+
347+
float hash12(vec2 p)
348+
{
349+
vec3 p3 = fract(vec3(p.xyx) * 0.1031);
350+
p3 += dot(p3, p3.yzx + 33.33);
351+
return fract((p3.x + p3.y) * p3.z);
352+
}
353+
354+
float
355+
filterPCFSimple(vec4 sc, vec2 offset, int textureIndex)
326356
{
327357
ivec2 texDim = textureSize(sampler2D(at2DTextures[nonuniformEXT(textureIndex)], tSamplerNearestClamp), 0).xy;
328358
float scale = 1.0;
329359
float dx = scale * 1.0 / (float(texDim.x));
330360
float dy = scale * 1.0 / (float(texDim.y));
361+
vec2 texelSize = 1.0 / vec2(texDim);
331362

332363
float shadowFactor = 0.0;
333364
// int count = 0;
@@ -337,15 +368,71 @@ filterPCF(vec4 sc, vec2 offset, int textureIndex)
337368
{
338369
for (int y = -range; y <= range; y++)
339370
{
340-
shadowFactor += textureProj(sc, vec2(dx*x, dy*y) + offset, textureIndex);
371+
vec2 jitter = (hash12(gl_FragCoord.xy + vec2(x, y)) - 0.5) * texelSize;
372+
shadowFactor += textureProj(sc, vec2(dx*x, dy*y) + offset + jitter, textureIndex);
341373
// count++;
342374
}
343375
}
344-
// return shadowFactor / count;
345376
return shadowFactor / 9.0;
346377
}
347378

348379

380+
float
381+
filterPCF(vec4 sc, vec2 offset, int textureIndex, uint cascadeIndex)
382+
383+
{
384+
ivec2 texDim =
385+
textureSize(
386+
sampler2D(
387+
at2DTextures[nonuniformEXT(textureIndex)],
388+
tSamplerNearestClamp),
389+
0).xy;
390+
391+
vec2 texelSize = 1.0 / vec2(texDim);
392+
393+
// ----- CHANGES BEGIN -----
394+
int range = 1; // 5×5 PCF instead of 3×3
395+
if(cascadeIndex == 0)
396+
range = 3;
397+
// ----- CHANGES END -----
398+
399+
float shadowSum = 0.0;
400+
float weightSum = 0.0;
401+
402+
for (int x = -range; x <= range; x++)
403+
{
404+
for (int y = -range; y <= range; y++)
405+
{
406+
// Texel-centered PCF offset
407+
408+
vec2 jitter = (hash12(gl_FragCoord.xy + vec2(x, y)) - 0.5) * texelSize;
409+
410+
vec2 pcfOffset =
411+
(vec2(x, y) + 0.5) * texelSize + jitter;
412+
413+
// ----- NEW: tent (pyramid) weights -----
414+
float wx = 1.0 - abs(float(x)) / (float(range) + 1.0);
415+
float wy = 1.0 - abs(float(y)) / (float(range) + 1.0);
416+
float w = wx * wy;
417+
// --------------------------------------
418+
419+
shadowSum +=
420+
w * textureProj2(
421+
sc,
422+
offset + pcfOffset,
423+
textureIndex);
424+
425+
weightSum += w;
426+
}
427+
}
428+
429+
return shadowSum / weightSum;
430+
}
431+
432+
433+
434+
435+
349436
// Smooth 0..1 falloff between inner..outer, 1 inside inner, 0 beyond outer
350437
float smoothFalloff(float d, float inner, float outer)
351438
{

0 commit comments

Comments
 (0)