Part of the Khronos Group
OpenGL.org

The Industry's Foundation for High Performance Graphics

from games to virtual reality, mobile phones to supercomputers

Results 1 to 7 of 7

Thread: Avoiding unnessecary if/else choice in loop for Virtual Camera

  1. #1
    Junior Member Regular Contributor
    Join Date
    Apr 2014
    Posts
    109

    Avoiding unnessecary if/else choice in loop for Virtual Camera

    Hello,

    So I have more of a best practice question that I am wondering what is the best approach for.

    I have a working virtual "camera" for OpenGL that builds the correct view and projection matrices for upload into OpenGL.

    Let's represent that with the psuedo-function UpdateCamera(). This camera is inside a class OpenGLCamera which also has another function to set whether we want to display a perspective view or orthogonal view. Let's call this function SwitchView() and the flag isPerspectiveOn.

    Now, inside the update camera function, I have code that looks something like this:
    Code :
    void OpenGLCamera::UpdateCamera() {
    	/*
     
    	Calculate position, direction, determine rotation, etc.
     
    	View Matrix is built.
     
    	*/
     
    	if (isPerspectiveOn == true)
    	{
    		ProjectionMatrix = glm::perspective(FieldOfView, AspectRatio, NearPlane, FarPlane);
    	} else {
    		Projection = glm::ortho(CurrentOrthoParameters.LeftPlane, CurrentOrthoParameters.RightPlane,
    		                        CurrentOrthoParameters.BottomPlane, CurrentOrthoParameters.TopPlane);
    	}
     
    	ProjectionViewMatrix = ProjectionMatrix * ViewMatrix;
     
    }
    What I am wondering is if there is a way to simply switch which projection function I am using without needing to perform if/else test all of the time?

    In other words, perhaps a way to set the isPerspectiveOn flag somewhere else and with a change in how I write the code I don't have to perform this test with every loop or per frame.

    I feel it may seem trivial but the least amount of testing I am doing per frame the better.

    Thank you for your time and if I need to clarify further let me know.

  2. #2
    Intern Newbie
    Join Date
    Apr 2014
    Posts
    47
    I applaud you recognizing an additional test you are doing per frame. However, I would really doubt that a single IF statement could be that much of a performance problem. The penalty from recalculating matrices with glm:: perspective and glm:: ortho and then a 4x4 matrix multiplication to get the projection-view matrix is where you can save time (cache these within functions you use to modify the eye, look, FOV, etc).

    Idea 1: Subclass OpenGLCamera and create a virtual method to call the appropriate projection function, but you won't be able to turn perspective off and on.

    Idea 2: Create a strategy object (google design patterns/strategy) for two objects, one to use perspective, the other not to. Then swap out the designated object.

    Idea 3: If you really want to get rid of the IF statement you could use a class-method pointer and then call it.
    WARNING: You are entering the Black-Magic Devilish Incantations that make C++ an extremely interesting language to use. If you have flashbacks to the days of vomiting punctuation to write Perl, I vehemently apologize. Using this language feature may cause your code to be much more confusing to use than this technique merits.
    Code :
    class OpenGLCamera {
      mat4 calcPerspectiveProjection();
      mat4 calcOrthoProjection();
     
    // private member of the class. This is a method pointer (reminiscent of a function pointer).
    // Make sure you point it somewhere when you create your instance of the class.
      mat4 (OpenGLCamera::*projectionFunction)();
    };
     
    void OpenGLCamera::setPerspectiveEnabled( bool enabled ) {
      if( enabled ) {
        projectionFunction = &OpenGLCamera.calcPerspectiveProjection;
      }
      else {
        projectionFunction = &OpenGLCamera.calcOrthoProjection;
      }
    }
     
    // Inside updateCamera()
    (this->*projectionFunction)();

    I'm sure Carmine or Dark Photon will suggest a significantly easier way to do this
    Last edited by MtRoad; 08-14-2014 at 03:35 PM.

  3. #3
    Junior Member Regular Contributor
    Join Date
    Apr 2014
    Posts
    109
    Quote Originally Posted by MtRoad View Post
    I would really doubt that a single IF statement could be that much of a performance problem. The penalty from recalculating matrices with glm:erspective and glm:rtho and then a 4x4 matrix multiplication to get the projection-view matrix is where you can save time (cache these within functions you use to modify the eye, look, FOV, etc).

    If you really want to get rid of the IF statement you could use a class-method pointer and then call it.

    Code :
    class OpenGLCamera {
      mat4 calcPerspectiveProjection();
      mat4 calcOrthoProjection();
     
    // private member of the class. This is a method pointer (reminiscent of a function pointer).
    // Make sure you point it somewhere when you create your instance of the class.
      mat4 (OpenGLCamera::*projectionFunction)();
    };
     
     
    void OpenGLCamera::setPerspectiveEnabled( bool enabled ) {
      if( enabled ) {
        projectionFunction = &OpenGLCamera.calcPerspectiveProjection;
      }
      else {
        projectionFunction = &OpenGLCamera.calcOrthoProjection;
      }
    }
     
    // Inside updateCamera()
    (this->*projectionFunction)();
    Thanks; that seems correct. I will try that.

  4. #4
    Junior Member Regular Contributor
    Join Date
    Apr 2014
    Posts
    109
    Meant to say I would try both suggestions.

  5. #5
    Intern Newbie
    Join Date
    Apr 2014
    Posts
    47
    You can get a significant boost of time savings from caching everything you can and using it as much as possible before you move onto something else or change something. The same concept works with OpenGL context state (changing programs, uniforms, textures, etc.).

    Here's how my camera class caches everything. Don't hate me, it's objective-c, so it will look a little weird. NOTE: I take a performance hit by recalculating the matrix every time I adjust ANY parameter individually, but I did that so the transform is always what I'd expect and I don't modify one parameter at a time (typically I do multiple and then build the transform).

    Code :
    - ( instancetype ) initWithFOVYDegrees:( float )fovyDegrees
                              aspectRatio:( float )aspectRatio
                                     near:( float )near
                                      far:( float )far {
        if( self = [ super init ] ) {
            // Set properties first, then build matrices to prevent from building
            // them multiple times.
            _fovyDegrees = fovyDegrees;
            _aspectRatio = aspectRatio;
            _near        = near;
            _far         = far;
            _up          = GLKVector3Make(0,1,0);
            [ self makeViewTransform ];
            [ self makeProjectionTransform ];
        }
        return self;
    }
     
    // extra stuff you don't care about...
     
    /** Updates the projection transform after far has been changed. */
    - ( void )setFar:( float )far {
        _far = far;
        [ self makeProjectionTransform ];
    }
     
     
    /**
     * Rebuilds the projection matrix from fovY, aspectRatio and near and far.
     */
    - ( void )makeProjectionTransform {
        _projectionTransform = GLKMatrix4MakePerspective(GLKMathDegreesToRadians(_fovyDegrees),_aspectRatio,_near,_far);
    }
    Then whenever I pull my transform out of the class it has already been calculated for that frame and is consistent with any property I query (eye and look positions, etc.).

  6. #6
    Junior Member Regular Contributor
    Join Date
    Apr 2014
    Posts
    109
    Thanks for your responses; I really appreciate it.

    I assume from the above code that you don't dampen the camera?

  7. #7
    Intern Newbie
    Join Date
    Apr 2014
    Posts
    47
    Quote Originally Posted by tmason View Post
    Thanks for your responses; I really appreciate it.

    I assume from the above code that you don't dampen the camera?
    This is a really basic camera class that I made for basic use. I've done animations between positions before, but I'm trying to keep my code as simple and easy to understand as possible right now since I'm only using a fixed camera in my main project (right now). I don't get paid to build programs (yet) and I have a day-job so I try to keep things quick and simple. (Anyone hiring graphics programmers?)

    Come to think of it, a better way to do it might be this, so you could animate parameters and then do update when it is requested:
    Code :
    /** Updates the projection transform after far has been changed. */
    - ( void )setFar:( float )far {
        _far = far;
        _projectionCached = NO; // << and similar for all set methods
    }
     
     
    /**
     * Rebuilds the projection matrix from fovY, aspectRatio and near and far.
     */
    - ( void )makeProjectionTransform {
        _projectionTransform = GLKMatrix4MakePerspective(GLKMathDegreesToRadians(_fovyDegrees),_aspectRatio,_near,_far);
        _projectionCached = YES;
    }
     
     
    /** Getter method. */
    - ( GLKMatrix4 )projectionTransform {
        if( _projectionCached == NO ) {
            [ self makeProjectionTransform ];
        }
        return _projectionTransform;
    }



    I've had problems with my camera class getting way too complicated when I try to handle the animations within the class. I did a camera class in C++ using a generic animation class I wrote using templates and function pointers (handling multiple types, vecs with multiple interpolation types), but it was excessive for what I need (and significantly overcomplicated obviously).

    To give you an idea, here's some of my old code. With my interpolator template (I also did non-linear interpolators) anything that properly overrides * for scalars and + for two similar components can be interpolated (i.e. floats and vecs). Note how my code style has significantly simplified. No more systems hungarian for me! :)
    Code :
    // Allowed me to create arbitrary interpolators
    template<typename tn_interpolated>
    tn_interpolated
    linearInterpolate(tn_interpolated start_, tn_interpolated end_, float fT_) {
    	// Clamp to values... return here if needed.
    	if(fT_ < 0.0f) return start_;
    	if(fT_ > 1.0f) return end_;
     
    // ----------------------MAGIC HAPPENS HERE --------------------------
            // Use * and + for vec3 and floats to allow interpolation
    	return start_*(1.0f-fT_) + end_*(fT_);
    }
     
     
    // Generic animation code which could handle vec's, floats, and matrices (by me... :( )
    /**
     * \brief
     *		Animates transition between two interpolatable types.
     */
    template <typename tn_interpolated>
    class west_InterpolatorAnimation {
    	float m_fElapsed;			// Elapsed seconds of animation
    	float m_fDuration;		// Total seconds of animation
    	tn_interpolated m_start;	// Starting value
    	tn_interpolated m_end;		// Ending value
     
    	tn_interpolated(*m_interpolator)(float, const tn_interpolated&, const tn_interpolated&);
    		// Interpolator function
     
    public:
        /**
         * Creates an interpolated animation.
         */
    	west_InterpolatorAnimation(float fSecondsDuration_,
    		const tn_interpolated &c_rStart_,
    		const tn_interpolated &c_rEnd_,
    		tn_interpolated(*interpolator_)(float, const tn_interpolated&, const tn_interpolated&)
    	):
    		m_fElapsed(0),
    		m_fDuration(fSecondsDuration_),
    		m_start(c_rStart_),
    		m_end(c_rEnd_),
    		m_interpolator(interpolator_)
    	{
    	}
     
        /**
         * Updates this animation for a duration of elapsed seconds.
         */
    	void update(float fElapsedSeconds_) {
    		m_fElapsed += fElapsedSeconds_;
    	}
     
     
    	/**
    	 * Returns duration of the animation, in seconds.
    	 */
    	float duration() const {
    		return m_fDuration;
    	}
     
     
    	float elapsed() const {
    		return m_fElapsed;
    	}
     
    	/**
             *
             * -------------------- THE MAGIC HAPPENS HERE ------------------
             *
             *
    	 * Returns the current position of the animation.  Starting position
    	 * if animation has not started, and ending position if isFinished().
    	 */
    	tn_interpolated currentValue() const {
    		return m_interpolator(m_fElapsed / m_fDuration, m_start, m_end);
    	}
     
    	tn_interpolated start() const {
    		return m_start;
    	}
    		// Starting value for the animation, when t = 0.0f
     
    	tn_interpolated end() const {
    		return m_end;
    	}
    		// Ending value for the animation when t = 1.0f
     
    	bool isRunning() const {
    		return m_fElapsed < m_fDuration;
    	}
    		// Animation is between start and end times.
     
    	bool isFinished() const {
    		return m_fElapsed >= m_fDuration;
    	}
    		// Animation is at or past its end time.
    };

    Sample interpolation:
    Code :
    vec3 from( 1, 5, 0 );
    vec3 to( 20, 50, 40 );
    west_InterpolatorAnimation animatedParameter( 5.0, from, to, linearInterpolation<vec3> );
    // .. also works for floats!

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •