flipCode - Tech File - Alexander Herz [an error occurred while processing this directive]
Alexander Herz
Click the name for some bio info

E-Mail: softcore@web.de



   06/22/1999, Introduction


firstly I gotta post the standart shit... I'm not responsible for any damage...blah blah I tend to write anyhing in small letters as I'm lazy and as I'm used to it from chatting (my ICQ is 18468966 contact me if ya like...preferably if you got a job for me... :) and I'm german...so don't bother me with grammer mistakes...many ppl said my english is ok.. my english teacher highly disagrees...I dunno.. argh..who's gonna read that anyway...

I code basically 3d engines..and anything else you might need to make some game...

In the last 2 years I spended coding some indoor/true outdoor portal and heightfield based environment engine which also features hierachically bones animated objects,chrome+lightmapping and much other shit... look at www.ethos.cx for some shots+demo..thought it's outdated...I'm not responsible for updating it...

lately I started coding some wingcomander like space sim for exilesoft... they offered me some neat job in the us..but I gotta finish school first..sadly..

anyhow..

the basic topic of my techfile wil be algos dealing with implementation of 3d rendering for hardware based rendering...

I see no real future for software... who has an old lame cpu (as me..p133) has to buy a 3d card to have any chance for reasonable framerates.. and if ya bought some newer pc lately it probably has a 3dcard included anyway..and if not you should also buy some 3d card..as it is faster and looks better..ad the 75bucks for a voodoo1(my card)

and there have been many egs for softrenderers around...we don't need another one.. well..I actually wrote some softrenderer some time ago..it was slow but interesting to learn from...

Everything I post here is far from being perfect (probably) and maybe I wrote it in the lae evening... so don't flame me..I won't flame you either... any comments or quests are welcome...send em to softcore@t-online.de

the first api I used was directx..but that was in executebuffer time.. so I switched to glide fastly... it is my favourite one..at least for useability sadly it is doomed to die out... 3dfx's strange politics...and the bad circimstance that M$'s win is the leading system and that dx supports all cards lead to this... I doubt M$ will learn from em..too bad...

I'm rather common to all 3 raster apis so I'll post egs and code snips in c++ and the api it was originally made in

I sended some landscape/particle demo to Lionhead and they were realy impressed...so I guess that might be interesting to some ppl... I'll assume standart 3d maths knowledge..and I won't use matrices...


Landscape

The landscape renderer draws up to 300.000 trias/sec on my p133 with voodoo1 which is actually nice...

the idea is reather simple and derivered from voxels(somehow)...

I load some grayscale image representing the heights... then I create some kinda linked list...

a part (the landscape is devided into rectangular parts) looks about like this:


struct LandScape
{
 BYTE * heightfield;//guesss what
 WORD LeadId[4];//contains the indices of the lanscape parts
                  touching this part in North(=[0]),
                  Est,South and West
}

The render func has to know in which landscape the player actually is... then it starts by rotating (to viewpos) ONLY the edges of the actual landscape

Let'S say the rotated Edges are Rot_Edges[4];


//Rot_Edges[0]=upper left
//Rot_Edges[1]=upper right
//Rot_Edges[2]=lower left
//Rot_Edges[3]=lower right

Then we calc The difference between the edges:


Vector VLeft=(Rot_Edges[2]-Rot_Edges[0])/height_of_heightfield;
Vector VDown=(Rot_Edges[3]-Rot_Edges[1])/width_of_heightfield;

Now we can adress every point of the heigtefield(in viewpos) by adding the VLeft and VDown to the Rot_Edges[0];

eg:

Vector pos=Rot_Edges[0]+VLeft*5+VDown*4;

pos now has the value which is corresponding to the heightfield[5][4](grayscale) but in viewpos...

as you see you save the rotations for all points(but the 4 edges) no matter how many points these are..

but yet it would be flat..we need to add the height... so we add (heightfield[5][4]*UpVector) to pos and store that for every vertex in a cache... you can make the res of the heightfield rather high as you can very simply add LOD to this..or you can easily make it render the tris back to front r other way around...as well as you have easily stripes

certainly we do start rendering at the row whiches zvalue is closest to us... we don't want trias behind us... alse we stop rendering if the act row's z value is smaller 0 (behind us) and both VLeft.z and VDown.z are smaller 0//we'll never get positive again analog for similar cases...(row reached right screenborder and VLeft.x>0)

texcoords are calced this way:

texadd.x=dist(Rot_Edges[2],Rot_Edges[0])/height_of_heightfield;
texadd.y=dist(Rot_Edges[3],Rot_Edges[1])/width_of_heightfield;
//again for heightfield[5][4]
tex.x=5*texadd.x;
tex.y=4*texadd.y;

ok...

now we rendered this actual heightfield..but the the parts touching this part are missing... so we simply check weather the northern edge of this actual landscape is not cliped away fully.. if that is the case that part has to be render either... you can do that recursive as well as with some stack... set some max renderdepth and be shure not to render parts several times...as two parts you render might lead to the same also visible part..mark that somehow the heightfield can be easily changed so holes in the scape due to explosions are no prob..(in realtime) for me vetex shading was enough..as it is outdoor only... save the second pass for detail map or maybe some texmixing methos to avoid doubled texs... I avoided it by having one unique 256*256 texmap per part... not the nicest way but rather fast...blurryness of close polys is acceptable...

at my homepage (www.ethos.cx) you can see some shots how that looks...I guess the vulcano pic will still be there..


Particles

I was talking about that vulcano...what is a vulcane without erruption.... boring...

this is rather forwad + easy but many ppl were very impressed.... so I'm gonna describe it...


struct Particle_
{
 long color;
 Vec Pos;//Pos relative to ..\Pos
 Vec Move;//how to contonue Pos
 bool active;
};
struct Particle_effect
{
 int Num_particles;
 Particle_ Particle[MAX_PARTICLES];
 int part_eff_act_livetime;//how long are we existing??
 int part_eff_livetime;//how long are we allowed to be there??
 bool grav;
 bool active;
};


//update func for particles...called every frame...
yet the particles' motion is framerate dependent.had no time to hange
that...

void _fastcall G_ProcessParticles(_Particles * MP,DWORD WorldTime,Vec Pos,
float xw, float yw , float zw)
{
 Vec RPos;
 G_VMUL(&Pos,1.f,&RPos);
 grDepthMask(0);//no zwrites
 grFogMode(GR_FOG_DISABLE); //no fog...
  if(yw)
  {
   G_Set_Actscbuf(0);// I use some statemachine for sin and cos...I have a
buffer (size = 3)..this command say the next sin+cos calced should be stored
in buffer[0]
   G_Get_SinCos(yw);//xw,yw,zw are the angles to rotate the world about to
get viewpos
  }
  if(xw)
  {
   G_Set_Actscbuf(1);
   G_Get_SinCos(xw);
  }
  if(zw)
  {
   G_Set_Actscbuf(2);
   G_Get_SinCos(zw);
  }

 for(int i=0;i<MP->Num_particles;i++)
 {
  if((MP->Particles+i)->used)//go through all particle_collections
                              and check weather processing is needed
  {
   int Not_Used=0;
   A_Particle * AP=(MP->Particles+i)->Parts;
   A_Particle LP;
   if((MP->Particles+i)->glow)//additive blending gives best result...they
sprites are munged...
   {
    if((MP->Particles+i)->add)
    {
     grAlphaBlendFunction( GR_BLEND_ONE,GR_BLEND_ONE,
GR_BLEND_ONE,GR_BLEND_ZERO );
     grColorCombine(GR_COMBINE_FUNCTION_SCALE_OTHER_MINUS_LOCAL,
GR_COMBINE_FACTOR_ONE, GR_COMBINE_LOCAL_ITERATED, GR_COMBINE_OTHER_TEXTURE,
false);
    }
    else
    {
     grAlphaBlendFunction( GR_BLEND_DST_COLOR, GR_BLEND_ZERO,GR_BLEND_ZERO,
GR_BLEND_ZERO );
     grColorCombine(GR_COMBINE_FUNCTION_SCALE_OTHER_MINUS_LOCAL,
GR_COMBINE_FACTOR_ONE, GR_COMBINE_LOCAL_ITERATED, GR_COMBINE_OTHER_TEXTURE,
true);
    }

   }
   for(int i2=0;i2<(MP->Particles+i)->Num_parts;i2++)//go through all single
particles
   {
    if(AP->used)
    {
     if(WorldTime-AP->StartTime>AP->LiveTime)//see if it died due to time
out
     {
      if((MP->Particles+i)->loop)/if died and loop recreate
      {
       AP->Light=AP->Orig_Light;
       AP->Pos=AP->Orig_Pos;
       AP->Size=AP->Orig_Size;
       AP->Dir=AP->Orig_Dir;
       AP->LiveTime=AP->Orig_LT;
       AP->StartTime=WorldTime;
       AP->AnimLight=AP->Orig_AnimL;

       AP->faded=0;
      }
      else AP->used=0;
     }
     else
     {
      AP->Dir.y+=GRAVITY*AP->Grav_Power;//apply garviy(if desired)
      G_VADD(&AP->Pos,&AP->Dir,&AP->Pos);//move particle (corresponding to
its motion)
      AP->Size+=AP->AnimSize;//animate size+light
      G_VADD(&AP->Light,&AP->AnimLight,&AP->Light);


if(!AP->faded&&WorldTime-AP->StartTime>(AP->LiveTime-(AP->LiveTime/5)))//if
short before dying fade out
      {
       float x,y,z;
       x=((WorldTime-AP->StartTime)-(AP->LiveTime-(AP->LiveTime/5)))*2.f;
       y=x;
       z=x;
       x=((255-AP->Light.x)/x);
       AP->AnimLight.x=x;
       y=((255-AP->Light.y)/y);
       AP->AnimLight.y=y;
       z=((255-AP->Light.z)/z);
       AP->AnimLight.z=z;
       AP->faded=1;
      }

     }
     G_VSUB(&AP->Pos,&RPos,&LP.Pos);

     if(yw)
     {
      G_Set_Actscbuf(0);//activate the buffer[0]..now it's values will be
used for the rotation calcs
      G_PROTY(&LP.Pos,&LP.Pos,yw);
     }
     if(xw)
     {
      G_Set_Actscbuf(1);
      G_PROTX(&LP.Pos,&LP.Pos,xw);
     }
     if(zw)
     {
      G_Set_Actscbuf(0);
      G_PROTZ(&LP.Pos,&LP.Pos,zw);
     }
     if(LP.Pos.z>0&&LP.Pos.z<C_ZBACK)//if on screen
     {
      G_Makepers(&LP.Pos);//do perpective projection
      float add=(LP.Pos.x+AP->Size/2)/(1+LP.Pos.z*ZMUL);
      extern bool Indoor;
      extern float Scale;

if(!CL_Clip_Single_Point(&LP.Pos))D_DrawParticle((MP->Particles+i),AP,LP.Pos
,add*Scale);//and draw the particle
     }
    }
    else Not_Used++;
    AP++;
   }
   if(Not_Used>=(MP->Particles+i)->Num_parts)(MP->Particles+i)->used=0;//if
all particles died free this particle_colection..so another one can be
started
   if((MP->Particles+i)->glow)//return old state
   {
    grAlphaBlendFunction( GR_BLEND_SRC_ALPHA,GR_BLEND_ONE_MINUS_SRC_ALPHA,
GR_BLEND_ONE,GR_BLEND_ZERO );
    grColorCombine(GR_COMBINE_FUNCTION_SCALE_OTHER_MINUS_LOCAL,
GR_COMBINE_FACTOR_ONE, GR_COMBINE_LOCAL_ITERATED, GR_COMBINE_OTHER_TEXTURE,
FXFALSE);
   }
  }
 }
 grDepthMask(1);
 grFogMode(GR_FOG_WITH_TABLE);
}


