Go to content Go to navigation

Screen Space transformation in OpenGL · 2009-12-13 16:10 by Black in

Transforming from World Space to Screen Space in OpenGl is useful for selecting and picking with a mouse or similar. OpenGL itself provides the function gluProject and gluUnProject to do this. This class replicates that functionality. Download the full header and source for a commented version. Below, a simplified header shows the API.

screenproject.h

  1. class ScreenProject
  2. {
  3. public: // ScreenSpace
  4.   void calculateMVP(GLint * vp, double * mv, double * p);
  5.   void calculateMVP();
  6.   Point transformToScreenSpace(Point _p, float _f = 1) const;
  7.   Point transformToClipSpace(Point _p, float _f = 1) const;
  8.   Point transformToWorldSpace(Point _p) const;
  9. private:
  10.   double mvp_[16];  /**< Product of Modelview and Projection Matrix */
  11.   double mvpInv_[16]; /**< Inverse of mvp_ */
  12.   long vp_[4];    /**< Viewport */
  13. }

Even if the class is initialized, it can not be used until one of calculateMVP is used to pass custom matrixes or read the matrixes from the current OpenGL context. After this is done, the matrix and inverse is stored internally, and the calculations work without accessing any outside data.

The various transform functions transform the passed point with the internal state. Going from screen space to world space is very useful in picking, selecting or generally interacting with the scene with a pointing device. Transforming to clip or screen space is used when calculating anchor points for labels on screen.

The Point type is a three-element vector. OpenGL works with homogenous coordinates, so a fourth value is needed. Because this is almost always 1, that was chosen as default value. There is one important exception: When transforming normals, the last coordinate is zero. Normals are not influenced by translations.

The source file contains code for the transformation with the matrixes, viewport transformation and matrix inversion. The code for the inversion was taken from Mesa, everything else is from me. OpenGL’s matrixes are column-major so the four numbers in the first column are mapped to the first four slots in the 16 slot array. The transformation matrix is built from the modelview matrix M and the projection matrix P with P*M, a point p is then projected as in P*M*p.

screenproject.cpp [6.51 kB]

  1. Point ScreenProject::transformToClipSpace(Point _p, float _f) const
  2. {
  3.   Point pT = transformPointWithMatrix(_p, mvp_, _f);
  4.   return Point((pT[0] + 1)/2, (pT[1] + 1)/2, (pT[2] + 1)/2);
  5. }
  6.  
  7. Point ScreenProject::transformToWorldSpace(Point _p) const
  8. {
  9.   // Transform to normalized coordinates
  10.   _p[0] = (_p[0] - vp_[0]) * 2 / vp_[2] - 1.0f;
  11.   _p[1] = (_p[1] - vp_[1]) * 2 / vp_[3] - 1.0f;
  12.   _p[2] = 2 * _p[2] - 1.0;
  13.  
  14.   // Transform
  15.   return transformPointWithMatrix(_p, mvpInv_);
  16. }
  17.  
  18. Point ScreenProject::transformPointWithMatrix(Point _p, const double * _m, float _f) const
  19. {
  20.     float xp = _m[0] * _p[0] + _m[4] * _p[1] + _m[8] * _p[2] + _f * _m[12];
  21.     float yp = _m[1] * _p[0] + _m[5] * _p[1] + _m[9] * _p[2] + _f * _m[13];
  22.     float zp = _m[2] * _p[0] + _m[6] * _p[1] + _m[10] * _p[2] + _f * _m[14];
  23.     float tp = _m[3] * _p[0] + _m[7] * _p[1] + _m[11] * _p[2] + _f * _m[15];
  24.     if (tp == 0)
  25.     return Point(xp, yp, zp);
  26.     else
  27.     return Point(xp / tp, yp / tp, zp / tp);
  28. }
  Textile help