mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-25 00:20:16 +00:00
Improve shadow stability and reduce foliage pop-in
This commit is contained in:
parent
979c0b5592
commit
ab4cb878ea
6 changed files with 48 additions and 16 deletions
|
|
@ -47,6 +47,8 @@ float calcShadow() {
|
|||
vec4 lsPos = uLightSpaceMatrix * vec4(FragPos, 1.0);
|
||||
vec3 proj = lsPos.xyz / lsPos.w * 0.5 + 0.5;
|
||||
if (proj.z > 1.0) return 1.0;
|
||||
float edgeDist = max(abs(proj.x - 0.5), abs(proj.y - 0.5));
|
||||
float coverageFade = 1.0 - smoothstep(0.40, 0.49, edgeDist);
|
||||
vec3 norm = normalize(Normal);
|
||||
vec3 lightDir = normalize(-uLightDir);
|
||||
float bias = max(0.005 * (1.0 - dot(norm, lightDir)), 0.001);
|
||||
|
|
@ -57,7 +59,8 @@ float calcShadow() {
|
|||
shadow += texture(uShadowMap, vec3(proj.xy + vec2(x, y) * texelSize, proj.z - bias));
|
||||
}
|
||||
}
|
||||
return shadow / 9.0;
|
||||
shadow /= 9.0;
|
||||
return mix(1.0, shadow, coverageFade);
|
||||
}
|
||||
|
||||
void main() {
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ struct M2ModelGPU {
|
|||
std::vector<uint32_t> globalSequenceDurations; // Loop durations for global sequence tracks
|
||||
bool hasAnimation = false; // True if any bone has keyframes
|
||||
bool isSmoke = false; // True for smoke models (UV scroll animation)
|
||||
bool disableAnimation = false; // Keep foliage/tree doodads visually stable
|
||||
std::vector<int> idleVariationIndices; // Sequence indices for idle variations (animId 0)
|
||||
|
||||
bool isValid() const { return vao != 0 && indexCount > 0; }
|
||||
|
|
|
|||
|
|
@ -124,6 +124,8 @@ bool CharacterRenderer::initialize() {
|
|||
vec4 lsPos = uLightSpaceMatrix * vec4(FragPos, 1.0);
|
||||
vec3 proj = lsPos.xyz / lsPos.w * 0.5 + 0.5;
|
||||
if (proj.z <= 1.0 && proj.x >= 0.0 && proj.x <= 1.0 && proj.y >= 0.0 && proj.y <= 1.0) {
|
||||
float edgeDist = max(abs(proj.x - 0.5), abs(proj.y - 0.5));
|
||||
float coverageFade = 1.0 - smoothstep(0.40, 0.49, edgeDist);
|
||||
float bias = max(0.005 * (1.0 - abs(dot(normal, lightDir))), 0.001);
|
||||
shadow = 0.0;
|
||||
vec2 texelSize = vec2(1.0 / 2048.0);
|
||||
|
|
@ -133,6 +135,7 @@ bool CharacterRenderer::initialize() {
|
|||
}
|
||||
}
|
||||
shadow /= 9.0;
|
||||
shadow = mix(1.0, shadow, coverageFade);
|
||||
}
|
||||
}
|
||||
shadow = mix(1.0, shadow, clamp(uShadowStrength, 0.0, 1.0));
|
||||
|
|
|
|||
|
|
@ -310,6 +310,8 @@ bool M2Renderer::initialize(pipeline::AssetManager* assets) {
|
|||
vec4 lsPos = uLightSpaceMatrix * vec4(FragPos, 1.0);
|
||||
vec3 proj = lsPos.xyz / lsPos.w * 0.5 + 0.5;
|
||||
if (proj.z <= 1.0 && proj.x >= 0.0 && proj.x <= 1.0 && proj.y >= 0.0 && proj.y <= 1.0) {
|
||||
float edgeDist = max(abs(proj.x - 0.5), abs(proj.y - 0.5));
|
||||
float coverageFade = 1.0 - smoothstep(0.40, 0.49, edgeDist);
|
||||
float bias = max(0.005 * (1.0 - abs(dot(normal, lightDir))), 0.001);
|
||||
shadow = 0.0;
|
||||
vec2 texelSize = vec2(1.0 / 2048.0);
|
||||
|
|
@ -319,6 +321,7 @@ bool M2Renderer::initialize(pipeline::AssetManager* assets) {
|
|||
}
|
||||
}
|
||||
shadow /= 9.0;
|
||||
shadow = mix(1.0, shadow, coverageFade);
|
||||
}
|
||||
}
|
||||
shadow = mix(1.0, shadow, clamp(uShadowStrength, 0.0, 1.0));
|
||||
|
|
@ -497,6 +500,7 @@ bool M2Renderer::loadModel(const pipeline::M2Model& model, uint32_t modelId) {
|
|||
tightMin = glm::min(tightMin, v.position);
|
||||
tightMax = glm::max(tightMax, v.position);
|
||||
}
|
||||
bool foliageOrTreeLike = false;
|
||||
{
|
||||
std::string lowerName = model.name;
|
||||
std::transform(lowerName.begin(), lowerName.end(), lowerName.begin(),
|
||||
|
|
@ -545,6 +549,7 @@ bool M2Renderer::loadModel(const pipeline::M2Model& model, uint32_t modelId) {
|
|||
(lowerName.find("lily") != std::string::npos) ||
|
||||
(lowerName.find("weed") != std::string::npos);
|
||||
bool treeLike = (lowerName.find("tree") != std::string::npos);
|
||||
foliageOrTreeLike = (foliageName || treeLike);
|
||||
bool hardTreePart =
|
||||
(lowerName.find("trunk") != std::string::npos) ||
|
||||
(lowerName.find("stump") != std::string::npos) ||
|
||||
|
|
@ -609,6 +614,7 @@ bool M2Renderer::loadModel(const pipeline::M2Model& model, uint32_t modelId) {
|
|||
break;
|
||||
}
|
||||
}
|
||||
gpuModel.disableAnimation = foliageOrTreeLike;
|
||||
|
||||
// Flag smoke models for UV scroll animation (particle emitters not implemented)
|
||||
{
|
||||
|
|
@ -773,7 +779,7 @@ uint32_t M2Renderer::createInstance(uint32_t modelId, const glm::vec3& position,
|
|||
|
||||
// Initialize animation: play first sequence (usually Stand/Idle)
|
||||
const auto& mdl = models[modelId];
|
||||
if (mdl.hasAnimation && !mdl.sequences.empty()) {
|
||||
if (mdl.hasAnimation && !mdl.disableAnimation && !mdl.sequences.empty()) {
|
||||
instance.currentSequenceIndex = 0;
|
||||
instance.idleSequenceIndex = 0;
|
||||
instance.animDuration = static_cast<float>(mdl.sequences[0].duration);
|
||||
|
|
@ -827,7 +833,7 @@ uint32_t M2Renderer::createInstanceWithMatrix(uint32_t modelId, const glm::mat4&
|
|||
transformAABB(instance.modelMatrix, localMin, localMax, instance.worldBoundsMin, instance.worldBoundsMax);
|
||||
// Initialize animation
|
||||
const auto& mdl2 = models[modelId];
|
||||
if (mdl2.hasAnimation && !mdl2.sequences.empty()) {
|
||||
if (mdl2.hasAnimation && !mdl2.disableAnimation && !mdl2.sequences.empty()) {
|
||||
instance.currentSequenceIndex = 0;
|
||||
instance.idleSequenceIndex = 0;
|
||||
instance.animDuration = static_cast<float>(mdl2.sequences[0].duration);
|
||||
|
|
@ -1040,7 +1046,7 @@ void M2Renderer::update(float deltaTime) {
|
|||
if (it == models.end()) continue;
|
||||
const M2ModelGPU& model = it->second;
|
||||
|
||||
if (!model.hasAnimation) {
|
||||
if (!model.hasAnimation || model.disableAnimation) {
|
||||
instance.animTime += dtMs;
|
||||
continue;
|
||||
}
|
||||
|
|
@ -1139,8 +1145,8 @@ void M2Renderer::render(const Camera& camera, const glm::mat4& view, const glm::
|
|||
|
||||
lastDrawCallCount = 0;
|
||||
|
||||
// Adaptive render distance: shorter in dense areas (cities), longer in open terrain
|
||||
const float maxRenderDistance = (instances.size() > 600) ? 180.0f : 2000.0f;
|
||||
// Adaptive render distance: keep longer tree/foliage visibility to reduce pop-in.
|
||||
const float maxRenderDistance = (instances.size() > 600) ? 320.0f : 2800.0f;
|
||||
const float maxRenderDistanceSq = maxRenderDistance * maxRenderDistance;
|
||||
const float fadeStartFraction = 0.75f;
|
||||
const glm::vec3 camPos = camera.getPosition();
|
||||
|
|
@ -1161,10 +1167,14 @@ void M2Renderer::render(const Camera& camera, const glm::mat4& view, const glm::
|
|||
float worldRadius = model.boundRadius * instance.scale;
|
||||
// Cull small objects (radius < 20) at distance, keep larger objects visible longer
|
||||
float effectiveMaxDistSq = maxRenderDistanceSq * std::max(1.0f, worldRadius / 12.0f);
|
||||
if (model.disableAnimation) {
|
||||
// Trees/foliage keep a larger horizon before culling.
|
||||
effectiveMaxDistSq *= 1.8f;
|
||||
}
|
||||
if (worldRadius < 0.8f) {
|
||||
effectiveMaxDistSq = std::min(effectiveMaxDistSq, 65.0f * 65.0f);
|
||||
} else if (worldRadius < 1.5f) {
|
||||
effectiveMaxDistSq = std::min(effectiveMaxDistSq, 95.0f * 95.0f);
|
||||
} else if (worldRadius < 1.5f) {
|
||||
effectiveMaxDistSq = std::min(effectiveMaxDistSq, 140.0f * 140.0f);
|
||||
}
|
||||
if (distSq > effectiveMaxDistSq) {
|
||||
continue;
|
||||
|
|
@ -1189,7 +1199,7 @@ void M2Renderer::render(const Camera& camera, const glm::mat4& view, const glm::
|
|||
shader->setUniform("uFadeAlpha", fadeAlpha);
|
||||
|
||||
// Upload bone matrices if model has skeletal animation
|
||||
bool useBones = model.hasAnimation && !instance.boneMatrices.empty();
|
||||
bool useBones = model.hasAnimation && !model.disableAnimation && !instance.boneMatrices.empty();
|
||||
shader->setUniform("uUseBones", useBones);
|
||||
if (useBones) {
|
||||
int numBones = std::min(static_cast<int>(instance.boneMatrices.size()), 128);
|
||||
|
|
|
|||
|
|
@ -1607,6 +1607,11 @@ uint32_t Renderer::compileShadowShader() {
|
|||
}
|
||||
|
||||
glm::mat4 Renderer::computeLightSpaceMatrix() {
|
||||
constexpr float kShadowHalfExtent = 180.0f;
|
||||
constexpr float kShadowLightDistance = 280.0f;
|
||||
constexpr float kShadowNearPlane = 1.0f;
|
||||
constexpr float kShadowFarPlane = 600.0f;
|
||||
|
||||
// Sun direction matching WMO light dir
|
||||
glm::vec3 sunDir = glm::normalize(glm::vec3(-0.3f, -0.7f, -0.6f));
|
||||
|
||||
|
|
@ -1630,7 +1635,7 @@ glm::mat4 Renderer::computeLightSpaceMatrix() {
|
|||
glm::vec3 center = shadowCenter;
|
||||
|
||||
// Texel snapping: round center to shadow texel boundaries to prevent shimmer
|
||||
float halfExtent = 120.0f;
|
||||
float halfExtent = kShadowHalfExtent;
|
||||
float texelWorld = (2.0f * halfExtent) / static_cast<float>(SHADOW_MAP_SIZE);
|
||||
|
||||
// Build light view to get stable axes
|
||||
|
|
@ -1639,7 +1644,7 @@ glm::mat4 Renderer::computeLightSpaceMatrix() {
|
|||
if (std::abs(glm::dot(sunDir, up)) > 0.99f) {
|
||||
up = glm::vec3(0.0f, 1.0f, 0.0f);
|
||||
}
|
||||
glm::mat4 lightView = glm::lookAt(center - sunDir * 200.0f, center, up);
|
||||
glm::mat4 lightView = glm::lookAt(center - sunDir * kShadowLightDistance, center, up);
|
||||
|
||||
// Snap center in light space to texel grid
|
||||
glm::vec4 centerLS = lightView * glm::vec4(center, 1.0f);
|
||||
|
|
@ -1650,13 +1655,19 @@ glm::mat4 Renderer::computeLightSpaceMatrix() {
|
|||
shadowCenter = center;
|
||||
|
||||
// Rebuild with snapped center
|
||||
lightView = glm::lookAt(center - sunDir * 200.0f, center, up);
|
||||
glm::mat4 lightProj = glm::ortho(-halfExtent, halfExtent, -halfExtent, halfExtent, 1.0f, 400.0f);
|
||||
lightView = glm::lookAt(center - sunDir * kShadowLightDistance, center, up);
|
||||
glm::mat4 lightProj = glm::ortho(-halfExtent, halfExtent, -halfExtent, halfExtent,
|
||||
kShadowNearPlane, kShadowFarPlane);
|
||||
|
||||
return lightProj * lightView;
|
||||
}
|
||||
|
||||
void Renderer::renderShadowPass() {
|
||||
constexpr float kShadowHalfExtent = 180.0f;
|
||||
constexpr float kShadowLightDistance = 280.0f;
|
||||
constexpr float kShadowNearPlane = 1.0f;
|
||||
constexpr float kShadowFarPlane = 600.0f;
|
||||
|
||||
// Compute light space matrix
|
||||
lightSpaceMatrix = computeLightSpaceMatrix();
|
||||
|
||||
|
|
@ -1698,11 +1709,12 @@ void Renderer::renderShadowPass() {
|
|||
// For simplicity, compute the split:
|
||||
glm::vec3 sunDir = glm::normalize(glm::vec3(-0.3f, -0.7f, -0.6f));
|
||||
glm::vec3 center = shadowCenterInitialized ? shadowCenter : characterPosition;
|
||||
float halfExtent = 120.0f;
|
||||
float halfExtent = kShadowHalfExtent;
|
||||
glm::vec3 up(0.0f, 0.0f, 1.0f);
|
||||
if (std::abs(glm::dot(sunDir, up)) > 0.99f) up = glm::vec3(0.0f, 1.0f, 0.0f);
|
||||
glm::mat4 lightView = glm::lookAt(center - sunDir * 200.0f, center, up);
|
||||
glm::mat4 lightProj = glm::ortho(-halfExtent, halfExtent, -halfExtent, halfExtent, 1.0f, 400.0f);
|
||||
glm::mat4 lightView = glm::lookAt(center - sunDir * kShadowLightDistance, center, up);
|
||||
glm::mat4 lightProj = glm::ortho(-halfExtent, halfExtent, -halfExtent, halfExtent,
|
||||
kShadowNearPlane, kShadowFarPlane);
|
||||
|
||||
// WMO renderShadow needs a Shader reference — but it only uses setUniform("uModel", ...)
|
||||
// We'll create a thin wrapper. Actually, WMO's renderShadow takes a Shader& and calls
|
||||
|
|
|
|||
|
|
@ -127,6 +127,8 @@ bool WMORenderer::initialize(pipeline::AssetManager* assets) {
|
|||
vec4 lsPos = uLightSpaceMatrix * vec4(FragPos, 1.0);
|
||||
vec3 proj = lsPos.xyz / lsPos.w * 0.5 + 0.5;
|
||||
if (proj.z <= 1.0 && proj.x >= 0.0 && proj.x <= 1.0 && proj.y >= 0.0 && proj.y <= 1.0) {
|
||||
float edgeDist = max(abs(proj.x - 0.5), abs(proj.y - 0.5));
|
||||
float coverageFade = 1.0 - smoothstep(0.40, 0.49, edgeDist);
|
||||
float bias = max(0.005 * (1.0 - dot(normal, lightDir)), 0.001);
|
||||
shadow = 0.0;
|
||||
vec2 texelSize = vec2(1.0 / 2048.0);
|
||||
|
|
@ -136,6 +138,7 @@ bool WMORenderer::initialize(pipeline::AssetManager* assets) {
|
|||
}
|
||||
}
|
||||
shadow /= 9.0;
|
||||
shadow = mix(1.0, shadow, coverageFade);
|
||||
}
|
||||
}
|
||||
shadow = mix(1.0, shadow, clamp(uShadowStrength, 0.0, 1.0));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue