Terrain Screenshot and Discussion

This is a discussion on Terrain Screenshot and Discussion within the Game Programming forums, part of the General Programming Boards category; Hey folks, Just posting a screenshot from the engine me and a buddy are working on. Currently we're tackling terrain ...

  1. #1
    Epo
    Epo is offline
    Registered User
    Join Date
    Jun 2003
    Posts
    361

    Terrain Screenshot and Discussion

    Hey folks,

    Just posting a screenshot from the engine me and a buddy are working on. Currently we're tackling terrain generation. I just wanted to say two big thanks to Bubba and Perspective who've helped out tons through this whole process (Perspective not as knowingly).

    I used the blending texture method from the article discussed here. I would've wanted to contribute this to the discussion, but since it's almost a year gone, and I know the stigma surrounding the revival of old threads, I'll post my two cents here (hopefully worthwhile pennies).

    If you look through the article, you'll notice that the water darkens to black at the lowest regions, which is a pretty cool effect, but not completely realistic. For the purpose of our engine, we're leaving water for a whole other day, and strictly leaving the terrain to be just that, terrain. So instead of a water texture, you can see below, I used a "stone"ish texture.

    Before I continue, just to note, when I say something "doesn't work well", or anything of the like, I mean for the purpose of the engine we're developing.

    Now, a little background on the blending, there are height regions for when each texture is used, and instead of cutting between textures as you move to the next region, this method blends the textures for a smooth, more natural look.

    Now, as for the darkening problem. As you get to the lower height values of a region, it uses less of the specified texture. This works well for every transition, save for the very lowest, because there is no texture to blend the bottom with, so it become black.

    Previously, I was cycling through the textures from the lowest region, to the highest region. But this left me at a very difficult point in knowing what has previously been blended. So, I changed my for loop to begin with the highest region, and blend if necessary, before moving down to the lower region.

    By doing this, I know exactly where I stand when it comes time to blend the lowest region. Here's a slightly modified version of Perspective's code:
    Code:
    //CYCLING THROUGH EACH PIXEL
    ...
    unsigned long Colour = 0;
    unsigned long B = 0;
    unsigned long G = 0;
    unsigned long R = 0;
    for(char I = (NUM_TEXTURES - 1); I >= 0; I = I - 1)
    {
    	float dT = TERRAIN_HEIGHT / NUM_TEXTURES;
    
    	float dY = Height - ((I + 1.0f) * dT - 1.0f);
    					
    	if(dY < 0.0f)
    		dY = dY * -1;
    
    	float Weight = (dT - dY) / dT;
    
    	if(Weight >= 0.0f)
    	{
    		if(I == 0)
    		{
    			if(B == 0 && G == 0 && R == 0)
    			{
    				Weight = 1.0f;
    			}
    		}
    
    		//Find the correct coordinates in our texture
    		unsigned long T = (unsigned long)(fX * (float)Widths[I]);
    		unsigned long U = (unsigned long)(fZ * (float)Heights[I]);
    
    		Colour = Data[I][U * Pitchs[I] / 4 + T];
    
    		B = B + (unsigned long) ((Colour        & 255) * Weight);
    		G = G + (unsigned long)(((Colour >> 8)  & 255) * Weight);
    		R = R + (unsigned long)(((Colour >> 16) & 255) * Weight);
    	}
    }
    ...
    //CONTINUE CYCLING
    I use B, G, and R to keep track of each channel. Now, since I start at the highest region (I == NUM_TEXTURES - 1), I know where I stand once I'm checking my very lowest region. If, at the lowest region (I == 0), B, G, and R are still 0, that means I have not blended any other region in yet because they don't apply. So I use all of the lowest region, without any blending. If they are not 0, then I know they already contain another texture, and the lowest texure should only be blended with the weighting that was assigned. That is done with the two if statements nested deep within the algorithm. This can be easily changed to cut off at a specific weight (I.e. If the Weight is less than 0.5f, set it to 0.5f), which will reduce how dark it gets.

    Hopefully that makes some sense. If it doesn't, and you haven't read Perspective's article (and this interests you), then read Perspective's article. It's top-quality, and the discussion surrounding it also generated some good ideas. If you have read the article, I guess I'm not too good at conveying my ideas.

    My code probably won't directly translate to other projects (though it is very similar in purpose, the style is a bit different), but if you read the article, I hope I managed to keep this concept clear enough.

    Thanks again to all that helped along the way. It's a good benchmark we've gotten to here, but definitely no time to stop. See you at the next milestone.

    The payoff from this is posted below (along with a closer-up of the stone).
    Attached Images Attached Images   
    Pentium 4 - 2.0GHz, 512MB RAM
    NVIDIA GeForce4 MX 440
    WinXP
    Visual Studio .Net 2003
    DX9 October 2004 Update (R.I.P. VC++ 6.0 Compatability)

  2. #2

    Join Date
    Jan 2006
    Posts
    28
    VERY nice. Looks like a professional 3D game mini-map

  3. #3
    Super Moderator VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,598
    Looks good epo. Really good. I can see the faint outline of the quads and I'm not sure why since it is blended. Very, very nice looking. You might want to add a detail texture for the up close portions but other than that it looks extremely good.

    Extremely impressed - it looks far better than my terrain system (that I used to have - key word 'used') ever did.

    Good work.

    Perhaps we could implement this now into some type of game eh? Just fog out the edges, perhaps implement Perspectives algo and your texture algo and you might have the makings of a decent RTS game.

    If you notice Medieval:Total War battlefields are not much larger than what you have there, and they don't look as good. Rome:Total War does, but it still looks very good for someone just tinkering with terrain.
    Last edited by VirtualAce; 01-04-2006 at 12:04 AM.

  4. #4
    Epo
    Epo is offline
    Registered User
    Join Date
    Jun 2003
    Posts
    361
    Thanks guys

    This is definitely the biggest programming related achievement of mine yet, and we're definitely pushing forwards. We just got a raw loader working (long overdue), which replaces our 24-bit heightmaps with simpler 8-bit ones. The 24-bits just seemed unnecessary and we can avoid some problems with this change.

    The goal is a more FPS Tank sort of deal, but we've still got a lot of work to go on the terrain. The key thing right now is getting the terrain as smooth and flexible as possible. What it's used for after shouldn't limit it's capabilities now...and it's capabilities shouldn't limit what it's used for...that's the goal anyways

    I believe the distinct quads in the terrain have to do with how steep they are. A more steep quad will cause the blending to change more drastically for the next one, since the height's is much different than the previous quad. On flatter portions of the terrain, the blending looks a lot smoother since the blending ratios aren't changing as quickly.

    My only thought is that the heightmap is 128x128. The textures are 1024x1024. So many portions of the texture use the same height value. If you consider starting at X = 0, and moving horizontally across the texture. Values X = 0 to X = 10 will all receive the same height value from the heightmap since I'm just rounding to the nearest vertex. However, if I interpolate a height value from the quad based on my X,Z coordinates in the texture (Working with Y going up), then I can get a more accurate height, and a more accurate blending value. Boy does it ever feel good when you find the answer to a question just because you were in the middle of asking someone else if they knew the answer? Least, this is what I'm hoping.

    Inbetween writing that and posting, I implemented it, and it's smooth now. I'll post a screenshot soon, just cause I'm so happy with myself (And I'll leave that part in there so you can understand what I fixed). It's hard to say, so if anyone feel's they can say it better, or want me to say it again, I will. But basically, I interpolated heights to find better blending values rather than just using the height of the closest vertex.

    Though the size of the terrain is fairly large (a 128x128 heightmap is used), we want to expand that to handle atleast a 1024x1024 heightmap. That's probably our next step, and we want to push it as far as storage limits will permit. The end result will be divided into 128x128 regions, in some sort of quadtree...not totally sure yet. Still doing a lot of research.

    Which leads to LOD algorithms. Perspective's method seems pretty fantastic, but the ROAM algorithm makes some pretty big claims as well. Pretty much the goal is to find the most efficient/best suited algorithm, regardless of complexity, and implement it.

    Of course, I have absolutely no idea where to go just yet, which is why it's a research/reading phase right now
    Last edited by Epo; 01-04-2006 at 10:47 PM.
    Pentium 4 - 2.0GHz, 512MB RAM
    NVIDIA GeForce4 MX 440
    WinXP
    Visual Studio .Net 2003
    DX9 October 2004 Update (R.I.P. VC++ 6.0 Compatability)

  5. #5
    Epo
    Epo is offline
    Registered User
    Join Date
    Jun 2003
    Posts
    361
    If code's your language:
    Code:
    bool cTerrain::CreateTexture()
    {
    	IDirect3DTexture9 *Textures[NUM_TEXTURES] = {NULL, NULL, NULL, NULL, NULL};
    	float Widths[NUM_TEXTURES] = {0, 0, 0, 0, 0};
    	float Heights[NUM_TEXTURES] = {0, 0, 0, 0, 0};
    	int Pitchs[NUM_TEXTURES] = {0, 0, 0, 0, 0};
    	unsigned long *Data[NUM_TEXTURES] = {NULL, NULL, NULL, NULL, NULL};
    
    	if(FAILED(D3DXCreateTexture(D3DDevice, 1024, 1024, 0, 0, D3DFMT_X8R8G8B8, D3DPOOL_MANAGED, &Texture)))
    		return false;
    	
    	if(FAILED(D3DXCreateTextureFromFile(D3DDevice, TEXTURE_WATER, &Textures[0])))
    		return false;
    
    	if(FAILED(D3DXCreateTextureFromFile(D3DDevice, TEXTURE_SAND, &Textures[1])))
    		return false;
    
    	if(FAILED(D3DXCreateTextureFromFile(D3DDevice, TEXTURE_GRASS, &Textures[2])))
    		return false;
    	
    	if(FAILED(D3DXCreateTextureFromFile(D3DDevice, TEXTURE_STONE, &Textures[3])))
    		return false;
    
    	if(FAILED(D3DXCreateTextureFromFile(D3DDevice, TEXTURE_SNOW, &Textures[4])))
    		return false;
    
    	for(unsigned char I = 0; I < NUM_TEXTURES; I++)
    	{
    		try
    		{
    			D3DSURFACE_DESC SampleDesc;	
    			D3DLOCKED_RECT SampleRect;
    
    			Textures[I]->GetLevelDesc(0, &SampleDesc);
    
    			Widths[I]	= (float)SampleDesc.Width;
    			Heights[I]	= (float)SampleDesc.Height;
    
    			Textures[I]->LockRect(0, &SampleRect, 0, 0);
    
    				Data[I]		= (unsigned long *)(SampleRect.pBits);
    				Pitchs[I]	= SampleRect.Pitch;
    
    			Textures[I]->UnlockRect(0);
    		}
    		catch(...)
    		{
    			return false;
    		}
    	}
    
    	D3DSURFACE_DESC TextureDesc;
    
    	Texture->GetLevelDesc(0, &TextureDesc);
    
    	if(TextureDesc.Format != D3DFMT_X8R8G8B8)
    		return false;
    
    	D3DLOCKED_RECT LockedRect;
    
    	Texture->LockRect(0, &LockedRect, 0, 0);
    
    		unsigned long *ImageData = (unsigned long *)(LockedRect.pBits);
    
    		for(unsigned long ZPos = 0; ZPos < TextureDesc.Height; ZPos++)
    		{
    			for(unsigned long XPos = 0; XPos < TextureDesc.Width; XPos++)
    			{
    				float fX = (float)XPos / (float)TextureDesc.Width;
    				float fZ = (float)ZPos / (float)TextureDesc.Height;
    
    				unsigned long X = (unsigned long)(fX * (float)NumCols);
    				unsigned long Z = (unsigned long)(fZ * (float)NumRows);
    
    				float Height = 0.0f;
    
    				float tA = Map[GetIndex(X    , Z    )].Y;
    				float tB = Map[GetIndex(X    , Z + 1)].Y;
    				float tC = Map[GetIndex(X + 1, Z    )].Y;
    				float tD = Map[GetIndex(X + 1, Z + 1)].Y;
    
    				float dX = ((float)fX * (float)NumCols) - ((float)X);
    				float dZ = ((float)fZ * (float)NumRows) - ((float)Z);
    
    				if(dX + dZ < 1.0f)
    				{
    					float uY = tB - tA;
    					float vY = tC - tA;
    
    					Height = tA + uY * dZ + vY * dX;
    				}
    				else
    				{
    					float uY = tC - tD;
    					float vY = tB - tD;
    
    					Height = tD + uY * (1.0f - dZ) + vY * (1.0f - dX);
    				}
    
    				unsigned long Colour = 0;
    				unsigned long B = 0;
    				unsigned long G = 0;
    				unsigned long R = 0;
    
    				for(char I = (NUM_TEXTURES - 1); I >= 0; I = I - 1)
    				{
    					float dT = TERRAIN_HEIGHT / NUM_TEXTURES;
    
    					float dY = Height - ((I + 1.0f) * dT - 1.0f);
    					
    					if(dY < 0.0f)
    						dY = dY * -1;
    
    					float Weight = (dT - dY) / dT;
    
    					if(Weight >= 0.0f)
    					{
    						if(I == 0)
    						{
    							if(B == 0 && G == 0 && R == 0)
    							{
    								Weight = 1.0f;
    							}
    						}
    
    						unsigned long T = (unsigned long)(fX * (float)Widths[I]);
    						unsigned long U = (unsigned long)(fZ * (float)Heights[I]);
    
    						Colour = Data[I][U * Pitchs[I] / 4 + T];
    
    						B = B + (unsigned long) ((Colour        & 255) * Weight);
    						G = G + (unsigned long)(((Colour >> 8)  & 255) * Weight);
    						R = R + (unsigned long)(((Colour >> 16) & 255) * Weight);
    					}
    				}
    
    				Colour = RGB(B, G, R);
    
    				ImageData[ZPos * LockedRect.Pitch / 4 + XPos] = Colour;
    			}
    		}
    
    	Texture->UnlockRect(0);
    
    	if(FAILED(D3DXFilterTexture(Texture, 0, 0, D3DX_DEFAULT)))
    		return false;
    
    	D3DDevice->SetTexture(0, Texture);
    
    	return true;
    }
    It's not commented. But. Well. If you have questions, ask away. It's roughly straightforward. There are a lot of ratio calculations going on in there though.
    Attached Images Attached Images  
    Last edited by Epo; 01-04-2006 at 10:47 PM. Reason: I'm picky
    Pentium 4 - 2.0GHz, 512MB RAM
    NVIDIA GeForce4 MX 440
    WinXP
    Visual Studio .Net 2003
    DX9 October 2004 Update (R.I.P. VC++ 6.0 Compatability)

  6. #6
    Super Moderator VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,598
    Ok for smoother terrain you may want to try the sphere's algorithm. It basically creates large blobs that disturb the terrain equidistant from the blob based on distance from center and height of center. Using this algo you can get extremely smooth terrains with a limited number of vertices. I have a screenshot of it on this board from about a month ago. Sadly my screenies and terrain experiment died with defrag, but I do remember what I did.

    The height map can be stored in the alpha section of your bitmap. Since the bitmap is blended by the hardware and alpha is set by you based on your texture blending algo, you can use alpha to store the 8 bits of height. When you load it, you can then scale this to alpha<<8 to gain more height values. Using the spheres method eliminates the need for a linear interpolation or bilinear interpolation. It creates smooth terrains from the word go.

    As far as quadtree good luck. I have yet to figure out how to effectively do this with the vast amounts of data that a terrain presents. And what happens to the view when it spans over a quadtree boundary? All these are fun to solve.

    Remember that you can make tanks and people and the camera fly over the terrain by using bilinear interpolation to get the height of the terrain at any point.

    Find the upper triangle and the lower triangle and find out which one your object is in and then use 2 linear interpolations to compute the height value. You can get the angle of the slope by taking the arctangent of y/x and thus you can angle the objects to match the angle of the terrain. You can also use slope values and set your object's angle to the arctangent of y/x, and if you notice, slope is just that -> change in y/change in x.

    And you thought good old y=mx+b would never come in handy.
    Once you get the triangle coords you can use this floating point bilinear interpolation. I've profiled this and on my system it runs much faster than using the C equivalent.

    You try it and see and compare framerates.

    Code:
    float BI(float v1,float v2,float v3,float v4,float ix,float iy)
    {
      float rvalue=0.0f;
      asm {
        
        //reserve room for 2 32-bit floats
        push ebp
        mov  ebp,esp
        sub esp,8
    
        //linear interp 1 value1=v1+f1*(v2-v1)
        fld v2
        fsub v1
        fmul f1
        fadd v1
        fstp [esp-4]
        
        //linear interp 2 value2=v3+f1*(v4-v3)
        fld v4
        fsub v3
        fmul f1
        fadd v3
        fstp [esp-8]
        
        //linear interp 3- finalvalue=value1+f2*(value2-value1)
        //esp - 8 = value1
        //esp -16 =value 2
        fld [esp-8]
        fsub [esp-4]
        fmul f2
        fadd [esp-4]
        fstp [rvalue]
    
        add esp,8
        pop ebp
    
      }
      return rvalue;
    }
    Note you could omit the rvalue and return since the float is already in st(0), however C will not understand that BI is supposed to return a float if you don't specify a return value, and if you do, it expects a return. Returning st(0) however may not be predictable even though theoretically nothing has happened in 'this' code between computation and return. However, it is possible the compiler may stick something between them that could alter the value of st(0) or it could reset the FPU in prep for it's next use. This is why I return it the way I do.

    This code should work but my assembly is a bit rusty. Fordy would know if the esp-4 and esp-8 are correct. Also I'm fairly sure you can load st(0) with a stack memory location - I couldn't see why it wouldn't be any different than just any memory location. Also I'm a bit leery of the prefix push ebp, mov ebp, esp because C will already stick this prefix on the code. But it is unsafe to assume that esp points to ebp upon entry into the asm block.

    If you disassemble this and find that there are 2 prefixes (2 sections of push ebp; mov ebp,esp then delete the one in the asm block and remove the pop ebp at the end.

    Profile and test. If it's not faster then by all means, discard it.
    The compiler also may add a pushf and popf as well as an ocode to preserve all the registers before the function and restore them after. I wouldn't remove these as they store the state of the application prior to the function executing. Since we have no way of knowing what's in the registers and the compiler doesn't either, prior to an asm block the assembler/compiler will preserve the registers on the stack and restore them just before returning.
    This takes CPU cycles, but in your case it shouldn't matter too much.

    The sky can be done with a skybox.

    Code:
    #define LEFT       0
    #define TOP        1
    #define RIGHT     2 
    #define BOTTOM 3
    #define FRONT    4
    #define BACK      5
    
    
    class CSkybox
    {
      IDirect3DTexture9 *m_pLeft;
      IDirect3DTexture9 *m_pTop;
      IDirect3DTexture9 *m_pRight;
      IDirect3DTexture9 *m_pBottom;
      IDirect3DTexture9 *m_pFront;
      IDirect3DTexture9 *m_pBack;
    
      ....
    };
    When you render the skybox, translate it the same distance as the camera. This will cause the skybox to never get closer or farther from the camera. No matter what the terrain does, the skybox stays the same.

    For a good utility to aid you in rendering skyboxes get Skypaint.
    It does have a nasty logo it sticks in the left view, but you can edit it out and still use the program. Google it.

    If you want to use an array of IDirect3DTexture9 objects feel free.
    Last edited by VirtualAce; 01-05-2006 at 02:31 AM.

  7. #7
    Epo
    Epo is offline
    Registered User
    Join Date
    Jun 2003
    Posts
    361
    Sorry bout the late reply, between school and work, I haven't had much free time...

    The sphere's algorithm is an interesting one, I remember coming across it back when I was first searching terrain creation. And I do remember even coming across screenshots from your engine. It's a very nice technique and definitely worth looking into. The only thing is that it's a little more difficult to visualize how the terrain will look from a heightmap. Perhaps eventually we will implement another algorithm, for now we'll most likely end up sticking to our heightmap matched with a box filter to smooth things out.

    To generate the heightmap, I just take the average of the surrounding 48 squares, plus the square itself, to find the new height. It's working pretty well, though I am getting one portion of the map which is slanted in a way I don't think should be possible. But, well, it's happening. But only in that one part, so I'm figuring it's just how the data is.

    I've been doing tons and tons of drawings on paper in hopes to find a way to get this quadtree going. So far, nothing. The problems you described are biggies, and, well, I don't know how to get around those...yet. I did some reading on Cells and Portals, I'll admit it left me a little confused, though I do think I got the general idea. Perhaps before all of that I'll stick to making just our one cell more efficient. I upped the max height for mountains to 10x the previous value, and tile widths are roughly 5x large. This definitely leaves the terrain large enough for the time being, with a frame rate that's yet to drop below 74fps.

    I haven't actually learned assembly yet (starting this term on the Motorolla 68K, so I kind of understand what you posted, though it's a different processor), but I'll give your bilinear interpolation code a shot and see what it does for the speed of the game. Just by the looks of it, it should probably speed it up. With everything we want this to be doing by the end, a speed gain anywhere will be helpful.

    And thanks for the tips on the skybox. We're going to try and implement one soon. With the skybox, should the fact that it's so far away make up for the obvious loss of detail that happens with textures stretched across the ground?

    As for LOD, I believe I'll be following Perspective's path and Roettger's algorithm. After reading a few papers on ROAM a few times, I don't know if I'll be able to pull it off. Perhaps some additional culling will be in order first. I'm not sure yet. It's a very uncertain time right now as you can see. No clue where to go next. Well. Not totally. I'll be back when I have something a bit more meaningful to say...
    Pentium 4 - 2.0GHz, 512MB RAM
    NVIDIA GeForce4 MX 440
    WinXP
    Visual Studio .Net 2003
    DX9 October 2004 Update (R.I.P. VC++ 6.0 Compatability)

  8. #8
    Super Moderator VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,598
    Well if you cannot get the quad tree going then you could use D3DX to split the mesh into smaller meshes and just load them later. It's not as elegant, but it still gets the job done.

    There is a way to do it with raycasting to figure out the frustrum.
    This is only a 2D raycast but it might work.


    Code:
    float left=0.0f;
    float right=0.0f;
    float front=0.0f;
    float back=0.0f;
    
    float LeftAngle=Player.ViewAngle-(Player.ViewConeSize*.5f);
    float RightAngle=LeftAngle+Player.ViewConeSize;
    
    left=Player.x-(cosf(LeftAngle)*Player.ViewDistance);
    right=Player.x+(cosf(RightAngle)*Player.ViewDistance);
    
    front=Player.z+(sinf(Player.ViewAngle)*Player.ViewDistance);
    back=Player.z-(sinf(Player.ViewAngle)*Player.ViewDistance);
    
    
    DWORD dwNumVerts=FindVerticesInArea(left,front,right,back,&RenderVerts);
    
    ...
    The code for FindVerticesInArea() could use a quadtree to find the vertices that are in the view frustrum.
    Last edited by VirtualAce; 01-08-2006 at 02:00 PM.

  9. #9
    Epo
    Epo is offline
    Registered User
    Join Date
    Jun 2003
    Posts
    361
    The code for FindVerticesInArea() could use a quadtree to find the vertices that are in the view frustrum.
    I believe this is the root of all my "problems" right now, and the reason I'm having so much trouble moving forwards. All these LOD, culling, fancy techniques I do all the reading about mentions quad-trees or oct-trees, and any other kind of tree imagineable, but I just can't find the starting point for "how" my vertices should be stored.

    The rendering/culling/etc. can all be figured out, but right now, I just have all my vertices stored in the current format:
    Code:
    //One Vertex
    struct VERTEX
    {
        float	X, Y, Z;
        float	TU, TV;
    };
    
    VERTEX *Map;
    
    Map = (VERTEX *)malloc(NumVertices * sizeof(VERTEX));
    And then the data in Map is filled. I've known for a while now that this would have to change, but I've done my best to do everything else so that I wouldn't spend time on creating a quadtree, only to find the way I created it isn't compatible with how things need to run.

    Do you have any suggestions as to the "format" of the tree? I was thinking that the root node would just encompass the dimensions of the entire terrain, then it would be split up recursively, keeping track each region boundaries, and then store individual vertices in the leaves. If for no other reason, applying any algorithms and optimization will be much easier on this sort of setup than my current one. This was the idea I had the whole time along, but like I said, I was hoping to find some article which confirmed it, but I didn't. Does it seem like a good starting point? Or do you know if there's some obvious inherent error in this that I don't know about?
    Pentium 4 - 2.0GHz, 512MB RAM
    NVIDIA GeForce4 MX 440
    WinXP
    Visual Studio .Net 2003
    DX9 October 2004 Update (R.I.P. VC++ 6.0 Compatability)

  10. #10
    Epo
    Epo is offline
    Registered User
    Join Date
    Jun 2003
    Posts
    361
    No no no, that won't do (referring to my last post)

    I forgot about the index buffer. So, here's my plan:

    - My vertices will stay in the same format they are
    - The quadtree will store the indices for my IndexBuffer
    - At each level, I will store 4 indices of the largest square at that level
    - So, the Root would store the four corners of my entire terrain
    - Each level beneath will store the four indices of the next largest squares
    - I will not store individual indices, because I will never render just one vertex. The smallest level will store one "tile" of the heightmap.
    - Not only will this save space since I'm storing ints instead of floats, but the fact that I'm not storing individual vertices/indices makes the quadtree one level shallower.

    So, the root would be extremely poor quality, going down to the leaves which are the highest quality. The problem right now that I see is that if I render a scene using different depths for different regions, cracks could appear between by terrain.

    This is something that I'll have to solve eventually, but for now I think this is a decent place to start...
    Pentium 4 - 2.0GHz, 512MB RAM
    NVIDIA GeForce4 MX 440
    WinXP
    Visual Studio .Net 2003
    DX9 October 2004 Update (R.I.P. VC++ 6.0 Compatability)

  11. #11
    Super Moderator VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,598
    Cracks and T intersections are the things you must counter when working with LOD terrain. These things are a pain and are complex and hard to get rid of completely. Good luck.

  12. #12
    Epo
    Epo is offline
    Registered User
    Join Date
    Jun 2003
    Posts
    361
    Thanks

    And thanks for all the tips and help so far (again). There're enough topics and readings in this thread to keep me busy for a long while.
    Pentium 4 - 2.0GHz, 512MB RAM
    NVIDIA GeForce4 MX 440
    WinXP
    Visual Studio .Net 2003
    DX9 October 2004 Update (R.I.P. VC++ 6.0 Compatability)

  13. #13
    Crazy Fool Perspective's Avatar
    Join Date
    Jan 2003
    Location
    Canada
    Posts
    2,640
    Your terrain is looking good! I'm glad my article could be of use to someone. If you jump to a 1024x1024 height field your going to need some kind of culling at minimum to run at a decent speed. I suggest adding a LOD system, you'd be surprised how few of those millions of triangles are actually visible because of their distance from the camera.

    I'm going to post the source for my Continous LOD Terrain engine once I get a little time to fix everything up and add some documentation and a driver program that demonstrates usage. It implements Rottgers Algorithm. I chose Roettgers algorithm over ROAM becuase the nature of the algorithm lends itself to other parts of game engine design (namely the use of quadtrees in the algorithm give you things like frustum culling for free). Maybe I'll write an article about it too, though I don't think I'll really have the time.

  14. #14
    Epo
    Epo is offline
    Registered User
    Join Date
    Jun 2003
    Posts
    361
    Thanks Perspective And it was a gigantic help. And seeing the screenshots of your engine definitely pushed me to try and make something just as awesome (which I'm still working towards).

    I've been tossing around alot of algorithms and approaches in my head for the last while. And I've actually decided to go with the quadtree approach as well. I've got it all planned out in my brain, but, well, that's a long ways away from working code. We have decided to follow in Rottgers as well, namely from how impressive your terrain looks, and as you mentioned, the natural fit of a quadtree structure.

    An article would definitely benefit the programming community. There are many articles out there, but they're either becoming out of date, or just rely on regurgitating the information a person's found in the previous five articles they've searched. Even though I've only dipped my pinky toe into the whole thing so far, I can see how it would take a big time investment though.

    Right now the engine we're working on is going to encounter a massive re-write. Better handling of classes, a better physics engine, and a complete reconstruction of the terrain engine's in order. Many unexpected...uh...features have been cropping up with the engine lately, mainly with the player class, but considering the next steps, a re-write's in order anyways.

    Hopefully a smoother, more efficient beast will be born once we're done. And if not, well that's why people back up files

    Thanks again for your help. It's been very much appreciated.
    Pentium 4 - 2.0GHz, 512MB RAM
    NVIDIA GeForce4 MX 440
    WinXP
    Visual Studio .Net 2003
    DX9 October 2004 Update (R.I.P. VC++ 6.0 Compatability)

  15. #15
    Super Moderator VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,598
    I just read an article on www.moon-labs.com about terrain texturing. You may be interested in it. You can use a blend-map to figure out which parts of which texture appear. This would give you a finer control over the appearance of your map.

    Also you can generate a normal map to do the lighting and then simply look up the normal in the map to compute the lighting.

    If my card supported VS 2.0 and PS 2.0 I would try it.

    Incidentally you can do this using the fixed function pipeline in DX.

    You can kill that code that mixes the textures and pre-mix it using the FFP prior to setting the texture for the Device.

    Most cards will support up to 3 or 4 textures so you should be able to mix in grass, rocks, dirt, and snow quite easily to produce a good scene.

    Remember the tiling problem we both were having? But also remember the detail problem? That article should help you overcome both. I'm eagerly awaiting the release of Frank's next book.
    Last edited by VirtualAce; 01-16-2006 at 02:05 AM.

Page 1 of 2 12 LastLast
Popular pages Recent additions subscribe to a feed

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21