Forum

[GLQuake] More Improved Sky

Post tutorials on how to do certain tasks within game or engine code here.

Moderator: InsideQC Admins

[GLQuake] More Improved Sky

Postby mh » Tue Feb 22, 2011 12:10 am

Sky in GLQuake is crap; just load up e4m2 and walk around the top area before you jump in the hole and you'll see what I mean. Once you notice it for the first time it's hard to stop noticing it, and it can get really irritating.

There are various ways of fixing it, with the best needing shaders, but here's one way that will work on any 3D card that supports OpenGL 1.1 or higher and that doesn't require any expensive subdivision. This doesn't replace the classic Quake sky with something that looks different; it looks just the same but it's a lot more solid and stable now.

It doesn't need an infinite projection and will work at any draw distance (the sky is actually a radius 5 sphere clamped to the far end of the depth range). I don't go into any great detail about the OpenGL calls I make because it's not my job to teach you OpenGL; there are plenty of better online resources for that. Once you know what the calls do you should be able to very easily understand what's happening here.

I adapted this code from DarkPlaces and it's been through a number of revisions since; credit for the original belongs to LordHavoc and any mistakes or omissions are mine.

This code assumes an unmodified GLQuake.

First of all open gl_warp.c and find the EmitSkyPolys function. Put this heap of steaming love above it:
Code: Select all
#define SKYGRID_SIZE 16
#define SKYGRID_SIZE_PLUS_1 (SKYGRID_SIZE + 1)
#define SKYGRID_RECIP (1.0f / (SKYGRID_SIZE))
#define SKYSPHERE_NUMVERTS (SKYGRID_SIZE_PLUS_1 * SKYGRID_SIZE_PLUS_1)
#define SKYSPHERE_NUMTRIS (SKYGRID_SIZE * SKYGRID_SIZE * 2)
#define SKYSPHERE_NUMINDEXES (SKYGRID_SIZE * SKYGRID_SIZE * 6)

qboolean r_drawsky = false;
qboolean r_skyinitialized = false;

typedef struct skyverts_s
{
   float v[3];
   float st[2];
} skyverts_t;


skyverts_t r_skyverts[SKYSPHERE_NUMVERTS];
unsigned short r_skyindexes[SKYSPHERE_NUMINDEXES];

void R_InitSkySphere (void)
{
   int i, j;
   float a, b, x, ax, ay, v[3], length;
   float dx, dy, dz;
   skyverts_t *ssv = r_skyverts;
   unsigned short *e = r_skyindexes;

   if (r_skyinitialized) return;

   dx = 16;
   dy = 16;
   dz = 16 / 3;

   i = SKYSPHERE_NUMVERTS;
   j = SKYSPHERE_NUMINDEXES;

   for (j = 0; j <= SKYGRID_SIZE; j++)
   {
      a = j * SKYGRID_RECIP;
      ax = cos (a * M_PI * 2);
      ay = -sin (a * M_PI * 2);

      for (i = 0; i <= SKYGRID_SIZE; i++)
      {
         b = i * SKYGRID_RECIP;
         x = cos ((b + 0.5) * M_PI);

         v[0] = ax * x * dx;
         v[1] = ay * x * dy;
         v[2] = -sin ((b + 0.5) * M_PI) * dz;

         // same calculation as classic Q1 sky but projected onto an actual physical sphere
         // (rather than on flat surfs) and calced as if from an origin of [0,0,0] to prevent
         // the heaving and buckling effect
         length = 3.0f / sqrt (v[0] * v[0] + v[1] * v[1] + (v[2] * v[2] * 9));

         ssv->st[0] = v[0] * length;
         ssv->st[1] = v[1] * length;
         ssv->v[0] = v[0];
         ssv->v[1] = v[1];
         ssv->v[2] = v[2];

         ssv++;
      }
   }

   for (j = 0; j < SKYGRID_SIZE; j++)
   {
      for (i = 0; i < SKYGRID_SIZE; i++)
      {
         *e++ =  j * SKYGRID_SIZE_PLUS_1 + i;
         *e++ =  j * SKYGRID_SIZE_PLUS_1 + i + 1;
         *e++ = (j + 1) * SKYGRID_SIZE_PLUS_1 + i;

         *e++ =  j * SKYGRID_SIZE_PLUS_1 + i + 1;
         *e++ = (j + 1) * SKYGRID_SIZE_PLUS_1 + i + 1;
         *e++ = (j + 1) * SKYGRID_SIZE_PLUS_1 + i;
      }
   }

   r_skyinitialized = true;
}


