Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

OpenTK/OpenGL Frustum Culling Clipping Too Soon

Tags:

c#

opentk

I just recently added frustum culling to my voxel game; it worked pretty well at first glance. However, I immediately noticed that the frustum seems to be a little off—being that some voxels near the edge of the window are prematurely cut-off, before completely falling out of visual scope. Here is an image that illustrates my endeavor:

Frustum Glitch

And Here are three classes which will likely be needed to solve this problem.

My FrustumWidget Class:

using System;
using OpenTK;

namespace GameProject.Game.Client.Widgets {

    public class FrustumWidget {
        private float[] _clipMatrix = new float[ 16 ];
        private float[ , ] _frustum = new float[ 6 , 4 ];
        const int RIGHT = 0 , LEFT = 1 , BOTTOM = 2 , TOP = 3 , BACK = 4 , FRONT = 5;

        private void NormalizePlane( float[ , ] frustum , int side ) {
            float magnitude = ( float )Math.Sqrt( ( frustum[ side , 0 ] * frustum[ side , 0 ] ) + ( frustum[ side , 1 ] * frustum[ side , 1 ] )
                                                + ( frustum[ side , 2 ] * frustum[ side , 2 ] ) );
            frustum[ side , 0 ] /= magnitude;
            frustum[ side , 1 ] /= magnitude;
            frustum[ side , 2 ] /= magnitude;
            frustum[ side , 3 ] /= magnitude;
        }

        public bool SphereInFrustum( float x , float y , float z , float radius ) {
            float d = 0;
            for( int p = 0; p < 6; p++ ) {
                d = _frustum[ p , 0 ] * x + _frustum[ p , 1 ] * y + _frustum[ p , 2 ] * z + _frustum[ p , 3 ];
                if( d <= -radius ) 
                {
                    return false;
                }
            }
            return true;
        }

        public bool VoxelWithinFrustum( float x1 , float y1 , float z1 , float x2 , float y2 , float z2 ) {
            for( int i = 0; i < 6; i++ ) {
                if( ( this._frustum[ i , 0 ] * x1 + this._frustum[ i , 1 ] * y1 + this._frustum[ i , 2 ] * z1 + this._frustum[ i, 3 ] <= 0.0F ) &&
                  ( this._frustum[ i , 0 ] * x2 + this._frustum[ i , 1 ] * y1 + this._frustum[ i , 2 ] * z1 + this._frustum[ i , 3 ] <= 0.0F ) &&
                  ( this._frustum[ i , 0 ] * x1 + this._frustum[ i , 1 ] * y2 + this._frustum[ i , 2 ] * z1 + this._frustum[ i , 3 ] <= 0.0F ) &&
                  ( this._frustum[ i , 0 ] * x2 + this._frustum[ i , 1 ] * y2 + this._frustum[ i , 2 ] * z1 + this._frustum[ i , 3 ] <= 0.0F ) &&
                  ( this._frustum[ i , 0 ] * x1 + this._frustum[ i , 1 ] * y1 + this._frustum[ i , 2 ] * z2 + this._frustum[ i , 3 ] <= 0.0F ) &&
                  ( this._frustum[ i , 0 ] * x2 + this._frustum[ i , 1 ] * y1 + this._frustum[ i , 2 ] * z2 + this._frustum[ i , 3 ] <= 0.0F ) &&
                  ( this._frustum[ i , 0 ] * x1 + this._frustum[ i , 1 ] * y2 + this._frustum[ i , 2 ] * z2 + this._frustum[ i , 3 ] <= 0.0F ) &&
                  ( this._frustum[ i , 0 ] * x2 + this._frustum[ i , 1 ] * y2 + this._frustum[ i , 2 ] * z2 + this._frustum[ i , 3 ] <= 0.0F ) ) {
                    return false;
                }
            }
            return true;
        }

        public void CalculateFrustum( Matrix4 projectionMatrix , Matrix4 modelViewMatrix ) {
            _clipMatrix[ 0 ] = ( modelViewMatrix.M11 * projectionMatrix.M11 ) + ( modelViewMatrix.M12 * projectionMatrix.M21 ) + ( modelViewMatrix.M13 * projectionMatrix.M31 ) + ( modelViewMatrix.M14 * projectionMatrix.M41 );
            _clipMatrix[ 1 ] = ( modelViewMatrix.M11 * projectionMatrix.M12 ) + ( modelViewMatrix.M12 * projectionMatrix.M22 ) + ( modelViewMatrix.M13 * projectionMatrix.M32 ) + ( modelViewMatrix.M14 * projectionMatrix.M42 );
            _clipMatrix[ 2 ] = ( modelViewMatrix.M11 * projectionMatrix.M13 ) + ( modelViewMatrix.M12 * projectionMatrix.M23 ) + ( modelViewMatrix.M13 * projectionMatrix.M33 ) + ( modelViewMatrix.M14 * projectionMatrix.M43 );
            _clipMatrix[ 3 ] = ( modelViewMatrix.M11 * projectionMatrix.M14 ) + ( modelViewMatrix.M12 * projectionMatrix.M24 ) + ( modelViewMatrix.M13 * projectionMatrix.M34 ) + ( modelViewMatrix.M14 * projectionMatrix.M44 );

            _clipMatrix[ 4 ] = ( modelViewMatrix.M21 * projectionMatrix.M11 ) + ( modelViewMatrix.M22 * projectionMatrix.M21 ) + ( modelViewMatrix.M23 * projectionMatrix.M31 ) + ( modelViewMatrix.M24 * projectionMatrix.M41 );
            _clipMatrix[ 5 ] = ( modelViewMatrix.M21 * projectionMatrix.M12 ) + ( modelViewMatrix.M22 * projectionMatrix.M22 ) + ( modelViewMatrix.M23 * projectionMatrix.M32 ) + ( modelViewMatrix.M24 * projectionMatrix.M42 );
            _clipMatrix[ 6 ] = ( modelViewMatrix.M21 * projectionMatrix.M13 ) + ( modelViewMatrix.M22 * projectionMatrix.M23 ) + ( modelViewMatrix.M23 * projectionMatrix.M33 ) + ( modelViewMatrix.M24 * projectionMatrix.M43 );
            _clipMatrix[ 7 ] = ( modelViewMatrix.M21 * projectionMatrix.M14 ) + ( modelViewMatrix.M22 * projectionMatrix.M24 ) + ( modelViewMatrix.M23 * projectionMatrix.M34 ) + ( modelViewMatrix.M24 * projectionMatrix.M44 );

            _clipMatrix[ 8 ] = ( modelViewMatrix.M31 * projectionMatrix.M11 ) + ( modelViewMatrix.M32 * projectionMatrix.M21 ) + ( modelViewMatrix.M33 * projectionMatrix.M31 ) + ( modelViewMatrix.M34 * projectionMatrix.M41 );
            _clipMatrix[ 9 ] = ( modelViewMatrix.M31 * projectionMatrix.M12 ) + ( modelViewMatrix.M32 * projectionMatrix.M22 ) + ( modelViewMatrix.M33 * projectionMatrix.M32 ) + ( modelViewMatrix.M34 * projectionMatrix.M42 );
            _clipMatrix[ 10 ] = ( modelViewMatrix.M31 * projectionMatrix.M13 ) + ( modelViewMatrix.M32 * projectionMatrix.M23 ) + ( modelViewMatrix.M33 * projectionMatrix.M33 ) + ( modelViewMatrix.M34 * projectionMatrix.M43 );
            _clipMatrix[ 11 ] = ( modelViewMatrix.M31 * projectionMatrix.M14 ) + ( modelViewMatrix.M32 * projectionMatrix.M24 ) + ( modelViewMatrix.M33 * projectionMatrix.M34 ) + ( modelViewMatrix.M34 * projectionMatrix.M44 );

            _clipMatrix[ 12 ] = ( modelViewMatrix.M41 * projectionMatrix.M11 ) + ( modelViewMatrix.M42 * projectionMatrix.M21 ) + ( modelViewMatrix.M43 * projectionMatrix.M31 ) + ( modelViewMatrix.M44 * projectionMatrix.M41 );
            _clipMatrix[ 13 ] = ( modelViewMatrix.M41 * projectionMatrix.M12 ) + ( modelViewMatrix.M42 * projectionMatrix.M22 ) + ( modelViewMatrix.M43 * projectionMatrix.M32 ) + ( modelViewMatrix.M44 * projectionMatrix.M42 );
            _clipMatrix[ 14 ] = ( modelViewMatrix.M41 * projectionMatrix.M13 ) + ( modelViewMatrix.M42 * projectionMatrix.M23 ) + ( modelViewMatrix.M43 * projectionMatrix.M33 ) + ( modelViewMatrix.M44 * projectionMatrix.M43 );
            _clipMatrix[ 15 ] = ( modelViewMatrix.M41 * projectionMatrix.M14 ) + ( modelViewMatrix.M42 * projectionMatrix.M24 ) + ( modelViewMatrix.M43 * projectionMatrix.M34 ) + ( modelViewMatrix.M44 * projectionMatrix.M44 );

            _frustum[ RIGHT , 0 ] = _clipMatrix[ 3 ] - _clipMatrix[ 0 ];
            _frustum[ RIGHT , 1 ] = _clipMatrix[ 7 ] - _clipMatrix[ 4 ];
            _frustum[ RIGHT , 2 ] = _clipMatrix[ 11 ] - _clipMatrix[ 8 ];
            _frustum[ RIGHT , 3 ] = _clipMatrix[ 15 ] - _clipMatrix[ 12 ];
            NormalizePlane( _frustum , RIGHT );

            _frustum[ LEFT , 0 ] = _clipMatrix[ 3 ] + _clipMatrix[ 0 ];
            _frustum[ LEFT , 1 ] = _clipMatrix[ 7 ] + _clipMatrix[ 4 ];
            _frustum[ LEFT , 2 ] = _clipMatrix[ 11 ] + _clipMatrix[ 8 ];
            _frustum[ LEFT , 3 ] = _clipMatrix[ 15 ] + _clipMatrix[ 12 ];
            NormalizePlane( _frustum , LEFT );

            _frustum[ BOTTOM , 0 ] = _clipMatrix[ 3 ] + _clipMatrix[ 1 ];
            _frustum[ BOTTOM , 1 ] = _clipMatrix[ 7 ] + _clipMatrix[ 5 ];
            _frustum[ BOTTOM , 2 ] = _clipMatrix[ 11 ] + _clipMatrix[ 9 ];
            _frustum[ BOTTOM , 3 ] = _clipMatrix[ 15 ] + _clipMatrix[ 13 ];
            NormalizePlane( _frustum , BOTTOM );

            _frustum[ TOP , 0 ] = _clipMatrix[ 3 ] - _clipMatrix[ 1 ];
            _frustum[ TOP , 1 ] = _clipMatrix[ 7 ] - _clipMatrix[ 5 ];
            _frustum[ TOP , 2 ] = _clipMatrix[ 11 ] - _clipMatrix[ 9 ];
            _frustum[ TOP , 3 ] = _clipMatrix[ 15 ] - _clipMatrix[ 13 ];
            NormalizePlane( _frustum , TOP );

            _frustum[ BACK , 0 ] = _clipMatrix[ 3 ] - _clipMatrix[ 2 ];
            _frustum[ BACK , 1 ] = _clipMatrix[ 7 ] - _clipMatrix[ 6 ];
            _frustum[ BACK , 2 ] = _clipMatrix[ 11 ] - _clipMatrix[ 10 ];
            _frustum[ BACK , 3 ] = _clipMatrix[ 15 ] - _clipMatrix[ 14 ];
            NormalizePlane( _frustum , BACK );

            _frustum[ FRONT , 0 ] = _clipMatrix[ 3 ] + _clipMatrix[ 2 ];
            _frustum[ FRONT , 1 ] = _clipMatrix[ 7 ] + _clipMatrix[ 6 ];
            _frustum[ FRONT , 2 ] = _clipMatrix[ 11 ] + _clipMatrix[ 10 ];
            _frustum[ FRONT , 3 ] = _clipMatrix[ 15 ] + _clipMatrix[ 14 ];
            NormalizePlane( _frustum , FRONT );
        }
    }
}

My FirstPersonCameraWidget class:

using System;
using System.Drawing;
using OpenTK;
using GameProject.Game.Framework.OpenTKExtensions;
using OpenTK.Graphics.OpenGL;
using OpenTK.Input;

namespace GameProject.Game.Client.Widgets {
    public class FirstPersonCameraWidget {

        public Matrix4 CameraMatrix;
        public Vector3 Location;
        public FrustumWidget Frustum;
        public float Pitch;
        public float Yaw;
        public float MoveSpeed;
        public float LookSpeed;
        public Vector2 MouseSpeed;
        public bool IsLooking;
        public float NearZ;
        public float FarZ;

        public FirstPersonCameraWidget(Vector3 cameraOrigin , float pitch , float yaw , float movementSpeed , float lookSpeed ) {
            CameraMatrix = Matrix4.Identity;
            this.Location = cameraOrigin;
            this.Pitch = pitch;
            this.Yaw = yaw;
            this.LookSpeed = lookSpeed;
            this.MoveSpeed = movementSpeed;
            Frustum = new FrustumWidget();
        }

        public void Update() {
            if( IsLooking ) {
                // Possible redundant method?
            }
        }

        public void ProcessMovement( KeyboardState keyboardState , FrameEventArgs e ) {
            if( keyboardState[ Key.W ] ) {
                MoveForward( e );
            }
            if( keyboardState[ Key.S ] ) {
                MoveBackward( e );
            }
            if( keyboardState[ Key.A ] ) {
                StrafeLeft( e );
            }
            if( keyboardState[ Key.D ] ) {
                StrafeRight( e );
            }
            if( keyboardState[ Key.Space ] ) {
                FlyUp( e );
            }
            if( keyboardState[ Key.LShift ] || keyboardState[ Key.RShift ] ) {
                FlyDown( e );
            }
        }

        public void MoveForward( FrameEventArgs e ) {
            Location.X += ( float )Math.Cos( Yaw ) * MoveSpeed * ( float )e.Time;
            //CameraPosition.Y += ( float )Math.Tan( Pitch ) * MoveSpeed * ( float )e.Time;
            Location.Z += ( float )Math.Sin( Yaw ) * MoveSpeed * ( float )e.Time;
        }

        public void MoveBackward( FrameEventArgs e ) {
            Location.X -= ( float )Math.Cos( Yaw ) * MoveSpeed * ( float )e.Time;
            //CameraPosition.Y -= ( float )Math.Tan( Pitch ) * MoveSpeed * ( float )e.Time;
            Location.Z -= ( float )Math.Sin( Yaw ) * MoveSpeed * ( float )e.Time;
        }

        public void StrafeLeft( FrameEventArgs e ) {
            Location.X -= ( float )Math.Cos( Yaw + Math.PI / 2 ) * MoveSpeed * ( float )e.Time;
            Location.Z -= ( float )Math.Sin( Yaw + Math.PI / 2 ) * MoveSpeed * ( float )e.Time;
        }

        public void StrafeRight( FrameEventArgs e ) {
            Location.X += ( float )Math.Cos( Yaw + Math.PI / 2 ) * MoveSpeed * ( float )e.Time;
            Location.Z += ( float )Math.Sin( Yaw + Math.PI / 2 ) * MoveSpeed * ( float )e.Time;
        }

        public void FlyUp( FrameEventArgs e ) {
            Location.Y += MoveSpeed * ( float )e.Time;
        }

        public void FlyDown( FrameEventArgs e ) {
            Location.Y -= MoveSpeed * ( float )e.Time;
        }

        public void LookThrough( GameWindow window , Point mouseLocation , FrameEventArgs e ) {
            if( IsLooking ) {
                Point windowCenter = new Point( window.Bounds.Left + window.Bounds.Width / 2 , window.Bounds.Top + window.Bounds.Height / 2 );
                Vector2 MouseDelta = new Vector2( mouseLocation.X - window.PointToClient( windowCenter ).X , mouseLocation.Y - window.PointToClient( windowCenter ).Y );
                System.Windows.Forms.Cursor.Position = windowCenter;
                MouseSpeed.X *= LookSpeed;
                MouseSpeed.Y *= LookSpeed;
                MouseSpeed.X += MouseDelta.X / 40.0f * ( float )e.Time;
                MouseSpeed.Y += MouseDelta.Y / 40.0f * ( float )e.Time;
                Yaw += MouseSpeed.X;
                Pitch -= MouseSpeed.Y;
                if( Pitch <= -( ( OpenTKExtensions.HalfPI * 2 ) * 0.5f ) + 0.01f ) {
                    Pitch = -( ( OpenTKExtensions.HalfPI * 2 ) * 0.5f ) + 0.01f;
                }
                if( Pitch >= ( ( OpenTKExtensions.HalfPI * 2 ) * 0.5f ) - 0.01f ) {
                    Pitch = ( ( OpenTKExtensions.HalfPI * 2 ) * 0.5f ) - 0.01f;
                }
            }
            Vector3 LookAtPoint = new Vector3( ( float )Math.Cos( Yaw ) , ( float )Math.Tan( Pitch ) , ( float )Math.Sin( Yaw ) );
            CameraMatrix = Matrix4.LookAt( Location , Location + LookAtPoint , Vector3.UnitY );

            /**
             * Recalculate the Viewport Frustum
             */
            Matrix4 projection = new Matrix4();
            GL.GetFloat(GetPName.ProjectionMatrix, out projection);
            Matrix4 modelview = new Matrix4();
            GL.GetFloat(GetPName.ModelviewMatrix, out modelview);
            Frustum.CalculateFrustum(projection,modelview);
        }
    }
}

And Lastly, my GameClient class:

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Threading.Tasks;
using GameProject.Game.Framework.DataStructure;
using GameProject.Game.Framework.Generators;
using GameProject.Game.Framework.Geometry;
using System.Diagnostics;
using GameProject.Game.Framework.OpenGLExtensions;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;
using OpenTK.Input;
using GameProject.Game.Client.Widgets;
using QuickFont;

namespace GameProject.Game.Client {
    public class GameClient : GameWindow {

        /**
         * Client Variables
         */
        public const String CLIENT_VERSION = "v0.0.1";
        public const String GAME_NAME = "Voxelbyte";
        public FirstPersonCameraWidget GameCamera;
        private Point _mousePosition;
        private Point _lastMousePosition;
        private bool _centerCursorAfterRelease = false;
        /**
         * Game Variables
         */
        public VoxelWorld World;
        public List<GameSelection> SelectionMemory;
        public QFont DeveloperText;

        public GameClient()
            : base( 800 , 600 , GraphicsMode.Default , GAME_NAME ) {
            GL.Enable( EnableCap.DepthTest );
            GL.ClearColor( 0.3f , 0.3f , 0.3f , 0.0f );

        }

        protected override void OnLoad( EventArgs e ) {
            base.OnLoad( e );

            DeveloperText = new QFont( "Resources/Fonts/times.ttf" , 14 , FontStyle.Regular );
            //TestFont.Options.UseDefaultBlendFunction = false;
            //this.VSync = VSyncMode.On;
            SelectionMemory = new List<GameSelection>();
            GameCamera = new FirstPersonCameraWidget(
                new Vector3( 30.0f , 8.0f , 30.0f ) ,
                0.0f ,
                0.0f ,
                15.0f ,
                0.4f );
            //this.WindowState = WindowState.Fullscreen;
            GL.CullFace( CullFaceMode.Back );
            GL.Enable( EnableCap.CullFace );
            GL.BlendFunc( BlendingFactorSrc.SrcAlpha , BlendingFactorDest.OneMinusSrcAlpha );
            GL.Enable( EnableCap.Blend );
            // Generate Map And Log the Time
            World = BattleWorldReader.ReadFromImage( "ColoredPyramid.png" );
            Stopwatch sw = new Stopwatch();
            sw.Start();
            World.Rebuild();
            sw.Stop();
            Console.WriteLine( "World Generation+Optimization took: {0}ms" , sw.ElapsedMilliseconds );
            //GLExtensions.EnableWireframe();
        }

        protected override void OnMouseMove( MouseMoveEventArgs e ) {
            base.OnMouseMove( e );
            _mousePosition = new Point( e.X , e.Y );
            if( e.Mouse.IsButtonDown( MouseButton.Right ) ) {

            }
        }

        protected override void OnMouseDown( MouseButtonEventArgs e ) {
            base.OnMouseDown( e );
            if( e.Mouse.IsButtonDown( MouseButton.Right ) ) {
                Point nativeMouse = System.Windows.Forms.Cursor.Position;
                _lastMousePosition = nativeMouse;
                Point windowCenter = new Point( Bounds.Left + Bounds.Width / 2 , Bounds.Top + Bounds.Height / 2 );
                System.Windows.Forms.Cursor.Position = windowCenter;
                GameCamera.IsLooking = true;
                this.CursorVisible = false;
            }
        }

        protected override void OnMouseUp( MouseButtonEventArgs e ) {
            base.OnMouseUp( e );
            if( e.Button == MouseButton.Right ) {
                GameCamera.IsLooking = false;
                if( !_centerCursorAfterRelease ) {
                    System.Windows.Forms.Cursor.Position = _lastMousePosition;
                }
                this.CursorVisible = true;
            }
        }

        protected override void OnRenderFrame( FrameEventArgs e ) {
            base.OnRenderFrame( e );
            GameCamera.LookThrough( this , _mousePosition , e );
            GL.MatrixMode( MatrixMode.Modelview );
            GL.Clear( ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit );
            GL.LoadMatrix( ref GameCamera.CameraMatrix );
            //
            //
            GL.EnableClientState( ArrayCap.VertexArray );
            GL.EnableClientState( ArrayCap.TextureCoordArray );
            GL.DisableClientState( ArrayCap.ColorArray );
            int numberOfVoxelsRendered = 0;
            foreach( Voxel voxel in World.Voxels ) {

                if( GameCamera.Frustum.VoxelWithinFrustum( voxel.Location.X, voxel.Location.Y, voxel.Location.Z, 1.0f,1.0f,1.0f ) ) {
                    voxel.Render( GameCamera );
                    numberOfVoxelsRendered++;
                }

            }
            GL.DisableClientState( ArrayCap.VertexArray );
            GL.DisableClientState( ArrayCap.TextureCoordArray );
            GL.DisableClientState( ArrayCap.ColorArray );

            // Render Dev Hud
            RenderDeveloperHud();
            //


            SwapBuffers();
            this.Title = GAME_NAME + " FPS: " + ( int )this.RenderFrequency + " Voxels: " + numberOfVoxelsRendered;
        }

        private void RenderDeveloperHud() {
            QFont.Begin();
            GL.PushMatrix();
            GL.Translate( 0.0f , 5 , 0f );
            DeveloperText.Print( "Voxelbyte" );
            GL.Translate( 0f , 30 , 0f );
            DeveloperText.Print( "X: " + ( int )GameCamera.Location.X );
            GL.Translate( 0f , 24 , 0f );
            DeveloperText.Print( "Y: " + ( int )GameCamera.Location.Y );
            GL.Translate( 0.0f , 24 , 0f );
            DeveloperText.Print( "Z: " + ( int )GameCamera.Location.Z );
            GL.PopMatrix();
            QFont.End();
        }

        private void CheckKeyboardInput( FrameEventArgs eventArgs ) {
            KeyboardState keyboardState = OpenTK.Input.Keyboard.GetState();
            GameCamera.ProcessMovement( keyboardState , eventArgs );
            if( keyboardState[ Key.Escape ] ) {
                Exit();
            }
        }

        protected override void OnUpdateFrame( FrameEventArgs e ) {
            CheckKeyboardInput( e );
        }

        protected override void OnResize( EventArgs e ) {
            base.OnResize( e );
            GL.Viewport( ClientRectangle.X , ClientRectangle.Y , ClientRectangle.Width , ClientRectangle.Height );
            Matrix4 projection = Matrix4.CreatePerspectiveFieldOfView( ( float )Math.PI / 4 , Width / ( float )Height , 0.1f , 1000.0f );
            GL.MatrixMode( MatrixMode.Projection );
            GL.LoadMatrix( ref projection );
        }
    }
}

Anyone know what the deal is here? Because I am stumped.

EDIT:

I just noticed that if I use a different detecting method (SphereInFrustum) everything seems to work perfectly; nothing is clipped prematurely. Here is my new renderloop:

    foreach( Voxel voxel in World.Voxels ) {
        if( voxel.IsVisible() ) {
            if( GameCamera.Frustum.SphereInFrustum(
                voxel.Location.X , voxel.Location.Y , voxel.Location.Z , 1.5f ) ) {
                voxel.Render( GameCamera );
                numberOfVoxelsRendered++;
            }
        }

    }

As you can see I am passing in a sphere radius of 1.5f, and basically pretending all my Voxels are spheres. It works, and there is no premature clipping, however I want to use my VoxelWithinFrustum method. Can anyone spot if I did something wrong with that method? Or if I am using the parameters incorrectly?

like image 480
Krythic Avatar asked Sep 13 '14 07:09

Krythic


2 Answers

I noticed that this post was one of the first google hits for opengl frustum culling, so I wanted to provide proper, working code as a new answer. Here is my updated frustum class:

public class Frustum
    {
        private readonly float[] _clipMatrix = new float[ 16 ];
        private readonly float[ , ] _frustum = new float[ 6 , 4 ];

        public const int A = 0;
        public const int B = 1;
        public const int C = 2;
        public const int D = 3;

        public enum ClippingPlane : int
        {
            Right = 0 ,
            Left = 1 ,
            Bottom = 2 ,
            Top = 3 ,
            Back = 4 ,
            Front = 5
        }

        private void NormalizePlane( float[ , ] frustum , int side )
        {
            float magnitude = ( float )Math.Sqrt( ( frustum[ side , 0 ] * frustum[ side , 0 ] ) + ( frustum[ side , 1 ] * frustum[ side , 1 ] )
                                                + ( frustum[ side , 2 ] * frustum[ side , 2 ] ) );
            frustum[ side , 0 ] /= magnitude;
            frustum[ side , 1 ] /= magnitude;
            frustum[ side , 2 ] /= magnitude;
            frustum[ side , 3 ] /= magnitude;
        }

        public bool PointVsFrustum( float x , float y , float z )
        {
            for( int i = 0; i < 6; i++ )
            {
                if( this._frustum[ i , 0 ] * x + this._frustum[ i , 1 ] * y + this._frustum[ i , 2 ] * z + this._frustum[ i , 3 ] <= 0.0f )
                {
                    return false;
                }
            }
            return true;
        }

        public bool PointVsFrustum( Vector3 location )
        {
            for( int i = 0; i < 6; i++ )
            {
                if( this._frustum[ i , 0 ] * location.X + this._frustum[ i , 1 ] * location.Y + this._frustum[ i , 2 ] * location.Z + this._frustum[ i , 3 ] <= 0.0f )
                {
                    return false;
                }
            }
            return true;
        }

        public bool SphereVsFrustum( float x , float y , float z , float radius )
        {
            for( int p = 0; p < 6; p++ )
            {
                float d = _frustum[ p , 0 ] * x + _frustum[ p , 1 ] * y + _frustum[ p , 2 ] * z + _frustum[ p , 3 ];
                if( d <= -radius )
                {
                    return false;
                }
            }
            return true;
        }

        public bool SphereVsFrustum( Vector3 location , float radius )
        {
            for( int p = 0; p < 6; p++ )
            {
                float d = _frustum[ p , 0 ] * location.X + _frustum[ p , 1 ] * location.Y + _frustum[ p , 2 ] * location.Z + _frustum[ p , 3 ];
                if( d <= -radius )
                {
                    return false;
                }
            }
            return true;
        }

        public bool VolumeVsFrustum( float x , float y , float z , float width , float height , float length )
        {
            for( int i = 0; i < 6; i++ )
            {
                if( _frustum[ i , A ] * ( x - width ) + _frustum[ i , B ] * ( y - height ) + _frustum[ i , C ] * ( z - length ) + _frustum[ i , D ] > 0 )
                    continue;
                if( _frustum[ i , A ] * ( x + width ) + _frustum[ i , B ] * ( y - height ) + _frustum[ i , C ] * ( z - length ) + _frustum[ i , D ] > 0 )
                    continue;
                if( _frustum[ i , A ] * ( x - width ) + _frustum[ i , B ] * ( y + height ) + _frustum[ i , C ] * ( z - length ) + _frustum[ i , D ] > 0 )
                    continue;
                if( _frustum[ i , A ] * ( x + width ) + _frustum[ i , B ] * ( y + height ) + _frustum[ i , C ] * ( z - length ) + _frustum[ i , D ] > 0 )
                    continue;
                if( _frustum[ i , A ] * ( x - width ) + _frustum[ i , B ] * ( y - height ) + _frustum[ i , C ] * ( z + length ) + _frustum[ i , D ] > 0 )
                    continue;
                if( _frustum[ i , A ] * ( x + width ) + _frustum[ i , B ] * ( y - height ) + _frustum[ i , C ] * ( z + length ) + _frustum[ i , D ] > 0 )
                    continue;
                if( _frustum[ i , A ] * ( x - width ) + _frustum[ i , B ] * ( y + height ) + _frustum[ i , C ] * ( z + length ) + _frustum[ i , D ] > 0 )
                    continue;
                if( _frustum[ i , A ] * ( x + width ) + _frustum[ i , B ] * ( y + height ) + _frustum[ i , C ] * ( z + length ) + _frustum[ i , D ] > 0 )
                    continue;
                return false;
            }
            return true;
        }

        public bool VolumeVsFrustum( BoundingVolume volume )
        {
            for( int i = 0; i < 6; i++ )
            {
                if( _frustum[ i , A ] * ( volume.X - volume.Width ) + _frustum[ i , B ] * ( volume.Y - volume.Height ) + _frustum[ i , C ] * ( volume.Z - volume.Length ) + _frustum[ i , D ] > 0 )
                    continue;
                if( _frustum[ i , A ] * ( volume.X + volume.Width ) + _frustum[ i , B ] * ( volume.Y - volume.Height ) + _frustum[ i , C ] * ( volume.Z - volume.Length ) + _frustum[ i , D ] > 0 )
                    continue;
                if( _frustum[ i , A ] * ( volume.X - volume.Width ) + _frustum[ i , B ] * ( volume.Y + volume.Height ) + _frustum[ i , C ] * ( volume.Z - volume.Length ) + _frustum[ i , D ] > 0 )
                    continue;
                if( _frustum[ i , A ] * ( volume.X + volume.Width ) + _frustum[ i , B ] * ( volume.Y + volume.Height ) + _frustum[ i , C ] * ( volume.Z - volume.Length ) + _frustum[ i , D ] > 0 )
                    continue;
                if( _frustum[ i , A ] * ( volume.X - volume.Width ) + _frustum[ i , B ] * ( volume.Y - volume.Height ) + _frustum[ i , C ] * ( volume.Z + volume.Length ) + _frustum[ i , D ] > 0 )
                    continue;
                if( _frustum[ i , A ] * ( volume.X + volume.Width ) + _frustum[ i , B ] * ( volume.Y - volume.Height ) + _frustum[ i , C ] * ( volume.Z + volume.Length ) + _frustum[ i , D ] > 0 )
                    continue;
                if( _frustum[ i , A ] * ( volume.X - volume.Width ) + _frustum[ i , B ] * ( volume.Y + volume.Height ) + _frustum[ i , C ] * ( volume.Z + volume.Length ) + _frustum[ i , D ] > 0 )
                    continue;
                if( _frustum[ i , A ] * ( volume.X + volume.Width ) + _frustum[ i , B ] * ( volume.Y + volume.Height ) + _frustum[ i , C ] * ( volume.Z + volume.Length ) + _frustum[ i , D ] > 0 )
                    continue;
                return false;
            }
            return true;
        }

        public bool VolumeVsFrustum( Vector3 location , float width , float height , float length )
        {
            for( int i = 0; i < 6; i++ )
            {
                if( _frustum[ i , A ] * ( location.X - width ) + _frustum[ i , B ] * ( location.Y - height ) + _frustum[ i , C ] * ( location.Z - length ) + _frustum[ i , D ] > 0 )
                    continue;
                if( _frustum[ i , A ] * ( location.X + width ) + _frustum[ i , B ] * ( location.Y - height ) + _frustum[ i , C ] * ( location.Z - length ) + _frustum[ i , D ] > 0 )
                    continue;
                if( _frustum[ i , A ] * ( location.X - width ) + _frustum[ i , B ] * ( location.Y + height ) + _frustum[ i , C ] * ( location.Z - length ) + _frustum[ i , D ] > 0 )
                    continue;
                if( _frustum[ i , A ] * ( location.X + width ) + _frustum[ i , B ] * ( location.Y + height ) + _frustum[ i , C ] * ( location.Z - length ) + _frustum[ i , D ] > 0 )
                    continue;
                if( _frustum[ i , A ] * ( location.X - width ) + _frustum[ i , B ] * ( location.Y - height ) + _frustum[ i , C ] * ( location.Z + length ) + _frustum[ i , D ] > 0 )
                    continue;
                if( _frustum[ i , A ] * ( location.X + width ) + _frustum[ i , B ] * ( location.Y - height ) + _frustum[ i , C ] * ( location.Z + length ) + _frustum[ i , D ] > 0 )
                    continue;
                if( _frustum[ i , A ] * ( location.X - width ) + _frustum[ i , B ] * ( location.Y + height ) + _frustum[ i , C ] * ( location.Z + length ) + _frustum[ i , D ] > 0 )
                    continue;
                if( _frustum[ i , A ] * ( location.X + width ) + _frustum[ i , B ] * ( location.Y + height ) + _frustum[ i , C ] * ( location.Z + length ) + _frustum[ i , D ] > 0 )
                    continue;
                return false;
            }
            return true;
        }

        public bool CubeVsFrustum( float x , float y , float z , float size )
        {
            for( int i = 0; i < 6; i++ )
            {
                if( _frustum[ i , A ] * ( x - size ) + _frustum[ i , B ] * ( y - size ) + _frustum[ i , C ] * ( z - size ) + _frustum[ i , D ] > 0 )
                    continue;
                if( _frustum[ i , A ] * ( x + size ) + _frustum[ i , B ] * ( y - size ) + _frustum[ i , C ] * ( z - size ) + _frustum[ i , D ] > 0 )
                    continue;
                if( _frustum[ i , A ] * ( x - size ) + _frustum[ i , B ] * ( y + size ) + _frustum[ i , C ] * ( z - size ) + _frustum[ i , D ] > 0 )
                    continue;
                if( _frustum[ i , A ] * ( x + size ) + _frustum[ i , B ] * ( y + size ) + _frustum[ i , C ] * ( z - size ) + _frustum[ i , D ] > 0 )
                    continue;
                if( _frustum[ i , A ] * ( x - size ) + _frustum[ i , B ] * ( y - size ) + _frustum[ i , C ] * ( z + size ) + _frustum[ i , D ] > 0 )
                    continue;
                if( _frustum[ i , A ] * ( x + size ) + _frustum[ i , B ] * ( y - size ) + _frustum[ i , C ] * ( z + size ) + _frustum[ i , D ] > 0 )
                    continue;
                if( _frustum[ i , A ] * ( x - size ) + _frustum[ i , B ] * ( y + size ) + _frustum[ i , C ] * ( z + size ) + _frustum[ i , D ] > 0 )
                    continue;
                if( _frustum[ i , A ] * ( x + size ) + _frustum[ i , B ] * ( y + size ) + _frustum[ i , C ] * ( z + size ) + _frustum[ i , D ] > 0 )
                    continue;
                return false;
            }
            return true;
        }


        public void CalculateFrustum( Matrix4 projectionMatrix , Matrix4 modelViewMatrix )
        {
            _clipMatrix[ 0 ] = ( modelViewMatrix.M11 * projectionMatrix.M11 ) + ( modelViewMatrix.M12 * projectionMatrix.M21 ) + ( modelViewMatrix.M13 * projectionMatrix.M31 ) + ( modelViewMatrix.M14 * projectionMatrix.M41 );
            _clipMatrix[ 1 ] = ( modelViewMatrix.M11 * projectionMatrix.M12 ) + ( modelViewMatrix.M12 * projectionMatrix.M22 ) + ( modelViewMatrix.M13 * projectionMatrix.M32 ) + ( modelViewMatrix.M14 * projectionMatrix.M42 );
            _clipMatrix[ 2 ] = ( modelViewMatrix.M11 * projectionMatrix.M13 ) + ( modelViewMatrix.M12 * projectionMatrix.M23 ) + ( modelViewMatrix.M13 * projectionMatrix.M33 ) + ( modelViewMatrix.M14 * projectionMatrix.M43 );
            _clipMatrix[ 3 ] = ( modelViewMatrix.M11 * projectionMatrix.M14 ) + ( modelViewMatrix.M12 * projectionMatrix.M24 ) + ( modelViewMatrix.M13 * projectionMatrix.M34 ) + ( modelViewMatrix.M14 * projectionMatrix.M44 );

            _clipMatrix[ 4 ] = ( modelViewMatrix.M21 * projectionMatrix.M11 ) + ( modelViewMatrix.M22 * projectionMatrix.M21 ) + ( modelViewMatrix.M23 * projectionMatrix.M31 ) + ( modelViewMatrix.M24 * projectionMatrix.M41 );
            _clipMatrix[ 5 ] = ( modelViewMatrix.M21 * projectionMatrix.M12 ) + ( modelViewMatrix.M22 * projectionMatrix.M22 ) + ( modelViewMatrix.M23 * projectionMatrix.M32 ) + ( modelViewMatrix.M24 * projectionMatrix.M42 );
            _clipMatrix[ 6 ] = ( modelViewMatrix.M21 * projectionMatrix.M13 ) + ( modelViewMatrix.M22 * projectionMatrix.M23 ) + ( modelViewMatrix.M23 * projectionMatrix.M33 ) + ( modelViewMatrix.M24 * projectionMatrix.M43 );
            _clipMatrix[ 7 ] = ( modelViewMatrix.M21 * projectionMatrix.M14 ) + ( modelViewMatrix.M22 * projectionMatrix.M24 ) + ( modelViewMatrix.M23 * projectionMatrix.M34 ) + ( modelViewMatrix.M24 * projectionMatrix.M44 );

            _clipMatrix[ 8 ] = ( modelViewMatrix.M31 * projectionMatrix.M11 ) + ( modelViewMatrix.M32 * projectionMatrix.M21 ) + ( modelViewMatrix.M33 * projectionMatrix.M31 ) + ( modelViewMatrix.M34 * projectionMatrix.M41 );
            _clipMatrix[ 9 ] = ( modelViewMatrix.M31 * projectionMatrix.M12 ) + ( modelViewMatrix.M32 * projectionMatrix.M22 ) + ( modelViewMatrix.M33 * projectionMatrix.M32 ) + ( modelViewMatrix.M34 * projectionMatrix.M42 );
            _clipMatrix[ 10 ] = ( modelViewMatrix.M31 * projectionMatrix.M13 ) + ( modelViewMatrix.M32 * projectionMatrix.M23 ) + ( modelViewMatrix.M33 * projectionMatrix.M33 ) + ( modelViewMatrix.M34 * projectionMatrix.M43 );
            _clipMatrix[ 11 ] = ( modelViewMatrix.M31 * projectionMatrix.M14 ) + ( modelViewMatrix.M32 * projectionMatrix.M24 ) + ( modelViewMatrix.M33 * projectionMatrix.M34 ) + ( modelViewMatrix.M34 * projectionMatrix.M44 );

            _clipMatrix[ 12 ] = ( modelViewMatrix.M41 * projectionMatrix.M11 ) + ( modelViewMatrix.M42 * projectionMatrix.M21 ) + ( modelViewMatrix.M43 * projectionMatrix.M31 ) + ( modelViewMatrix.M44 * projectionMatrix.M41 );
            _clipMatrix[ 13 ] = ( modelViewMatrix.M41 * projectionMatrix.M12 ) + ( modelViewMatrix.M42 * projectionMatrix.M22 ) + ( modelViewMatrix.M43 * projectionMatrix.M32 ) + ( modelViewMatrix.M44 * projectionMatrix.M42 );
            _clipMatrix[ 14 ] = ( modelViewMatrix.M41 * projectionMatrix.M13 ) + ( modelViewMatrix.M42 * projectionMatrix.M23 ) + ( modelViewMatrix.M43 * projectionMatrix.M33 ) + ( modelViewMatrix.M44 * projectionMatrix.M43 );
            _clipMatrix[ 15 ] = ( modelViewMatrix.M41 * projectionMatrix.M14 ) + ( modelViewMatrix.M42 * projectionMatrix.M24 ) + ( modelViewMatrix.M43 * projectionMatrix.M34 ) + ( modelViewMatrix.M44 * projectionMatrix.M44 );

            _frustum[ ( int )ClippingPlane.Right , 0 ] = _clipMatrix[ 3 ] - _clipMatrix[ 0 ];
            _frustum[ ( int )ClippingPlane.Right , 1 ] = _clipMatrix[ 7 ] - _clipMatrix[ 4 ];
            _frustum[ ( int )ClippingPlane.Right , 2 ] = _clipMatrix[ 11 ] - _clipMatrix[ 8 ];
            _frustum[ ( int )ClippingPlane.Right , 3 ] = _clipMatrix[ 15 ] - _clipMatrix[ 12 ];
            NormalizePlane( _frustum , ( int )ClippingPlane.Right );

            _frustum[ ( int )ClippingPlane.Left , 0 ] = _clipMatrix[ 3 ] + _clipMatrix[ 0 ];
            _frustum[ ( int )ClippingPlane.Left , 1 ] = _clipMatrix[ 7 ] + _clipMatrix[ 4 ];
            _frustum[ ( int )ClippingPlane.Left , 2 ] = _clipMatrix[ 11 ] + _clipMatrix[ 8 ];
            _frustum[ ( int )ClippingPlane.Left , 3 ] = _clipMatrix[ 15 ] + _clipMatrix[ 12 ];
            NormalizePlane( _frustum , ( int )ClippingPlane.Left );

            _frustum[ ( int )ClippingPlane.Bottom , 0 ] = _clipMatrix[ 3 ] + _clipMatrix[ 1 ];
            _frustum[ ( int )ClippingPlane.Bottom , 1 ] = _clipMatrix[ 7 ] + _clipMatrix[ 5 ];
            _frustum[ ( int )ClippingPlane.Bottom , 2 ] = _clipMatrix[ 11 ] + _clipMatrix[ 9 ];
            _frustum[ ( int )ClippingPlane.Bottom , 3 ] = _clipMatrix[ 15 ] + _clipMatrix[ 13 ];
            NormalizePlane( _frustum , ( int )ClippingPlane.Bottom );

            _frustum[ ( int )ClippingPlane.Top , 0 ] = _clipMatrix[ 3 ] - _clipMatrix[ 1 ];
            _frustum[ ( int )ClippingPlane.Top , 1 ] = _clipMatrix[ 7 ] - _clipMatrix[ 5 ];
            _frustum[ ( int )ClippingPlane.Top , 2 ] = _clipMatrix[ 11 ] - _clipMatrix[ 9 ];
            _frustum[ ( int )ClippingPlane.Top , 3 ] = _clipMatrix[ 15 ] - _clipMatrix[ 13 ];
            NormalizePlane( _frustum , ( int )ClippingPlane.Top );

            _frustum[ ( int )ClippingPlane.Back , 0 ] = _clipMatrix[ 3 ] - _clipMatrix[ 2 ];
            _frustum[ ( int )ClippingPlane.Back , 1 ] = _clipMatrix[ 7 ] - _clipMatrix[ 6 ];
            _frustum[ ( int )ClippingPlane.Back , 2 ] = _clipMatrix[ 11 ] - _clipMatrix[ 10 ];
            _frustum[ ( int )ClippingPlane.Back , 3 ] = _clipMatrix[ 15 ] - _clipMatrix[ 14 ];
            NormalizePlane( _frustum , ( int )ClippingPlane.Back );

            _frustum[ ( int )ClippingPlane.Front , 0 ] = _clipMatrix[ 3 ] + _clipMatrix[ 2 ];
            _frustum[ ( int )ClippingPlane.Front , 1 ] = _clipMatrix[ 7 ] + _clipMatrix[ 6 ];
            _frustum[ ( int )ClippingPlane.Front , 2 ] = _clipMatrix[ 11 ] + _clipMatrix[ 10 ];
            _frustum[ ( int )ClippingPlane.Front , 3 ] = _clipMatrix[ 15 ] + _clipMatrix[ 14 ];
            NormalizePlane( _frustum , ( int )ClippingPlane.Front );
        }
    }

I hope someone finds it useful, instead of getting this dead page link like they were getting.

like image 101
Krythic Avatar answered Oct 23 '22 21:10

Krythic


Probably you have wrong height value set ( y ). I had the SAME problem, it was a bit stupid, I was generating Y value in glsl, for sphere check it was like (x,0.0,z) that's why.

like image 32
neko_code Avatar answered Oct 23 '22 21:10

neko_code