Tutorial *100*

Improved Shadows

We've all noticed that the shadow mode in GLQuake is a little dodgy, with shadows rotating around lightning beams, tumbling with gibs etc, and if you've ever run GLQuake in chasecam mode, you'll have noticed the shadow rotates with the player, and sticks into slopes. YUCK.

Well now you can do something about it.


This is version alpha 2 of my 'improvement' of the shadow code. Very few people saw the first version (Only Koolio I think), and so this is the first publically available version. It's still not perfect, but I think it's ready for the masses to pull to bits at least.



I'm going to assume that you've implemented Phoenix's interpolation code (QER tutorial). If you haven't I strongly suggest that you do, not just to make sense of the code below, but because it's a worthy addition to the engine.

OK. Down to the code.

Open gl_rmain.c

First off we need to change the definition of one of Phoenix's interpolation routines


        //muff - definition changed to allow for new shadow code

        void R_BlendedRotateForEntity (entity_t *e, int shadow)

This will allow us to use the same code for object and shadows, without much work.
Next, at the bottom of the same routine, change the section at the bottom of the routine.

from :-


        glRotatef ( e->angles1[1] + ( blend * d[1]),  0, 0, 1);

        glRotatef (-e->angles1[0] + (-blend * d[0]),  0, 1, 0);

        glRotatef ( e->angles1[2] + ( blend * d[2]),  1, 0, 0);

to :-


        glRotatef ( e->angles1[1] + ( blend * d[1]),  0, 0, 1);



	// muff@yakko.globalnet.co.uk - 05 Mar 00

	// added to allow shadows to behave properly

	// if shadow it only rotates in horizontal plane

	// will need updating when slope stuff is in place

	if (shadow==0)

	{

              glRotatef (-e->angles1[0] + (-blend * d[0]),  0, 1, 0); // this rotates tilt

              glRotatef ( e->angles1[2] + ( blend * d[2]),  1, 0, 0); // this is the one that causes the weird missile stuff

	}


As the commment says, this means that when we call the routine with the shadow flag set the object(shadow in this case) is only rotated in the horizontal plane, which stops the bizarre shadow code that is obvious with grenades, gibs, lightning gun etc.

Next find the GL_DrawAliasShadow routine.

What is below is a copy of the routine with my additions. I think it's easier to get things right if you look at the finished thing. All my added/changed stuff is clearly commented (bad habit I got into :)

