// renderShadowLayer - Calculates projection for, and renders, one shadow layer void DistantLand::renderShadowLayer(int layer, float radius) { DECLARE_MWBRIDGE D3DXVECTOR3 lookAt, nearPos; D3DXVECTOR3 up(0, 0, 1); D3DXMATRIX *view = &smView[layer], *proj = &smProj[layer], *viewproj = &smViewproj[layer]; // Select light vector, sunPos during daytime, sunVec during night D3DXVECTOR4 lightVec = (sunPos.z > 0) ? -sunPos : sunVec; // Centre of projection is ahead of the player // Not as far in z direction as player is likely looking at the ground plane rather than below lookAt.x = eyePos.x + radius * eyeVec.x; lookAt.y = eyePos.y + radius * eyeVec.y; lookAt.z = eyePos.z + 0.5 * radius * eyeVec.z; // Quantize projection centre to reduce texture swimming lookAt.x = 16.0 * floor(lookAt.x / 16.0); lookAt.y = 16.0 * floor(lookAt.y / 16.0); lookAt.z = 16.0 * floor(lookAt.z / 16.0); // Create shadow frustum centred on lookAt, looking along lightVec const float zrange = 8192.0; nearPos.x = lookAt.x - zrange * lightVec.x; nearPos.y = lookAt.y - zrange * lightVec.y; nearPos.z = lookAt.z - zrange * lightVec.z; D3DXMatrixLookAtRH(view, &nearPos, &lookAt, &up); D3DXMatrixOrthoRH(proj, 2 * radius, (1 + fabs(lightVec.z)) * radius, 0, 2.0 * zrange); *viewproj = (*view) * (*proj); // Texel quantization produces hideous temporal aliasing //viewproj->_41 = floor(viewproj->_41 * 512.0) / 512.0; //viewproj->_42 = floor(viewproj->_42 * 512.0) / 512.0; effect->SetMatrixArray(ehShadowViewproj, viewproj, 1); effectShadow->CommitChanges(); // Cull ViewFrustum range_frustum(viewproj); VisibleSet visible_set; currentWorldSpace->NearStatics->GetVisibleMeshesCoarse(range_frustum, visible_set); currentWorldSpace->FarStatics->GetVisibleMeshesCoarse(range_frustum, visible_set); currentWorldSpace->VeryFarStatics->GetVisibleMeshesCoarse(range_frustum, visible_set); // Render land and statics if(mwBridge->IsExterior()) renderDistantLand(effectShadow, view, proj); device->SetVertexDeclaration(StaticDecl); visible_set.Render(device, effectShadow, effect, &ehTex0, &ehHasAlpha, &ehWorld, SIZEOFSTATICVERT); }
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); }
// renderStage0 - Render distant land at beginning of scene 0, after sky void DistantLand::renderStage0() { DECLARE_MWBRIDGE IDirect3DStateBlock9 *stateSaved; UINT passes; // Get Morrowind camera matrices device->GetTransform(D3DTS_VIEW, &mwView); device->GetTransform(D3DTS_PROJECTION, &mwProj); // Set variables derived from current camera configuration setView(&mwView); adjustFog(); setupCommonEffect(&mwView, &mwProj); // Select distant static set selectDistantCell(); isRenderCached &= (Configuration.MGEFlags & USE_MENU_CACHING) && mwBridge->IsMenu(); if(!isRenderCached) { ///LOG::logline("Sky prims: %d", recordSky.size()); if(isDistantCell()) { // Save state block manually since we can change FVF/decl device->CreateStateBlock(D3DSBT_ALL, &stateSaved); effect->BeginPass(PASS_SETUP); effect->EndPass(); // Shadow map early render if(Configuration.MGEFlags & USE_SHADOWS) { if(mwBridge->CellHasWeather() && !mwBridge->IsMenu()) { effectShadow->Begin(&passes, D3DXFX_DONOTSAVESTATE); renderShadowMap(); effectShadow->End(); } } // Distant everything; bias the projection matrix such that // distant land gets drawn behind anything Morrowind would draw D3DXMATRIX distProj = mwProj; distProj._33 += kDistantZBias; effect->SetMatrix(ehProj, &distProj); effect->Begin(&passes, D3DXFX_DONOTSAVESTATE); if(!mwBridge->IsUnderwater(eyePos.z)) { // Draw distant landscape if(mwBridge->IsExterior()) { effect->BeginPass(PASS_RENDERLAND); renderDistantLand(effect, &mwView, &distProj); effect->EndPass(); } // Draw distant statics, with alpha dissolve as they pass the near view boundary DWORD p = mwBridge->CellHasWeather() ? PASS_RENDERSTATICSEXTERIOR : PASS_RENDERSTATICSINTERIOR; effect->SetFloat(ehDissolveRange, 7168.0); effect->BeginPass(p); cullDistantStatics(&mwView, &distProj); renderDistantStatics(); effect->EndPass(); } // Sky scattering and sky objects (should be drawn late as possible) if((Configuration.MGEFlags & USE_ATM_SCATTER) && mwBridge->CellHasWeather()) { effect->BeginPass(PASS_RENDERSKY); renderSky(); effect->EndPass(); } // Update reflection if(mwBridge->CellHasWater()) renderWaterReflection(&mwView, &distProj); // Update water simulation if(Configuration.MGEFlags & DYNAMIC_RIPPLES) simulateDynamicWaves(); effect->End(); // Reset matrices effect->SetMatrix(ehView, &mwView); effect->SetMatrix(ehProj, &mwProj); // Save distant land only frame to texture if(~Configuration.MGEFlags & NO_MW_MGE_BLEND) texDistantBlend = PostShaders::borrowBuffer(1); // Restore render state stateSaved->Apply(); stateSaved->Release(); } else { // Clear water reflection to avoid seeing previous cell environment reflected // Must be done every frame to react to lighting changes clearReflection(); // Update water simulation if(Configuration.MGEFlags & DYNAMIC_RIPPLES) { // Save state block manually since we can change FVF/decl device->CreateStateBlock(D3DSBT_ALL, &stateSaved); effect->Begin(&passes, D3DXFX_DONOTSAVESTATE); simulateDynamicWaves(); effect->End(); // Restore render state stateSaved->Apply(); stateSaved->Release(); } } } // Clear stray recordings recordMW.clear(); recordSky.clear(); }