From f152f26cd15fb77cd4fdfedad5602758619bba89 Mon Sep 17 00:00:00 2001 From: hydrogendeuteride Date: Tue, 2 Dec 2025 23:32:10 +0900 Subject: [PATCH] ADD: emissive, occlusion --- shaders/deferred_lighting.frag | 8 ++++- shaders/deferred_lighting_nort.frag | 8 ++++- shaders/gbuffer.frag | 9 +++++ shaders/input_structures.glsl | 4 ++- shaders/mesh.frag | 13 ++++++- src/core/assets/manager.cpp | 39 ++++++++++++++++++++- src/core/assets/manager.h | 4 +++ src/core/device/swapchain.cpp | 6 ++++ src/core/device/swapchain.h | 2 ++ src/core/engine.cpp | 5 +-- src/render/graph/graph.cpp | 12 +++++++ src/render/graph/graph.h | 1 + src/render/materials.cpp | 11 ++++-- src/render/materials.h | 4 +++ src/render/passes/geometry.cpp | 12 +++++-- src/render/passes/geometry.h | 2 ++ src/render/passes/lighting.cpp | 10 ++++-- src/render/passes/lighting.h | 4 ++- src/scene/vk_loader.cpp | 54 +++++++++++++++++++++++++++-- texture_compression.py | 36 +++++++++++++++---- 20 files changed, 219 insertions(+), 25 deletions(-) diff --git a/shaders/deferred_lighting.frag b/shaders/deferred_lighting.frag index b87292d..894d3ab 100644 --- a/shaders/deferred_lighting.frag +++ b/shaders/deferred_lighting.frag @@ -11,6 +11,7 @@ layout(location=0) out vec4 outColor; layout(set=1, binding=0) uniform sampler2D posTex; layout(set=1, binding=1) uniform sampler2D normalTex; layout(set=1, binding=2) uniform sampler2D albedoTex; +layout(set=1, binding=3) uniform sampler2D extraTex; layout(set=2, binding=0) uniform sampler2D shadowTex[4]; // TLAS for ray query (optional, guarded by sceneData.rtOptions.x) #ifdef GL_EXT_ray_query @@ -279,6 +280,10 @@ void main(){ vec3 albedo = albedoSample.rgb; float metallic = clamp(albedoSample.a, 0.0, 1.0); + vec4 extraSample = texture(extraTex, inUV); + float ao = extraSample.x; + vec3 emissive = extraSample.yzw; + vec3 camPos = vec3(inverse(sceneData.view)[3]); vec3 V = normalize(camPos - pos); @@ -340,7 +345,8 @@ void main(){ vec3 specIBL = prefiltered * (F0 * brdf.x + brdf.y); vec3 diffIBL = (1.0 - metallic) * albedo * sh_eval_irradiance(N); - vec3 color = direct + diffIBL + specIBL; + vec3 indirect = diffIBL + specIBL; + vec3 color = direct + indirect * ao + emissive; outColor = vec4(color, 1.0); } diff --git a/shaders/deferred_lighting_nort.frag b/shaders/deferred_lighting_nort.frag index 5215297..f1f029e 100644 --- a/shaders/deferred_lighting_nort.frag +++ b/shaders/deferred_lighting_nort.frag @@ -10,6 +10,7 @@ layout(location=0) out vec4 outColor; layout(set=1, binding=0) uniform sampler2D posTex; layout(set=1, binding=1) uniform sampler2D normalTex; layout(set=1, binding=2) uniform sampler2D albedoTex; +layout(set=1, binding=3) uniform sampler2D extraTex; layout(set=2, binding=0) uniform sampler2D shadowTex[4]; // Tunables for shadow quality and blending @@ -208,6 +209,10 @@ void main(){ vec3 albedo = albedoSample.rgb; float metallic = clamp(albedoSample.a, 0.0, 1.0); + vec4 extraSample = texture(extraTex, inUV); + float ao = extraSample.x; + vec3 emissive = extraSample.yzw; + vec3 camPos = vec3(inverse(sceneData.view)[3]); vec3 V = normalize(camPos - pos); @@ -235,7 +240,8 @@ void main(){ vec3 specIBL = prefiltered * (F0 * brdf.x + brdf.y); vec3 diffIBL = (1.0 - metallic) * albedo * sh_eval_irradiance(N); - vec3 color = direct + diffIBL + specIBL; + vec3 indirect = diffIBL + specIBL; + vec3 color = direct + indirect * ao + emissive; outColor = vec4(color, 1.0); } diff --git a/shaders/gbuffer.frag b/shaders/gbuffer.frag index 28ad70c..8dff01a 100644 --- a/shaders/gbuffer.frag +++ b/shaders/gbuffer.frag @@ -13,6 +13,7 @@ layout(location = 0) out vec4 outPos; layout(location = 1) out vec4 outNorm; layout(location = 2) out vec4 outAlbedo; layout(location = 3) out uint outObjectID; +layout(location = 4) out vec4 outExtra; // Keep push constants layout in sync with mesh.vert / GPUDrawPushConstants struct Vertex { @@ -58,5 +59,13 @@ void main() { outPos = vec4(inWorldPos, 1.0); outNorm = vec4(Nw, roughness); outAlbedo = vec4(albedo, metallic); + // Extra G-buffer: x = AO, yzw = emissive + float aoStrength = clamp(materialData.extra[0].y, 0.0, 1.0); + float aoTex = texture(occlusionTex, inUV).r; + float ao = 1.0 - aoStrength + aoStrength * aoTex; + vec3 emissiveFactor = materialData.extra[1].rgb; + vec3 emissiveTex = texture(emissiveTex, inUV).rgb; + vec3 emissive = emissiveTex * emissiveFactor; + outExtra = vec4(ao, emissive); outObjectID = PushConstants.objectID; } diff --git a/shaders/input_structures.glsl b/shaders/input_structures.glsl index abf20ce..5ba9ca5 100644 --- a/shaders/input_structures.glsl +++ b/shaders/input_structures.glsl @@ -44,4 +44,6 @@ layout(set = 1, binding = 0) uniform GLTFMaterialData{ layout(set = 1, binding = 1) uniform sampler2D colorTex; layout(set = 1, binding = 2) uniform sampler2D metalRoughTex; -layout(set = 1, binding = 3) uniform sampler2D normalMap; // tangent-space normal, UNORM +layout(set = 1, binding = 3) uniform sampler2D normalMap; // tangent-space normal, UNORM +layout(set = 1, binding = 4) uniform sampler2D occlusionTex; // occlusion (R channel) +layout(set = 1, binding = 5) uniform sampler2D emissiveTex; // emissive (RGB, sRGB) diff --git a/shaders/mesh.frag b/shaders/mesh.frag index 4d7a68d..b6565c7 100644 --- a/shaders/mesh.frag +++ b/shaders/mesh.frag @@ -61,7 +61,18 @@ void main() vec3 specIBL = prefiltered * (F0 * brdf.x + brdf.y); vec3 diffIBL = (1.0 - metallic) * albedo * sh_eval_irradiance(N); - vec3 color = direct + diffIBL + specIBL; + // Ambient occlusion from texture + strength (indirect only) + float aoStrength = clamp(materialData.extra[0].y, 0.0, 1.0); + float aoTex = texture(occlusionTex, inUV).r; + float ao = 1.0 - aoStrength + aoStrength * aoTex; + + // Emissive from texture and factor + vec3 emissiveFactor = materialData.extra[1].rgb; + vec3 emissiveTex = texture(emissiveTex, inUV).rgb; + vec3 emissive = emissiveTex * emissiveFactor; + + vec3 indirect = diffIBL + specIBL; + vec3 color = direct + indirect * ao + emissive; // Alpha from baseColor texture and factor (glTF spec) float alpha = clamp(baseTex.a * materialData.colorFactors.a, 0.0, 1.0); diff --git a/src/core/assets/manager.cpp b/src/core/assets/manager.cpp index 549466c..7eac8a4 100644 --- a/src/core/assets/manager.cpp +++ b/src/core/assets/manager.cpp @@ -208,12 +208,16 @@ std::shared_ptr AssetManager::createMesh(const MeshCreateInfo &info) AllocatedBuffer matBuffer = createMaterialBufferWithConstants(opt.constants); GLTFMetallic_Roughness::MaterialResources res{}; - res.colorImage = _engine->_errorCheckerboardImage; // visible fallback for albedo + res.colorImage = _engine->_errorCheckerboardImage; res.colorSampler = _engine->_samplerManager->defaultLinear(); res.metalRoughImage = _engine->_whiteImage; res.metalRoughSampler = _engine->_samplerManager->defaultLinear(); res.normalImage = _engine->_flatNormalImage; res.normalSampler = _engine->_samplerManager->defaultLinear(); + res.occlusionImage = _engine->_whiteImage; + res.occlusionSampler = _engine->_samplerManager->defaultLinear(); + res.emissiveImage = _engine->_blackImage; + res.emissiveSampler = _engine->_samplerManager->defaultLinear(); res.dataBuffer = matBuffer.buffer; res.dataBufferOffset = 0; @@ -267,6 +271,27 @@ std::shared_ptr AssetManager::createMesh(const MeshCreateInfo &info) cache->watchBinding(handle, mat->data.materialSet, 3u, samp, _engine->_flatNormalImage.imageView); } } + if (!opt.occlusionPath.empty()) + { + auto key = buildKey(opt.occlusionPath, opt.occlusionSRGB); + key.channels = TextureCache::TextureKey::ChannelsHint::R; + if (key.hash != 0) + { + VkSampler samp = _engine->_samplerManager->defaultLinear(); + auto handle = cache->request(key, samp); + cache->watchBinding(handle, mat->data.materialSet, 4u, samp, _engine->_whiteImage.imageView); + } + } + if (!opt.emissivePath.empty()) + { + auto key = buildKey(opt.emissivePath, opt.emissiveSRGB); + if (key.hash != 0) + { + VkSampler samp = _engine->_samplerManager->defaultLinear(); + auto handle = cache->request(key, samp); + cache->watchBinding(handle, mat->data.materialSet, 5u, samp, _engine->_blackImage.imageView); + } + } } mesh = createMesh(info.name, vertsSpan, indsSpan, mat); @@ -466,6 +491,10 @@ AllocatedBuffer AssetManager::createMaterialBufferWithConstants( { matConstants->extra[0].x = 1.0f; // normal scale default } + if (matConstants->extra[0].y == 0.0f) + { + matConstants->extra[0].y = 1.0f; + } // Ensure writes are visible on non-coherent memory vmaFlushAllocation(_engine->_deviceManager->allocator(), matBuffer.allocation, 0, sizeof(GLTFMetallic_Roughness::MaterialConstants)); @@ -525,6 +554,10 @@ std::shared_ptr AssetManager::createMesh(const std::string &name, matResources.metalRoughSampler = _engine->_samplerManager->defaultLinear(); matResources.normalImage = _engine->_flatNormalImage; matResources.normalSampler = _engine->_samplerManager->defaultLinear(); + matResources.occlusionImage = _engine->_whiteImage; + matResources.occlusionSampler = _engine->_samplerManager->defaultLinear(); + matResources.emissiveImage = _engine->_blackImage; + matResources.emissiveSampler = _engine->_samplerManager->defaultLinear(); AllocatedBuffer matBuffer = createMaterialBufferWithConstants({}); matResources.dataBuffer = matBuffer.buffer; @@ -569,6 +602,10 @@ std::shared_ptr AssetManager::createMaterialFromConstants( res.metalRoughSampler = _engine->_samplerManager->defaultLinear(); res.normalImage = _engine->_flatNormalImage; res.normalSampler = _engine->_samplerManager->defaultLinear(); + res.occlusionImage = _engine->_whiteImage; + res.occlusionSampler = _engine->_samplerManager->defaultLinear(); + res.emissiveImage = _engine->_blackImage; + res.emissiveSampler = _engine->_samplerManager->defaultLinear(); AllocatedBuffer buf = createMaterialBufferWithConstants(constants); res.dataBuffer = buf.buffer; diff --git a/src/core/assets/manager.h b/src/core/assets/manager.h index f003403..95929f7 100644 --- a/src/core/assets/manager.h +++ b/src/core/assets/manager.h @@ -28,10 +28,14 @@ public: // Optional tangent-space normal map for PBR (placeholder; not wired yet) // When enabled later, this will be sampled in shaders and requires tangents. std::string normalPath; + std::string occlusionPath; + std::string emissivePath; bool albedoSRGB = true; bool metalRoughSRGB = false; bool normalSRGB = false; // normal maps are typically non-sRGB + bool occlusionSRGB = false; + bool emissiveSRGB = true; GLTFMetallic_Roughness::MaterialConstants constants{}; diff --git a/src/core/device/swapchain.cpp b/src/core/device/swapchain.cpp index 6073e9d..85900aa 100644 --- a/src/core/device/swapchain.cpp +++ b/src/core/device/swapchain.cpp @@ -70,6 +70,8 @@ void SwapchainManager::init_swapchain() VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT); _gBufferAlbedo = _resourceManager->create_image(drawImageExtent, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT); + _gBufferExtra = _resourceManager->create_image(drawImageExtent, VK_FORMAT_R16G16B16A16_SFLOAT, + VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT); _idBuffer = _resourceManager->create_image(drawImageExtent, VK_FORMAT_R32_UINT, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | @@ -85,6 +87,7 @@ void SwapchainManager::init_swapchain() _resourceManager->destroy_image(_gBufferPosition); _resourceManager->destroy_image(_gBufferNormal); _resourceManager->destroy_image(_gBufferAlbedo); + _resourceManager->destroy_image(_gBufferExtra); _resourceManager->destroy_image(_idBuffer); }); }; @@ -196,6 +199,8 @@ void SwapchainManager::resize_swapchain(struct SDL_Window *window) VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT); _gBufferAlbedo = _resourceManager->create_image(drawImageExtent, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT); + _gBufferExtra = _resourceManager->create_image(drawImageExtent, VK_FORMAT_R16G16B16A16_SFLOAT, + VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT); _idBuffer = _resourceManager->create_image(drawImageExtent, VK_FORMAT_R32_UINT, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | @@ -211,6 +216,7 @@ void SwapchainManager::resize_swapchain(struct SDL_Window *window) _resourceManager->destroy_image(_gBufferPosition); _resourceManager->destroy_image(_gBufferNormal); _resourceManager->destroy_image(_gBufferAlbedo); + _resourceManager->destroy_image(_gBufferExtra); _resourceManager->destroy_image(_idBuffer); }); diff --git a/src/core/device/swapchain.h b/src/core/device/swapchain.h index 4e253d0..c00e1c4 100644 --- a/src/core/device/swapchain.h +++ b/src/core/device/swapchain.h @@ -29,6 +29,7 @@ public: AllocatedImage gBufferPosition() const { return _gBufferPosition; } AllocatedImage gBufferNormal() const { return _gBufferNormal; } AllocatedImage gBufferAlbedo() const { return _gBufferAlbedo; } + AllocatedImage gBufferExtra() const { return _gBufferExtra; } AllocatedImage idBuffer() const { return _idBuffer; } VkExtent2D windowExtent() const { return _windowExtent; } @@ -51,6 +52,7 @@ private: AllocatedImage _gBufferPosition = {}; AllocatedImage _gBufferNormal = {}; AllocatedImage _gBufferAlbedo = {}; + AllocatedImage _gBufferExtra = {}; AllocatedImage _idBuffer = {}; DeletionQueue _deletionQueue; diff --git a/src/core/engine.cpp b/src/core/engine.cpp index 47324f3..33a4342 100644 --- a/src/core/engine.cpp +++ b/src/core/engine.cpp @@ -613,6 +613,7 @@ void VulkanEngine::draw() RGImageHandle hGBufferPosition = _renderGraph->import_gbuffer_position(); RGImageHandle hGBufferNormal = _renderGraph->import_gbuffer_normal(); RGImageHandle hGBufferAlbedo = _renderGraph->import_gbuffer_albedo(); + RGImageHandle hGBufferExtra = _renderGraph->import_gbuffer_extra(); RGImageHandle hSwapchain = _renderGraph->import_swapchain_image(swapchainImageIndex); // For debug overlays (IBL volumes), re-use HDR draw image as a color target. RGImageHandle hDebugColor = hDraw; @@ -656,7 +657,7 @@ void VulkanEngine::draw() if (auto *geometry = _renderPassManager->getPass()) { RGImageHandle hID = _renderGraph->import_id_buffer(); - geometry->register_graph(_renderGraph.get(), hGBufferPosition, hGBufferNormal, hGBufferAlbedo, hID, hDepth); + geometry->register_graph(_renderGraph.get(), hGBufferPosition, hGBufferNormal, hGBufferAlbedo, hGBufferExtra, hID, hDepth); // If ID-buffer picking is enabled and a pick was requested this frame, // add a small transfer pass to read back 1 pixel from the ID buffer. @@ -719,7 +720,7 @@ void VulkanEngine::draw() } if (auto *lighting = _renderPassManager->getPass()) { - lighting->register_graph(_renderGraph.get(), hDraw, hGBufferPosition, hGBufferNormal, hGBufferAlbedo, + lighting->register_graph(_renderGraph.get(), hDraw, hGBufferPosition, hGBufferNormal, hGBufferAlbedo, hGBufferExtra, std::span(hShadowCascades.data(), hShadowCascades.size())); } diff --git a/src/render/graph/graph.cpp b/src/render/graph/graph.cpp index 513f0fd..b921003 100644 --- a/src/render/graph/graph.cpp +++ b/src/render/graph/graph.cpp @@ -956,6 +956,18 @@ RGImageHandle RenderGraph::import_gbuffer_albedo() return import_image(d); } +RGImageHandle RenderGraph::import_gbuffer_extra() +{ + RGImportedImageDesc d{}; + d.name = "gBuffer.extra"; + d.image = _context->getSwapchain()->gBufferExtra().image; + d.imageView = _context->getSwapchain()->gBufferExtra().imageView; + d.format = _context->getSwapchain()->gBufferExtra().imageFormat; + d.extent = _context->getDrawExtent(); + d.currentLayout = VK_IMAGE_LAYOUT_UNDEFINED; + return import_image(d); +} + RGImageHandle RenderGraph::import_id_buffer() { RGImportedImageDesc d{}; diff --git a/src/render/graph/graph.h b/src/render/graph/graph.h index 14aca92..932ec6f 100644 --- a/src/render/graph/graph.h +++ b/src/render/graph/graph.h @@ -56,6 +56,7 @@ struct Pass; // fwd RGImageHandle import_gbuffer_position(); RGImageHandle import_gbuffer_normal(); RGImageHandle import_gbuffer_albedo(); + RGImageHandle import_gbuffer_extra(); RGImageHandle import_id_buffer(); RGImageHandle import_swapchain_image(uint32_t index); void add_present_chain(RGImageHandle sourceDraw, diff --git a/src/render/materials.cpp b/src/render/materials.cpp index 1177fb6..a0e0213 100644 --- a/src/render/materials.cpp +++ b/src/render/materials.cpp @@ -20,6 +20,8 @@ void GLTFMetallic_Roughness::build_pipelines(VulkanEngine *engine) layoutBuilder.add_binding(1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); layoutBuilder.add_binding(2, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); layoutBuilder.add_binding(3, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); + layoutBuilder.add_binding(4, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); + layoutBuilder.add_binding(5, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); materialLayout = layoutBuilder.build(engine->_deviceManager->device(), VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, @@ -93,9 +95,10 @@ void GLTFMetallic_Roughness::build_pipelines(VulkanEngine *engine) engine->_swapchainManager->gBufferPosition().imageFormat, engine->_swapchainManager->gBufferNormal().imageFormat, engine->_swapchainManager->gBufferAlbedo().imageFormat, - engine->_swapchainManager->idBuffer().imageFormat + engine->_swapchainManager->idBuffer().imageFormat, + engine->_swapchainManager->gBufferExtra().imageFormat }; - b.set_color_attachment_formats(std::span(gFormats, 4)); + b.set_color_attachment_formats(std::span(gFormats, 5)); b.set_depth_format(engine->_swapchainManager->depthImage().imageFormat); }; engine->_pipelineManager->registerGraphics("mesh.gbuffer", gbufferInfo); @@ -139,6 +142,10 @@ MaterialInstance GLTFMetallic_Roughness::write_material(VkDevice device, Materia VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); writer.write_image(3, resources.normalImage.imageView, resources.normalSampler, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); + writer.write_image(4, resources.occlusionImage.imageView, resources.occlusionSampler, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); + writer.write_image(5, resources.emissiveImage.imageView, resources.emissiveSampler, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); writer.update_set(device, matData.materialSet); diff --git a/src/render/materials.h b/src/render/materials.h index 7b135a2..136d4d7 100644 --- a/src/render/materials.h +++ b/src/render/materials.h @@ -30,6 +30,10 @@ struct GLTFMetallic_Roughness VkSampler metalRoughSampler; AllocatedImage normalImage; VkSampler normalSampler; + AllocatedImage occlusionImage; + VkSampler occlusionSampler; + AllocatedImage emissiveImage; + VkSampler emissiveSampler; VkBuffer dataBuffer; uint32_t dataBufferOffset; }; diff --git a/src/render/passes/geometry.cpp b/src/render/passes/geometry.cpp index 3cf217a..bf4efc5 100644 --- a/src/render/passes/geometry.cpp +++ b/src/render/passes/geometry.cpp @@ -69,10 +69,12 @@ void GeometryPass::register_graph(RenderGraph *graph, RGImageHandle gbufferPosition, RGImageHandle gbufferNormal, RGImageHandle gbufferAlbedo, + RGImageHandle gbufferExtra, RGImageHandle idHandle, RGImageHandle depthHandle) { if (!graph || !gbufferPosition.valid() || !gbufferNormal.valid() || !gbufferAlbedo.valid() || + !gbufferExtra.valid() || !idHandle.valid() || !depthHandle.valid()) { return; @@ -81,7 +83,7 @@ void GeometryPass::register_graph(RenderGraph *graph, graph->add_pass( "Geometry", RGPassType::Graphics, - [gbufferPosition, gbufferNormal, gbufferAlbedo, idHandle, depthHandle](RGPassBuilder &builder, EngineContext *ctx) + [gbufferPosition, gbufferNormal, gbufferAlbedo, gbufferExtra, idHandle, depthHandle](RGPassBuilder &builder, EngineContext *ctx) { VkClearValue clear{}; clear.color = {{0.f, 0.f, 0.f, 0.f}}; @@ -89,6 +91,9 @@ void GeometryPass::register_graph(RenderGraph *graph, builder.write_color(gbufferPosition, true, clear); builder.write_color(gbufferNormal, true, clear); builder.write_color(gbufferAlbedo, true, clear); + VkClearValue clearExtra{}; + clearExtra.color = {{1.f, 0.f, 0.f, 0.f}}; // AO=1, emissive=0 + builder.write_color(gbufferExtra, true, clearExtra); VkClearValue clearID{}; clearID.color.uint32[0] = 0u; builder.write_color(idHandle, true, clearID); @@ -123,11 +128,11 @@ void GeometryPass::register_graph(RenderGraph *graph, builder.read_buffer(b, RGBufferUsage::StorageRead, 0, "geom.vertex"); } }, - [this, gbufferPosition, gbufferNormal, gbufferAlbedo, idHandle, depthHandle](VkCommandBuffer cmd, + [this, gbufferPosition, gbufferNormal, gbufferAlbedo, gbufferExtra, idHandle, depthHandle](VkCommandBuffer cmd, const RGPassResources &res, EngineContext *ctx) { - draw_geometry(cmd, ctx, res, gbufferPosition, gbufferNormal, gbufferAlbedo, idHandle, depthHandle); + draw_geometry(cmd, ctx, res, gbufferPosition, gbufferNormal, gbufferAlbedo, gbufferExtra, idHandle, depthHandle); }); } @@ -137,6 +142,7 @@ void GeometryPass::draw_geometry(VkCommandBuffer cmd, RGImageHandle gbufferPosition, RGImageHandle gbufferNormal, RGImageHandle gbufferAlbedo, + RGImageHandle /*gbufferExtra*/, RGImageHandle /*idHandle*/, RGImageHandle depthHandle) const { diff --git a/src/render/passes/geometry.h b/src/render/passes/geometry.h index c5e0b23..221e58f 100644 --- a/src/render/passes/geometry.h +++ b/src/render/passes/geometry.h @@ -18,6 +18,7 @@ public: RGImageHandle gbufferPosition, RGImageHandle gbufferNormal, RGImageHandle gbufferAlbedo, + RGImageHandle gbufferExtra, RGImageHandle idHandle, RGImageHandle depthHandle); @@ -30,6 +31,7 @@ private: RGImageHandle gbufferPosition, RGImageHandle gbufferNormal, RGImageHandle gbufferAlbedo, + RGImageHandle gbufferExtra, RGImageHandle idHandle, RGImageHandle depthHandle) const; }; diff --git a/src/render/passes/lighting.cpp b/src/render/passes/lighting.cpp index 7f19b32..9464034 100644 --- a/src/render/passes/lighting.cpp +++ b/src/render/passes/lighting.cpp @@ -39,6 +39,7 @@ void LightingPass::init(EngineContext *context) builder.add_binding(0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); builder.add_binding(1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); builder.add_binding(2, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); + builder.add_binding(3, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); _gBufferInputDescriptorLayout = builder.build( _context->getDevice()->device(), VK_SHADER_STAGE_FRAGMENT_BIT, nullptr, VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT); @@ -55,6 +56,8 @@ void LightingPass::init(EngineContext *context) VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); writer.write_image(2, _context->getSwapchain()->gBufferAlbedo().imageView, _context->getSamplers()->defaultLinear(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); + writer.write_image(3, _context->getSwapchain()->gBufferExtra().imageView, _context->getSamplers()->defaultLinear(), + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); writer.update_set(_context->getDevice()->device(), _gBufferInputDescriptorSet); } @@ -136,9 +139,11 @@ void LightingPass::register_graph(RenderGraph *graph, RGImageHandle gbufferPosition, RGImageHandle gbufferNormal, RGImageHandle gbufferAlbedo, + RGImageHandle gbufferExtra, std::span shadowCascades) { - if (!graph || !drawHandle.valid() || !gbufferPosition.valid() || !gbufferNormal.valid() || !gbufferAlbedo.valid()) + if (!graph || !drawHandle.valid() || !gbufferPosition.valid() || !gbufferNormal.valid() || !gbufferAlbedo.valid() || + !gbufferExtra.valid()) { return; } @@ -146,11 +151,12 @@ void LightingPass::register_graph(RenderGraph *graph, graph->add_pass( "Lighting", RGPassType::Graphics, - [drawHandle, gbufferPosition, gbufferNormal, gbufferAlbedo, shadowCascades](RGPassBuilder &builder, EngineContext *) + [drawHandle, gbufferPosition, gbufferNormal, gbufferAlbedo, gbufferExtra, shadowCascades](RGPassBuilder &builder, EngineContext *) { builder.read(gbufferPosition, RGImageUsage::SampledFragment); builder.read(gbufferNormal, RGImageUsage::SampledFragment); builder.read(gbufferAlbedo, RGImageUsage::SampledFragment); + builder.read(gbufferExtra, RGImageUsage::SampledFragment); for (size_t i = 0; i < shadowCascades.size(); ++i) { if (shadowCascades[i].valid()) builder.read(shadowCascades[i], RGImageUsage::SampledFragment); diff --git a/src/render/passes/lighting.h b/src/render/passes/lighting.h index aa6cf7e..5c8ae66 100644 --- a/src/render/passes/lighting.h +++ b/src/render/passes/lighting.h @@ -19,7 +19,9 @@ public: RGImageHandle drawHandle, RGImageHandle gbufferPosition, RGImageHandle gbufferNormal, - RGImageHandle gbufferAlbedo, std::span shadowCascades); + RGImageHandle gbufferAlbedo, + RGImageHandle gbufferExtra, + std::span shadowCascades); private: EngineContext *_context = nullptr; diff --git a/src/scene/vk_loader.cpp b/src/scene/vk_loader.cpp index abd0634..92fdbbe 100644 --- a/src/scene/vk_loader.cpp +++ b/src/scene/vk_loader.cpp @@ -353,9 +353,7 @@ std::optional > loadGltf(VulkanEngine *engine, std:: materials.push_back(newMat); file.materials[mat.name.c_str()] = newMat; - GLTFMetallic_Roughness::MaterialConstants constants; - // Defaults - constants.extra[0].x = 1.0f; // normalScale + GLTFMetallic_Roughness::MaterialConstants constants{}; constants.colorFactors.x = mat.pbrData.baseColorFactor[0]; constants.colorFactors.y = mat.pbrData.baseColorFactor[1]; constants.colorFactors.z = mat.pbrData.baseColorFactor[2]; @@ -363,6 +361,11 @@ std::optional > loadGltf(VulkanEngine *engine, std:: constants.metal_rough_factors.x = mat.pbrData.metallicFactor; constants.metal_rough_factors.y = mat.pbrData.roughnessFactor; + constants.extra[0].x = 1.0f; + constants.extra[0].y = mat.occlusionTexture.has_value() ? mat.occlusionTexture->strength : 1.0f; + constants.extra[1].x = mat.emissiveFactor[0]; + constants.extra[1].y = mat.emissiveFactor[1]; + constants.extra[1].z = mat.emissiveFactor[2]; // write material parameters to buffer sceneMaterialConstants[data_index] = constants; @@ -380,6 +383,10 @@ std::optional > loadGltf(VulkanEngine *engine, std:: materialResources.metalRoughSampler = engine->_samplerManager->defaultLinear(); materialResources.normalImage = engine->_flatNormalImage; materialResources.normalSampler = engine->_samplerManager->defaultLinear(); + materialResources.occlusionImage = engine->_whiteImage; + materialResources.occlusionSampler = engine->_samplerManager->defaultLinear(); + materialResources.emissiveImage = engine->_blackImage; + materialResources.emissiveSampler = engine->_samplerManager->defaultLinear(); // set the uniform buffer for the material data materialResources.dataBuffer = file.materialDataBuffer.buffer; @@ -389,6 +396,8 @@ std::optional > loadGltf(VulkanEngine *engine, std:: TextureCache::TextureHandle hColor = TextureCache::InvalidHandle; TextureCache::TextureHandle hMRO = TextureCache::InvalidHandle; TextureCache::TextureHandle hNorm = TextureCache::InvalidHandle; + TextureCache::TextureHandle hOcc = TextureCache::InvalidHandle; + TextureCache::TextureHandle hEmissive = TextureCache::InvalidHandle; if (cache && mat.pbrData.baseColorTexture.has_value()) { @@ -418,6 +427,35 @@ std::optional > loadGltf(VulkanEngine *engine, std:: } } + if (cache && mat.occlusionTexture.has_value()) + { + const auto &tex = gltf.textures[mat.occlusionTexture->textureIndex]; + const size_t imgIndex = tex.imageIndex.value(); + const bool hasSampler = tex.samplerIndex.has_value(); + const VkSampler sampler = hasSampler ? file.samplers[tex.samplerIndex.value()] : engine->_samplerManager->defaultLinear(); + auto key = buildTextureKey(imgIndex, false); + key.channels = TextureCache::TextureKey::ChannelsHint::R; + if (key.hash != 0) + { + hOcc = cache->request(key, sampler); + materialResources.occlusionSampler = sampler; + } + } + + if (cache && mat.emissiveTexture.has_value()) + { + const auto &tex = gltf.textures[mat.emissiveTexture->textureIndex]; + const size_t imgIndex = tex.imageIndex.value(); + const bool hasSampler = tex.samplerIndex.has_value(); + const VkSampler sampler = hasSampler ? file.samplers[tex.samplerIndex.value()] : engine->_samplerManager->defaultLinear(); + auto key = buildTextureKey(imgIndex, true); + if (key.hash != 0) + { + hEmissive = cache->request(key, sampler); + materialResources.emissiveSampler = sampler; + } + } + if (cache && mat.normalTexture.has_value()) { const auto &tex = gltf.textures[mat.normalTexture.value().textureIndex]; @@ -456,6 +494,16 @@ std::optional > loadGltf(VulkanEngine *engine, std:: cache->watchBinding(hNorm, newMat->data.materialSet, 3u, materialResources.normalSampler, engine->_flatNormalImage.imageView); } + if (hOcc != TextureCache::InvalidHandle) + { + cache->watchBinding(hOcc, newMat->data.materialSet, 4u, materialResources.occlusionSampler, + engine->_whiteImage.imageView); + } + if (hEmissive != TextureCache::InvalidHandle) + { + cache->watchBinding(hEmissive, newMat->data.materialSet, 5u, materialResources.emissiveSampler, + engine->_blackImage.imageView); + } } data_index++; diff --git a/texture_compression.py b/texture_compression.py index e5c0505..c871329 100644 --- a/texture_compression.py +++ b/texture_compression.py @@ -9,9 +9,11 @@ except Exception: PIL_OK = False DEFAULT_SUFFIX = { - "albedo": ["_albedo", "_basecolor", "_base_colour", "_base_color", "_base", "baseColor", "BaseColor"], - "mr": ["_mr", "_orm", "_metalrough", "_metallicroughness", "metallicRoughness", "Metallic"], - "normal": ["_normal", "_norm", "_nrm", "_normalgl", "Normal"] + "albedo": ["_albedo", "_basecolor", "_base_colour", "_base_color", "_base", "baseColor", "BaseColor"], + "mr": ["_mr", "_orm", "_metalrough", "_metallicroughness", "metallicRoughness", "Metallic"], + "normal": ["_normal", "_norm", "_nrm", "_normalgl", "Normal"], + "occlusion": ["_occlusion", "_occ", "_ao"], + "emissive": ["_emissive", "_emission", "_emit"], } SUPPORTED_IMAGE_EXTS = {".png", ".jpg", ".jpeg", ".tga", ".tif", ".tiff"} @@ -34,8 +36,8 @@ def detect_role_by_suffix(stem, rx): return None def parse_gltf_roles(gltf_path: Path): - """glTF(.gltf JSON) to get image role""" - roles = {} # uri-> role (albedo/mr/normal) + """glTF(.gltf JSON) to get image role (albedo/mr/normal/occlusion/emissive)""" + roles = {} # uri -> role if not gltf_path.exists(): return roles if gltf_path.suffix.lower() == ".gltf": @@ -60,9 +62,16 @@ def parse_gltf_roles(gltf_path: Path): if not uri: return - prio = {"normal": 3, "albedo": 2, "mr": 1} + prio = { + "normal": 4, + "albedo": 3, + "emissive": 3, + "mr": 2, + "occlusion": 2, + } + new_prio = prio.get(role, 0) old = roles.get(uri) - if old is None or prio[role] > prio.get(old, 0): + if old is None or new_prio > prio.get(old, 0): roles[uri] = role for mat in materials: @@ -70,6 +79,8 @@ def parse_gltf_roles(gltf_path: Path): base = pbr.get("baseColorTexture", {}) mr = pbr.get("metallicRoughnessTexture", {}) nor = mat.get("normalTexture", {}) + occ = mat.get("occlusionTexture", {}) + emis = mat.get("emissiveTexture", {}) if "index" in base and base["index"] in tex_to_uri: mark(tex_to_uri[base["index"]], "albedo") @@ -77,6 +88,10 @@ def parse_gltf_roles(gltf_path: Path): mark(tex_to_uri[mr["index"]], "mr") if "index" in nor and nor["index"] in tex_to_uri: mark(tex_to_uri[nor["index"]], "normal") + if "index" in occ and occ["index"] in tex_to_uri: + mark(tex_to_uri[occ["index"]], "occlusion") + if "index" in emis and emis["index"] in tex_to_uri: + mark(tex_to_uri[emis["index"]], "emissive") return roles @@ -101,6 +116,9 @@ def decide_targets(role, albedo_target, img_path): return "bc5", "linear" if role == "mr": return "bc7", "linear" + if role == "occlusion": + # AO is data, not color + return "bc7", "linear" # albedo if albedo_target == "auto": if has_meaningful_alpha(img_path): @@ -177,6 +195,8 @@ def main(): help="albedo suffix CSV (Base: %s)" % ",".join(DEFAULT_SUFFIX["albedo"])) p.add_argument("--suffix-mr", default=",".join(DEFAULT_SUFFIX["mr"])) p.add_argument("--suffix-normal", default=",".join(DEFAULT_SUFFIX["normal"])) + p.add_argument("--suffix-occlusion", default=",".join(DEFAULT_SUFFIX["occlusion"])) + p.add_argument("--suffix-emissive", default=",".join(DEFAULT_SUFFIX["emissive"])) p.add_argument("--albedo-target", choices=["auto", "bc1", "bc3", "bc7"], default="bc7", help="albedo BC format(auto=non alpha BC1, alpha BC3)") p.add_argument("--uastc-quality", type=int, default=2, help="UASTC quality(0~4)") @@ -194,6 +214,8 @@ def main(): "albedo": build_suffix_regex([s.strip() for s in opts.suffix_albedo.split(",") if s.strip()]), "mr": build_suffix_regex([s.strip() for s in opts.suffix_mr.split(",") if s.strip()]), "normal": build_suffix_regex([s.strip() for s in opts.suffix_normal.split(",") if s.strip()]), + "occlusion": build_suffix_regex([s.strip() for s in opts.suffix_occlusion.split(",") if s.strip()]), + "emissive": build_suffix_regex([s.strip() for s in opts.suffix_emissive.split(",") if s.strip()]), } gltf_roles = {}