What it basically does is adjust the height of the shadow points based on the angle of the slope below the player, and the angle the player is standing at relative to the slope (in the horizontal plane).


   void GL_DrawAliasShadow (aliashdr_t *paliashdr, int posenum)

   {

	float	s, t, l;

	int		i, j;

	int		index;

	trivertx_t	*v, *verts;

	int		list;

	int		*order;

	vec3_t	point;

	float	*normal;

	float	height, lheight;

	int		count;



	// muff@yakko.globalnet.co.uk - 17 Mar 2000

	trace_t		steptrace, downtrace;

	vec3_t		dest,stop,downmove;

	float		s1,c1; //this will store the sin and cos values as they are used so heavily

	//end of muff



	lheight = currententity->origin[2] - lightspot[2];



	height = 0;

	verts = (trivertx_t *)((byte *)paliashdr + paliashdr->posedata);

	verts += posenum * paliashdr->poseverts;

	order = (int *)((byte *)paliashdr + paliashdr->commands);



	height = -lheight ;//+ 1.0; //was 1.0 - muff



	// muff@yakko.globalnet.co.uk - 17 Mar 2000

	// better shadowing, now takes angle of ground into account

	// cast a traceline into the floor directly below the player

	// and gets normals from this

	VectorCopy (currententity->origin, downmove);

	downmove[2] = downmove[2] - 4096;

	memset (&downtrace, 0, sizeof(downtrace));

	SV_RecursiveHullCheck (cl.worldmodel->hulls, 0, 0, 1, currententity->origin, downmove, &downtrace);



	// calculate the all important angles to keep speed up

	s1 = sin( currententity->angles[1]/180*M_PI);

	c1 = cos( currententity->angles[1]/180*M_PI);



	// end of muff



	while (1)

	{

		// get the vertex count and primitive type

		count = *order++;

		if (!count)

			break;		// done

		if (count < 0)

		{

			count = -count;

			glBegin (GL_TRIANGLE_FAN);

		}

		else

			glBegin (GL_TRIANGLE_STRIP);



		do

		{

			// texture coordinates come from the draw list

			// (skipped for shadows) glTexCoord2fv ((float *)order);

			order += 2;



			// normals and vertexes come from the frame list

			point[0] = verts->v[0] * paliashdr->scale[0] + paliashdr->scale_origin[0];

			point[1] = verts->v[1] * paliashdr->scale[1] + paliashdr->scale_origin[1];

			point[2] = verts->v[2] * paliashdr->scale[2] + paliashdr->scale_origin[2];



			// muff@yakko.globalnet.co.uk - 17 Mar 2000

			// first remove the existing height adjustment, as we are going to do this ourselves

			//original code

			// point[0] -= shadevector[0]*(point[0] +lheight);

			// point[1] -= shadevector[1]*(point[1] +lheight);

			//	point[2] = height;

			//	height -= 0.001;

			// end of original code



			point[0] -= shadevector[0]*(point[0]);

			point[1] -= shadevector[1]*(point[1]);

			point[2] -= shadevector[2]*(point[2]);



			//drop it down to floor

			point[2] = point[2] - (currententity->origin[2] - downtrace.endpos[2]) ;



			// now adjust the point with respect to all the normals of the tracepoint

			// I'm probably missing an easy piece of maths to do this

			// it reads messy, but works ;)

			point[2] +=	 (    	(point[1] * (s1 * downtrace.plane.normal[0])) -

					     	(point[0] * (c1 * downtrace.plane.normal[0])) -

						(point[0] * (s1 * downtrace.plane.normal[1])) -

						(point[1] * (c1 * downtrace.plane.normal[1]))

					 	 ) +  ((1.0 - downtrace.plane.normal[2])*20)

						 + 0.2 ;



		        //end of muff



			glVertex3fv (point);



			verts++;

		} while (--count);



		glEnd ();

	}

    }

