mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
Add shader-driven tree beautification: wind sway, SSS, color variation, AO
- Vertex wind animation: 3-layer displacement (trunk/branch/leaf) with quadratic height scaling so bases stay grounded - Shadow pass: matching vertex displacement split into foliage/non-foliage passes, removed UV-wiggle approach - Leaf subsurface scattering: warm backlit glow when looking toward sun - Per-instance color variation: hue/brightness from position hash via flat varying to avoid interpolation flicker - Canopy ambient occlusion: height-based darkening of tree interiors - Detail normal perturbation: UV-only procedural normals to break flat cards - Bayer 4x4 ordered dither replacing sin-hash noise for alpha edges - Foliage skips shadow map sampling and specular to prevent flicker from swaying geometry sampling unstable shadow/highlight values
This commit is contained in:
parent
4511de8d38
commit
ef1e5abe8e
11 changed files with 199 additions and 69 deletions
|
|
@ -32,12 +32,28 @@ layout(set = 0, binding = 1) uniform sampler2DShadow uShadowMap;
|
||||||
layout(location = 0) in vec3 FragPos;
|
layout(location = 0) in vec3 FragPos;
|
||||||
layout(location = 1) in vec3 Normal;
|
layout(location = 1) in vec3 Normal;
|
||||||
layout(location = 2) in vec2 TexCoord;
|
layout(location = 2) in vec2 TexCoord;
|
||||||
|
layout(location = 3) flat in vec3 InstanceOrigin;
|
||||||
|
layout(location = 4) in float ModelHeight;
|
||||||
|
|
||||||
layout(location = 0) out vec4 outColor;
|
layout(location = 0) out vec4 outColor;
|
||||||
|
|
||||||
|
// 4x4 Bayer dither matrix (normalized to 0..1)
|
||||||
|
float bayerDither4x4(ivec2 p) {
|
||||||
|
int idx = (p.x & 3) + (p.y & 3) * 4;
|
||||||
|
float m[16] = float[16](
|
||||||
|
0.0/16.0, 8.0/16.0, 2.0/16.0, 10.0/16.0,
|
||||||
|
12.0/16.0, 4.0/16.0, 14.0/16.0, 6.0/16.0,
|
||||||
|
3.0/16.0, 11.0/16.0, 1.0/16.0, 9.0/16.0,
|
||||||
|
15.0/16.0, 7.0/16.0, 13.0/16.0, 5.0/16.0
|
||||||
|
);
|
||||||
|
return m[idx];
|
||||||
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec4 texColor = hasTexture != 0 ? texture(uTexture, TexCoord) : vec4(1.0);
|
vec4 texColor = hasTexture != 0 ? texture(uTexture, TexCoord) : vec4(1.0);
|
||||||
|
|
||||||
|
bool isFoliage = (alphaTest == 2);
|
||||||
|
|
||||||
float alphaCutoff = 0.5;
|
float alphaCutoff = 0.5;
|
||||||
if (alphaTest == 2) {
|
if (alphaTest == 2) {
|
||||||
// Vegetation cutout: lower threshold to preserve leaf coverage at grazing angles.
|
// Vegetation cutout: lower threshold to preserve leaf coverage at grazing angles.
|
||||||
|
|
@ -50,13 +66,13 @@ void main() {
|
||||||
}
|
}
|
||||||
if (alphaTest == 2) {
|
if (alphaTest == 2) {
|
||||||
float alpha = texColor.a;
|
float alpha = texColor.a;
|
||||||
float softBand = 0.12;
|
float softBand = 0.15;
|
||||||
if (alpha < (alphaCutoff - softBand)) discard;
|
if (alpha < (alphaCutoff - softBand)) discard;
|
||||||
if (alpha < alphaCutoff) {
|
if (alpha < alphaCutoff) {
|
||||||
vec2 p = floor(gl_FragCoord.xy);
|
ivec2 p = ivec2(gl_FragCoord.xy);
|
||||||
float n = fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453);
|
float threshold = bayerDither4x4(p);
|
||||||
float keep = clamp((alpha - (alphaCutoff - softBand)) / softBand, 0.0, 1.0);
|
float keep = clamp((alpha - (alphaCutoff - softBand)) / softBand, 0.0, 1.0);
|
||||||
if (n > keep) discard;
|
if (threshold > keep) discard;
|
||||||
}
|
}
|
||||||
} else if (alphaTest != 0 && texColor.a < alphaCutoff) {
|
} else if (alphaTest != 0 && texColor.a < alphaCutoff) {
|
||||||
discard;
|
discard;
|
||||||
|
|
@ -67,10 +83,26 @@ void main() {
|
||||||
}
|
}
|
||||||
if (blendMode == 1 && texColor.a < 0.004) discard;
|
if (blendMode == 1 && texColor.a < 0.004) discard;
|
||||||
|
|
||||||
|
// Per-instance color variation (foliage only)
|
||||||
|
if (isFoliage) {
|
||||||
|
float hash = fract(sin(dot(InstanceOrigin.xy, vec2(127.1, 311.7))) * 43758.5453);
|
||||||
|
float hueShiftR = 1.0 + (hash - 0.5) * 0.16; // ±8% red
|
||||||
|
float hueShiftB = 1.0 + (fract(hash * 7.13) - 0.5) * 0.16; // ±8% blue
|
||||||
|
float brightness = 0.85 + hash * 0.30; // 85–115%
|
||||||
|
texColor.rgb *= vec3(hueShiftR, 1.0, hueShiftB) * brightness;
|
||||||
|
}
|
||||||
|
|
||||||
vec3 norm = normalize(Normal);
|
vec3 norm = normalize(Normal);
|
||||||
bool foliageTwoSided = (alphaTest == 2);
|
bool foliageTwoSided = (alphaTest == 2);
|
||||||
if (!foliageTwoSided && !gl_FrontFacing) norm = -norm;
|
if (!foliageTwoSided && !gl_FrontFacing) norm = -norm;
|
||||||
|
|
||||||
|
// Detail normal perturbation (foliage only) — UV-based only so wind doesn't cause flicker
|
||||||
|
if (isFoliage) {
|
||||||
|
float nx = sin(TexCoord.x * 12.0 + TexCoord.y * 5.3) * 0.10;
|
||||||
|
float ny = sin(TexCoord.y * 14.0 + TexCoord.x * 4.7) * 0.10;
|
||||||
|
norm = normalize(norm + vec3(nx, ny, 0.0));
|
||||||
|
}
|
||||||
|
|
||||||
vec3 ldir = normalize(-lightDir.xyz);
|
vec3 ldir = normalize(-lightDir.xyz);
|
||||||
float nDotL = dot(norm, ldir);
|
float nDotL = dot(norm, ldir);
|
||||||
float diff = foliageTwoSided ? abs(nDotL) : max(nDotL, 0.0);
|
float diff = foliageTwoSided ? abs(nDotL) : max(nDotL, 0.0);
|
||||||
|
|
@ -80,32 +112,58 @@ void main() {
|
||||||
result = texColor.rgb;
|
result = texColor.rgb;
|
||||||
} else {
|
} else {
|
||||||
vec3 viewDir = normalize(viewPos.xyz - FragPos);
|
vec3 viewDir = normalize(viewPos.xyz - FragPos);
|
||||||
vec3 halfDir = normalize(ldir + viewDir);
|
|
||||||
float spec = pow(max(dot(norm, halfDir), 0.0), 32.0) * specularIntensity;
|
|
||||||
|
|
||||||
|
// Foliage: no specular, no shadow map — both flicker on swaying thin cards
|
||||||
|
float spec = 0.0;
|
||||||
float shadow = 1.0;
|
float shadow = 1.0;
|
||||||
if (shadowParams.x > 0.5) {
|
if (isFoliage) {
|
||||||
vec4 lsPos = lightSpaceMatrix * vec4(FragPos, 1.0);
|
// Use a fixed gentle shadow from the shadow system strength
|
||||||
vec3 proj = lsPos.xyz / lsPos.w;
|
if (shadowParams.x > 0.5) {
|
||||||
proj.xy = proj.xy * 0.5 + 0.5;
|
shadow = mix(1.0, 0.75, shadowParams.y);
|
||||||
if (proj.x >= 0.0 && proj.x <= 1.0 &&
|
|
||||||
proj.y >= 0.0 && proj.y <= 1.0 &&
|
|
||||||
proj.z >= 0.0 && proj.z <= 1.0) {
|
|
||||||
float bias = max(0.0005 * (1.0 - abs(dot(norm, ldir))), 0.00005);
|
|
||||||
shadow = texture(uShadowMap, vec3(proj.xy, proj.z - bias));
|
|
||||||
}
|
}
|
||||||
shadow = mix(1.0, shadow, shadowParams.y);
|
} else {
|
||||||
if (foliageTwoSided) shadow = max(shadow, 0.45);
|
vec3 halfDir = normalize(ldir + viewDir);
|
||||||
|
spec = pow(max(dot(norm, halfDir), 0.0), 32.0) * specularIntensity;
|
||||||
|
|
||||||
|
if (shadowParams.x > 0.5) {
|
||||||
|
vec4 lsPos = lightSpaceMatrix * vec4(FragPos, 1.0);
|
||||||
|
vec3 proj = lsPos.xyz / lsPos.w;
|
||||||
|
proj.xy = proj.xy * 0.5 + 0.5;
|
||||||
|
if (proj.x >= 0.0 && proj.x <= 1.0 &&
|
||||||
|
proj.y >= 0.0 && proj.y <= 1.0 &&
|
||||||
|
proj.z >= 0.0 && proj.z <= 1.0) {
|
||||||
|
float bias = max(0.0005 * (1.0 - abs(dot(norm, ldir))), 0.00005);
|
||||||
|
shadow = texture(uShadowMap, vec3(proj.xy, proj.z - bias));
|
||||||
|
}
|
||||||
|
shadow = mix(1.0, shadow, shadowParams.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Leaf subsurface scattering (foliage only) — uses stable normal, no FragPos dependency
|
||||||
|
vec3 sss = vec3(0.0);
|
||||||
|
if (isFoliage) {
|
||||||
|
float backLit = max(-nDotL, 0.0);
|
||||||
|
float viewDotLight = max(dot(viewDir, -ldir), 0.0);
|
||||||
|
float sssAmount = backLit * pow(viewDotLight, 4.0) * 0.35 * texColor.a;
|
||||||
|
sss = sssAmount * vec3(1.0, 0.9, 0.5) * lightColor.rgb;
|
||||||
}
|
}
|
||||||
|
|
||||||
result = ambientColor.rgb * texColor.rgb
|
result = ambientColor.rgb * texColor.rgb
|
||||||
+ shadow * (diff * lightColor.rgb * texColor.rgb + spec * lightColor.rgb);
|
+ shadow * (diff * lightColor.rgb * texColor.rgb + spec * lightColor.rgb)
|
||||||
|
+ sss;
|
||||||
|
|
||||||
if (interiorDarken > 0.0) {
|
if (interiorDarken > 0.0) {
|
||||||
result *= mix(1.0, 0.5, interiorDarken);
|
result *= mix(1.0, 0.5, interiorDarken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Canopy ambient occlusion (foliage only)
|
||||||
|
if (isFoliage) {
|
||||||
|
float normalizedHeight = clamp(ModelHeight / 18.0, 0.0, 1.0);
|
||||||
|
float aoFactor = mix(0.55, 1.0, smoothstep(0.0, 0.6, normalizedHeight));
|
||||||
|
result *= aoFactor;
|
||||||
|
}
|
||||||
|
|
||||||
float dist = length(viewPos.xyz - FragPos);
|
float dist = length(viewPos.xyz - FragPos);
|
||||||
float fogFactor = clamp((fogParams.y - dist) / (fogParams.y - fogParams.x), 0.0, 1.0);
|
float fogFactor = clamp((fogParams.y - dist) / (fogParams.y - fogParams.x), 0.0, 1.0);
|
||||||
result = mix(fogColor.rgb, result, fogFactor);
|
result = mix(fogColor.rgb, result, fogFactor);
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -18,6 +18,7 @@ layout(push_constant) uniform Push {
|
||||||
vec2 uvOffset;
|
vec2 uvOffset;
|
||||||
int texCoordSet;
|
int texCoordSet;
|
||||||
int useBones;
|
int useBones;
|
||||||
|
int isFoliage;
|
||||||
} push;
|
} push;
|
||||||
|
|
||||||
layout(set = 2, binding = 0) readonly buffer BoneSSBO {
|
layout(set = 2, binding = 0) readonly buffer BoneSSBO {
|
||||||
|
|
@ -34,6 +35,8 @@ layout(location = 5) in vec2 aTexCoord2;
|
||||||
layout(location = 0) out vec3 FragPos;
|
layout(location = 0) out vec3 FragPos;
|
||||||
layout(location = 1) out vec3 Normal;
|
layout(location = 1) out vec3 Normal;
|
||||||
layout(location = 2) out vec2 TexCoord;
|
layout(location = 2) out vec2 TexCoord;
|
||||||
|
layout(location = 3) flat out vec3 InstanceOrigin;
|
||||||
|
layout(location = 4) out float ModelHeight;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec4 pos = vec4(aPos, 1.0);
|
vec4 pos = vec4(aPos, 1.0);
|
||||||
|
|
@ -49,11 +52,40 @@ void main() {
|
||||||
norm = skinMat * norm;
|
norm = skinMat * norm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wind animation for foliage
|
||||||
|
if (push.isFoliage != 0) {
|
||||||
|
float windTime = fogParams.z;
|
||||||
|
vec3 worldRef = push.model[3].xyz;
|
||||||
|
float heightFactor = clamp(pos.z / 20.0, 0.0, 1.0);
|
||||||
|
heightFactor *= heightFactor; // quadratic — base stays grounded
|
||||||
|
|
||||||
|
// Layer 1: Trunk sway — slow, large amplitude
|
||||||
|
float trunkPhase = windTime * 0.8 + dot(worldRef.xy, vec2(0.1, 0.13));
|
||||||
|
float trunkSwayX = sin(trunkPhase) * 0.35 * heightFactor;
|
||||||
|
float trunkSwayY = cos(trunkPhase * 0.7) * 0.25 * heightFactor;
|
||||||
|
|
||||||
|
// Layer 2: Branch sway — medium frequency, per-branch phase
|
||||||
|
float branchPhase = windTime * 1.7 + dot(worldRef.xy, vec2(0.37, 0.71));
|
||||||
|
float branchSwayX = sin(branchPhase + pos.y * 0.4) * 0.15 * heightFactor;
|
||||||
|
float branchSwayY = cos(branchPhase * 1.1 + pos.x * 0.3) * 0.12 * heightFactor;
|
||||||
|
|
||||||
|
// Layer 3: Leaf flutter — fast, small amplitude, per-vertex
|
||||||
|
float leafPhase = windTime * 4.5 + dot(aPos, vec3(1.7, 2.3, 0.9));
|
||||||
|
float leafFlutterX = sin(leafPhase) * 0.06 * heightFactor;
|
||||||
|
float leafFlutterY = cos(leafPhase * 1.3) * 0.05 * heightFactor;
|
||||||
|
|
||||||
|
pos.x += trunkSwayX + branchSwayX + leafFlutterX;
|
||||||
|
pos.y += trunkSwayY + branchSwayY + leafFlutterY;
|
||||||
|
}
|
||||||
|
|
||||||
vec4 worldPos = push.model * pos;
|
vec4 worldPos = push.model * pos;
|
||||||
FragPos = worldPos.xyz;
|
FragPos = worldPos.xyz;
|
||||||
Normal = mat3(push.model) * norm.xyz;
|
Normal = mat3(push.model) * norm.xyz;
|
||||||
|
|
||||||
TexCoord = (push.texCoordSet == 1 ? aTexCoord2 : aTexCoord) + push.uvOffset;
|
TexCoord = (push.texCoordSet == 1 ? aTexCoord2 : aTexCoord) + push.uvOffset;
|
||||||
|
|
||||||
|
InstanceOrigin = push.model[3].xyz;
|
||||||
|
ModelHeight = pos.z;
|
||||||
|
|
||||||
gl_Position = projection * view * worldPos;
|
gl_Position = projection * view * worldPos;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -16,22 +16,7 @@ layout(location = 1) in vec3 WorldPos;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
if (useTexture != 0) {
|
if (useTexture != 0) {
|
||||||
vec2 uv = TexCoord;
|
vec4 texColor = textureLod(uTexture, TexCoord, 0.0);
|
||||||
if (foliageSway != 0) {
|
|
||||||
float sway = sin(windTime + WorldPos.x * 0.5) * 0.02 * foliageMotionDamp;
|
|
||||||
uv += vec2(sway, sway * 0.5);
|
|
||||||
}
|
|
||||||
vec4 texColor = textureLod(uTexture, uv, 0.0);
|
|
||||||
if (alphaTest != 0 && texColor.a < 0.5) discard;
|
if (alphaTest != 0 && texColor.a < 0.5) discard;
|
||||||
|
|
||||||
if (foliageSway != 0) {
|
|
||||||
vec2 uv2 = TexCoord + vec2(
|
|
||||||
sin(windTime * 1.3 + WorldPos.z * 0.7) * 0.015 * foliageMotionDamp,
|
|
||||||
sin(windTime * 0.9 + WorldPos.x * 0.6) * 0.01 * foliageMotionDamp
|
|
||||||
);
|
|
||||||
vec4 texColor2 = textureLod(uTexture, uv2, 0.0);
|
|
||||||
float blended = (texColor.a + texColor2.a) * 0.5;
|
|
||||||
if (alphaTest != 0 && blended < 0.5) discard;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -23,7 +23,34 @@ layout(location = 0) out vec2 TexCoord;
|
||||||
layout(location = 1) out vec3 WorldPos;
|
layout(location = 1) out vec3 WorldPos;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec4 worldPos = push.model * vec4(aPos, 1.0);
|
vec4 pos = vec4(aPos, 1.0);
|
||||||
|
|
||||||
|
// Wind vertex displacement for foliage (matches m2.vert.glsl)
|
||||||
|
if (foliageSway != 0) {
|
||||||
|
vec3 worldRef = push.model[3].xyz;
|
||||||
|
float heightFactor = clamp(pos.z / 20.0, 0.0, 1.0);
|
||||||
|
heightFactor *= heightFactor;
|
||||||
|
|
||||||
|
// Layer 1: Trunk sway
|
||||||
|
float trunkPhase = windTime * 0.8 + dot(worldRef.xy, vec2(0.1, 0.13));
|
||||||
|
float trunkSwayX = sin(trunkPhase) * 0.35 * heightFactor;
|
||||||
|
float trunkSwayY = cos(trunkPhase * 0.7) * 0.25 * heightFactor;
|
||||||
|
|
||||||
|
// Layer 2: Branch sway
|
||||||
|
float branchPhase = windTime * 1.7 + dot(worldRef.xy, vec2(0.37, 0.71));
|
||||||
|
float branchSwayX = sin(branchPhase + pos.y * 0.4) * 0.15 * heightFactor;
|
||||||
|
float branchSwayY = cos(branchPhase * 1.1 + pos.x * 0.3) * 0.12 * heightFactor;
|
||||||
|
|
||||||
|
// Layer 3: Leaf flutter
|
||||||
|
float leafPhase = windTime * 4.5 + dot(aPos, vec3(1.7, 2.3, 0.9));
|
||||||
|
float leafFlutterX = sin(leafPhase) * 0.06 * heightFactor;
|
||||||
|
float leafFlutterY = cos(leafPhase * 1.3) * 0.05 * heightFactor;
|
||||||
|
|
||||||
|
pos.x += trunkSwayX + branchSwayX + leafFlutterX;
|
||||||
|
pos.y += trunkSwayY + branchSwayY + leafFlutterY;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 worldPos = push.model * pos;
|
||||||
WorldPos = worldPos.xyz;
|
WorldPos = worldPos.xyz;
|
||||||
TexCoord = aTexCoord;
|
TexCoord = aTexCoord;
|
||||||
gl_Position = push.lightSpaceMatrix * worldPos;
|
gl_Position = push.lightSpaceMatrix * worldPos;
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -251,7 +251,7 @@ public:
|
||||||
/**
|
/**
|
||||||
* Render depth-only pass for shadow casting
|
* Render depth-only pass for shadow casting
|
||||||
*/
|
*/
|
||||||
void renderShadow(VkCommandBuffer cmd, const glm::mat4& lightSpaceMatrix);
|
void renderShadow(VkCommandBuffer cmd, const glm::mat4& lightSpaceMatrix, float globalTime = 0.0f);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render M2 particle emitters (point sprites)
|
* Render M2 particle emitters (point sprites)
|
||||||
|
|
|
||||||
|
|
@ -401,7 +401,7 @@ bool M2Renderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout
|
||||||
VkPushConstantRange pushRange{};
|
VkPushConstantRange pushRange{};
|
||||||
pushRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
|
pushRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
|
||||||
pushRange.offset = 0;
|
pushRange.offset = 0;
|
||||||
pushRange.size = 80; // mat4(64) + vec2(8) + int(4) + int(4)
|
pushRange.size = 84; // mat4(64) + vec2(8) + int(4) + int(4) + int(4)
|
||||||
|
|
||||||
VkPipelineLayoutCreateInfo ci{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO};
|
VkPipelineLayoutCreateInfo ci{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO};
|
||||||
ci.setLayoutCount = 3;
|
ci.setLayoutCount = 3;
|
||||||
|
|
@ -2109,6 +2109,7 @@ void M2Renderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const
|
||||||
glm::vec2 uvOffset;
|
glm::vec2 uvOffset;
|
||||||
int texCoordSet;
|
int texCoordSet;
|
||||||
int useBones;
|
int useBones;
|
||||||
|
int isFoliage;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Bind per-frame descriptor set (set 0) — shared across all draws
|
// Bind per-frame descriptor set (set 0) — shared across all draws
|
||||||
|
|
@ -2382,6 +2383,7 @@ void M2Renderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const
|
||||||
pc.uvOffset = uvOffset;
|
pc.uvOffset = uvOffset;
|
||||||
pc.texCoordSet = static_cast<int>(batch.textureUnit);
|
pc.texCoordSet = static_cast<int>(batch.textureUnit);
|
||||||
pc.useBones = useBones ? 1 : 0;
|
pc.useBones = useBones ? 1 : 0;
|
||||||
|
pc.isFoliage = model.shadowWindFoliage ? 1 : 0;
|
||||||
vkCmdPushConstants(cmd, pipelineLayout_, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(pc), &pc);
|
vkCmdPushConstants(cmd, pipelineLayout_, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(pc), &pc);
|
||||||
|
|
||||||
vkCmdDrawIndexed(cmd, batch.indexCount, 1, batch.indexStart, 0, 0);
|
vkCmdDrawIndexed(cmd, batch.indexCount, 1, batch.indexStart, 0, 0);
|
||||||
|
|
@ -2625,46 +2627,72 @@ bool M2Renderer::initializeShadow(VkRenderPass shadowRenderPass) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void M2Renderer::renderShadow(VkCommandBuffer cmd, const glm::mat4& lightSpaceMatrix) {
|
void M2Renderer::renderShadow(VkCommandBuffer cmd, const glm::mat4& lightSpaceMatrix, float globalTime) {
|
||||||
if (!shadowPipeline_ || !shadowParamsSet_) return;
|
if (!shadowPipeline_ || !shadowParamsSet_) return;
|
||||||
if (instances.empty() || models.empty()) return;
|
if (instances.empty() || models.empty()) return;
|
||||||
|
|
||||||
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, shadowPipeline_);
|
|
||||||
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, shadowPipelineLayout_,
|
|
||||||
0, 1, &shadowParamsSet_, 0, nullptr);
|
|
||||||
|
|
||||||
struct ShadowPush { glm::mat4 lightSpaceMatrix; glm::mat4 model; };
|
struct ShadowPush { glm::mat4 lightSpaceMatrix; glm::mat4 model; };
|
||||||
|
|
||||||
uint32_t currentModelId = UINT32_MAX;
|
// Helper lambda to draw instances with a given foliageSway setting
|
||||||
const M2ModelGPU* currentModel = nullptr;
|
auto drawPass = [&](bool foliagePass) {
|
||||||
|
// Update ShadowParams UBO for this pass
|
||||||
|
struct ShadowParamsUBO {
|
||||||
|
int32_t useBones = 0;
|
||||||
|
int32_t useTexture = 0;
|
||||||
|
int32_t alphaTest = 0;
|
||||||
|
int32_t foliageSway = 0;
|
||||||
|
float windTime = 0.0f;
|
||||||
|
float foliageMotionDamp = 1.0f;
|
||||||
|
};
|
||||||
|
ShadowParamsUBO params{};
|
||||||
|
params.foliageSway = foliagePass ? 1 : 0;
|
||||||
|
params.windTime = globalTime;
|
||||||
|
params.foliageMotionDamp = 1.0f;
|
||||||
|
|
||||||
for (const auto& instance : instances) {
|
VmaAllocationInfo allocInfo{};
|
||||||
auto modelIt = models.find(instance.modelId);
|
vmaGetAllocationInfo(vkCtx_->getAllocator(), shadowParamsAlloc_, &allocInfo);
|
||||||
if (modelIt == models.end()) continue;
|
std::memcpy(allocInfo.pMappedData, ¶ms, sizeof(params));
|
||||||
const M2ModelGPU& model = modelIt->second;
|
|
||||||
if (!model.isValid() || model.isSmoke || model.isInvisibleTrap) continue;
|
|
||||||
|
|
||||||
// Bind vertex/index buffers when model changes
|
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, shadowPipeline_);
|
||||||
if (instance.modelId != currentModelId) {
|
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, shadowPipelineLayout_,
|
||||||
currentModelId = instance.modelId;
|
0, 1, &shadowParamsSet_, 0, nullptr);
|
||||||
currentModel = &model;
|
|
||||||
VkDeviceSize offset = 0;
|
uint32_t currentModelId = UINT32_MAX;
|
||||||
vkCmdBindVertexBuffers(cmd, 0, 1, ¤tModel->vertexBuffer, &offset);
|
const M2ModelGPU* currentModel = nullptr;
|
||||||
vkCmdBindIndexBuffer(cmd, currentModel->indexBuffer, 0, VK_INDEX_TYPE_UINT16);
|
|
||||||
|
for (const auto& instance : instances) {
|
||||||
|
auto modelIt = models.find(instance.modelId);
|
||||||
|
if (modelIt == models.end()) continue;
|
||||||
|
const M2ModelGPU& model = modelIt->second;
|
||||||
|
if (!model.isValid() || model.isSmoke || model.isInvisibleTrap) continue;
|
||||||
|
|
||||||
|
// Filter: only draw foliage models in foliage pass, non-foliage in non-foliage pass
|
||||||
|
if (model.shadowWindFoliage != foliagePass) continue;
|
||||||
|
|
||||||
|
// Bind vertex/index buffers when model changes
|
||||||
|
if (instance.modelId != currentModelId) {
|
||||||
|
currentModelId = instance.modelId;
|
||||||
|
currentModel = &model;
|
||||||
|
VkDeviceSize offset = 0;
|
||||||
|
vkCmdBindVertexBuffers(cmd, 0, 1, ¤tModel->vertexBuffer, &offset);
|
||||||
|
vkCmdBindIndexBuffer(cmd, currentModel->indexBuffer, 0, VK_INDEX_TYPE_UINT16);
|
||||||
|
}
|
||||||
|
|
||||||
|
ShadowPush push{lightSpaceMatrix, instance.modelMatrix};
|
||||||
|
vkCmdPushConstants(cmd, shadowPipelineLayout_, VK_SHADER_STAGE_VERTEX_BIT,
|
||||||
|
0, 128, &push);
|
||||||
|
|
||||||
|
for (const auto& batch : model.batches) {
|
||||||
|
if (batch.submeshLevel > 0) continue;
|
||||||
|
vkCmdDrawIndexed(cmd, batch.indexCount, 1, batch.indexStart, 0, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
ShadowPush push{lightSpaceMatrix, instance.modelMatrix};
|
// Pass 1: non-foliage (no wind displacement)
|
||||||
vkCmdPushConstants(cmd, shadowPipelineLayout_, VK_SHADER_STAGE_VERTEX_BIT,
|
drawPass(false);
|
||||||
0, 128, &push);
|
// Pass 2: foliage (wind displacement enabled)
|
||||||
|
drawPass(true);
|
||||||
// Draw all batches in shadow pass.
|
|
||||||
// Blend-mode filtering was excluding many valid world casters after
|
|
||||||
// Vulkan material path changes (trees/buildings losing shadows).
|
|
||||||
for (const auto& batch : model.batches) {
|
|
||||||
if (batch.submeshLevel > 0) continue; // skip LOD submeshes
|
|
||||||
vkCmdDrawIndexed(cmd, batch.indexCount, 1, batch.indexStart, 0, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- M2 Particle Emitter Helpers ---
|
// --- M2 Particle Emitter Helpers ---
|
||||||
|
|
|
||||||
|
|
@ -3975,7 +3975,7 @@ void Renderer::renderShadowPass() {
|
||||||
wmoRenderer->renderShadow(currentCmd, lightSpaceMatrix);
|
wmoRenderer->renderShadow(currentCmd, lightSpaceMatrix);
|
||||||
}
|
}
|
||||||
if (m2Renderer) {
|
if (m2Renderer) {
|
||||||
m2Renderer->renderShadow(currentCmd, lightSpaceMatrix);
|
m2Renderer->renderShadow(currentCmd, lightSpaceMatrix, globalTime);
|
||||||
}
|
}
|
||||||
if (characterRenderer) {
|
if (characterRenderer) {
|
||||||
characterRenderer->renderShadow(currentCmd, lightSpaceMatrix);
|
characterRenderer->renderShadow(currentCmd, lightSpaceMatrix);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue