mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-24 08:00:14 +00:00
Add glass pipeline for WMO windows with Fresnel-based transparency
Dedicated Vulkan pipeline with alpha blending AND depth writes so windows look transparent at oblique angles without see-through artifacts. Fresnel alpha ranges from 0.4 (grazing) to 0.95 (head-on) with sun glint, reflections, and silver-lining specular.
This commit is contained in:
parent
fb4ff46fe3
commit
1b16bcf71f
4 changed files with 99 additions and 16 deletions
|
|
@ -21,6 +21,7 @@ layout(set = 1, binding = 1) uniform WMOMaterial {
|
|||
int unlit;
|
||||
int isInterior;
|
||||
float specularIntensity;
|
||||
int isWindow;
|
||||
};
|
||||
|
||||
layout(set = 0, binding = 1) uniform sampler2DShadow uShadowMap;
|
||||
|
|
@ -78,5 +79,44 @@ void main() {
|
|||
float fogFactor = clamp((fogParams.y - dist) / (fogParams.y - fogParams.x), 0.0, 1.0);
|
||||
result = mix(fogColor.rgb, result, fogFactor);
|
||||
|
||||
outColor = vec4(result, texColor.a);
|
||||
float alpha = texColor.a;
|
||||
|
||||
// Window glass: opaque but simulates dark tinted glass with reflections.
|
||||
// No real alpha blending — we darken the base texture and add reflection
|
||||
// on top so it reads as glass without needing the transparent pipeline.
|
||||
if (isWindow != 0) {
|
||||
vec3 viewDir = normalize(viewPos.xyz - FragPos);
|
||||
float NdotV = abs(dot(norm, viewDir));
|
||||
// Fresnel: strong reflection at grazing angles
|
||||
float fresnel = 0.08 + 0.92 * pow(1.0 - NdotV, 4.0);
|
||||
|
||||
// Glass darkness depends on view angle — bright when sun glints off,
|
||||
// darker when looking straight on with no sun reflection.
|
||||
vec3 ldir = normalize(-lightDir.xyz);
|
||||
vec3 reflectDir = reflect(-viewDir, norm);
|
||||
float sunGlint = pow(max(dot(reflectDir, ldir), 0.0), 32.0);
|
||||
|
||||
// Base ranges from dark (0.3) to bright (0.9) based on sun reflection
|
||||
float baseBrightness = mix(0.3, 0.9, sunGlint);
|
||||
vec3 glass = result * baseBrightness;
|
||||
|
||||
// Reflection: blend sky/ambient color based on Fresnel
|
||||
vec3 reflectTint = mix(ambientColor.rgb * 1.2, vec3(0.6, 0.75, 1.0), 0.6);
|
||||
glass = mix(glass, reflectTint, fresnel * 0.8);
|
||||
|
||||
// Sharp sun glint on glass
|
||||
vec3 halfDir = normalize(ldir + viewDir);
|
||||
float spec = pow(max(dot(norm, halfDir), 0.0), 256.0);
|
||||
glass += spec * lightColor.rgb * 0.8;
|
||||
|
||||
// Broad warm sheen when sun is nearby
|
||||
float specBroad = pow(max(dot(norm, halfDir), 0.0), 12.0);
|
||||
glass += specBroad * lightColor.rgb * 0.12;
|
||||
|
||||
result = glass;
|
||||
// Fresnel-based transparency: more transparent at oblique angles
|
||||
alpha = mix(0.4, 0.95, NdotV);
|
||||
}
|
||||
|
||||
outColor = vec4(result, alpha);
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -310,6 +310,8 @@ private:
|
|||
int32_t unlit;
|
||||
int32_t isInterior;
|
||||
float specularIntensity;
|
||||
int32_t isWindow;
|
||||
float pad[2]; // pad to 32 bytes
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -346,6 +348,7 @@ private:
|
|||
bool alphaTest = false;
|
||||
bool unlit = false;
|
||||
bool isTransparent = false; // blendMode >= 2
|
||||
bool isWindow = false; // F_SIDN or F_WINDOW material
|
||||
// For multi-draw: store index ranges
|
||||
struct DrawRange { uint32_t firstIndex; uint32_t indexCount; };
|
||||
std::vector<DrawRange> draws;
|
||||
|
|
@ -558,6 +561,7 @@ private:
|
|||
// Vulkan pipelines
|
||||
VkPipeline opaquePipeline_ = VK_NULL_HANDLE;
|
||||
VkPipeline transparentPipeline_ = VK_NULL_HANDLE;
|
||||
VkPipeline glassPipeline_ = VK_NULL_HANDLE; // alpha blend + depth write (windows)
|
||||
VkPipeline wireframePipeline_ = VK_NULL_HANDLE;
|
||||
VkPipelineLayout pipelineLayout_ = VK_NULL_HANDLE;
|
||||
|
||||
|
|
|
|||
|
|
@ -213,6 +213,21 @@ bool WMORenderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayou
|
|||
core::Logger::getInstance().warning("WMORenderer: transparent pipeline not available");
|
||||
}
|
||||
|
||||
// --- Build glass pipeline (alpha blend WITH depth write for windows) ---
|
||||
glassPipeline_ = PipelineBuilder()
|
||||
.setShaders(vertShader.stageInfo(VK_SHADER_STAGE_VERTEX_BIT),
|
||||
fragShader.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT))
|
||||
.setVertexInput({ vertexBinding }, vertexAttribs)
|
||||
.setTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST)
|
||||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, true, VK_COMPARE_OP_LESS_OR_EQUAL)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
|
||||
.setMultisample(vkCtx_->getMsaaSamples())
|
||||
.setLayout(pipelineLayout_)
|
||||
.setRenderPass(mainPass)
|
||||
.setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR })
|
||||
.build(device);
|
||||
|
||||
// --- Build wireframe pipeline ---
|
||||
wireframePipeline_ = PipelineBuilder()
|
||||
.setShaders(vertShader.stageInfo(VK_SHADER_STAGE_VERTEX_BIT),
|
||||
|
|
@ -299,6 +314,7 @@ void WMORenderer::shutdown() {
|
|||
// Destroy pipelines
|
||||
if (opaquePipeline_) { vkDestroyPipeline(device, opaquePipeline_, nullptr); opaquePipeline_ = VK_NULL_HANDLE; }
|
||||
if (transparentPipeline_) { vkDestroyPipeline(device, transparentPipeline_, nullptr); transparentPipeline_ = VK_NULL_HANDLE; }
|
||||
if (glassPipeline_) { vkDestroyPipeline(device, glassPipeline_, nullptr); glassPipeline_ = VK_NULL_HANDLE; }
|
||||
if (wireframePipeline_) { vkDestroyPipeline(device, wireframePipeline_, nullptr); wireframePipeline_ = VK_NULL_HANDLE; }
|
||||
if (pipelineLayout_) { vkDestroyPipelineLayout(device, pipelineLayout_, nullptr); pipelineLayout_ = VK_NULL_HANDLE; }
|
||||
if (materialDescPool_) { vkDestroyDescriptorPool(device, materialDescPool_, nullptr); materialDescPool_ = VK_NULL_HANDLE; }
|
||||
|
|
@ -511,9 +527,9 @@ bool WMORenderer::loadModel(const pipeline::WMOModel& model, uint32_t id) {
|
|||
unlit = (matFlags & 0x01) != 0;
|
||||
}
|
||||
|
||||
// Skip materials that are sky/window panes (render as grey curtains if drawn opaque)
|
||||
// 0x20 = F_SIDN (night sky window), 0x40 = F_WINDOW
|
||||
if (matFlags & 0x60) continue;
|
||||
// Window materials (F_SIDN=0x20, F_WINDOW=0x40) render as
|
||||
// slightly transparent reflective glass.
|
||||
bool isWindow = (matFlags & 0x60) != 0;
|
||||
|
||||
BatchKey key{ reinterpret_cast<uintptr_t>(tex), alphaTest, unlit };
|
||||
auto& mb = batchMap[key];
|
||||
|
|
@ -523,6 +539,7 @@ bool WMORenderer::loadModel(const pipeline::WMOModel& model, uint32_t id) {
|
|||
mb.alphaTest = alphaTest;
|
||||
mb.unlit = unlit;
|
||||
mb.isTransparent = (blendMode >= 2);
|
||||
mb.isWindow = isWindow;
|
||||
}
|
||||
GroupResources::MergedBatch::DrawRange dr;
|
||||
dr.firstIndex = batch.startIndex;
|
||||
|
|
@ -552,6 +569,7 @@ bool WMORenderer::loadModel(const pipeline::WMOModel& model, uint32_t id) {
|
|||
matData.unlit = mb.unlit ? 1 : 0;
|
||||
matData.isInterior = isInterior ? 1 : 0;
|
||||
matData.specularIntensity = 0.5f;
|
||||
matData.isWindow = mb.isWindow ? 1 : 0;
|
||||
if (matBuf.info.pMappedData) {
|
||||
memcpy(matBuf.info.pMappedData, &matData, sizeof(matData));
|
||||
}
|
||||
|
|
@ -1276,7 +1294,8 @@ void WMORenderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const
|
|||
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout_,
|
||||
0, 1, &perFrameSet, 0, nullptr);
|
||||
|
||||
bool inTransparentPipeline = false;
|
||||
// Track which pipeline is currently bound: 0=opaque, 1=transparent, 2=glass
|
||||
int currentPipelineKind = 0;
|
||||
|
||||
for (const auto& dl : drawLists) {
|
||||
if (dl.instanceIndex >= instances.size()) continue;
|
||||
|
|
@ -1307,21 +1326,26 @@ void WMORenderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const
|
|||
for (const auto& mb : group.mergedBatches) {
|
||||
if (!mb.materialSet) continue;
|
||||
|
||||
// Switch pipeline for transparent batches
|
||||
if (mb.isTransparent && !inTransparentPipeline && transparentPipeline_) {
|
||||
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, transparentPipeline_);
|
||||
// Determine which pipeline this batch needs
|
||||
int neededPipeline = 0; // opaque
|
||||
if (mb.isWindow && glassPipeline_) {
|
||||
neededPipeline = 2; // glass (alpha blend + depth write)
|
||||
} else if (mb.isTransparent && transparentPipeline_) {
|
||||
neededPipeline = 1; // transparent (alpha blend, no depth write)
|
||||
}
|
||||
|
||||
// Switch pipeline if needed
|
||||
if (neededPipeline != currentPipelineKind) {
|
||||
VkPipeline targetPipeline = activePipeline;
|
||||
if (neededPipeline == 1) targetPipeline = transparentPipeline_;
|
||||
else if (neededPipeline == 2) targetPipeline = glassPipeline_;
|
||||
|
||||
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, targetPipeline);
|
||||
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout_,
|
||||
0, 1, &perFrameSet, 0, nullptr);
|
||||
vkCmdPushConstants(cmd, pipelineLayout_, VK_SHADER_STAGE_VERTEX_BIT,
|
||||
0, sizeof(GPUPushConstants), &push);
|
||||
inTransparentPipeline = true;
|
||||
} else if (!mb.isTransparent && inTransparentPipeline) {
|
||||
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, activePipeline);
|
||||
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout_,
|
||||
0, 1, &perFrameSet, 0, nullptr);
|
||||
vkCmdPushConstants(cmd, pipelineLayout_, VK_SHADER_STAGE_VERTEX_BIT,
|
||||
0, sizeof(GPUPushConstants), &push);
|
||||
inTransparentPipeline = false;
|
||||
currentPipelineKind = neededPipeline;
|
||||
}
|
||||
|
||||
// Bind material descriptor set (set 1)
|
||||
|
|
@ -2969,6 +2993,7 @@ void WMORenderer::recreatePipelines() {
|
|||
// Destroy old main-pass pipelines (NOT shadow, NOT pipeline layout)
|
||||
if (opaquePipeline_) { vkDestroyPipeline(device, opaquePipeline_, nullptr); opaquePipeline_ = VK_NULL_HANDLE; }
|
||||
if (transparentPipeline_) { vkDestroyPipeline(device, transparentPipeline_, nullptr); transparentPipeline_ = VK_NULL_HANDLE; }
|
||||
if (glassPipeline_) { vkDestroyPipeline(device, glassPipeline_, nullptr); glassPipeline_ = VK_NULL_HANDLE; }
|
||||
if (wireframePipeline_) { vkDestroyPipeline(device, wireframePipeline_, nullptr); wireframePipeline_ = VK_NULL_HANDLE; }
|
||||
|
||||
// --- Load shaders ---
|
||||
|
|
@ -3032,6 +3057,20 @@ void WMORenderer::recreatePipelines() {
|
|||
.setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR })
|
||||
.build(device);
|
||||
|
||||
glassPipeline_ = PipelineBuilder()
|
||||
.setShaders(vertShader.stageInfo(VK_SHADER_STAGE_VERTEX_BIT),
|
||||
fragShader.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT))
|
||||
.setVertexInput({ vertexBinding }, vertexAttribs)
|
||||
.setTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST)
|
||||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, true, VK_COMPARE_OP_LESS_OR_EQUAL)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
|
||||
.setMultisample(vkCtx_->getMsaaSamples())
|
||||
.setLayout(pipelineLayout_)
|
||||
.setRenderPass(mainPass)
|
||||
.setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR })
|
||||
.build(device);
|
||||
|
||||
wireframePipeline_ = PipelineBuilder()
|
||||
.setShaders(vertShader.stageInfo(VK_SHADER_STAGE_VERTEX_BIT),
|
||||
fragShader.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue