Forum

Tutorial: Adding Alpha Transparency to stock GLQUAKE

Post tutorials on how to do certain tasks within game or engine code here.

Moderator: InsideQC Admins

Postby Baker » Sun Nov 30, 2008 12:10 am

goldenboy wrote:I think I remember transparent water in software in FTE - dunno if it also does alpha entities. I should test. :-E


I've heard that FTE uses 24 bit color in software mode. If true, the issue of the constrained limitation of 256 colors for the whole window doesn't come into play.
User avatar
Baker
 
Posts: 3661
Joined: Tue Mar 14, 2006 5:15 am

Postby Tomaz » Mon Dec 01, 2008 1:32 pm

This is like the 5:th tutorial from you that i read that shows a feature and list a few engines that supports it, and also the 5:th tutorial that doesnt list TomazQuake as one of the engines supporting it.

Nice tutorial, but why do I get the feeling you're not really sure what you're doing?

1) You send a 2 then the transluceny value, then you read it into a temp which isnt used, why not just not send it?

2) Why are you using floats when a byte is more than enough?
0-255, just do / 255.0f;

3) So now you have support for transparent object, but where is the alpha sorter? Sure the objects will be transparent, but 99.9% of the time there will be objects in behind the transparent object that wont be rendered.

Here is why:

Lets use a window in a map as the transparent object.

Whenever the window is rendered it also writes to the z-buffer. If this is rendered BEFORE an object behind the window, lets say a monster, that monster wont be visible through the window because the 3d card will reject it isnce its to far away in the z-buffer.

The sollution, always render opaque objects first ( thatd be the ones not being transparent ) and then render all transparent object SORTED from back to front. This will work fine for things like a window, but for object with a more complex geometry it might not be enough to sort the object from back to front, one might have sort every triangle from back to front ( which i think DP does, or at least did at some point ).

TomazQuake didnt go that far, one thing I DID do tho was fixing so the particle system works with water ( which isnt covered here either ) which does a similar thing. There i sort all the particles as being either in front or behind the water plane. So the render order is, Particles behind water, water, particles in front of water.

Enough from me in this thread, im gonna comment a few more of your tutorials tho.
Tomaz
 
Posts: 67
Joined: Fri Nov 05, 2004 8:21 pm

Postby r00k » Mon Dec 01, 2008 9:59 pm

Code: Select all
void SortEntitiesByTransparency (void)
{
   int      i, j;
   entity_t   *tmp;

   for (i = 0 ; i < cl_numvisedicts ; i++)
   {
      if (cl_visedicts[i]->transparency < 1 && cl_visedicts[i]->transparency > 0)
      {
         for (j = cl_numvisedicts - 1 ; j > i ; j--)
         {
            // if not transparent, exchange with transparent
            if (cl_visedicts[j]->transparency == 1)
            {
               tmp = cl_visedicts[i];
               cl_visedicts[i] = cl_visedicts[j];
               cl_visedicts[j] = tmp;
               break;
            }
            else
               continue;
         }
         if (j == i)
            return;
      }
   }
}
r00k
 
Posts: 1108
Joined: Sat Nov 13, 2004 10:39 pm

Postby Baker » Mon Dec 01, 2008 10:36 pm

Tomaz wrote:Whenever the window is rendered it also writes to the z-buffer. If this is rendered BEFORE an object behind the window, lets say a monster, that monster wont be visible through the window because the 3d card will reject it isnce its to far away in the z-buffer.


Ah! I noticed that but didn't know why that was happening. I wasn't seeing monsters behind windows, etc.

@Rook: where should have that called from?

Edit = nevermind, got it

Code: Select all
void R_DrawEntitiesOnList (void)
{
   int   i;

   if (!r_drawentities.value)
      return;

   SortEntitiesByTransparency ();


Edit #2 = Cool! Much better. :D
User avatar
Baker
 
Posts: 3661
Joined: Tue Mar 14, 2006 5:15 am

Postby mh » Sat Aug 22, 2009 11:23 pm

r00k wrote:
Code: Select all
void SortEntitiesByTransparency (void)
{
   int      i, j;
   entity_t   *tmp;

   for (i = 0 ; i < cl_numvisedicts ; i++)
   {
      if (cl_visedicts[i]->transparency < 1 && cl_visedicts[i]->transparency > 0)
      {
         for (j = cl_numvisedicts - 1 ; j > i ; j--)
         {
            // if not transparent, exchange with transparent
            if (cl_visedicts[j]->transparency == 1)
            {
               tmp = cl_visedicts[i];
               cl_visedicts[i] = cl_visedicts[j];
               cl_visedicts[j] = tmp;
               break;
            }
            else
               continue;
         }
         if (j == i)
            return;
      }
   }
}

No, that's gonna group the entities, but it won't sort them back to front. Not to mention being the WRONG sorting algorithm. ;) You need to check entities at the point at which they're added to the cl_visedicts array, if they have transparency then add them to a second cl_transedicts array, then do something like this:
Code: Select all
int D3D_EntityDepthCompare (const void *a, const void *b)
{
   entity_t *enta = *((entity_t **) a);
   entity_t *entb = *((entity_t **) b);

   // sort back to front
   if (enta->dist > entb->dist)
      return 1;
   else if (enta->dist < entb->dist)
      return -1;
   else return 0;
}


void D3D_DrawTranslucentEntities (void)
{
   if (!r_drawentities.value) return;
   if (!d3d_RenderDef.numtransedicts) return;

   // if there's only one then the list is already sorted!
   if (d3d_RenderDef.numtransedicts > 1)
   {
      // evaluate distances
      for (int i = 0; i < d3d_RenderDef.numtransedicts; i++)
      {
         entity_t *ent = d3d_RenderDef.transedicts[i];

         // set distance from viewer - no need to sqrt them as the order will be the same
         // (fixme - should we, and then subtract a radius?)
         ent->dist = (ent->origin[0] - r_origin[0]) * (ent->origin[0] - r_origin[0]) +
            (ent->origin[1] - r_origin[1]) * (ent->origin[1] - r_origin[1]) +
            (ent->origin[2] - r_origin[2]) * (ent->origin[2] - r_origin[2]);
      }

      if (d3d_RenderDef.numtransedicts == 2)
      {
         // trivial case - 2 entities
         if (d3d_RenderDef.transedicts[0]->dist < d3d_RenderDef.transedicts[1]->dist)
         {
            // reorder correctly
            entity_t *tmp = d3d_RenderDef.transedicts[1];
            d3d_RenderDef.transedicts[1] = d3d_RenderDef.transedicts[0];
            d3d_RenderDef.transedicts[0] = tmp;
         }
      }
      else
      {
         // general case - depth sort the transedicts from back to front
         qsort ((void *) d3d_RenderDef.transedicts, d3d_RenderDef.numtransedicts, sizeof (entity_t *), D3D_EntityDepthCompare);
      }
   }

   // global state for translucent ents - we're drawing back to front so we can write to Z
   D3D_EnableAlphaBlend (D3DBLENDOP_ADD, D3DBLEND_SRCALPHA, D3DBLEND_INVSRCALPHA, false);

   // now draw 'em
   // we can't state-batch these as they need correct ordering so we'll just live with the state changes...
   for (int i = 0; i < d3d_RenderDef.numtransedicts; i++)
   {
      d3d_RenderDef.currententity = d3d_RenderDef.transedicts[i];

      if (d3d_RenderDef.currententity->model->type == mod_sprite)
         D3D_DrawSpriteModel (d3d_RenderDef.currententity);
      else if (d3d_RenderDef.currententity->model->type == mod_alias)
         D3D_DrawTranslucentAliasModel (d3d_RenderDef.currententity);
      else if (d3d_RenderDef.currententity->model->type == mod_brush)
         R_DrawBrushModel (d3d_RenderDef.currententity);
      else
      {
         // just print a warning
         Con_DPrintf
         (
            "Unknown model type %i for %s\n",
            d3d_RenderDef.currententity->model->type,
            d3d_RenderDef.currententity->model->name
         );
      }
   }

   // take down global state
   D3D_DisableAlphaBlend ();
}


You could also accomplish similar with a Mod_PointInLeaf, storing translucent ents in leafs, then doing a BSP tree walk, but this way is much simpler overall, especially as there will (hopefully!) only be a small handful of translucent ents in any given scene. qsort speeds are comparable to a BSP walk even for all surfs in the view anyway, so best not to even go there.

Even then it won't handle cases where alpha ents are overlapping, but that's probably getting into too much complexity (especially if they have different model types).
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
User avatar
mh
 
Posts: 2287
Joined: Sat Jan 12, 2008 1:38 am

Postby revelator » Sat Aug 22, 2009 11:35 pm

hmm strange that one seems kinda familiar :shock:

ah aye

Code: Select all
// sort draworder by depth
int R_DepthSortSprites(const void *arg1, const void *arg2)
{
   draworder_t      *pOrder1, *pOrder2;
   static int      diff;

   pOrder1 = (draworder_t *) arg1;
   pOrder2 = (draworder_t *) arg2;

   if (pOrder1->depth > pOrder2->depth)
   {
      diff = -1;
   }
   else if (pOrder1->depth < pOrder2->depth)
   {
      diff = 1;
   }
   else
   {
      diff = 0;
   }
   return diff;
}


Code: Select all
typedef struct draworder_s
{
   float   depth;
   int      order;
} draworder_t;


Code: Select all
void R_DrawSpritesOnList (void)
{
   int         i;
   entity_t   *tempentity;
   draworder_t   sprite_order[MAX_EDICTS];
   vec3_t      temp_org = {0, 0, 0};
   vec3_t      sprite_org;

   if (!r_drawentities.value) return;

   // make new list of ents by depth and current order in list
   for (i=0 ; i<cl_numvisedicts ; i++)
   {
      tempentity = cl_visedicts[i];
      VectorCopy(tempentity->origin, sprite_org);
      VectorSubtract(sprite_org, r_origin, temp_org);
      sprite_order[i].depth = Length(temp_org);
      sprite_order[i].order = i;
   }

   // sort this new list by depth, back to front
   qsort(sprite_order, cl_numvisedicts, sizeof(draworder_t), R_DepthSortSprites);

   // classic quake only has a handful of sprites: light globes, bubbles and explosions.
   // hipnotic added a few more, but they looked ghastly even back in the late 90s, so
   // we'll just pretend they never happened.  the main ones we want to deal with are
   // the globes and bubbles.  sprite replacement is handled in the drawing routine.
   for (i=0 ; i<cl_numvisedicts ; i++)
   {
      currententity = cl_visedicts[ sprite_order[i].order ];

      // not an sprite model
      if (currententity->model->type != mod_sprite) continue;

      // occluded during previous pass
      if (currententity->occluded) continue;

      R_DrawSpriteModel (currententity);
   }
}


could probably be used for the same ?
User avatar
revelator
 
Posts: 2518
Joined: Thu Jan 24, 2008 12:04 pm
Location: inside tha debugger

Postby mh » Sun Aug 23, 2009 10:53 am

Kinda, but it's not really calculating the distance correctly. Check my dist calc above, and remember that you don't need to sqrt the result cos if x > y then sqrt(x) also > sqrt (y).
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
User avatar
mh
 
Posts: 2287
Joined: Sat Jan 12, 2008 1:38 am

Postby revelator » Sun Aug 23, 2009 11:27 am

noticed :)

hmm lemme see what i can cook up.
User avatar
revelator
 
Posts: 2518
Joined: Thu Jan 24, 2008 12:04 pm
Location: inside tha debugger

Postby Lardarse » Sun Aug 23, 2009 9:10 pm

mh wrote:Kinda, but it's not really calculating the distance correctly. Check my dist calc above, and remember that you don't need to sqrt the result cos if x > y then sqrt(x) also > sqrt (y).

I think that there are exceptions when both are less than 1, but I might be wrong...
Roaming status: Testing and documentation
User avatar
Lardarse
 
Posts: 266
Joined: Sat Nov 05, 2005 1:58 pm
Location: Bristol, UK

Postby Team Xlink » Sun Aug 23, 2009 9:13 pm

So does this mean that this tutorial in the first post doesn't work right?
Team Xlink
 
Posts: 368
Joined: Thu Jun 25, 2009 4:45 am
Location: Michigan

Postby Electro » Sun Aug 23, 2009 9:22 pm

Team Xlink: correct
Benjamin Darling
http://www.bendarling.net/

Reflex - In development competitive arena fps combining modern tech with the speed, precision and freedom of 90's shooters.
http://www.reflexfps.net/
Electro
 
Posts: 312
Joined: Wed Dec 29, 2004 11:25 pm
Location: Brisbane, Australia

Postby Team Xlink » Sun Aug 23, 2009 10:17 pm

Electro wrote:Team Xlink: correct



Time to go dig through my source code to remove it or fix it then.
Team Xlink
 
Posts: 368
Joined: Thu Jun 25, 2009 4:45 am
Location: Michigan

Postby revelator » Sun Sep 20, 2009 2:43 am

Code: Select all
   // if there's only one then the list is already sorted!
   if (cl_numvisedicts > 1)
   {
      // evaluate distances
      for (i = 0; i < cl_numvisedicts; i++)
      {
         entity_t *ent = cl_visedicts[i];

         // set distance from viewer - no need to sqrt them as the order will be the same
         // (fixme - should we, and then subtract a radius?)
         ent->alphadist = (ent->origin[0] - r_origin[0]) * (ent->origin[0] - r_origin[0]) + (ent->origin[1] - r_origin[1]) * (ent->origin[1] - r_origin[1]) + (ent->origin[2] - r_origin[2]) * (ent->origin[2] - r_origin[2]);
      }

      if (cl_numvisedicts == 2)
      {
         // trivial case - 2 entities
         if (cl_visedicts[0]->alphadist < cl_visedicts[1]->alphadist)
         {
            entity_t *tmpent = cl_visedicts[1];

            // reorder correctly
            cl_visedicts[1] = cl_visedicts[0];
            cl_visedicts[0] = tmpent;
         }
      }
      else
      {
         // general case - depth sort the transedicts from back to front
         qsort ((void *) cl_visedicts, cl_numvisedicts, sizeof (entity_t *), R_DepthSortEntities);
      }
   }
   //Enable alpha blending
   glEnable(GL_BLEND);
   glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

        /* your drawing functions here */

   //Disable alpha blending
   glDisable(GL_BLEND);


seems to work. not sure if i need to depthcheck first ?

add float alphadist; to entity_t struct.
User avatar
revelator
 
Posts: 2518
Joined: Thu Jan 24, 2008 12:04 pm
Location: inside tha debugger

Re: Tutorial: Adding Alpha Transparency to stock GLQUAKE

Postby revelator » Fri Apr 14, 2017 2:26 am

Late as fuck to this one but better late than newer :)

the fullbright flag in nehahra has nothing at all to do with fullbrights its only use is the atom bomb effect from the demo movie that blanks the screen to white by turning of drawing brushmodels and letting the lightmap got all white. later versions of joequake does not even use it anymore i guess because it can do some nasty stuff to some mods that actually try to set a real fullbright value.

for those interested here it is in all its glory hackiness :)

Code: Select all
void R_RenderBrushPoly (msurface_t *fa)
{
   texture_t   *t;
   byte      *base;
   int         maps;
   glRect_t    *theRect;
   int smax, tmax;

   c_brush_polys++;

   if (fa->flags & SURF_DRAWSKY)
   {   // warp texture, no lightmaps
      EmitBothSkyLayers (fa);
      return;
   }
      
   t = R_TextureAnimation (fa->texinfo->texture);
   GL_Bind (t->gl_texturenum);

   if (fa->flags & SURF_DRAWTURB)
   {   // warp texture, no lightmaps
      EmitWaterPolys (fa);
      return;
   }

   if (fa->flags & SURF_UNDERWATER)
      DrawGLWaterPoly (fa->polys);
   else
      DrawGLPoly (fa->polys);

        if (DoFullbright) return;  // Ender: Fullbright this one. (revelator not as i understand fullbrights)

   // add the poly to the proper lightmap chain

   fa->polys->chain = lightmap_polys[fa->lightmaptexturenum];
   lightmap_polys[fa->lightmaptexturenum] = fa->polys;

   // check for lightmap modification
   for (maps = 0 ; maps < MAXLIGHTMAPS && fa->styles[maps] != 255 ;
       maps++)
      if (d_lightstylevalue[fa->styles[maps]] != fa->cached_light[maps])
         goto dynamic;

   if (fa->dlightframe == r_framecount   // dynamic this frame
      || fa->cached_dlight)         // dynamic previously
   {
dynamic:
      if (r_dynamic.value && !r_fullbright.value)
      {
         lightmap_modified[fa->lightmaptexturenum] = true;
         theRect = &lightmap_rectchange[fa->lightmaptexturenum];
         if (fa->light_t < theRect->t) {
            if (theRect->h)
               theRect->h += theRect->t - fa->light_t;
            theRect->t = fa->light_t;
         }
         if (fa->light_s < theRect->l) {
            if (theRect->w)
               theRect->w += theRect->l - fa->light_s;
            theRect->l = fa->light_s;
         }
         smax = (fa->extents[0]>>4)+1;
         tmax = (fa->extents[1]>>4)+1;
         if ((theRect->w + theRect->l) < (fa->light_s + smax))
            theRect->w = (fa->light_s-theRect->l)+smax;
         if ((theRect->h + theRect->t) < (fa->light_t + tmax))
            theRect->h = (fa->light_t-theRect->t)+tmax;
         base = lightmaps + fa->lightmaptexturenum*lightmap_bytes*BLOCK_WIDTH*BLOCK_HEIGHT;
         base += fa->light_t * BLOCK_WIDTH * lightmap_bytes + fa->light_s * lightmap_bytes;
         R_BuildLightMap (fa, base, BLOCK_WIDTH*lightmap_bytes);
      }
   }
}


and here is where its set.

Code: Select all
void R_DrawBrushModel (entity_t *e)
{
   int      j, k;
   vec3_t      mins, maxs;
   int      i, numsurfaces;
   msurface_t   *psurf;
   float      dot;
   mplane_t   *pplane;
   model_t      *clmodel;
   qboolean   rotated;
   float      oldtexsort;

   oldtexsort = gl_texsort.value;

   currententity = e;
   currenttexture = -1;

   model_alpha = currententity->transparency;
   if (model_alpha == 0)
      model_alpha = 1;

   clmodel = e->model;

   if (e->angles[0] || e->angles[1] || e->angles[2])
   {
      rotated = true;
      for (i=0 ; i<3 ; i++)
      {
         mins[i] = e->origin[i] - clmodel->radius;
         maxs[i] = e->origin[i] + clmodel->radius;
      }
   }
   else
   {
      rotated = false;
      VectorAdd (e->origin, clmodel->mins, mins);
      VectorAdd (e->origin, clmodel->maxs, maxs);
   }

   if (R_CullBox (mins, maxs))
      return;

        if (model_alpha != 1)
   {
      glEnable(GL_BLEND);
      glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); // TNT Fix
      glColor4f (1,1,1, model_alpha);
      Cvar_SetValue ("gl_texsort", 1);
   }
   else
      glColor3f (1, 1, 1);

   memset (lightmap_polys, 0, sizeof(lightmap_polys));

   VectorSubtract (r_refdef.vieworg, e->origin, modelorg);
   if (rotated)
   {
      vec3_t   temp;
      vec3_t   forward, right, up;

      VectorCopy (modelorg, temp);
      AngleVectors (e->angles, forward, right, up);
      modelorg[0] = DotProduct (temp, forward);
      modelorg[1] = -DotProduct (temp, right);
      modelorg[2] = DotProduct (temp, up);
   }

   psurf = &clmodel->surfaces[clmodel->firstmodelsurface];

// calculate dynamic lighting for bmodel if it's not an
// instanced model
   if (clmodel->firstmodelsurface != 0 && !gl_flashblend.value)
   {
      for (k=0 ; k<MAX_DLIGHTS ; k++)
      {
         if ((cl_dlights[k].die < cl.time) ||
            (!cl_dlights[k].radius))
            continue;

         R_MarkLights (&cl_dlights[k], 1<<k,
            clmodel->nodes + clmodel->hulls[0].firstclipnode);
      }
   }

    glPushMatrix ();
e->angles[0] = -e->angles[0];   // stupid quake bug
   R_RotateForEntity (e, false);
e->angles[0] = -e->angles[0];   // stupid quake bug

   if (psurf && psurf->polys) // This is probably not needed anymore
   {
      //
      // draw texture
      //
      for (i=0 ; i<clmodel->nummodelsurfaces ; i++, psurf++)
      {
      // find which side of the node we are on
         pplane = psurf->plane;

         dot = DotProduct (modelorg, pplane->normal) - pplane->dist;

      // draw the polygon
         if (((psurf->flags & SURF_PLANEBACK) && (dot < -BACKFACE_EPSILON)) ||
            (!(psurf->flags & SURF_PLANEBACK) && (dot > BACKFACE_EPSILON)))
         {
            if (e->fullbright == 1) DoFullbright = 1; // revelator: Zomg HACK
            if (gl_texsort.value)
               R_RenderBrushPoly (psurf);
            else
               R_DrawSequentialPoly (psurf);
            DoFullbright = 0;
         }
      }
   }

   R_BlendLightmaps ();

   glPopMatrix ();
   if (model_alpha != 1)
   {
      Cvar_SetValue ("gl_texsort", oldtexsort);
      glDisable(GL_BLEND);
   }
}


Marked the fun stuff with my avatar name ;)
Productivity is a state of mind.
User avatar
revelator
 
Posts: 2518
Joined: Thu Jan 24, 2008 12:04 pm
Location: inside tha debugger

Previous

Return to Programming Tutorials

Who is online

Users browsing this forum: No registered users and 1 guest