From 5dd445fe0bd6d0d7a0a563226167242fb2d02cac Mon Sep 17 00:00:00 2001 From: Ahmet Fatih Cengiz <37782582+afc-afc0@users.noreply.github.com> Date: Sun, 12 Apr 2026 15:01:19 -0400 Subject: [PATCH 1/3] bugfix(water): Fix river borders appearing darkened under shroud (#2364) The river shroud was applied as a separate multiplicative pass on the framebuffer, which double-darkened terrain showing through transparent river edges. Apply shroud per-vertex in the diffuse color instead, so only the water contribution is shrouded before alpha blending. --- .../W3DDevice/GameClient/Water/W3DWater.cpp | 42 ++++++++++++------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp index 3b99c8d389c..2660180422b 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp @@ -2814,6 +2814,11 @@ void WaterRenderObjClass::drawRiverWater(PolygonTrigger *pTrig) Real constA=3*m_riverVOrigin; + // TheSuperHackers @bugfix Apply shroud per-vertex to avoid double-darkening at river borders. + // The old separate multiplicative shroud pass darkened the entire framebuffer, including + // already-shrouded terrain showing through transparent river edges. + W3DShroud *shroud = TheTerrainRenderObject ? TheTerrainRenderObject->getShroud() : nullptr; + for (i=0; i<(pTrig->getNumPoints()/2); i++) { Real x,y; @@ -2834,7 +2839,16 @@ void WaterRenderObjClass::drawRiverWater(PolygonTrigger *pTrig) vb->y=y; vb->z=innerPt.z; - vb->diffuse= diffuse; + + if (shroud) { + Int cellX = REAL_TO_INT_FLOOR(x / shroud->getCellWidth()); + Int cellY = REAL_TO_INT_FLOOR(y / shroud->getCellHeight()); + W3DShroudLevel level = shroud->getShroudLevel(cellX, cellY); + Real shroudScale = (Real)level / 255.0f; + vb->diffuse = REAL_TO_INT(shadeB * shroudScale) | (REAL_TO_INT(shadeG * shroudScale) << 8) | (REAL_TO_INT(shadeR * shroudScale) << 16) | (diffuse & 0xff000000); + } else { + vb->diffuse = diffuse; + } Real wobbleConst=-m_riverVOrigin+vScale*(Real)i + WWMath::Fast_Sin(2*PI*(vScale*(Real)i) - constA)/22.0f; //old slower version @@ -2856,7 +2870,16 @@ void WaterRenderObjClass::drawRiverWater(PolygonTrigger *pTrig) vb->x=x; vb->y=y; vb->z=outerPt.z; - vb->diffuse= diffuse; + + if (shroud) { + Int cellX = REAL_TO_INT_FLOOR(x / shroud->getCellWidth()); + Int cellY = REAL_TO_INT_FLOOR(y / shroud->getCellHeight()); + W3DShroudLevel level = shroud->getShroudLevel(cellX, cellY); + Real shroudScale = (Real)level / 255.0f; + vb->diffuse = REAL_TO_INT(shadeB * shroudScale) | (REAL_TO_INT(shadeG * shroudScale) << 8) | (REAL_TO_INT(shadeR * shroudScale) << 16) | (diffuse & 0xff000000); + } else { + vb->diffuse = diffuse; + } //old slower version //vb->v1=-m_riverVOrigin+vScale*(Real)i + wobble(vScale*i, m_riverVOrigin, doWobble); vb->v1=wobbleConst; @@ -2908,19 +2931,8 @@ void WaterRenderObjClass::drawRiverWater(PolygonTrigger *pTrig) if (TheWaterTransparency->m_additiveBlend) DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND, D3DBLEND_ONE ); - //do second pass to apply the shroud on water plane - if (TheTerrainRenderObject->getShroud()) - { - W3DShaderManager::setTexture(0,TheTerrainRenderObject->getShroud()->getShroudTexture()); - W3DShaderManager::setShader(W3DShaderManager::ST_SHROUD_TEXTURE, 0); - DX8Wrapper::_Get_D3D_Device8()->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE); - //Shroud shader uses z-compare of EQUAL which wouldn't work on water because it doesn't - //write to the zbuffer. Change to LESSEQUAL. - DX8Wrapper::_Get_D3D_Device8()->SetRenderState(D3DRS_ZFUNC, D3DCMP_LESSEQUAL); - DX8Wrapper::Draw_Triangles( 0,rectangleCount*2, 0, (rectangleCount+1)*2); - DX8Wrapper::_Get_D3D_Device8()->SetRenderState(D3DRS_ZFUNC, D3DCMP_EQUAL); - W3DShaderManager::resetShader(W3DShaderManager::ST_SHROUD_TEXTURE); - } + // TheSuperHackers @bugfix Shroud is now applied per-vertex above, removing the old + // separate multiplicative shroud pass that caused double-darkening at river borders. DX8Wrapper::_Get_D3D_Device8()->SetRenderState(D3DRS_CULLMODE, cull); From 70a7a3c63e9e2b4d8c2549e7bdac68ad7c08cb8f Mon Sep 17 00:00:00 2001 From: Ahmet Fatih Cengiz <37782582+afc-afc0@users.noreply.github.com> Date: Tue, 14 Apr 2026 00:44:11 -0400 Subject: [PATCH 2/3] bugfix(water): cleanup shroud issues --- .../W3DDevice/GameClient/Water/W3DWater.cpp | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp index 2660180422b..720c4639219 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp @@ -56,6 +56,7 @@ #include "Common/Xfer.h" #include "Common/GameLOD.h" +#include "GameClient/Color.h" #include "GameClient/Water.h" #include "GameLogic/GameLogic.h" #include "GameLogic/PolygonTrigger.h" @@ -165,6 +166,19 @@ static ShaderClass blendStagesShader(SC_DETAIL_BLEND); WaterRenderObjClass *TheWaterRenderObj=nullptr; ///getCellWidth()); + Int cellY = (Int)(y / shroud->getCellHeight()); + W3DShroudLevel level = shroud->getShroudLevel(cellX, cellY); + Real shroudScale = (Real)level / 255.0f; + return GameMakeColor( + REAL_TO_INT(shadeR * shroudScale), + REAL_TO_INT(shadeG * shroudScale), + REAL_TO_INT(shadeB * shroudScale), + (diffuse >> 24) & 0xff); +} + void doSkyBoxSet(Bool startDraw) { if (TheWritableGlobalData) @@ -2814,9 +2828,8 @@ void WaterRenderObjClass::drawRiverWater(PolygonTrigger *pTrig) Real constA=3*m_riverVOrigin; - // TheSuperHackers @bugfix Apply shroud per-vertex to avoid double-darkening at river borders. - // The old separate multiplicative shroud pass darkened the entire framebuffer, including - // already-shrouded terrain showing through transparent river edges. + // TheSuperHackers @bugfix afc-afc0 14/04/2026 Apply shroud per-vertex to avoid double-darkening + // at river borders. W3DShroud *shroud = TheTerrainRenderObject ? TheTerrainRenderObject->getShroud() : nullptr; for (i=0; i<(pTrig->getNumPoints()/2); i++) @@ -2841,11 +2854,7 @@ void WaterRenderObjClass::drawRiverWater(PolygonTrigger *pTrig) vb->z=innerPt.z; if (shroud) { - Int cellX = REAL_TO_INT_FLOOR(x / shroud->getCellWidth()); - Int cellY = REAL_TO_INT_FLOOR(y / shroud->getCellHeight()); - W3DShroudLevel level = shroud->getShroudLevel(cellX, cellY); - Real shroudScale = (Real)level / 255.0f; - vb->diffuse = REAL_TO_INT(shadeB * shroudScale) | (REAL_TO_INT(shadeG * shroudScale) << 8) | (REAL_TO_INT(shadeR * shroudScale) << 16) | (diffuse & 0xff000000); + vb->diffuse = getRiverVertexDiffuseWithShroud(shroud, x, y, shadeR, shadeG, shadeB, diffuse); } else { vb->diffuse = diffuse; } @@ -2872,11 +2881,7 @@ void WaterRenderObjClass::drawRiverWater(PolygonTrigger *pTrig) vb->z=outerPt.z; if (shroud) { - Int cellX = REAL_TO_INT_FLOOR(x / shroud->getCellWidth()); - Int cellY = REAL_TO_INT_FLOOR(y / shroud->getCellHeight()); - W3DShroudLevel level = shroud->getShroudLevel(cellX, cellY); - Real shroudScale = (Real)level / 255.0f; - vb->diffuse = REAL_TO_INT(shadeB * shroudScale) | (REAL_TO_INT(shadeG * shroudScale) << 8) | (REAL_TO_INT(shadeR * shroudScale) << 16) | (diffuse & 0xff000000); + vb->diffuse = getRiverVertexDiffuseWithShroud(shroud, x, y, shadeR, shadeG, shadeB, diffuse); } else { vb->diffuse = diffuse; } @@ -2931,8 +2936,6 @@ void WaterRenderObjClass::drawRiverWater(PolygonTrigger *pTrig) if (TheWaterTransparency->m_additiveBlend) DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND, D3DBLEND_ONE ); - // TheSuperHackers @bugfix Shroud is now applied per-vertex above, removing the old - // separate multiplicative shroud pass that caused double-darkening at river borders. DX8Wrapper::_Get_D3D_Device8()->SetRenderState(D3DRS_CULLMODE, cull); From ee91667e9798c19fa28d9af23ec1a0036b68e9b7 Mon Sep 17 00:00:00 2001 From: Ahmet Fatih Cengiz <37782582+afc-afc0@users.noreply.github.com> Date: Tue, 14 Apr 2026 21:24:06 -0400 Subject: [PATCH 3/3] Address PR Review --- .../W3DDevice/GameClient/Water/W3DWater.cpp | 23 ++++++++----------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp index 720c4639219..564cc008e42 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp @@ -166,16 +166,19 @@ static ShaderClass blendStagesShader(SC_DETAIL_BLEND); WaterRenderObjClass *TheWaterRenderObj=nullptr; ///getCellWidth()); Int cellY = (Int)(y / shroud->getCellHeight()); W3DShroudLevel level = shroud->getShroudLevel(cellX, cellY); Real shroudScale = (Real)level / 255.0f; return GameMakeColor( - REAL_TO_INT(shadeR * shroudScale), - REAL_TO_INT(shadeG * shroudScale), - REAL_TO_INT(shadeB * shroudScale), + (Int)(shadeR * shroudScale), + (Int)(shadeG * shroudScale), + (Int)(shadeB * shroudScale), (diffuse >> 24) & 0xff); } @@ -2853,11 +2856,7 @@ void WaterRenderObjClass::drawRiverWater(PolygonTrigger *pTrig) vb->z=innerPt.z; - if (shroud) { - vb->diffuse = getRiverVertexDiffuseWithShroud(shroud, x, y, shadeR, shadeG, shadeB, diffuse); - } else { - vb->diffuse = diffuse; - } + vb->diffuse = getRiverVertexDiffuse(shroud, x, y, shadeR, shadeG, shadeB, diffuse); Real wobbleConst=-m_riverVOrigin+vScale*(Real)i + WWMath::Fast_Sin(2*PI*(vScale*(Real)i) - constA)/22.0f; //old slower version @@ -2880,11 +2879,7 @@ void WaterRenderObjClass::drawRiverWater(PolygonTrigger *pTrig) vb->y=y; vb->z=outerPt.z; - if (shroud) { - vb->diffuse = getRiverVertexDiffuseWithShroud(shroud, x, y, shadeR, shadeG, shadeB, diffuse); - } else { - vb->diffuse = diffuse; - } + vb->diffuse = getRiverVertexDiffuse(shroud, x, y, shadeR, shadeG, shadeB, diffuse); //old slower version //vb->v1=-m_riverVOrigin+vScale*(Real)i + wobble(vScale*i, m_riverVOrigin, doWobble); vb->v1=wobbleConst;