Skip to content

Commit

Permalink
Implement shadow checkerboard
Browse files Browse the repository at this point in the history
This is a variant of the experiment on-stream, but somewhat less
aggressive: we trace half of the rays in a checkerboard pattern, and
reconstruct missing pixels from 4 neighbors using bilateral weights.

This also results in increased aliasing but it's less severe vs
half-res, and of course the performance gains are less substantial.
  • Loading branch information
zeux committed Dec 15, 2024
1 parent 8863e1b commit 015ba9b
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 4 deletions.
41 changes: 38 additions & 3 deletions src/niagara.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ bool clusterOcclusionEnabled = true;
bool taskShadingEnabled = false; // disabled to have good performance on AMD HW
bool shadingEnabled = true;
bool shadowblurEnabled = true;
bool shadowCheckerboard = false;
int shadowQuality = 1;
int debugGuiMode = 1;
int debugLodStep = 0;
Expand Down Expand Up @@ -135,6 +136,7 @@ struct alignas(16) ShadowData
mat4 inverseViewProjection;

vec2 imageSize;
unsigned int checkerboard;
};

struct alignas(16) ShadeData
Expand Down Expand Up @@ -513,6 +515,10 @@ void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods
{
shadowblurEnabled = !shadowblurEnabled;
}
if (key == GLFW_KEY_X)
{
shadowCheckerboard = !shadowCheckerboard;
}
if (key == GLFW_KEY_Q)
{
shadowQuality = 1 - shadowQuality;
Expand Down Expand Up @@ -746,11 +752,13 @@ int main(int argc, const char** argv)

Program shadeProgram = {};
Program shadowProgram = {};
Program shadowfillProgram = {};
Program shadowblurProgram = {};
if (raytracingSupported)
{
shadeProgram = createProgram(device, VK_PIPELINE_BIND_POINT_COMPUTE, { &shaders["shade.comp"] }, sizeof(ShadeData));
shadowProgram = createProgram(device, VK_PIPELINE_BIND_POINT_COMPUTE, { &shaders["shadow.comp"] }, sizeof(ShadowData), textureSetLayout);
shadowfillProgram = createProgram(device, VK_PIPELINE_BIND_POINT_COMPUTE, { &shaders["shadowfill.comp"] }, sizeof(vec4));
shadowblurProgram = createProgram(device, VK_PIPELINE_BIND_POINT_COMPUTE, { &shaders["shadowblur.comp"] }, sizeof(vec4));
}

Expand All @@ -775,6 +783,7 @@ int main(int argc, const char** argv)
VkPipeline shadePipeline = 0;
VkPipeline shadowlqPipeline = 0;
VkPipeline shadowhqPipeline = 0;
VkPipeline shadowfillPipeline = 0;
VkPipeline shadowblurPipeline = 0;

auto pipelines = [&]()
Expand Down Expand Up @@ -822,6 +831,7 @@ int main(int argc, const char** argv)
replace(shadePipeline, createComputePipeline(device, pipelineCache, shadeProgram));
replace(shadowlqPipeline, createComputePipeline(device, pipelineCache, shadowProgram, { /* QUALITY= */ 0 }));
replace(shadowhqPipeline, createComputePipeline(device, pipelineCache, shadowProgram, { /* QUALITY= */ 1 }));
replace(shadowfillPipeline, createComputePipeline(device, pipelineCache, shadowfillProgram));
replace(shadowblurPipeline, createComputePipeline(device, pipelineCache, shadowblurProgram));
}
};
Expand Down Expand Up @@ -1665,6 +1675,9 @@ int main(int argc, const char** argv)
{
uint32_t timestamp = 16;

// checkerboard rendering: we dispatch half as many columns and xform them to fill the screen
int shadowWidthCB = shadowCheckerboard ? (swapchain.width + 1) / 2 : swapchain.width;

vkCmdWriteTimestamp(commandBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, queryPoolTimestamp, timestamp + 0);

VkImageMemoryBarrier2 preshadowBarrier =
Expand All @@ -1687,13 +1700,33 @@ int main(int argc, const char** argv)
shadowData.sunJitter = shadowblurEnabled ? 1e-2f : 0;
shadowData.inverseViewProjection = inverse(projection * view);
shadowData.imageSize = vec2(float(swapchain.width), float(swapchain.height));
shadowData.checkerboard = shadowCheckerboard;

vkCmdPushConstants(commandBuffer, shadowProgram.layout, shadowProgram.pushConstantStages, 0, sizeof(shadowData), &shadowData);
vkCmdDispatch(commandBuffer, getGroupCount(swapchain.width, shadowProgram.localSizeX), getGroupCount(swapchain.height, shadowProgram.localSizeY), 1);
vkCmdDispatch(commandBuffer, getGroupCount(shadowWidthCB, shadowProgram.localSizeX), getGroupCount(swapchain.height, shadowProgram.localSizeY), 1);
}

vkCmdWriteTimestamp(commandBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, queryPoolTimestamp, timestamp + 1);

if (shadowCheckerboard)
{
VkImageMemoryBarrier2 fillBarrier = imageBarrier(shadowTarget.image,
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_ACCESS_SHADER_WRITE_BIT, VK_IMAGE_LAYOUT_GENERAL,
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, VK_IMAGE_LAYOUT_GENERAL);

pipelineBarrier(commandBuffer, VK_DEPENDENCY_BY_REGION_BIT, 0, nullptr, 1, &fillBarrier);

vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, shadowfillPipeline);

