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 FrustumCuller(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 FrustumCuller (ref frustumMatrix);
        }
Пример #2
0
        public static void SimpleShadowmapProjection(
            List <SSObject> objects,
            SSLight light,
            FrustumCuller frustum, // can be null (disabled)
            SSCamera camera,
            out float width, out float height, out float nearZ, out float farZ,
            out Vector3 viewEye, out Vector3 viewTarget, out Vector3 viewUp)
        {
            if (light.Type != SSLight.LightType.Directional)
            {
                throw new NotSupportedException();
            }

            // light-aligned unit vectors
            Vector3 lightZ = light.Direction.Normalized();
            Vector3 lightX, lightY;

            OpenTKHelper.TwoPerpAxes(lightZ, out lightX, out lightY);

            // Step 1: light-direction aligned AABB of the visible objects
            Vector3 projBBMin = new Vector3(float.PositiveInfinity);
            Vector3 projBBMax = new Vector3(float.NegativeInfinity);

            foreach (var obj in objects)
            {
                if (obj.renderState.toBeDeleted ||
                    !obj.renderState.visible ||
                    !obj.renderState.castsShadow)
                {
                    continue;
                }
                else if (frustum == null || (obj.boundingSphere != null &&
                                             frustum.isSphereInsideFrustum(obj.Pos, obj.ScaledRadius)))
                {
                    // determine AABB in light coordinates of the objects so far
                    Vector3 lightAlignedPos = OpenTKHelper.ProjectCoord(obj.Pos, lightX, lightY, lightZ);
                    Vector3 rad             = new Vector3(obj.ScaledRadius);
                    Vector3 localMin        = lightAlignedPos - rad;
                    Vector3 localMax        = lightAlignedPos + rad;
                    projBBMin = Vector3.ComponentMin(projBBMin, localMin);
                    projBBMax = Vector3.ComponentMax(projBBMax, localMax);
                }
            }

            if (frustum != null)
            {
                // then we need to do a second pass, including shadow-casters that
                // are between the camera-frusum and the light

                // compute the camera's position in lightspace, because we need to
                // include everything "closer" that the midline of the camera frustum
                Vector3 lightAlignedCameraPos = OpenTKHelper.ProjectCoord(camera.Pos, lightX, lightY, lightZ);
                float   minZTest = lightAlignedCameraPos.Z;

                // TODO what happens if all objects are exluded?

                // Step 2: Extend Z of AABB to cover objects "between" current AABB and the light
                foreach (var obj in objects)
                {
                    if (obj.renderState.toBeDeleted ||
                        !obj.renderState.visible ||
                        !obj.renderState.castsShadow)
                    {
                        continue;
                    }

                    Vector3 lightAlignedPos = OpenTKHelper.ProjectCoord(obj.Pos, lightX, lightY, lightZ);
                    Vector3 rad             = new Vector3(obj.ScaledRadius);
                    Vector3 localMin        = lightAlignedPos - rad;
                    Vector3 localMax        = lightAlignedPos + rad;

                    if (OpenTKHelper.RectsOverlap(projBBMin.Xy, projBBMax.Xy, localMin.Xy, localMax.Xy) &&
                        localMin.Z < minZTest)
                    {
                        projBBMin = Vector3.ComponentMin(projBBMin, localMin);
                        projBBMax = Vector3.ComponentMax(projBBMax, localMax);
                    }
                }
            }
            // Finish the projection matrix

            // Use center of AABB in regular coordinates to get the view matrix
            Vector3 centerAligned = (projBBMin + projBBMax) / 2f;

            viewTarget = centerAligned.X * lightX
                         + centerAligned.Y * lightY
                         + centerAligned.Z * lightZ;
            float farEnough = (centerAligned.Z - projBBMin.Z) + 1f;

            viewEye = viewTarget - farEnough * lightZ;
            viewUp  = lightY;

            width  = projBBMax.X - projBBMin.X;
            height = projBBMax.Y - projBBMin.Y;
            nearZ  = 1f;
            farZ   = 1f + (projBBMax.Z - projBBMin.Z);
        }
Пример #3
0
        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)
            FrustumCuller cameraFrustum = new FrustumCuller (ref cameraViewProj);

            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 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.localBoundingSphereRadius <= 0f
                 || !obj.renderState.visible || !obj.renderState.castsShadow) {
                    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 FrustumCuller (ref frustumMatrix);
        }