[GLQuake] More Improved Sky

Post tutorials on how to do certain tasks within game or engine code here.
Post Reply
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

[GLQuake] More Improved Sky

Post by mh »

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
Tomaz
Posts: 67
Joined: Fri Nov 05, 2004 8:21 pm

Post by Tomaz »

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.
Spike
Posts: 2914
Joined: Fri Nov 05, 2004 3:12 am
Location: UK
Contact:

Post by Spike »

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.
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Post by mh »

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
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Post by mh »

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
Post Reply