repaired occlusion culling via depth pyramids

This commit is contained in:
ecker 2026-04-20 23:40:55 -05:00
parent 42aaf444ff
commit fd9036ee39
6 changed files with 124 additions and 129 deletions

View File

@ -4,13 +4,13 @@
"start": "StartMenu",
"matrix": { "reverseInfinite": true },
"meshes": { "interleaved": false },
"lights": { "enabled": true,
"lights": { "enabled": false,
"useLightmaps": false,
"max": 32,
"max": 16,
"shadows": {
"enabled": true,
"update": 8,
"max": 32,
"enabled": false,
"update": 4,
"max": 16,
"samples": 2
},
"bloom": {
@ -29,8 +29,8 @@
}
},
"vxgi": {
// "limiter": 0,
"limiter": 0.0125,
"limiter": 0,
// "limiter": 0.0125,
"size": 256,
"dispatch": 16,
"cascades": 3,
@ -117,7 +117,7 @@
"gui": true,
"vsync": true, // vsync on vulkan side rather than engine-side
"hdr": true,
"vxgi": true,
"vxgi": false,
"culling": true,
"bloom": true,
"rt": false,
@ -355,7 +355,7 @@
"render modes": { "gui": true, "deferred": true },
"limiters": {
"deltaTime": 5,
"framerate": 0 // "auto" // for some reason drops to 60
"framerate": 120 // "auto" // for some reason drops to 60
},
"threads": {
"workers" : "auto",

View File

@ -1,9 +1,9 @@
{
// "import": "./rp_downtown_v2.json"
// "import": "./ss2_medsci1.json"
"import": "./ss2_medsci1.json"
// "import": "./test_grid.json"
// "import": "./sh2_mcdonalds.json"
// "import": "./animal_crossing.json"
"import": "./mds_mcdonalds.json"
// "import": "./mds_mcdonalds.json"
// "import": "./gm_construct.json"
}

View File

@ -16,15 +16,20 @@ layout( push_constant ) uniform PushBlock {
uint pass;
} PushConstant;
layout (binding = 3) uniform sampler2D inImage[MIPS];
layout (binding = 4, r32f) uniform writeonly image2D outImage[MIPS];
layout (binding = 0) uniform sampler2D samplerDepth;
layout (binding = 1) uniform sampler2D inImage[MIPS];
layout (binding = 2, r32f) uniform writeonly image2D outImage[MIPS];
void main() {
vec2 imageSize = imageSize(outImage[PushConstant.pass]);
uvec2 pos = gl_GlobalInvocationID.xy;
if ( pos.x >= imageSize.x || pos.y >= imageSize.y ) return;
int mip = int(PushConstant.pass);
float depth = texture(inImage[PushConstant.pass], (vec2(pos) + vec2(0.5)) / imageSize).x;
float depth;
ivec2 pos = ivec2(gl_GlobalInvocationID.xy);
if ( mip == 0 ) {
depth = texelFetch(samplerDepth, pos, 0).r;
} else {
depth = texture(inImage[mip - 1], (vec2(gl_GlobalInvocationID.xy) + vec2(0.5)) / imageSize( outImage[mip] )).x;
}
imageStore(outImage[PushConstant.pass], ivec2(pos), vec4(depth));
imageStore(outImage[mip], pos, vec4(depth));
}

View File

@ -51,8 +51,8 @@ bool projectSphere(vec3 C, float r, float znear, float P00, float P11, out vec4
}
layout( push_constant ) uniform PushBlock {
uint pass;
uint passes;
uint pass;
uint passes;
} PushConstant;
layout (binding = 0) uniform Camera {
@ -73,10 +73,6 @@ layout (std140, binding = 3) buffer Objects {
layout (binding = 4) uniform sampler2D samplerDepth;
struct Frustum {
vec4 planes[6];
};
vec4 normalizePlane( vec4 p ) {
return p / length(p.xyz);
}
@ -90,7 +86,7 @@ bool frustumCull( uint id ) {
if ( drawCommand.indices == 0 || drawCommand.vertices == 0 ) return false;
bool visible = true;
bool visible = false;
for ( uint pass = 0; pass < PushConstant.passes; ++pass ) {
mat4 mat = camera.viewport[pass].projection * camera.viewport[pass].view * object.model;
vec4 planes[6]; {
@ -104,62 +100,69 @@ bool frustumCull( uint id ) {
}
}
bool insideFrustum = true;
for ( uint p = 0; p < 6; ++p ) {
float d = max(instance.bounds.min.x * planes[p].x, instance.bounds.max.x * planes[p].x)
+ max(instance.bounds.min.y * planes[p].y, instance.bounds.max.y * planes[p].y)
+ max(instance.bounds.min.z * planes[p].z, instance.bounds.max.z * planes[p].z);
if (d < -planes[p].w) {
visible = false;
break;
}
}
if ( !visible ) break;
for ( uint p = 0; p < 6; ++p ) {
float d = max(instance.bounds.min.x * planes[p].x, instance.bounds.max.x * planes[p].x)
+ max(instance.bounds.min.y * planes[p].y, instance.bounds.max.y * planes[p].y)
+ max(instance.bounds.min.z * planes[p].z, instance.bounds.max.z * planes[p].z);
if (d < -planes[p].w) {
insideFrustum = false;
break;
}
}
if ( insideFrustum ) {
visible = true;
break;
}
}
return visible;
}
bool occlusionCull( uint id ) {
if ( PushConstant.passes == 0 ) return true;
const DrawCommand drawCommand = drawCommands[id];
const Instance instance = instances[drawCommand.instanceID];
const Object object = objects[instance.objectID];
bool visible = true;
bool visible = false;
for ( uint pass = 0; pass < PushConstant.passes; ++pass ) {
vec4 aabb;
vec4 sphere = aabbToSphere( instance.bounds );
float scale = length(object.model[0].xyz);
vec3 center = (camera.viewport[pass].view * object.model * vec4(sphere.xyz, 1)).xyz;
float radius = (object.model * vec4(sphere.w, 0, 0, 0)).x;
float radius = scale * sphere.w;
mat4 proj = camera.viewport[pass].projection;
float znear = proj[3][2];
float P00 = proj[0][0];
float P11 = proj[1][1];
if (projectSphere(center, radius, znear, P00, P11, aabb)) {
ivec2 pyramidSize = textureSize( samplerDepth, 0 );
float mips = mipLevels( pyramidSize );
if ( projectSphere( center, radius, znear, P00, P11, aabb ) ) {
vec2 pyramidSize = vec2(textureSize( samplerDepth, 0 ));
float width = (aabb.z - aabb.x) * pyramidSize.x;
float height = (aabb.w - aabb.y) * pyramidSize.y;
//find the mipmap level that will match the screen size of the sphere
float level = floor(log2(max(width, height)));
// if ( level == mips )
--level;
level = clamp( level, 0, mips );
float level = max(0.0, floor(log2(max(width, height))));
//sample the depth pyramid at that specific level
float depth = textureLod(samplerDepth, (aabb.xy + aabb.zw) * 0.5, level).x;
float d1 = textureLod(samplerDepth, vec2(aabb.x, aabb.y), level).x;
float d2 = textureLod(samplerDepth, vec2(aabb.z, aabb.y), level).x;
float d3 = textureLod(samplerDepth, vec2(aabb.x, aabb.w), level).x;
float d4 = textureLod(samplerDepth, vec2(aabb.z, aabb.w), level).x;
float depth = min(min(d1, d2), min(d3, d4)); // min for reverse-z projection, max for standard
float depthSphere = znear / (center.z - radius);
instances[drawCommand.instanceID].bounds.padding1 = depth;
instances[drawCommand.instanceID].bounds.padding2 = proj[3][2];
//if the depth of the sphere is in front of the depth pyramid value, then the object is visible
visible = visible && depthSphere >= depth - DEPTH_BIAS;
if ( depthSphere >= depth - DEPTH_BIAS ) {
visible = true;
break;
}
} else {
visible = true;
break;
}
}
return visible;
@ -170,6 +173,6 @@ void main() {
if ( !(0 <= gID && gID < drawCommands.length()) ) return;
bool visible = frustumCull( gID );
// if ( visible ) visible = occlusionCull( gID );
if ( visible ) visible = occlusionCull( gID );
drawCommands[gID].instances = visible ? 1 : 0;
}

View File

@ -237,15 +237,15 @@ namespace pod {
uint32_t reserveCount = 32; // amount of elements to reserve for vectors used in this system, to-do: have it tie to a memory pool allocator
// increasing these make things lag for reasons I can imagine why
uint32_t broadphaseBvhCapacity = 4; // number of bodies per leaf node
uint32_t meshBvhCapacity = 4; // number of triangles per leaf node
uint32_t broadphaseBvhCapacity = 2; // number of bodies per leaf node
uint32_t meshBvhCapacity = 2; // number of triangles per leaf node
// additionally flattens a BVH for linear iteration, rather than a recursive / stack-based traversal
bool flattenBvhBodies = true;
bool flattenBvhMeshes = true;
// use surface area heuristics for building the BVH, rather than naive splits
bool useBvhSahBodies = true; // it actually seems slower to use these......
bool useBvhSahBodies = true;
bool useBvhSahMeshes = true;
bool useSplitBvhs = true; // creates separate BVHs for static / dynamic objects

View File

@ -56,6 +56,7 @@ void ext::vulkan::DeferredRenderMode::initialize( Device& device ) {
uint32_t width = this->width > 0 ? this->width : (ext::vulkan::settings::width * this->scale);
uint32_t height = this->height > 0 ? this->height : (ext::vulkan::settings::height * this->scale);
uint32_t mips = uf::vector::mips( pod::Vector2ui{ width, height } );
renderTarget.device = &device;
renderTarget.views = metadata.eyes;
@ -148,7 +149,7 @@ void ext::vulkan::DeferredRenderMode::initialize( Device& device ) {
/*.usage = */ VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
/*.blend = */false,
/*.samples = */1,
/*.mips = */1
/*.mips = */mips,
});
metadata.attachments["id"] = attachments.id;
@ -347,19 +348,18 @@ void ext::vulkan::DeferredRenderMode::initialize( Device& device ) {
shader.aliasAttachment("scratch", this, VK_IMAGE_LAYOUT_GENERAL);
}
if ( false && settings::pipelines::culling ) {
if ( settings::pipelines::culling ) {
uf::stl::string computeShaderFilename = uf::io::resolveURI(uf::io::root+"/shaders/display/depth-pyramid/comp.spv");
blitter.material.attachShader(computeShaderFilename, uf::renderer::enums::Shader::COMPUTE, "depth-pyramid");
auto& shader = blitter.material.getShader("compute", "depth-pyramid");
auto attachment = this->getAttachment("depth");
auto mips = uf::vector::mips( pod::Vector2ui{ width, height } );
shader.setSpecializationConstants({
{ "MIPS", mips - 1 },
{ "MIPS", mips },
});
shader.setDescriptorCounts({
{ "inImage", mips - 1 },
{ "outImage", mips - 1 },
{ "inImage", mips },
{ "outImage", mips },
});
shader.aliasAttachment("depth", this);
@ -373,11 +373,11 @@ void ext::vulkan::DeferredRenderMode::initialize( Device& device ) {
VK_UNREGISTER_HANDLE(view);
}
::depthPyramidViews.clear();
::depthPyramidViews.resize(mips-1);
::depthPyramidViews.resize(mips);
shader.textures.clear();
for ( auto i = 1; i < mips; ++i ) {
auto& view = ::depthPyramidViews[i-1];
for ( auto i = 0; i < mips; ++i ) {
auto& view = ::depthPyramidViews[i];
VkImageViewCreateInfo viewCreateInfo = {};
viewCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
viewCreateInfo.pNext = NULL;
@ -389,22 +389,23 @@ void ext::vulkan::DeferredRenderMode::initialize( Device& device ) {
viewCreateInfo.viewType = source.viewType;
viewCreateInfo.format = source.format;
viewCreateInfo.image = source.image;
VK_CHECK_RESULT(vkCreateImageView(device.logicalDevice, &viewCreateInfo, nullptr, &view));
VK_REGISTER_HANDLE(view);
if ( i + 1 < mips ) {
auto& texture = shader.textures.emplace_back();
texture.aliasTexture( source );
texture.view = view;
texture.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
texture.updateDescriptors();
}
}
for ( auto i = 1; i < mips; ++i ) {
auto& view = ::depthPyramidViews[i-1];
for ( auto i = 0; i < mips; ++i ) {
auto& texture = shader.textures.emplace_back();
texture.aliasTexture( source );
texture.view = view;
texture.view = ::depthPyramidViews[i];
texture.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
texture.updateDescriptors();
}
for ( auto i = 0; i < mips; ++i ) {
auto& texture = shader.textures.emplace_back();
texture.aliasTexture( source );
texture.view = ::depthPyramidViews[i];
texture.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
texture.updateDescriptors();
}
@ -444,7 +445,7 @@ void ext::vulkan::DeferredRenderMode::initialize( Device& device ) {
}
}
if ( false && settings::pipelines::culling ) {
if ( settings::pipelines::culling ) {
descriptor.aux = uf::vector::mips( pod::Vector2ui{ width, height } );
descriptor.pipeline = "depth-pyramid";
descriptor.subpass = 0;
@ -473,18 +474,19 @@ void ext::vulkan::DeferredRenderMode::tick() {
rebuild = true;
renderTarget.initialize( *renderTarget.device );
if ( false && settings::pipelines::culling ) {
if ( settings::pipelines::culling ) {
auto& shader = blitter.material.getShader("compute", "depth-pyramid");
auto attachment = this->getAttachment("depth");
auto mips = uf::vector::mips( pod::Vector2ui{ width, height } );
shader.setSpecializationConstants({
{ "MIPS", mips - 1 },
{ "MIPS", mips },
});
shader.setDescriptorCounts({
{ "inImage", mips - 1 },
{ "outImage", mips - 1 },
{ "inImage", mips },
{ "outImage", mips },
});
shader.aliasAttachment("depth", this);
ext::vulkan::Texture2D source; source.aliasAttachment( this->getAttachment("depthPyramid") );
source.sampler.descriptor.reduction.enabled = true;
source.sampler.descriptor.reduction.mode = VK_SAMPLER_REDUCTION_MODE_MIN;
@ -494,11 +496,11 @@ void ext::vulkan::DeferredRenderMode::tick() {
VK_UNREGISTER_HANDLE(view);
}
::depthPyramidViews.clear();
::depthPyramidViews.resize(mips-1);
::depthPyramidViews.resize(mips);
shader.textures.clear();
for ( auto i = 1; i < mips; ++i ) {
auto& view = ::depthPyramidViews[i-1];
for ( auto i = 0; i < mips; ++i ) {
auto& view = ::depthPyramidViews[i];
VkImageViewCreateInfo viewCreateInfo = {};
viewCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
viewCreateInfo.pNext = NULL;
@ -510,22 +512,24 @@ void ext::vulkan::DeferredRenderMode::tick() {
viewCreateInfo.viewType = source.viewType;
viewCreateInfo.format = source.format;
viewCreateInfo.image = source.image;
VK_CHECK_RESULT(vkCreateImageView(device->logicalDevice, &viewCreateInfo, nullptr, &view));
VK_REGISTER_HANDLE(view);
if ( i + 1 < mips ) {
auto& texture = shader.textures.emplace_back();
texture.aliasTexture( source );
texture.view = view;
texture.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
texture.updateDescriptors();
}
}
for ( auto i = 1; i < mips; ++i ) {
auto& view = ::depthPyramidViews[i-1];
for ( auto i = 0; i < mips; ++i ) {
auto& texture = shader.textures.emplace_back();
texture.aliasTexture( source );
texture.view = view;
texture.view = ::depthPyramidViews[i];
texture.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
texture.updateDescriptors();
}
for ( auto i = 0; i < mips; ++i ) {
auto& texture = shader.textures.emplace_back();
texture.aliasTexture( source );
texture.view = ::depthPyramidViews[i];
texture.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
texture.updateDescriptors();
}
@ -588,7 +592,7 @@ void ext::vulkan::DeferredRenderMode::tick() {
}
}
if ( false && settings::pipelines::culling ) {
if ( settings::pipelines::culling ) {
descriptor.aux = uf::vector::mips( pod::Vector2ui{ width, height } );
descriptor.pipeline = "depth-pyramid";
descriptor.subpass = 0;
@ -896,55 +900,39 @@ void ext::vulkan::DeferredRenderMode::createCommandBuffers( const uf::stl::vecto
}
// construct depth-pyramid
#if 0
#if 1
if ( settings::pipelines::culling && blitter.material.hasShader("compute", "depth-pyramid") ) {
auto& shader = blitter.material.getShader("compute", "depth-pyramid");
// auto mips = attachment.descriptor.mips; // uf::vector::mips( pod::Vector2ui{ renderTarget.width, renderTarget.height } );
auto mips = uf::vector::mips( pod::Vector2ui{ width, height } );
ext::vulkan::GraphicDescriptor descriptor = blitter.descriptor;
descriptor.renderMode = "";
descriptor.aux = uf::vector::mips( pod::Vector2ui{ width, height } );
descriptor.aux = mips;
descriptor.pipeline = "depth-pyramid";
descriptor.bind.width = width;
descriptor.bind.height = height;
descriptor.bind.depth = metadata.eyes;
descriptor.bind.point = VK_PIPELINE_BIND_POINT_COMPUTE;
descriptor.subpass = 0;
/*
// transition attachments to general attachments for imageStore
VkImageSubresourceRange subresourceRange;
subresourceRange.baseMipLevel = 0;
subresourceRange.levelCount = 1;
subresourceRange.baseArrayLayer = 0;
subresourceRange.layerCount = mips;
subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
subresourceRange.layerCount = this->metadata.eyes;
auto& attachment = this->getAttachment("depth");
uf::renderer::Texture::setImageLayout( commandBuffer, attachment.image, VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL, subresourceRange );
*/
// dispatch compute shader
VkMemoryBarrier memoryBarrier{VK_STRUCTURE_TYPE_MEMORY_BARRIER};
memoryBarrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
memoryBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
for ( auto i = 0; i < mips - 1; ++i ) {
descriptor.bind.width = width >> i;
descriptor.bind.height = height >> i;
if ( descriptor.bind.width < 1 ) descriptor.bind.width = 1;
if ( descriptor.bind.height < 1 ) descriptor.bind.height = 1;
device->UF_CHECKPOINT_MARK( commandBuffer, pod::Checkpoint::GENERIC, "setImageLayout" );
::transitionAttachmentsTo( this, shader, commandBuffer );
for ( auto i = 0; i < mips; ++i ) {
// for some reason it dispatches at half the width without offsetting back...
descriptor.bind.width = std::max(1u, width >> (i - 1));
descriptor.bind.height = std::max(1u, height >> (i - 1));
blitter.record(commandBuffer, descriptor, 0, i);
blitter.record(commandBuffer, descriptor, 0, i, frame);
vkCmdPipelineBarrier( commandBuffer, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_FLAGS_NONE, 1, &memoryBarrier, 0, NULL, 0, NULL );
}
/*
// transition attachments to general attachments for imageStore
uf::renderer::Texture::setImageLayout( commandBuffer, attachment.image, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL, subresourceRange );
*/
device->UF_CHECKPOINT_MARK( commandBuffer, pod::Checkpoint::GENERIC, "setImageLayout" );
::transitionAttachmentsFrom( this, shader, commandBuffer );
}
#endif
// post-renderpass commands
@ -968,7 +956,6 @@ void ext::vulkan::DeferredRenderMode::createCommandBuffers( const uf::stl::vecto
uf::renderer::Texture::setImageLayout( commandBuffer, attachment.image, VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, imageMemoryBarrier.subresourceRange );
#endif
for ( size_t eye = 0; eye < metadata.eyes; ++eye ) {
texture.generateMipmaps(commandBuffer, eye);
}