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;
}
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;}
Code: Select all
R_InitSkySphere ();
Code: Select all
void R_DrawSkySphere (void);
Code: Select all
R_DrawSkySphere ();
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.