Now we do the same to Phoenix's interpolation code version of the shadow routine.


	void GL_DrawAliasBlendedShadow (aliashdr_t *paliashdr, int pose1, int pose2, entity_t* e)

	{

            trivertx_t* verts1;

            trivertx_t* verts2;

            int*        order;

            vec3_t      point1;

            vec3_t      point2;

            vec3_t      d;

            float       height;

            float       lheight;

            int         count;

            float       blend;



	// muff@yakko.globalnet.co.uk - 17 Mar 2000

	trace_t		steptrace, downtrace;

	vec3_t		dest,stop,downmove;

	float		s1,c1; //this will store the sin and cos values as they are used so heavily

	//end of muff





            blend = (realtime - e->frame_start_time) / e->frame_interval;



            if (blend > 1) blend = 1;



            lheight = e->origin[2] - lightspot[2];

            height  = -lheight;// + 1.0; //was 1.0 - muff





	// muff@yakko.globalnet.co.uk - 17 Mar 2000

	// better shadowing, now takes angle of ground into account

	// cast a traceline into the floor directly below the player

	// and gets normals from this

	// for proper surface shadowing, each point should be taken

	// seperately, we do it this way for speed

	// NOTE need fixing for when at liquid surface

	VectorCopy (currententity->origin, downmove);

	downmove[2] = downmove[2] - 4096;

	memset (&downtrace, 0, sizeof(downtrace));

	SV_RecursiveHullCheck (cl.worldmodel->hulls, 0, 0, 1, currententity->origin, downmove, &downtrace);



	// calculate the all important angles

	s1 = sin( currententity->angles[1]/180*M_PI);

	c1 = cos( currententity->angles[1]/180*M_PI);



	// end of muff



            verts1 = (trivertx_t *)((byte *)paliashdr + paliashdr->posedata);

            verts2 = verts1;



            verts1 += pose1 * paliashdr->poseverts;

            verts2 += pose2 * paliashdr->poseverts;



            order = (int *)((byte *)paliashdr + paliashdr->commands);



            for (;;)

               {

               // get the vertex count and primitive type

               count = *order++;



               if (!count) break;



               if (count < 0)

                  {

                  count = -count;

                  glBegin (GL_TRIANGLE_FAN);

                  }

               else

                  {

                  glBegin (GL_TRIANGLE_STRIP);

                  }



               do

                  {

                  order += 2;



                  point1[0] = verts1->v[0] * paliashdr->scale[0] + paliashdr->scale_origin[0];

                  point1[1] = verts1->v[1] * paliashdr->scale[1] + paliashdr->scale_origin[1];

                  point1[2] = verts1->v[2] * paliashdr->scale[2] + paliashdr->scale_origin[2];



                  point1[0] -= shadevector[0]*(point1[2]+lheight);

                  point1[1] -= shadevector[1]*(point1[2]+lheight);



                  point2[0] = verts2->v[0] * paliashdr->scale[0] + paliashdr->scale_origin[0];

                  point2[1] = verts2->v[1] * paliashdr->scale[1] + paliashdr->scale_origin[1];

                  point2[2] = verts2->v[2] * paliashdr->scale[2] + paliashdr->scale_origin[2];



                  point2[0] -= shadevector[0]*(point2[2]+lheight);

                  point2[1] -= shadevector[1]*(point2[2]+lheight);



                  VectorSubtract(point2, point1, d);





			// muff@yakko.globalnet.co.uk - 17 Mar 2000

			point1[0] = point1[0] + (blend * d[0]);  //+lheight); //muff

			point1[1] = point1[1] + (blend * d[1]);  //+lheight); //muff

			point1[2] = point1[2] + (blend * d[2]);  //+lheight); //muff



			//drop it down to floor

			point1[2] =  - (currententity->origin[2] - downtrace.endpos[2]) ;



			//now move the z-coordinate as appropriate

			point1[2] += (	(point1[1] * (s1 * downtrace.plane.normal[0])) -

				        (point1[0] * (c1 * downtrace.plane.normal[0])) -

					(point1[0] * (s1 * downtrace.plane.normal[1])) -

					(point1[1] * (c1 * downtrace.plane.normal[1]))

					) +  ((1.0 - downtrace.plane.normal[2])*20)

					+ 0.2 ;





			// now load the point we have just calculated

			glVertex3fv (point1);



			// remove this as we have new commands in above

			//  glVertex3f (point1[0] + (blend * d[0]),

                        //  point1[1] + (blend * d[1]),

                        //  height);





	            //end of muff



				  verts1++;

                  verts2++;

                  } while (--count);



                  glEnd ();

               }

         }



Next find the R_DrawAliasModel routine to enable all this to work.

Add the following lines near the top, with the other variable definitions

ADD:


	//muff@yakko.globalnet.co.uk - 05 Mar 00 - added

	trace_t		steptrace, downtrace;

	vec3_t		dest,stop,downmove;

	//end of muff

This sets up the variables used later.

A little lower down you will find a piece of code that looks like this :-


    GL_DisableMultitexture();



    glPushMatrix ();



         // fenix@io.com: model transform interpolation

         if (r_interpolate_model_transform.value)

            {

               R_BlendedRotateForEntity (e);

      	}

         else

            {

               R_RotateForEntity (e);

            }



CHANGE to :-


    GL_DisableMultitexture();



    glPushMatrix ();



         // fenix@io.com: model transform interpolation

         if (r_interpolate_model_transform.value)

            {

               R_BlendedRotateForEntity (e, 0);

      	}

         else

            {

               R_RotateForEntity (e);

            }