void R_DrawSkySphere (void)
{
   if (!r_drawsky) return;

   GL_Bind (solidskytexture);

   glEnableClientState (GL_VERTEX_ARRAY);
   glVertexPointer (3, GL_FLOAT, sizeof (skyverts_t), r_skyverts->v);

   glEnableClientState (GL_TEXTURE_COORD_ARRAY);
   glTexCoordPointer (2, GL_FLOAT, sizeof (skyverts_t), r_skyverts->st);

   // sky is drawn as an radius 5 sphere clamped to the extreme end of the depth range and it follows the viewpoint around
   glDepthMask (GL_FALSE);
   glDepthRange (gldepthmax, gldepthmax);
   glPushMatrix ();
   glTranslatef (r_refdef.vieworg[0], r_refdef.vieworg[1], r_refdef.vieworg[2]);

   glMatrixMode (GL_TEXTURE);

   speedscale = cl.time * 8;
   speedscale -= (int) speedscale & ~127;
   speedscale /= 128.0f;

   glLoadIdentity ();
   glTranslatef (speedscale, speedscale, 0);

   glDrawElements (GL_TRIANGLES, SKYSPHERE_NUMINDEXES, GL_UNSIGNED_SHORT, r_skyindexes);

   speedscale = cl.time * 16;
   speedscale -= (int) speedscale & ~127;
   speedscale /= 128.0f;

   glLoadIdentity ();
   glTranslatef (speedscale, speedscale, 0);

   glEnable (GL_BLEND);
   GL_Bind (alphaskytexture);
   glDrawElements (GL_TRIANGLES, SKYSPHERE_NUMINDEXES, GL_UNSIGNED_SHORT, r_skyindexes);
   glDisable (GL_BLEND);

   glLoadIdentity ();
   glMatrixMode (GL_MODELVIEW);

   glPopMatrix ();
   glDepthRange (gldepthmin, gldepthmax);
   glDepthMask (GL_TRUE);

   glDisableClientState (GL_TEXTURE_COORD_ARRAY);
   glDisableClientState (GL_VERTEX_ARRAY);

   // for the next frame
   r_drawsky = false;
}

Now replace EmitSkyPolys, EmitBothSkyLayers and R_DrawSkyChain with these versions:
Code: Select all
void EmitSkyPolys (msurface_t *surf) {r_drawsky = true;}
void EmitBothSkyLayers (msurface_t *surf) {r_drawsky = true;}
void R_DrawSkyChain (msurface_t *s) {r_drawsky = true;}

Next pop the following somewhere (where doesn't really matter) into R_InitSky:
Code: Select all
   R_InitSkySphere ();

Now onto gl_rmain.c; here we just need to call R_DrawSkySphere in the appropriate place, so pop a prototype for it somewhere towads the top of the file like so:
Code: Select all
void R_DrawSkySphere (void);

Then in R_RenderScene put the call to it after R_DrawEntitiesOnList like so:
Code: Select all
R_DrawSkySphere ();

Done.



Now, there are a few things I deliberately left out. These are left as exercises for individual implementers, and include:
  • Adapt it all to work with my Z-fail method.
  • Multitexture it.
  • Put it in a VBO.
  • Make the sky detail level user-controllable through a cvar.
  • Make the scroll speeds user-controllable through a cvar.
  • Implement sky alpha.
  • Remove the use of GL_SubdivideSurface from sky.
  • Etc.
Have fun.
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
User avatar
mh
 
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Postby Tomaz » Tue Feb 22, 2011 9:49 am

Looks alot like the skysphere from CleanQuakeCpp and DarkPlaces, it really makes it look alot better than the "original" glQuake sky.

I personally removed pretty much all code related to the sky, the only ting I left is that each frame it checks if any sky polygons is visible, and only render the sphere if there is.
Tomaz
 
Posts: 67
Joined: Fri Nov 05, 2004 8:21 pm

Postby Spike » Tue Feb 22, 2011 3:55 pm

My concern with it is the excessive overdraw in maps like dm2.
Shaders are obviously a much better solution, as they avoid that entirely, but if you can't use shaders for some reason, there's some interesting code in zquake that projects the sky in 2d-space, resulting in low distortion (about same as with a sky 'sphere') but much less overdraw and thus noticably higher fps.
Spike
 
Posts: 2892
Joined: Fri Nov 05, 2004 3:12 am
Location: UK

Postby mh » Tue Feb 22, 2011 4:10 pm

Spike wrote:My concern with it is the excessive overdraw in maps like dm2.

That's a valid concern. The Z-fail method should help a lot with that, especially on hardware that can do early Z rejection (i.e. anything that's not powered by a team of horses) but I haven't tested it to see how it behaves with my depthrange abuse. If the worst comes to the worst the depthrange could be dropped, the projection could be switched to a large zfar (or infinite) and Z-fail could be used instead.
there's some interesting code in zquake that projects the sky in 2d-space, resulting in low distortion (about same as with a sky 'sphere') but much less overdraw and thus noticably higher fps.

I must check that out. :D
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
User avatar
mh
 
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Postby mh » Wed Apr 06, 2011 7:44 pm

Interesting thought - if you were using something like this for skyboxes you could do something with the old R_ClipSky function and have a 5x5x5 skybox.
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
User avatar
mh
 
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am


Return to Programming Tutorials

Who is online

Users browsing this forum: No registered users and 1 guest