// renderShadowMap // Renders multiple shadow map layers to channels in one texture // Applies filtering to soften shadow edges void DistantLand::renderShadowMap() { IDirect3DSurface9 *target, *targetSoft; texShadow->GetSurfaceLevel(0, &target); texSoftShadow->GetSurfaceLevel(0, &targetSoft); // Switch to render target RenderTargetSwitcher rtsw(targetSoft, surfShadowZ); // Unbind shadow samplers effect->SetTexture(ehTex0, 0); effect->SetTexture(ehTex2, 0); // Clear floating point buffer to far depth effectShadow->BeginPass(PASS_CLEARSHADOWMAP); effect->SetBool(ehHasAlpha, false); effectShadow->CommitChanges(); device->SetVertexDeclaration(WaterDecl); device->SetStreamSource(0, vbFullFrame, 0, 12); device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2); effectShadow->EndPass(); // Render near layer (note: just ENABLE_GREEN fails at writing green with some drivers (i.e. mine)) effectShadow->BeginPass(PASS_RENDERSHADOWMAP); device->Clear(0, 0, D3DCLEAR_ZBUFFER, 0, 1.0, 0); device->SetRenderState(D3DRS_COLORWRITEENABLE, ~D3DCOLORWRITEENABLE_RED); renderShadowLayer(0, shadowNearRadius); effectShadow->EndPass(); // Render far layer effectShadow->BeginPass(PASS_RENDERSHADOWMAP); device->Clear(0, 0, D3DCLEAR_ZBUFFER, 0, 1.0, 0); device->SetRenderState(D3DRS_COLORWRITEENABLE, D3DCOLORWRITEENABLE_RED); renderShadowLayer(1, shadowFarRadius); effectShadow->EndPass(); // Soften shadow map device->SetRenderState(D3DRS_COLORWRITEENABLE, 0x0f); device->SetRenderTarget(0, target); effectShadow->BeginPass(PASS_SOFTENSHADOWMAP); effect->SetTexture(ehTex3, texSoftShadow); effect->SetBool(ehHasAlpha, false); // flag as horizontal filter pass effectShadow->CommitChanges(); device->SetVertexDeclaration(WaterDecl); device->SetStreamSource(0, vbFullFrame, 0, 12); device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2); device->SetRenderTarget(0, targetSoft); effect->SetTexture(ehTex3, texShadow); effect->SetBool(ehHasAlpha, true); // flag as vertical filter pass effectShadow->CommitChanges(); device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2); effectShadow->EndPass(); // Clean up surface pointers target->Release(); targetSoft->Release(); }
void DistantLand::renderWaterReflection(const D3DXMATRIX *view, const D3DXMATRIX *proj) { DECLARE_MWBRIDGE // Switch to render target RenderTargetSwitcher rtsw(texReflection, surfReflectionZ); device->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, horizonCol, 1.0, 0); // Calculate reflected view matrix, mirror plane at water mesh level D3DXMATRIX reflView; D3DXPLANE plane(0, 0, 1.0, -(mwBridge->WaterLevel() - 1.0)); D3DXMatrixReflect(&reflView, &plane); D3DXMatrixMultiply(&reflView, &reflView, view); effect->SetMatrix(ehView, &reflView); // Calculate new projection D3DXMATRIX reflProj = *proj; editProjectionZ(&reflProj, 4.0, Configuration.DL.DrawDist * kCellSize); effect->SetMatrix(ehProj, &reflProj); // Clipping setup D3DXMATRIX clipMat; // Clip geometry on opposite side of water plane plane *= mwBridge->IsUnderwater(eyePos.z) ? -1.0 : 1.0; // If using dynamic ripples, the water level can be lowered by up to 0.5 * waveheight // so move clip plane downwards at the cost of some reflection errors if(Configuration.MGEFlags & DYNAMIC_RIPPLES) plane.d += 0.5 * Configuration.DL.WaterWaveHeight; // Doing inverses separately is a lot more numerically stable D3DXMatrixInverse(&clipMat, 0, &reflView); D3DXMatrixTranspose(&clipMat, &clipMat); D3DXPlaneTransform(&plane, &plane, &clipMat); D3DXMatrixInverse(&clipMat, 0, &reflProj); D3DXMatrixTranspose(&clipMat, &clipMat); D3DXPlaneTransform(&plane, &plane, &clipMat); if(visDistant.size() == 0) { // Workaround for a Direct3D bug with clipping planes, where SetClipPlane // has no effect on the shader pipeline if the last rendered draw call was using // the fixed function pipeline. This is usually covered by distant statics, but // not in compact interiors where all distant statics may be culled. // Provoking a DrawPrimitive with shader here makes the following SetClipPlane work. effect->BeginPass(PASS_WORKAROUND); device->SetVertexDeclaration(WaterDecl); device->SetStreamSource(0, vbFullFrame, 0, 12); device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2); effect->EndPass(); } device->SetClipPlane(0, plane); device->SetRenderState(D3DRS_CLIPPLANEENABLE, 1); // Rendering if(mwBridge->IsExterior() && (Configuration.MGEFlags & REFLECTIVE_WATER)) { // Draw land reflection, with opposite culling effect->BeginPass(PASS_RENDERLANDREFL); device->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW); renderDistantLand(effect, &reflView, &reflProj); effect->EndPass(); } if(isDistantCell() && (Configuration.MGEFlags & REFLECT_NEAR)) { // Draw statics reflection, with opposite culling and no dissolve DWORD p = (mwBridge->CellHasWeather() && !mwBridge->IsUnderwater(eyePos.z)) ? PASS_RENDERSTATICSEXTERIOR : PASS_RENDERSTATICSINTERIOR; effect->SetFloat(ehNearViewRange, 0); effect->BeginPass(p); device->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW); renderReflectedStatics(&reflView, &reflProj); effect->EndPass(); effect->SetFloat(ehNearViewRange, nearViewRange); } if((Configuration.MGEFlags & REFLECT_SKY) && !recordSky.empty() && !mwBridge->IsUnderwater(eyePos.z)) { // Draw sky reflection, with opposite culling effect->BeginPass(PASS_RENDERSKY); device->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW); renderReflectedSky(); effect->EndPass(); } // Restore view state device->SetRenderState(D3DRS_CLIPPLANEENABLE, 0); effect->SetMatrix(ehView, view); effect->SetMatrix(ehProj, proj); }
void DistantLand::simulateDynamicWaves() { DECLARE_MWBRIDGE static bool resetRippleSurface = true; static float remainingWaveTime = 0; static const float waveStep = 0.0125f; // time per wave simulation step (1/80 sec) // Simulation paused in menu mode if(mwBridge->IsMenu()) return; device->SetFVF(fvfWave); device->SetStreamSource(0, vbWaveSim, 0, 32); // Calc number of wave iterations to run this frame float frameTime = std::min(mwBridge->frameTime(), 0.5f); remainingWaveTime += frameTime; int numWaveSteps = (int)(remainingWaveTime / waveStep); remainingWaveTime -= numWaveSteps * waveStep; // Preciptation (rain/snow) ripples if(mwBridge->CellHasWeather()) { static float remainingRipples = 0; // Reset surface when not needed next time resetRippleSurface = true; // Weather types: rain = 4; thunderstorm = 5; snow = 8; blizzard = 9 // Thunderstorm causes 50% more ripples int w0 = mwBridge->GetCurrentWeather(), w1 = mwBridge->GetNextWeather(); float precipitation0 = (w0 == 4 || w0 == 5 || w0 == 8 || w0 == 9) ? 1.0 : -1.5; float precipitation1 = (w1 == 4 || w1 == 5 || w1 == 8 || w1 == 9) ? 1.0 : -1.5; precipitation0 += (w0 == 5) ? 0.5 : 0; precipitation1 += (w1 == 5) ? 0.5 : 0; // 150 drops per second for normal precipitation float precipitation = (1.0 - mwBridge->GetWeatherRatio()) * precipitation0 + mwBridge->GetWeatherRatio() * precipitation1; float rippleFrequency = 150.0 * precipitation; if(rippleFrequency > 0) { static double randomizer = 0.546372819; int ripplePos[2]; RECT drop; remainingRipples += rippleFrequency * frameTime; int n = floor(remainingRipples); remainingRipples -= n; while(n-- > 0) { // Place rain ripple at random location for(int i = 0; i != 2; ++i) { randomizer = randomizer * (1337.134511337451 + 0.0001 * rand()) + 0.12351523; randomizer -= floor(randomizer); ripplePos[i] = (int)(randomizer * waveTexResolution); } drop.left = ripplePos[0] - 2; drop.right = ripplePos[0] + 2; drop.top = ripplePos[1] - 1; drop.bottom = ripplePos[1] + 1; device->ColorFill(surfRain, &drop, 0x6060); drop.left = ripplePos[0] - 1; drop.right = ripplePos[0] + 1; drop.top = ripplePos[1] - 2; drop.bottom = ripplePos[1] + 2; device->ColorFill(surfRain, &drop, 0x6060); drop.left = ripplePos[0] - 1; drop.right = ripplePos[0] + 1; drop.top = ripplePos[1] - 1; drop.bottom = ripplePos[1] + 1; device->ColorFill(surfRain, &drop, 0x4040); } } // Apply wave equation numWaveSteps times // Uses double buffering to avoid reads and writes on the same target RenderTargetSwitcher rtsw(surfRippleBuffer, NULL); SurfaceDoubleBuffer doublebuffer; doublebuffer.init(texRain, surfRain, texRippleBuffer, surfRippleBuffer); effect->BeginPass(PASS_WAVESTEP); for(int i = 0; i != numWaveSteps; ++i) { device->SetRenderTarget(0, doublebuffer.sinkSurface()); effect->SetTexture(ehTex4, doublebuffer.sourceTexture()); effect->CommitChanges(); device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 1); doublebuffer.cycle(); } effect->EndPass(); if(doublebuffer.sourceSurface() != surfRain) device->StretchRect(surfRippleBuffer, 0, surfRain, 0, D3DTEXF_NONE); } else if(resetRippleSurface) { // No weather - clear rain ripples device->ColorFill(surfRain, NULL, 0); resetRippleSurface = false; } // Player local ripples // Move ripple texture with player; lock to texel alignment to prevent visible jitter const D3DXVECTOR3 *playerPos = (const D3DXVECTOR3 *)mwBridge->PlayerPositionPointer(); static int lastXpos = (int)floor(playerPos->x / waveTexWorldRes); static int lastYpos = (int)floor(playerPos->y / waveTexWorldRes); int newXpos = (int)floor(playerPos->x / waveTexWorldRes); int newYpos = (int)floor(playerPos->y / waveTexWorldRes); int shiftX = newXpos - lastXpos; int shiftY = newYpos - lastYpos; lastXpos = newXpos; lastYpos = newYpos; int shiftXp = (shiftX > 0) ? +shiftX : 0; int shiftXn = (shiftX < 0) ? -shiftX : 0; int shiftYp = (shiftY > 0) ? +shiftY : 0; int shiftYn = (shiftY < 0) ? -shiftY : 0; // Shift texture by (shiftX, shiftY) pixels RECT source; source.left = 1 + shiftXp; source.right = waveTexResolution - shiftXn; source.top = 1 + shiftYp; source.bottom = waveTexResolution - shiftYn; RECT target; target.left = 1 + shiftXn; target.right = waveTexResolution - shiftXp; target.top = 1 + shiftYn; target.bottom = waveTexResolution - shiftYp; device->ColorFill(surfRippleBuffer, 0, 0); device->StretchRect(surfRipples, &source, surfRippleBuffer, &target, D3DTEXF_NONE); // Water simulation; realigned water starts in surfRippleBuffer // Uses double buffering to avoid reads and writes on the same target RenderTargetSwitcher rtsw(surfRipples, NULL); SurfaceDoubleBuffer doublebuffer; doublebuffer.init(texRippleBuffer, surfRippleBuffer, texRipples, surfRipples); float rippleOrigin[2]; float dz = playerPos->z - mwBridge->WaterLevel(); if(dz < 0 && dz > -128.0f * mwBridge->PlayerHeight()) { // Create waves around the player effect->BeginPass(PASS_PLAYERWAVE); for(int i = 0; i != numWaveSteps; ++i) { // Interpolate between starting and ending point, so that low framerates do not cause less waves float w = -(float)i / (float)numWaveSteps / (float)waveTexResolution; rippleOrigin[0] = w * shiftX + 0.5f; rippleOrigin[1] = w * shiftY + 0.5f; device->SetRenderTarget(0, doublebuffer.sinkSurface()); effect->SetTexture(ehTex4, doublebuffer.sourceTexture()); effect->SetFloatArray(ehRippleOrigin, rippleOrigin, 2); effect->CommitChanges(); device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 1); doublebuffer.cycle(); } effect->EndPass(); } // Apply wave equation numWaveSteps times effect->BeginPass(PASS_WAVESTEP); for(int i = 0; i != numWaveSteps; ++i) { device->SetRenderTarget(0, doublebuffer.sinkSurface()); effect->SetTexture(ehTex4, doublebuffer.sourceTexture()); effect->CommitChanges(); device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 1); doublebuffer.cycle(); } effect->EndPass(); if(doublebuffer.sourceSurface() != surfRipples) device->StretchRect(surfRippleBuffer, 0, surfRipples, 0, D3DTEXF_NONE); // Set wave texture world origin static float halfWaveTexWorldSize = 0.5f * waveTexWorldRes * waveTexResolution; rippleOrigin[0] = lastXpos*waveTexWorldRes - halfWaveTexWorldSize; rippleOrigin[1] = lastYpos*waveTexWorldRes - halfWaveTexWorldSize; effect->SetFloatArray(ehRippleOrigin, rippleOrigin, 2); // Set weather-dependent wave height effect->SetFloat(ehWaveHeight, (float)Configuration.DL.WaterWaveHeight); }