ADD: emissive, occlusion

This commit is contained in:
2025-12-02 23:32:10 +09:00
parent d5ae159f73
commit f152f26cd1
20 changed files with 219 additions and 25 deletions

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -45,3 +45,5 @@ 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 = 4) uniform sampler2D occlusionTex; // occlusion (R channel)
layout(set = 1, binding = 5) uniform sampler2D emissiveTex; // emissive (RGB, sRGB)

View File

@@ -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);

View File

@@ -208,12 +208,16 @@ std::shared_ptr<MeshAsset> 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<MeshAsset> 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<MeshAsset> 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<GLTFMaterial> 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;

View File

@@ -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{};

View File

@@ -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);
});

View File

@@ -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;

View File

@@ -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<GeometryPass>())
{
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<LightingPass>())
{
lighting->register_graph(_renderGraph.get(), hDraw, hGBufferPosition, hGBufferNormal, hGBufferAlbedo,
lighting->register_graph(_renderGraph.get(), hDraw, hGBufferPosition, hGBufferNormal, hGBufferAlbedo, hGBufferExtra,
std::span<RGImageHandle>(hShadowCascades.data(), hShadowCascades.size()));
}

View File

@@ -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{};

View File

@@ -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,

View File

@@ -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<VkFormat>(gFormats, 4));
b.set_color_attachment_formats(std::span<VkFormat>(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);

View File

@@ -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;
};

View File

@@ -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
{

View File

@@ -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;
};

View File

@@ -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<RGImageHandle> 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);

View File

@@ -19,7 +19,9 @@ public:
RGImageHandle drawHandle,
RGImageHandle gbufferPosition,
RGImageHandle gbufferNormal,
RGImageHandle gbufferAlbedo, std::span<RGImageHandle> shadowCascades);
RGImageHandle gbufferAlbedo,
RGImageHandle gbufferExtra,
std::span<RGImageHandle> shadowCascades);
private:
EngineContext *_context = nullptr;

View File

@@ -353,9 +353,7 @@ std::optional<std::shared_ptr<LoadedGLTF> > 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<std::shared_ptr<LoadedGLTF> > 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<std::shared_ptr<LoadedGLTF> > 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<std::shared_ptr<LoadedGLTF> > 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<std::shared_ptr<LoadedGLTF> > 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<std::shared_ptr<LoadedGLTF> > 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++;

View File

@@ -11,7 +11,9 @@ except Exception:
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"]
"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 = {}