void PSSMLightShadowMap::_render( RenderPassManager* renderPass, const SceneRenderState *diffuseState ) { PROFILE_SCOPE(PSSMLightShadowMap_render); const ShadowMapParams *params = mLight->getExtended<ShadowMapParams>(); const LightMapParams *lmParams = mLight->getExtended<LightMapParams>(); const bool bUseLightmappedGeometry = lmParams ? !lmParams->representedInLightmap || lmParams->includeLightmappedGeometryInShadow : true; const U32 texSize = getBestTexSize( params->numSplits < 4 ? params->numSplits : 2 ); if ( mShadowMapTex.isNull() || mNumSplits != params->numSplits || mTexSize != texSize ) { _setNumSplits( params->numSplits, texSize ); mShadowMapDepth = _getDepthTarget( mShadowMapTex->getWidth(), mShadowMapTex->getHeight() ); } mLogWeight = params->logWeight; Frustum fullFrustum( diffuseState->getCameraFrustum() ); fullFrustum.cropNearFar(fullFrustum.getNearDist(), params->shadowDistance); GFXFrustumSaver frustSaver; GFXTransformSaver saver; // Set our render target GFX->pushActiveRenderTarget(); mTarget->attachTexture( GFXTextureTarget::Color0, mShadowMapTex ); mTarget->attachTexture( GFXTextureTarget::DepthStencil, mShadowMapDepth ); GFX->setActiveRenderTarget( mTarget ); GFX->clear( GFXClearStencil | GFXClearZBuffer | GFXClearTarget, ColorI(255,255,255), 1.0f, 0 ); // Calculate our standard light matrices MatrixF lightMatrix; calcLightMatrices( lightMatrix, diffuseState->getCameraFrustum() ); lightMatrix.inverse(); MatrixF lightViewProj = GFX->getProjectionMatrix() * lightMatrix; // TODO: This is just retrieving the near and far calculated // in calcLightMatrices... we should make that clear. F32 pnear, pfar; GFX->getFrustum( NULL, NULL, NULL, NULL, &pnear, &pfar, NULL ); // Set our view up GFX->setWorldMatrix(lightMatrix); MatrixF toLightSpace = lightMatrix; // * invCurrentView; _calcSplitPos(fullFrustum); mWorldToLightProj = GFX->getProjectionMatrix() * toLightSpace; // Apply the PSSM const F32 savedSmallestVisible = TSShapeInstance::smSmallestVisiblePixelSize; const F32 savedDetailAdjust = TSShapeInstance::smDetailAdjust; TSShapeInstance::smDetailAdjust *= smDetailAdjustScale; TSShapeInstance::smSmallestVisiblePixelSize = smSmallestVisiblePixelSize; for (U32 i = 0; i < mNumSplits; i++) { GFXTransformSaver saver; // Calculate a sub-frustum Frustum subFrustum(fullFrustum); subFrustum.cropNearFar(mSplitDist[i], mSplitDist[i+1]); // Calculate our AABB in the light's clip space. Box3F clipAABB = _calcClipSpaceAABB(subFrustum, lightViewProj, fullFrustum.getFarDist()); // Calculate our crop matrix Point3F scale(2.0f / (clipAABB.maxExtents.x - clipAABB.minExtents.x), 2.0f / (clipAABB.maxExtents.y - clipAABB.minExtents.y), 1.0f); // TODO: This seems to produce less "pops" of the // shadow resolution as the camera spins around and // it should produce pixels that are closer to being // square. // // Still is it the right thing to do? // scale.y = scale.x = ( getMin( scale.x, scale.y ) ); //scale.x = mFloor(scale.x); //scale.y = mFloor(scale.y); Point3F offset( -0.5f * (clipAABB.maxExtents.x + clipAABB.minExtents.x) * scale.x, -0.5f * (clipAABB.maxExtents.y + clipAABB.minExtents.y) * scale.y, 0.0f ); MatrixF cropMatrix(true); cropMatrix.scale(scale); cropMatrix.setPosition(offset); _roundProjection(lightMatrix, cropMatrix, offset, i); cropMatrix.setPosition(offset); // Save scale/offset for shader computations mScaleProj[i].set(scale); mOffsetProj[i].set(offset); // Adjust the far plane to the max z we got (maybe add a little to deal with split overlap) bool isOrtho; { F32 left, right, bottom, top, nearDist, farDist; GFX->getFrustum(&left, &right, &bottom, &top, &nearDist, &farDist,&isOrtho); // BTRTODO: Fix me! farDist = clipAABB.maxExtents.z; if (!isOrtho) GFX->setFrustum(left, right, bottom, top, nearDist, farDist); else { // Calculate a new far plane, add a fudge factor to avoid bringing // the far plane in too close. F32 newFar = pfar * clipAABB.maxExtents.z + 1.0f; mFarPlaneScalePSSM[i] = (pfar - pnear) / (newFar - pnear); GFX->setOrtho(left, right, bottom, top, pnear, newFar, true); } } // Crop matrix multiply needs to be post-projection. MatrixF alightProj = GFX->getProjectionMatrix(); alightProj = cropMatrix * alightProj; // Set our new projection GFX->setProjectionMatrix(alightProj); // Render into the quad of the shadow map we are using. GFX->setViewport(mViewports[i]); SceneManager* sceneManager = diffuseState->getSceneManager(); // The frustum is currently the full size and has not had // cropping applied. // // We make that adjustment here. const Frustum& uncroppedFrustum = GFX->getFrustum(); Frustum croppedFrustum; scale *= 0.5f; croppedFrustum.set( isOrtho, uncroppedFrustum.getNearLeft() / scale.x, uncroppedFrustum.getNearRight() / scale.x, uncroppedFrustum.getNearTop() / scale.y, uncroppedFrustum.getNearBottom() / scale.y, uncroppedFrustum.getNearDist(), uncroppedFrustum.getFarDist(), uncroppedFrustum.getTransform() ); MatrixF camera = GFX->getWorldMatrix(); camera.inverse(); croppedFrustum.setTransform( camera ); // Setup the scene state and use the diffuse state // camera position and screen metrics values so that // lod is done the same as in the diffuse pass. SceneRenderState shadowRenderState ( sceneManager, SPT_Shadow, SceneCameraState( diffuseState->getViewport(), croppedFrustum, GFX->getWorldMatrix(), GFX->getProjectionMatrix() ), renderPass ); shadowRenderState.getMaterialDelegate().bind( this, &LightShadowMap::getShadowMaterial ); shadowRenderState.renderNonLightmappedMeshes( true ); shadowRenderState.renderLightmappedMeshes( bUseLightmappedGeometry ); shadowRenderState.setDiffuseCameraTransform( diffuseState->getCameraTransform() ); shadowRenderState.setWorldToScreenScale( diffuseState->getWorldToScreenScale() ); U32 objectMask = SHADOW_TYPEMASK; if ( i == mNumSplits-1 && params->lastSplitTerrainOnly ) objectMask = TerrainObjectType; sceneManager->renderSceneNoLights( &shadowRenderState, objectMask ); _debugRender( &shadowRenderState ); } // Restore the original TS lod settings. TSShapeInstance::smSmallestVisiblePixelSize = savedSmallestVisible; TSShapeInstance::smDetailAdjust = savedDetailAdjust; // Release our render target mTarget->resolve(); GFX->popActiveRenderTarget(); }
void PSSMLightShadowMap::_calcPlanesCullForShadowCasters(Vector< Vector<PlaneF> > &out, const Frustum &viewFrustum, const Point3F &_ligthDir) { #define ENABLE_CULL_ASSERT PROFILE_SCOPE(PSSMLightShadowMap_render_getCullFrustrum); Point3F ligthDir = _ligthDir; PlaneF lightFarPlane, lightNearPlane; MatrixF lightFarPlaneMat(true); MatrixF invLightFarPlaneMat(true); // init data { ligthDir.normalize(); Point3F viewDir = viewFrustum.getTransform().getForwardVector(); viewDir.normalize(); const Point3F viewPosition = viewFrustum.getPosition(); const F32 viewDistance = viewFrustum.getBounds().len(); lightNearPlane = PlaneF(viewPosition + (viewDistance * -ligthDir), ligthDir); const Point3F lightFarPlanePos = viewPosition + (viewDistance * ligthDir); lightFarPlane = PlaneF(lightFarPlanePos, -ligthDir); lightFarPlaneMat = MathUtils::createOrientFromDir(-ligthDir); lightFarPlaneMat.setPosition(lightFarPlanePos); lightFarPlaneMat.invertTo(&invLightFarPlaneMat); } Vector<Point2F> projVertices; //project all frustum vertices into plane // all vertices are 2d and local to far plane projVertices.setSize(8); for (int i = 0; i < 8; ++i) // { const Point3F &point = viewFrustum.getPoints()[i]; #ifdef ENABLE_CULL_ASSERT AssertFatal( PlaneF::Front == lightNearPlane.whichSide(point), "" ); AssertFatal( PlaneF::Front == lightFarPlane.whichSide(point), "" ); #endif Point3F localPoint(lightFarPlane.project(point)); invLightFarPlaneMat.mulP(localPoint); projVertices[i] = Point2F(localPoint.x, localPoint.z); } //create hull arround projected proints Vector<Point2F> hullVerts; MathUtils::mBuildHull2D(projVertices, hullVerts); Vector<PlaneF> planes; planes.push_back(lightNearPlane); planes.push_back(lightFarPlane); //build planes for (int i = 0; i < (hullVerts.size() - 1); ++i) { Point2F pos2D = (hullVerts[i] + hullVerts[i + 1]) / 2; Point3F pos3D(pos2D.x, 0, pos2D.y); Point3F pos3DA(hullVerts[i].x, 0, hullVerts[i].y); Point3F pos3DB(hullVerts[i + 1].x, 0, hullVerts[i + 1].y); // move hull points to 3d space lightFarPlaneMat.mulP(pos3D); lightFarPlaneMat.mulP(pos3DA); lightFarPlaneMat.mulP(pos3DB); PlaneF plane(pos3D, MathUtils::mTriangleNormal(pos3DB, pos3DA, (pos3DA - ligthDir))); planes.push_back(plane); } //recalculate planes for each splits for (int split = 0; split < mNumSplits; ++split) { Frustum subFrustum(viewFrustum); subFrustum.cropNearFar(mSplitDist[split], mSplitDist[split + 1]); subFrustum.setFarDist(getMin(subFrustum.getFarDist()*2.5f, viewFrustum.getFarDist())); subFrustum.update(); Vector<PlaneF> subPlanes = planes; for (int planeIdx = 0; planeIdx < subPlanes.size(); ++planeIdx) { PlaneF &plane = subPlanes[planeIdx]; F32 minDist = 0; //calculate near vertex distance for (int vertexIdx = 0; vertexIdx < 8; ++vertexIdx) { Point3F point = subFrustum.getPoints()[vertexIdx]; minDist = getMin(plane.distToPlane(point), minDist); } // move plane to near vertex Point3F newPos = plane.getPosition() + (plane.getNormal() * minDist); plane = PlaneF(newPos, plane.getNormal()); #ifdef ENABLE_CULL_ASSERT for(int x = 0; x < 8; ++x) { AssertFatal( PlaneF::Back != plane.whichSide( subFrustum.getPoints()[x] ), ""); } #endif } out.push_back(subPlanes); } #undef ENABLE_CULL_ASSERT }