Vec inline CreateRndVec(float rnd)//create a vec with random values between
+/- md
{
 Vec Add;
 Add.x=random(rnd);
 if(random(2)<1)Add.x=-Add.x;
 Add.y=random(rnd);
 if(random(2)<1)Add.y=-Add.y;
 Add.z=random(rnd);
 if(random(2)<1)Add.z=-Add.z;
 return Add;
}


void CreateA_Particle(A_Particle * AP,Vec Pos,int ID,DWORD WT,DWORD LT,float
rnd)// create some mtions/aniations...
{
 Vec Add;
 switch(ID)
 {
  case ID_EXPLOSION:
    AP->used=1;
    Add=CreateRndVec(0.1f);
    G_VADD(&Pos,&Add,&AP->Orig_Pos);
    AP->Orig_Dir.x=0;
    AP->Orig_Dir.y=0;
    AP->Orig_Dir.z=-0.08f;
    Add=CreateRndVec(0.0143f);
    G_VADD(&AP->Orig_Dir,&Add,&AP->Orig_Dir);
    Add=CreateRndVec(-0.0143f);
    G_VADD(&AP->Orig_Dir,&Add,&AP->Orig_Dir);
    //AP->Orig_Dir.y=0;
    AP->Orig_Light.x=random(20);
    AP->Orig_Light.y=100-random(50);
    AP->Orig_Light.z=200;

    Add=CreateRndVec(0.01f);

    AP->AnimLight=Add;
    G_VADD(&AP->Orig_Light,&Add,&AP->Orig_Light);
    AP->faded=0;

    AP->Orig_Size=random(1.5f);
    AP->AnimSize=random(0.001f);
    AP->StartTime=WT;
    AP->LiveTime=(LT/2.5f)/10.f+random(((LT/2.5f)/10.f)*9.f);

    AP->Orig_LT=AP->LiveTime;
    AP->Light=AP->Orig_Light;
    AP->Pos=AP->Orig_Pos;
    AP->Size=AP->Orig_Size;
    AP->Dir=AP->Orig_Dir;
    AP->Alpha=200+random(rnd*4);
    AP->Grav_Power=0;//0.80f+random(0.04f);
    AP->Orig_AnimL=AP->AnimLight;

  break;
  default:return;
 }
}


int _fastcall G_CreateParticles(_Particles * MP,DWORD WorldTime,Vec
Pos,DWORD LiveTime,int ID,int Num,bool loop,float random, int tnum)//init
new particle_collision
{
 Part_Coll * PP=MP->Particles;

 for(int i=0;i<PARTICLE_COLLS;i++)
 {
  if(!PP->used)
  {
   MP->Num_particles++;
   PP->ID=ID;//type..maybe explosion...
   PP->loop=loop;
   if(Num>PARTICLES_PER_COLL)Num=PARTICLES_PER_COLL;
   PP->Num_parts=Num;
   PP->used=1;
   PP->glow=1;
   PP->add=1;
   PP->TNUM=tnum;//number of the texture in my texarray
   for(int i2=0;i2<PP->Num_parts;i2++)
   {
    CreateA_Particle((PP->Parts+i2),Pos,PP->ID,WorldTime,LiveTime,random);
   }
   return i;
  }
  PP++;
 }
 return -1;//full with running effects
}


as said nothin too innovative... nothing more to say... ask me if ya miss something or want a shot of that..I think there is non on the homepage

...enough for the first update... nexttime I'll add bones anim..

Alex





  • 07/05/2000 - fastculling
  • 05/15/2000 - parties&fpu fun
  • 04/03/2000 - Tech File Update
  • 02/11/2000 - Tech File Update
  • 01/14/2000 - Tech File Update
  • 06/28/1999 - Rendering Cities
  • 06/24/1999 - Bones Animation
  • 06/22/1999 - Introduction

  • This document may not be reproduced in any way without explicit permission from the author and flipCode. All Rights Reserved. Best viewed at a high resolution. The views expressed in this document are the views of the author and NOT neccesarily of anyone else associated with flipCode.

    [an error occurred while processing this directive]