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) 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); 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 (); } } 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 (); } } 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 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); } 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 |