The code below is a direct replacement for a section of code a little further down in the same routine.


	if (r_shadows.value)

	{





		// muff@yakko.globalnet.co.uk - 05 Mar 00

		// lightning bolts really shouldn't cast a shadow

		// Neither should rockets

		// lots of other to go in here no doubt, but we'll add them as and when

		if ( (!strncmp (clmodel->name, "progs/bolt",10)) ||

		     (!strcmp  (clmodel->name, "progs/missile.mdl")) ||

		     (!strcmp  (clmodel->name, "progs/laser.mdl")) ||

		     (!strcmp  (clmodel->name, "progs/lavaball.mdl")) ||

		     (!strcmp  (clmodel->name, "progs/quaddama.mdl")) ||

		     (!strcmp  (clmodel->name, "progs/invulner.mdl"))

		   )

		   return;



		// in first person the engine currently draws the player's weapon,

		// but no player. This means a weapon shadow is drawn

		// we'll remove weapon shadow until this is fixed

		if (!strncmp (clmodel->name, "progs/v_",8))

			return;

		// end of muff



		glPushMatrix ();



		// muff@yakko.globalnet.co.uk - 05 Mar 00

		// this determines the shadow vector relative to origin of entity

		// changed always be directly below entity for the moment.

		// will alter later (hopefully) when I got proper light source shadows working

		shadevector[0] = 0;

		shadevector[1] = 0;

		shadevector[2] = 1;

		VectorNormalize (shadevector);



		//original code

		//		R_RotateForEntity (e);



		//muff@yakko.globalnet.co.uk - 050300 - added

		// this translates the location of the shadow, and rotates in horizontal plane only

		// this means that shadows now work properly for chase_cam, grenade etc

		// hence removal of code above

		if (r_interpolate_model_animation.value)

		 R_BlendedRotateForEntity (e, 1); // we've re-written this to work properly

		else

		{

		 glTranslatef (e->origin[0],  e->origin[1],  e->origin[2]);

	 	 glRotatef (e->angles[1],  0, 0, 1);

		}

		//end of muff

		// muff@yakko.globalnet.co.uk - 14 Mar 2000

		// this is so we can calculate height of the ground accurately

		VectorCopy (e->origin, downmove);



		downmove[2] = downmove[2] - 4096;

		memset (&downtrace, 0, sizeof(downtrace));

		SV_RecursiveHullCheck (cl.worldmodel->hulls, 0, 0, 1, e->origin, downmove, &downtrace);

		// end of muff



		glDisable (GL_TEXTURE_2D);

		glEnable (GL_BLEND);



		// original code

		//	glColor4f (0,0,0,0.5);





		// muff@yakko.globalnet.co.uk - 14 Mar 2000

		// set the shade of the shadow based on height off ground

		glColor4f (0,0,0,1.0 - ((mins[2]-downtrace.endpos[2])/60));

		//end of muff



		//original code

		//	GL_DrawAliasShadow (paliashdr, lastposenum);



		// fenix@io.com: model animation interpolation

		if (r_interpolate_model_animation.value)

		 GL_DrawAliasBlendedShadow (paliashdr, lastposenum0, lastposenum, currententity);

		else

		 GL_DrawAliasShadow (paliashdr, lastposenum);



		glEnable (GL_TEXTURE_2D);

		glDisable (GL_BLEND);

		glColor4f (1,1,1,1);

		glPopMatrix ();

	}


And that should be it.

Compile and run. Switch to chasecam mode (CHASE_ACTIVE 1). As the player runs up and down slopes his shadow now works properly (it behaves a little off at the edge between 2 slopes and I'm working on that in my next version at the moment)

By the way, if you haven't yet fixed your chasecam code, goto the QSG site as they have a tutorial there.

It's not rocket science (no pun intended), but I think it makes the shadows a much better option now.

There's obviously a lot of stuff I need to work on with it, and the code may not be the best, but it'll do for the moment :P

If anyone has any problems with the code above let me know and I'll try to help.

Enjoy,

Muff



 
Not logged in
Sign up
Login:
Passwd: