Tutorial *31*

When a player enters an area of a Quake 2 map that contains open sky, the Quake 2 engine draws a large fullbright cube in the background with 6 textures mapped onto the insides of the cube, with the player placed at the center. The textures are pre-warped so the edges don't appear broken and it looks as if the player is standing in the middle of a large landscape. The problem is this cube isn't very large - only 4096 map units from the player to the edge of the cube, and for the map maker this poses a limitation on the size of open landscape maps because the map will "disappear" into the sides of the sky cube if the map brushes are drawn farther than 2048 units from the player. (Which is pitifully small since even an average sized room in a map can be 768 units - connect that to a long hallway with another room in the distance and in no time you'll start seeing your room starts getting clipped in the distance!)

This modification to the engine will add an engine variable "skydistance" that will allow the user to adjust their skybox size on-the-fly if they encounter any maps where this limitation poses a problem.

In order to change the skybox size, we must do two things:

1) Adjust the size of the skybox dimensions.
2) Adjust the size of the world in relation to the OpenGL Z-buffer.

Note that #2 means the larger the world, the less precision there is for the Z-buffer. However, this shouldn't be too much of problem with modern OpenGL videocards as they all have precision to spare in their Z-buffers in relation to Quake 2 since Quake 2 was released at a time when gaming cards had limited Z-buffers.

So let's get started making the changes! All of the changes are made in the ref_gl project, so note that this mod won't have any effect if you use the software renderer or the 3dFX mini-GL renderer.

First, let's add a new variable to the engine. In gl_local.h after the intensity variable is declared, (around line 238) add the following line:

extern cvar_t *skydistance; // DMP - skybox size change

Now the other ref_gl modules can access the new variable.

Next, go to gl_rmain.c and add the following after the vid_ref cvar is declared (around line 136):

cvar_t *skydistance; // DMP - skybox size change

Here we declare the actual variable that will hold the value the user inputs at the console.

Next, find the R_Register() function in the same module, and after the vid_ref variable is registered (around line 1050) add the following line:

skydistance = ri.Cvar_Get("skydistance", "2300", 0); // DMP - skybox size change

This tells the engine about the new variable and thus the user can now access it like any other Quake engine variable. The default value is the value one the unmodified engine uses for the skybox size. This value is the distance from the player to the skybox. (The skybox follows the player around as they move so it always has the player at its center.) Note that if you want the variable to be saved with the config.cfg file, you can replace the third parameter to the function (0) with CVAR_ARCHIVE. The reason I didn't do this outright is because I have other plans in the future which will make this variable change based on a setting to be made available to map makers. However, that endeavour is for another time.

Now, scroll back up in the file to the function R_SetupGL() (around line 700) and add the following local variables to the function:

 static GLdouble farz; // DMP skybox size change

 GLdouble boxsize;  // DMP skybox size change

Because of some calculations we will do below and because this function is called every frame, we don't want to have to waste any CPU cycles recalculating the same value over and over again, so that is the reason for the static variable.

Now move a few lines down and after the call to qglViewport(), add the following block of code:

// DMP: calc farz value from skybox size

if (skydistance->modified)


	skydistance->modified = false;

	boxsize = skydistance->value;

	boxsize -= 252 * ceil(boxsize / 2300);

	farz = 1.0;

	while (farz < boxsize)  // DMP: make this value a power-of-2


		farz *= 2.0;

		if (farz >= 65536.0)  // DMP: don't make it larger than this



	farz *= 2.0;	// DMP: double since boxsize is distance from camera to

			// edge of skybox - not total size of skybox

	ri.Con_Printf(PRINT_DEVELOPER, "farz now set to %g\n", farz);


There's a lot of code here, but basically this code calculates the Z-buffer size for our dynamic skybox. First we only execute the code if the user changes the new skydistance variable, then we set the modified flag to false so we don't keep executing this code on the next frame. Then there's an arcane equation that converts the skybox value to a preliminary value for the Z-buffer size. The equation is based on the original code using 2300 for the skybox size and 2048 being the distance from the player to the edge of the Z-buffer space. That preliminary value is then forced to be a power-of-2 in the while loop below since some OpenGL drivers don't like having Z-buffers specified in sizes other than powers-of-2. Note that we limit the size of the Z-buffer in case the user specifies some outrageous value or something goes haywire with the engine and the skydistance cvar isn't initialized properly. Then the value is doubled since the OpenGL function that takes this value wants the full size of the world, not just the distance from the camera/player to the edge of the world. Finally, we print the fact that a change took place if the user is in developer mode (cvar developer = 1).

Next, we need to change the call to MYgluPerspective() so it uses our calculated farz value rather than the fixed farz value. Look a few lines down and you should see:

MYgluPerspective (r_newrefdef.fov_y,  screenaspect, 4, 4096);

Change it to read:

MYgluPerspective (r_newrefdef.fov_y,  screenaspect, 4, farz); // DMP skybox

We need to make one more change to gl_rmain.c. In R_SetMode() after the line "fullscreen = vid_fullscreen->value;" (around line 1088) we need to add the following line of code:

 skydistance->modified = true; // DMP skybox size change

This is needed because when the user changes video modes, we need to recalculate the static variable (presumably) because the engine unloads and reloads the ref_gl.dll, which clears our static variable.

After this, the engine will now adjust the Z-buffer size based on the value you enter in skydistance. However, we haven't actually changed the size of the skybox yet! To do this, open gl_warp.c and go to the function MakeSkyVec(). After the local variables are declared (around line 525) change the following lines from:

	b[0] = s*2300;

	b[1] = t*2300;

	b[2] = 2300;


	b[0] = s * skydistance->value; // DMP skybox size changes

	b[1] = t * skydistance->value; // DMP

	b[2] = skydistance->value;  // DMP

This is the actual code that creates the dimensions of the skybox. All we do here is make it use our skydistance cvar instead of the hard-coded value.

And that's all there is to it! You can now grow (or shrink?) the skybox to handle most large open area maps by simply going to the console and typing "skydistance ".

Note that the engine doesn't draw the skybox when there are no potentially visible sky brushes present. This is why it is possible to create a very large room with no skies with the current Q2 engine and not have it clip away in the distance - the skybox is never drawn when no sky brushes are in the potentially visible set.

As I alluded to above, my ultimate goal is to add an optional field to the worldspawn entity that will allow map designers to specify the skybox size on a map-by-map basis. However, it appears the client doesn't actually parse the worldspawn entity when it loads a map - the server sends the fields to the client! This poses a problem because having the server send extra data means the network protocal would change and would make everyone incompatible! So I've put that part of the mod on the backburner until I can figure out a way to code the client to also parse the worldspawn entity. (Currently the Q2 client blindly loads the .bsp file and doesn't pay any attention to entitities.)

- David Pochron dpoch@ticon.net

Not logged in
Sign up