private void ComputeProjections(List <SSObject> objects, SSLightBase light, Matrix4 cameraView, Matrix4 cameraProj) { if (light.GetType() != typeof(SSDirectionalLight)) { throw new NotSupportedException(); } SSDirectionalLight dirLight = (SSDirectionalLight)light; // light-aligned unit vectors Vector3 lightZ = dirLight.Direction.Normalized(); Vector3 lightX, lightY; OpenTKHelper.TwoPerpAxes(lightZ, out lightX, out lightY); // transform matrix from regular space into light aligned space Matrix4 lightTransform = new Matrix4( lightX.X, lightX.Y, lightX.Z, 0f, lightY.X, lightY.Y, lightY.Z, 0f, lightZ.X, lightZ.Y, lightZ.Z, 0f, 0f, 0f, 0f, 0f ); // Find AABB of frustum corners in light coordinates Matrix4 cameraViewProj = cameraView * cameraProj; SSAABB frustumLightBB = SSAABB.FromFrustum(ref lightTransform, ref cameraViewProj); bool shrink = false; SSAABB objsLightBB = new SSAABB(float.PositiveInfinity, float.NegativeInfinity); #if true // (optional) scene dependent optimization // Trim the light-bounding box by the shadow receivers (only in light-space x,y,maxz) SSFrustumCuller cameraFrustum = new SSFrustumCuller(ref cameraViewProj); foreach (var obj in objects) { // pass through all shadow casters and receivers if (obj.renderState.toBeDeleted || !obj.renderState.visible || !obj.renderState.receivesShadows || obj.localBoundingSphereRadius <= 0f) { continue; } else if (cameraFrustum.isSphereInsideFrustum(obj.worldBoundingSphere)) { // determine AABB in light coordinates of the objects so far shrink = true; Vector3 lightAlignedPos = Vector3.Transform(obj.worldBoundingSphereCenter, lightTransform); Vector3 rad = new Vector3(obj.worldBoundingSphereRadius); Vector3 localMin = lightAlignedPos - rad; Vector3 localMax = lightAlignedPos + rad; objsLightBB.UpdateMin(localMin); objsLightBB.UpdateMax(localMax); } } #endif // Optimize the light-frustum-projection bounding box by the object-bounding-box SSAABB resultLightBB = new SSAABB(float.PositiveInfinity, float.NegativeInfinity); if (shrink) { // shrink the XY & far-Z coordinates.. resultLightBB.Min.Xy = Vector2.ComponentMax(frustumLightBB.Min.Xy, objsLightBB.Min.Xy); resultLightBB.Min.Z = objsLightBB.Min.Z; resultLightBB.Max = Vector3.ComponentMin(frustumLightBB.Max, objsLightBB.Max); } else { resultLightBB = frustumLightBB; } // View and projection matrices, used by the scene later viewProjFromLightAlignedBB(ref resultLightBB, ref lightTransform, ref lightY, out m_shadowViewMatrix, out m_shadowProjMatrix); // Now extend Z of the result AABB to cover shadow-casters closer to the light inside the // original box foreach (var obj in objects) { if (obj.renderState.toBeDeleted || !obj.renderState.visible || !obj.renderState.castsShadow || obj.localBoundingSphereRadius <= 0f) { continue; } Vector3 lightAlignedPos = Vector3.Transform(obj.worldBoundingSphereCenter, lightTransform); Vector3 rad = new Vector3(obj.worldBoundingSphereRadius); Vector3 localMin = lightAlignedPos - rad; if (localMin.Z < resultLightBB.Min.Z) { Vector3 localMax = lightAlignedPos + rad; if (OpenTKHelper.RectsOverlap(resultLightBB.Min.Xy, resultLightBB.Max.Xy, localMin.Xy, localMax.Xy)) { resultLightBB.Min.Z = localMin.Z; } } } // Generate frustum culler from the BB extended towards light to include shadow casters Matrix4 frustumView, frustumProj; viewProjFromLightAlignedBB(ref resultLightBB, ref lightTransform, ref lightY, out frustumView, out frustumProj); Matrix4 frustumMatrix = frustumView * frustumProj; FrustumCuller = new SSFrustumCuller(ref frustumMatrix); }
protected void ComputeProjections( List <SSObject> objects, Matrix4 cameraView, Matrix4 cameraProj, float fov, float aspect, float cameraNearZ, float cameraFarZ) { if (m_light.GetType() != typeof(SSDirectionalLight)) { throw new NotSupportedException(); } SSDirectionalLight dirLight = (SSDirectionalLight)m_light; // light-aligned unit vectors Vector3 lightZ = dirLight.Direction.Normalized(); Vector3 lightX, lightY; OpenTKHelper.TwoPerpAxes(lightZ, out lightX, out lightY); // transform matrix from regular space into light aligned space Matrix4 lightTransform = new Matrix4( lightX.X, lightX.Y, lightX.Z, 0f, lightY.X, lightY.Y, lightY.Z, 0f, lightZ.X, lightZ.Y, lightZ.Z, 0f, 0f, 0f, 0f, 0f ); // Step 0: camera projection matrix (nearZ and farZ modified) for each frustum split float prevFarZ = cameraNearZ; for (int i = 0; i < c_numberOfSplits; ++i) { // generate frustum splits using Practical Split Scheme (GPU Gems 3, 10.2.1) float iRatio = (float)(i + 1) / (float)c_numberOfSplits; float cLog = cameraNearZ * (float)Math.Pow(cameraFarZ / cameraNearZ, iRatio); float cUni = cameraNearZ + (cameraFarZ - cameraNearZ) * iRatio; float nextFarZ = LogVsLinearSplitFactor * cLog + (1f - LogVsLinearSplitFactor) * cUni; float nextNearZ = prevFarZ; // exported to the shader m_viewSplits [i] = nextFarZ; // create a view proj matrix with the nearZ, farZ values for the current split m_frustumViewProjMatrices[i] = cameraView * Matrix4.CreatePerspectiveFieldOfView(fov, aspect, nextNearZ, nextFarZ); // create light-aligned AABBs of frustums m_frustumLightBB [i] = SSAABB.FromFrustum(ref lightTransform, ref m_frustumViewProjMatrices [i]); prevFarZ = nextFarZ; } #if true // Optional scene-dependent optimization for (int i = 0; i < c_numberOfSplits; ++i) { m_objsLightBB[i] = new SSAABB(float.PositiveInfinity, float.NegativeInfinity); m_splitFrustums[i] = new SSFrustumCuller(ref m_frustumViewProjMatrices[i]); m_shrink[i] = false; } foreach (var obj in objects) { // pass through all shadow casters and receivers if (obj.renderState.toBeDeleted || obj.localBoundingSphereRadius <= 0f || !obj.renderState.visible || !obj.renderState.receivesShadows) { continue; } else { for (int i = 0; i < c_numberOfSplits; ++i) { if (m_splitFrustums[i].isSphereInsideFrustum(obj.worldBoundingSphere)) { // determine AABB in light coordinates of the objects so far m_shrink[i] = true; Vector3 lightAlignedPos = Vector3.Transform(obj.worldBoundingSphereCenter, lightTransform); Vector3 rad = new Vector3(obj.worldBoundingSphereRadius); Vector3 localMin = lightAlignedPos - rad; Vector3 localMax = lightAlignedPos + rad; m_objsLightBB[i].UpdateMin(localMin); m_objsLightBB[i].UpdateMax(localMax); } } } } #endif for (int i = 0; i < c_numberOfSplits; ++i) { if (m_shrink [i]) { m_resultLightBB[i].Min = Vector3.ComponentMax(m_frustumLightBB [i].Min, m_objsLightBB [i].Min); m_resultLightBB [i].Max = Vector3.ComponentMin(m_frustumLightBB [i].Max, m_objsLightBB [i].Max); } else { m_resultLightBB [i] = m_frustumLightBB [i]; } } for (int i = 0; i < c_numberOfSplits; ++i) { // Obtain view + projection + crop matrix, need it later Matrix4 shadowView, shadowProj; viewProjFromLightAlignedBB(ref m_resultLightBB [i], ref lightTransform, ref lightY, out shadowView, out shadowProj); m_shadowViewProjMatrices[i] = shadowView * shadowProj * c_cropMatrices[i]; // obtain view + projection + clio + bias m_shadowViewProjBiasMatrices[i] = m_shadowViewProjMatrices[i] * c_biasMatrix; // There is, currently, no mathematically derived solution to how much Poisson spread scaling // you need for each split. Current improvisation combines 1) increasing spread for the near // splits; reducing spread for the far splits and 2) reducing spread for splits with larger // light-aligned areas; increasing spread for splits with smaller light-aligned areas m_poissonScaling [i] = m_resultLightBB [i].Diff().Xy / (100f * (float)Math.Pow(3.0, i - 1)); } // Combine all splits' BB into one and extend it to include shadow casters closer to light SSAABB castersLightBB = new SSAABB(float.PositiveInfinity, float.NegativeInfinity); for (int i = 0; i < c_numberOfSplits; ++i) { castersLightBB.Combine(ref m_resultLightBB [i]); } // extend Z of the AABB to cover shadow-casters closer to the light foreach (var obj in objects) { if (obj.renderState.toBeDeleted || obj.localBoundingSphereRadius <= 0f || !obj.renderState.visible || !obj.renderState.castsShadow) { continue; } else { Vector3 lightAlignedPos = Vector3.Transform(obj.worldBoundingSphereCenter, lightTransform); Vector3 rad = new Vector3(obj.worldBoundingSphereRadius); Vector3 localMin = lightAlignedPos - rad; Vector3 localMax = lightAlignedPos + rad; if (localMin.Z < castersLightBB.Min.Z) { if (OpenTKHelper.RectsOverlap(castersLightBB.Min.Xy, castersLightBB.Max.Xy, localMin.Xy, localMax.Xy)) { castersLightBB.Min.Z = localMin.Z; } } } } // Generate frustum culler from the BB extended towards light to include shadow casters Matrix4 frustumView, frustumProj; viewProjFromLightAlignedBB(ref castersLightBB, ref lightTransform, ref lightY, out frustumView, out frustumProj); Matrix4 frustumMatrix = frustumView * frustumProj; FrustumCuller = new SSFrustumCuller(ref frustumMatrix); }
protected virtual void setupScene() { scene = new SSScene (mainShader, pssmShader, instancingShader, instancingPssmShader); sunDiskScene = new SSScene (); sunFlareScene = new SSScene (); hudScene = new SSScene (); environmentScene = new SSScene (); scene.renderConfig.frustumCulling = true; // TODO: fix the frustum math, since it seems to be broken. scene.BeforeRenderObject += beforeRenderObjectHandler; // 0. Add Lights var light = new SSDirectionalLight (LightName.Light0); light.Direction = new Vector3(0f, 0f, -1f); #if true if (OpenTKHelper.areFramebuffersSupported ()) { if (scene.renderConfig.pssmShader != null && scene.renderConfig.instancePssmShader != null) { light.ShadowMap = new SSParallelSplitShadowMap (TextureUnit.Texture7); } else { light.ShadowMap = new SSSimpleShadowMap (TextureUnit.Texture7); } } if (!light.ShadowMap.IsValid) { light.ShadowMap = null; } #endif scene.AddLight(light); #if true var smapDebug = new SSObjectHUDQuad (light.ShadowMap.TextureID); smapDebug.Scale = new Vector3(0.3f); smapDebug.Pos = new Vector3(50f, 200, 0f); hudScene.AddObject(smapDebug); #endif // setup a sun billboard object and a sun flare spriter renderer { var sunDisk = new SSMeshDisk (); var sunBillboard = new SSObjectBillboard (sunDisk, true); sunBillboard.MainColor = new Color4 (1f, 1f, 0.8f, 1f); sunBillboard.Pos = new Vector3 (0f, 0f, 18000f); sunBillboard.Scale = new Vector3 (600f); sunBillboard.renderState.frustumCulling = false; sunBillboard.renderState.lighted = false; sunBillboard.renderState.castsShadow = false; sunDiskScene.AddObject(sunBillboard); SSTexture flareTex = SSAssetManager.GetInstance<SSTextureWithAlpha>(".", "sun_flare.png"); const float bigOffset = 0.8889f; const float smallOffset = 0.125f; RectangleF[] flareSpriteRects = { new RectangleF(0f, 0f, 1f, bigOffset), new RectangleF(0f, bigOffset, smallOffset, smallOffset), new RectangleF(smallOffset, bigOffset, smallOffset, smallOffset), new RectangleF(smallOffset*2f, bigOffset, smallOffset, smallOffset), new RectangleF(smallOffset*3f, bigOffset, smallOffset, smallOffset), }; float[] spriteScales = { 20f, 1f, 2f, 1f, 1f }; var sunFlare = new SimpleSunFlareMesh (sunDiskScene, sunBillboard, flareTex, flareSpriteRects, spriteScales); sunFlare.Scale = new Vector3 (2f); sunFlare.renderState.lighted = false; sunFlareScene.AddObject(sunFlare); } }