Add normal mapping and parallax occlusion mapping for character models

Extends the WMO normal mapping/POM system to character M2 models with
bone-skinned tangents. Auto-generates normal/height maps from diffuse
textures using luminance→height, Sobel→normals (same algorithm as WMO).

- Expand vertex buffer from M2Vertex (48B) to CharVertexGPU (56B) with
  tangent vec4 computed via Lengyel's method in setupModelBuffers()
- Tangents are bone-transformed and Gram-Schmidt orthogonalized in the
  vertex shader, output as TBN for fragment shader consumption
- Fragment shader gains POM ray marching, normal map blending, and LOD
  crossfade via dFdx/dFdy (identical to WMO shader)
- Descriptor set 1 extended with binding 2 for normal/height sampler
- Settings (enable, strength, POM quality) wired from game_screen.cpp
  to both WMO and character renderers via shared UI controls
This commit is contained in:
Kelsi 2026-02-23 01:40:23 -08:00
parent 3c31c43ca6
commit 9eeb9ce64d
7 changed files with 420 additions and 40 deletions

View file

@ -287,6 +287,12 @@ void GameScreen::render(game::GameHandler& gameHandler) {
wr->setNormalMapStrength(pendingNormalMapStrength);
wr->setPOMEnabled(pendingPOM);
wr->setPOMQuality(pendingPOMQuality);
if (auto* cr = renderer->getCharacterRenderer()) {
cr->setNormalMappingEnabled(pendingNormalMapping);
cr->setNormalMapStrength(pendingNormalMapStrength);
cr->setPOMEnabled(pendingPOM);
cr->setPOMQuality(pendingPOMQuality);
}
normalMapSettingsApplied_ = true;
}
}
@ -5914,6 +5920,9 @@ void GameScreen::renderSettingsWindow() {
if (auto* wr = renderer->getWMORenderer()) {
wr->setNormalMappingEnabled(pendingNormalMapping);
}
if (auto* cr = renderer->getCharacterRenderer()) {
cr->setNormalMappingEnabled(pendingNormalMapping);
}
}
saveSettings();
}
@ -5923,6 +5932,9 @@ void GameScreen::renderSettingsWindow() {
if (auto* wr = renderer->getWMORenderer()) {
wr->setNormalMapStrength(pendingNormalMapStrength);
}
if (auto* cr = renderer->getCharacterRenderer()) {
cr->setNormalMapStrength(pendingNormalMapStrength);
}
}
saveSettings();
}
@ -5932,6 +5944,9 @@ void GameScreen::renderSettingsWindow() {
if (auto* wr = renderer->getWMORenderer()) {
wr->setPOMEnabled(pendingPOM);
}
if (auto* cr = renderer->getCharacterRenderer()) {
cr->setPOMEnabled(pendingPOM);
}
}
saveSettings();
}
@ -5942,6 +5957,9 @@ void GameScreen::renderSettingsWindow() {
if (auto* wr = renderer->getWMORenderer()) {
wr->setPOMQuality(pendingPOMQuality);
}
if (auto* cr = renderer->getCharacterRenderer()) {
cr->setPOMQuality(pendingPOMQuality);
}
}
saveSettings();
}
@ -5991,6 +6009,12 @@ void GameScreen::renderSettingsWindow() {
wr->setPOMEnabled(pendingPOM);
wr->setPOMQuality(pendingPOMQuality);
}
if (auto* cr = renderer->getCharacterRenderer()) {
cr->setNormalMappingEnabled(pendingNormalMapping);
cr->setNormalMapStrength(pendingNormalMapStrength);
cr->setPOMEnabled(pendingPOM);
cr->setPOMQuality(pendingPOMQuality);
}
}
saveSettings();
}