DescriptorInfo descriptors[] = { { shadowTarget.imageView, VK_IMAGE_LAYOUT_GENERAL }, { readSampler, depthTarget.imageView, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL } };
vkCmdPushDescriptorSetWithTemplateKHR(commandBuffer, shadowfillProgram.updateTemplate, shadowfillProgram.layout, 0, descriptors);

vec4 fillData = vec4(float(swapchain.width), float(swapchain.height), 0, 0);

vkCmdPushConstants(commandBuffer, shadowProgram.layout, shadowProgram.pushConstantStages, 0, sizeof(fillData), &fillData);
vkCmdDispatch(commandBuffer, getGroupCount(shadowWidthCB, shadowProgram.localSizeX), getGroupCount(swapchain.height, shadowProgram.localSizeY), 1);
}

for (int pass = 0; pass < (shadowblurEnabled ? 2 : 0); ++pass)
{
const Image& blurFrom = pass == 0 ? shadowTarget : shadowblurTarget;
Expand Down Expand Up @@ -1841,10 +1874,10 @@ int main(int argc, const char** argv)
taskSubmit ? "ON" : "OFF", taskSubmit && taskShadingEnabled ? "ON" : "OFF",
clusterOcclusionEnabled ? "ON" : "OFF");

debugtext(8, "RT shading %s, shadow blur %s, shadow quality %d",
debugtext(8, "RT shading %s, shadow blur %s, shadow quality %d, shadow checkerboard %s",
raytracingSupported && shadingEnabled ? "ON" : "OFF",
raytracingSupported && shadingEnabled && shadowblurEnabled ? "ON" : "OFF",
shadowQuality);
shadowQuality, shadowCheckerboard ? "ON" : "OFF");
}
}

Expand Down Expand Up @@ -2013,8 +2046,10 @@ int main(int argc, const char** argv)

vkDestroyPipeline(device, shadowlqPipeline, 0);
vkDestroyPipeline(device, shadowhqPipeline, 0);
vkDestroyPipeline(device, shadowfillPipeline, 0);
vkDestroyPipeline(device, shadowblurPipeline, 0);
destroyProgram(device, shadowProgram);
destroyProgram(device, shadowfillProgram);
destroyProgram(device, shadowblurProgram);
}

Expand Down
11 changes: 10 additions & 1 deletion src/shaders/shadow.comp.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ struct ShadowData
mat4 inverseViewProjection;

vec2 imageSize;
uint checkerboard;
};

layout(push_constant) uniform block
Expand Down Expand Up @@ -115,15 +116,23 @@ bool shadowTraceTransparent(vec3 wpos, vec3 dir, uint rayflags)
void main()
{
uvec2 pos = gl_GlobalInvocationID.xy;
vec2 uv = (vec2(pos) + 0.5) / shadowData.imageSize;

if (shadowData.checkerboard == 1)
{
// checkerboard even
pos.x *= 2;
pos.x += pos.y & 1;
}

vec2 uv = (vec2(pos) + 0.5) / shadowData.imageSize;
float depth = texture(depthImage, uv).r;

vec4 clip = vec4(uv.x * 2 - 1, 1 - uv.y * 2, depth, 1);
vec4 wposh = shadowData.inverseViewProjection * clip;
vec3 wpos = wposh.xyz / wposh.w;

vec3 dir = shadowData.sunDirection;

// TODO: a lot more tuning required here
// TODO: this should actually be doing cone sampling, not random XZ offsets
float dir0 = gradientNoise(vec2(pos.xy));
Expand Down
44 changes: 44 additions & 0 deletions src/shaders/shadowfill.comp.glsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#version 460

#extension GL_GOOGLE_include_directive: require

layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;

layout(push_constant) uniform block
{
vec2 imageSize;
};

layout(binding = 0, r8) uniform image2D shadowImage;
layout(binding = 1) uniform sampler2D depthImage;

void main()
{
ivec2 pos = ivec2(gl_GlobalInvocationID.xy);

// checkerboard odd
pos.x *= 2;
pos.x += 1 - (pos.y & 1);

float depth = texelFetch(depthImage, pos, 0).r;

vec4 depths = vec4(
texelFetch(depthImage, pos + ivec2(-1, 0), 0).r,
texelFetch(depthImage, pos + ivec2(+1, 0), 0).r,
texelFetch(depthImage, pos + ivec2(0, -1), 0).r,
texelFetch(depthImage, pos + ivec2(0, +1), 0).r
);

vec4 shadows = vec4(
imageLoad(shadowImage, pos + ivec2(-1, 0)).r,
imageLoad(shadowImage, pos + ivec2(+1, 0)).r,
imageLoad(shadowImage, pos + ivec2(0, -1)).r,
imageLoad(shadowImage, pos + ivec2(0, +1)).r
);

vec4 weights = exp2(-abs(depths / depth - 1) * 20);

float shadow = dot(weights, shadows) / (dot(weights, vec4(1)) + 1e-2);

imageStore(shadowImage, pos, vec4(shadow, 0, 0, 0));
}

0 comments on commit 015ba9b

Please sign in to comment.