I wanted another version of Planitia released Sunday; I need to stop pushing these things. It should be this weekend.
Back to Work
Um…not work-work, just work on Planitia. I’ll be going back to work-work soon, though.
Now, let’s get back to the feedback!
First, I’d like to thank everyone who sent feedback:
From the comments: Ryan, Keith Weatherby II, bingbong, Delve, SteelGolem, WonderMellon and sol_hsa
From GameDev.Net: Ravuya, Matrin, Cranky, kudi, Skeezix, ZippoLag, nordwindranger, polyfrag, Ezbez, DarkInsanePyro and cannonicus
From RetroRemakes.com: Dragon
From the Gibbage forums: bignobodski, Dan, delve and Simon Archer
From direct email: Ken
All right, now let’s get to what you guys actually said!
The most common feedback I got was “Holy crap it’srunning too fast my guys all flew off why’d they do that now the computer is here destroying my villages and now I’m dead.” So I will be slowing down the overall gameplay, for starters.
Lots of people said that clicking the minimap to jump the camera around didn’t work consistently. This is due to the fact that it won’t work if you’ve got a spell on your cursor. I’ll get this fixed.
One person (Dragon) said that he had trouble clicking the minimap to jump the camera around because he kept accidentally band-selecting. Band selection isn’t even supposed to be there any more since the introduction of the General. I’ll be taking it out.
The most common strategy for human victory seemed to be either lightning-bolting the enemy general to death (which wasn’t supposed to happen, the general should be invulnerable) or quickly lightning-bolting the enemy village to death (which you can only do because I started players with full mana, which won’t be in the final game).
Some players liked to fortify themselves and then use a combination of god powers and a large army to destroy the computer’s army, then push on the computer’s cities. This is closer to how Planitia is “supposed” to be played, but one problem is that the computer’s general just sits in the player’s base, pumping out units as often as it can. Because of this, I’m starting to think that the General should die when all units under his command are dead (just like villages die when all their villagers are dead). You won’t start the game with a general, he will appear near your main city as soon as you create a military unit. If all your military units die, he disappears, but he’ll reappear (again, back at your cities) when you create some more. This way neither you nor the computer will be able to camp inside the enemy’s base like that.
Units are supposed to drown a few seconds after they fall into deep water. If you’re quick with the terrain tool you can save them. I’ll get this in soon.
Simon Archer and many others asked for more animation on the sprites so players can tell what is going on better. I’d love, love, love to but I Are No Artist. I’ve been looking for a sprite sheet that includes eight-way animations – something like this, only with run, walk and fight animations. So far, nothing. Other than Final Fantasy Tactics stuff, yes, I know. I don’t want to use those because they are too recognizable. And The sprites I ripped out of Powermonger are just too primitive-looking when applied to 3D quads.
I had a couple people state that the multiple tabs were confusing, forcing them to either use god powers or the army or both. One person suggested consolidating the UI onto one tab, which…might happen.
A couple people mentioned that it’s hard to tell when the terrain is actually flat. I was going to have flat cells draw with a different texture so you could tell…I probably will, but I don’t have an appropriate texture at the moment.
So I’ve got a bit of work to do. I’m hoping to have a new version for you guys to try out Sunday.
Planitia Playable Beta Version .73 NOW AVAILABLE!
All right, here it is. Click here to download the beta.
Ya’ll be gentle with her, now. She’s sensitive.
Readme follows:
This is a work-in-progress demo of my game Planitia.
Current version is .73.
NOTE: IF YOU GET A MESSAGE SAYING THAT D3D9_**.DLL IS MISSING THEN YOU NEED TO UPDATE YOUR DIRECTX.
Planitia is a real-time strategy game that allows you to both build an army and use god powers to crush your enemies.
In this demo you play Green. In the main window you will see the game world, your general (the large guy with the green cape) and your village.
———————
BASIC CAMERA CONTROLS
———————
Planitia is meant to be played with one hand on the mouse and one hand on the WASD cluster on your keyboard. (Sorry left-handers – I’ll get controls in for you soon!) You use the mouse to interact with the interface. You can also move the camera by pushing the mouse to an edge of the screen, but it’s much easier to move the camera with the WASD cluster. You can rotate the camera using the Q and E keys. You can also click on the minimap to jump the camera to that location.
———-
GOD POWERS
———-
On the right side you will see a GUI display with a minimap. The blue bar below the minimap is your mana. Below that are buttons for the god powers. Only some of the god powers work now, inactive god powers have their buttons greyed out. The working god powers are:
Flatten Land (looks like up/down arrows): Costs 3 mana per second.
Allows you to raise or lower land to village height. Click and hold in the world window on any terrain to affect it. Flattening the land around your village will allow it to grow, and the more villagers you have the faster your mana will regenerate. Villages only grow at certain populations, so your village may not grow immediately even if you’ve flattened the land properly. Just be patient. You may also need to use this tool to create land bridges so that you can attack your enemy.
Earthquake (looks like concentric circles): Costs 25 mana.
Drops an earthquake wherever you click in the world window. Be careful not to cast it on your own village. The earthquake prevents enemy villages from growing and forces their gods to use more mana fixing what you’ve done.
Lightning Bolt (looks like…um…a lightning bolt): Costs 10 mana.
Casts a lightning bolt wherever you click. The lightning bolt damages units and throws them into the air. You can even knock them off the game world this way.
All god powers have a shared 1.5 second cooldown.
————–
MILITARY UNITS
————–
You can left-click on your general to select him and move him by right-clicking on the terrain. You cannot select your villagers.
If you click on the second tab on the GUI (looks like a red general) you will see the military buttons. You will see buttons for archers, barbarians and warriors, along with a display of how many you currently have of each.
Clicking the archer, barbarian or warrior buttons converts a villager into a military unit of that type and adds it to your army. Your army will always follow your general so you don’t have to worry about controlling units individually. You can attack enemy armies or villages simply by moving near them – once your units get within combat range they will attack automatically.
One thing to keep in mind is that once you convert a villager to a military unit it no longer gives you mana.
——-
OPTIONS
——-
If you click on the third tab on the GUI (which is currently blank) you’ll see the exit button. You can also exit the demo by pressing ESC.
If you wish to give me feedback on this demo, you can do so at anthony.salter@gmail.com, or comment on my blog at www.viridiangames.com.
Copyright 2006, 2007, 2008 by Anthony Salter. All rights reserved.
Well, Poop.
I had the beta ready to go (though it’s not as complete as I wanted it), but when I put together the package some release-only bugs popped up – and I mean “only happens in release mode when you’re not running in the debugger” bugs. So I will be delaying the release of the Planitia beta until Monday night. Sorry about that.
Quick Planitia Update
Okay, I said there would be a demo by the end of January and now it’s February 1st. Events have conspired (like they always do when I set a release date) to make it more difficult to work on Planitia that usual.
The demo will be out on Sunday, February 3. Just give me this weekend and you’ll finally get to play it.
Let’s Play Planitia!
With apologies to DeceasedCrab 🙂
As always, you can download a higher-quality version of the video here.
Planitia Update 27: I CAN HAS GAME?!
When I started working on Planitia full-bore again after the holidays were over I mentioned that I’m going to release a new beta by the end of January. I want this beta to have actual gameplay in it, and for that I need three things.
* I need to get the villages spawning new villages. They’ve been expanding for months, but once they hit the pop cap they are supposed to spawn another village nearby.
* I need to put combat back in. I ripped it out for debugging purposes – and I know it’s at least partially broken. That needs to be reactivated and debugged.
* I need to get more god powers implemented. Right now the only two that do exactly what they are supposed to are Flatten and Lightning Bolt.
I got the first two requirements done over the weekend and an amazing thing happened…
It’s a game now.
It’s got a definite beginning, requirements for success, and those requirements can be fulfilled – the game can even tell when you (or someone else) has won. The first time I crushed the AI player and had the game actually feed back to me that I’d won…well, that was a great moment for me personally.
So finally, fourteen months after I started this project (and eight months after it was supposed to have been finished), Planitia is a playable game! It’s not a very good game, but I wasn’t expecting it to be – this game is a perfect candidate for the iterative game design process.
And this means I still have two weeks to polish it up and add features before I post it. I’ve even started adding – GASP! – sound!
Surprise!
Well, as some people guessed, the surprise is a new trailer for Planitia!
This is the in-game cinematic that introduces the first mission in the story mode of the game. I’m very proud of this because it’s completely script-driven – all I did to produce this video was turn on Fraps.
This video is a bit “heavier” than my first trailer, but it’s not really indicative of the feeling of the game overall. I do want to keep the game light and funny, but I also wanted to make it very clear why you, the player, are summoned to this world.
As usual, if you have trouble with the above video (or you just want a better version) you can download the trailer here.
Please feel free to tell me what you think!
Practical Direct3D Programming
Overview
First, let’s talk about what was actually necessary for Planitia.
Planitia is a 3D real-time-strategy game, played from a 3/4 perspective. The terrain of the game world is a heightfield and a second heightfield is used to represent water. Units are presented as billboarded sprites (simply because I had no animated models I could use). Other game objects like the meteor are true meshes. So the Planitia engine needed to be able to render all of these at a minimum.
Planitia’s design presented some interesting challenges because the terrain of the entire map is deformable. The player (as a god) can raise and lower terrain to make it more suitable for villagers to live on. Earthquakes and volcanoes can also deform the terrain at just about any moment of play. Thus, it was necessary for the game to constantly check to see if the game world had significantly changed and regenerate the Direct3D data if it had.
Initializing Direct3D
Since this was my first Direct3D project, I deliberately limited the number of technologies that I was going to use. I decided that I would not use any vertex or pixel shaders since I didn’t want to start learning them until I felt I was familiar enough with fixed-function Direct3D. I also wanted to make the game friendly to older hardware and laptops.
To this end, I don’t do a lot of capability checks when I initialize Direct3D. But one check that I did find useful was the check for hardware vertex processing. If that capability check fails, it’s a pretty good indicator of older/laptop hardware and I actually make some changes about how the terrain is rendered based on it (that I will detail in a bit).
Vertex Structure and FVF
My vertex structure is as follows:
class Vertex { public: Vertex(); Vertex(float x, float y, float z, DWORD color, float u, float v, float u2 = 0, float v2 = 0); float _x, _y, _z; DWORD _color; float _u, _v; float _u2, _v2; };
And my FVF:
DWORD FVF = D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX2;
Notice that there are no normals. I’m using baked lightning for Planitia (as described in Frank Luna’s book – indeed, I used his code) and thus normals aren’t necessary. I am using two sets of UV coordinates because I “paint” various effects on top of the normal grass for the terrain (more on that in a minute).
Division of Labor – Creating the Index and Vertex Buffers
Okay, so what exactly is a Planitia map?
A Planitia map consists of a 64×64 grid of terrain cells. Thus, it must be drawn with 65×65 vertices. Each map has a heightfield of 65×65 values, as well as a 64×64 array of “terrain types”. Terrain types are identifiers I created that basically record what kind of terrain is in the cell. Values in the heightfield range from 0 to 8. If all four corners of a cell have a height of .2 or less, that cell is underwater has terrain type TT_WATER. If one corner of the cell is .2 but others are higher then the terrain type is TT_BEACH. Otherwise the terrain cell is TT_GRASS. Other terrain types like lava, flowers, ruined land and swamps are drawn over grass terrain and have their own terrain types.
And here’s my first fast/slow split. If I detect that hardware vertex processing is available, then each cell consists of five vertices – one each for the corners and one for the center. Drawing a terrain cell requires drawing four triangles.
If hardware vertex processing is not available, then I only use four vertices for each cell and only draw two triangles.
I set the UV coordinates across the entire terrain to the X/Y position of vertex in question. Thus the UV coordinates of vertex (0, 0) are (0, 0), the UV coordinates of (0, 1) are (0, 1), etc. This allows textures to tile properly while also giving me access to a few tricks (which I will get to in a minute). You’ll notice that this means that I’m not specifying what texture to draw with the UV coordinates – I do not have all my terrain textures on one big tile. That’s a good technique but I couldn’t use it for Planitia.
The diffuse color of each vertex actually stores two different sets of information. The RGB values are combined with the grass texture based on the lighting for that particular cell (again, using the pre-baked lighting code from Frank Luna’s book, page 224). The alpha value isn’t used for lighting. It’s actually used to create the beach effect, where sand blends evenly into grass. There’s more information on how this works in the Rendering section.
I actually create eight vertex buffers – one for each terrain type. Each vertex buffer contains data about the geometry of the terrain mesh and the shading of the terrain, but doesn’t contain any data about what texture to draw or how the vertices form into triangles.
Once the vertex buffers are done, I create index buffers to sort the vertices into triangles. Again, there’s an index buffer for every terrain type. And again, if hardware vertex processing is supported I create four triangles per quad; otherwise I only create two…but I use a technique called triangle flipping.
Triangle Flipping
Here’s how it works: for every cell in the terrain that you create, you test its upper-left corner against the upper-left corner of three other cells – the one diagonally up-left from the target cell, the one to the left of the target cell, and the one above the target cell.
If the difference between the target cell and the one to the upper-left is higher than the cell to the left and the one above, we flip the cell by specifying a different set of vertices to draw than the standard.
If you didn’t completely understand that, that’s okay. Here’s the code.
float diffA = abs(GetValue(x, y) - GetValue(x - 1, y - 1)); float diffB = abs(GetValue(x, y - 1) - GetValue(x - 1, y)); bool triFlip = diffA > diffB;
If triFlip is false, we create the triangles normally.
If the test is true, we create the triangles like this instead:
The results are pretty impressive. Here’s Planitia with two-quads-per-triangle without triangle flipping:
Notice all the jagged edges. When we use triangle flipping, they go away:
That’s much better – it gets rid of the spikes – but now we’ve got lots of straight lines and the coast looks a bit boring. Using a center point on our quads looks even better:
Now it looks smooth and interesting. Which is why I do that when the hardware supports it.
Drawing The Scene
All right, the vertex and index buffers are created and it’s time to actually draw the terrain. Here’s the procedure I use.
The first thing I do is to turn alpha blending off. Then I draw all eight of my vertex buffers. I set the texture to be drawn based on the terrain type I am drawing (this is why data about what texture to draw isn’t stored anywhere in the vertex or index buffers). If the terrain type is “water” or “beach”, I set the sand texture and draw it. If it’s anything else, I set the grass texture and draw it. The result:
Time to do some blending. I turn alpha blending back on and set the grass texture as the active texture, and then I redraw the vertex buffer for the beach. Since blending is on, the grass is drawn fading out over the sand, resulting in a sand-to-grass effect. Now it looks like this:
This technique is called multipass multitexturing. Instead of…
Oh, good grief…
Must…resist…
Can’t…stop…
There. Got it out of my system.
Instead of using multiple sets of UV coordinates and setting multiple textures at once, you draw the same geometry twice with different textures. The upside of this is that it’s easy to do and very hardware-compatible. The downside is that you are drawing more polygons than you technically need to, but if you’ve got a good visibility test (which we’ll get to in a minute) it shouldn’t be a problem.
Alpha Masking
This is the one thing in Planitia that I’m proudest of (well, along with the water).
The other terrain types – lava, flowers, ruined land and swamp – are all drawn over grass and are masked so that the grass shows through. This is why I already drew these once with grass set as the active texture. But I’m using an additional trick here. These textures won’t get their alpha information from the vertices and they don’t have any alpha information of their own. They get their alpha information from another set of textures altogether.
You see, practically any grass terrain cell can be turned into any of the other four types at practically any time during the game. If I simply draw the cell with the new texture, I get big chunks of new terrain on top of the grass:
I can alter the textures so that they fade out at the edges, but that still gets me soft tiles of terrain lined up in neat columns and rows.
What I really needed was for tiles that were next to each other to sort of glom together…and be able to do so no matter how they were configured.
Hmmm…
And then I remembered that I’d seen this problem already solved in Ultima VI! The slimes in that game would divide if you hurt them without killing them, but instead of making smaller slimes they’d make one big mass of connected slime. So I grabbed the Ultima VI tiles to take a look at how the Origin guys had done it.
Turns out that they had done it by disallowing diagonal connections, thus reducing the number of connection possibilities from 256 to 16, and then they had drawn custom tiles for each connection permutation. This would still look better than either of the previous two solutions.
So I fired up Photoshop and created an alpha mask texture based on the slime texture.
The thing was…I didn’t just want to burn this filter onto each of my terrain type textures. I had a couple reasons for this. First, it would make the terrain type textures very specialized. Second, I’d have to make them much bigger to handle the sixteen permutations. And third, it would mean I wouldn’t be able to make my lava move by altering its UV coordinates (more on that in a second).
So what I needed to do was to set two textures – the mask texture and whatever texture I was drawing with. I needed to tell Direct3D to take the alpha information from the mask texture and the color information from the other texture.
I’ve tried to keep this article code-light, but this was tricksy enough that I want to go ahead and post the complete code. So here it is!
First we set our lava texture to be texture 0 and our masking texture to be texture 1.
gp_Display->m_D3DDevice->SetTexture(0, m_LavaTexture->m_Bitmap); gp_Display->m_D3DDevice->SetTexture(1, m_MaskTexture->m_Bitmap);
In the first texture stage state, we select both our alpha value and our color value to come from texture 0 (the lava texture). Note that I am modulating the color value with a texture factor – I’ll talk a bit more about that in a minute.
gp_Display->m_D3DDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); gp_Display->m_D3DDevice->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); gp_Display->m_D3DDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE); gp_Display->m_D3DDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE); gp_Display->m_D3DDevice->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_TFACTOR);
In the second stage we simply select the color value we already had (meaning the lava value) but we overwrite the previous alpha value with the alpha value from texture 1, which is the mask texture.
gp_Display->m_D3DDevice->SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_SELECTARG1); gp_Display->m_D3DDevice->SetTextureStageState(1, D3DTSS_COLORARG1, D3DTA_CURRENT); gp_Display->m_D3DDevice->SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); gp_Display->m_D3DDevice->SetTextureStageState(1, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
The end result was that I could use one mask plus four terrain textures to get four terrain types that stuck together no matter how they were positioned.
UV Transformation
The ruin, flowers and slime terrain types are all drawn the same way in the manner I just described, but I did a little extra work on the lava to make it look better.
First, I turn off the diffuse color when I draw the lava using the following render states:
gp_Display->m_D3DDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE); gp_Display->m_D3DDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
This means that the lava is always drawn fullbright and isn’t affected by the baked-in lighting. This makes the lava seem to glow with its own light.
I enhanced this effect by using the texture factor. This is simply an arbitrary number that you can set and then multiply the texture color by. I alter it on a per-frame basis to make the lava brighten and darken, thus looking like it’s glowing. Again, this is simply a render state that you set.
gp_Display->m_D3DDevice->SetRenderState(D3DRS_TEXTUREFACTOR, D3DCOLOR_XRGB(redvalue, greenvalue, bluevalue));
And finally I use a UV transformation to offset the lava’s UV coordinates over time, causing the lava to look like it’s flowing. A UV transform is just what it sounds like – it’s a matrix that the UV coordinates are multiplied by before they are applied.
Now, warning warning danger Will Robinson. Whenever a Direct3D programmer starts using this feature for the first time they almost always get confused. They typically try (just like I did) to create a transformation matrix using D3DXMatrixTransformation() or D3DXMatrixTransformation2D() and they end up (just like I did) with a very strange problem – for some reason, scaling and rotation seem to work just fine but translation does not.
That’s because the UV transformation matrix is a two-dimensional transformation matrix and two-dimensional transformation matrices are 3×3 matrices, not 4×4. The scaling and rotation numbers are in the same place in both, but the translation information is on line 3 of the 3×3 instead of on line 4 like the 4×4. This is why scaling and rotation work but translation does not. Put your translation values into the _31 and _32 values in your D3DXMATRIX structure and it’ll work fine.
(Now you may be asking, “Why doesn’t D3DXMatrixTransformation2D() produce a 3×3 matrix?” Good question. I have no idea why, but it doesn’t.)
Here’s the result:
All of these little tricks were suggested to me by Ryan Clark. Except the alpha masking, which is the one thing I came up with on my own which is why I’m ridiculously proud of it.
A Good Raypicker Is A 3D Programmer’s Best Friend
You can’t really write a 3D game without a raypicker, and this is where I’m going to ding Frank Luna a few points. While he does present the concept behind raypicking and some of the math behind it, in the end he cops out and does a line/sphere test once the ray has been transformed into world space. This is accurate enough for picking objects in a 3D world, but it’s not accurate enough to pick polygons within an object, and that’s exactly what I needed. I needed to be able to tell exactly in what triangle (or at least what cell) on the terrain the user clicked.
I made some manual improvements to the raypicker but it never seemed great. So I used a little google-fu and came up with…well, it’s pretty much the perfect ray-triangle intersection test. C source is included, which I was able to drop into my code pretty much unaltered and I was amazed at how much better it worked without any discernable performance hit. Get it, use it, love it.
(Not) Seeing the Unseen
“But why?” you may ask. “Sure, raypicking involves some 3D math, but it doesn’t involve 3D rendering, now does it?”
Actually, it does, because you can use a raypicker to find out which parts of your world are visible and which aren’t, and only draw the visible parts.
Which means that when I talked about how I fill out the index buffers above, I left out a step. Sorry, but it’s a big step and deserves a section of its own.
I think the most important thing I learned on this project was just how slow drawing triangles is. It’s slow. It’s dog-slow. It’s slow as Christmas. Slow as molasses flowing uphill in January.
When I first started programming I thought that a Planitia map would be small enough that I wouldn’t have to do any visibility testing. But it turns out that you can test your entire game world for visibility and compile a list of the visible triangles in less time than it takes to just draw the whole world. Even if your world is just a little 64×64 heightfield and some billboarded sprites.
That’s how slow drawing triangles is.
In case I haven’t made my point, drawing triangles is damn slow and you should only do it as a last resort. It’s so bad that actually having to draw a triangle should almost be seen as a failure case. Your code should not be gleefully throwing triangles at the hardware willy-nilly. Indeed, it should do so grudgingly, after forms have been filled out in triplicate. And duly notarized.
“Enough!” I hear you cry. “We get it! Drawing triangles is slow! Now would you please tell us how you did your visibility testing?”
Oh, right, the visibility testing. Well, there are actually two techniques I use.
The first is a simple distance test from the center of each cell to the camera’s look-at point. If the distance is larger than 25 (an arbitrary number I arrived at through experimentation) the cell cannot possibly be visible. This very quickly excludes most of the terrain on the first pass. There are 4096 terrain cells in a Planitia map; this first pass will let no more than 1964 (25 * 25 * pi) of them through.
In this video I have drawn the camera back so that you can see the circle of passing cells that moves as the camera does.
Now, that’s good, but it’s not good enough. Typically fewer than five hundred cells are actually visible and the circle test still has us drawing almost four times as many. So all the cells that passed the first test now go to the second test, which involves the raypicking code. Actually, it involves the inverse of the raypicking code. Instead of projecting a ray from screen space into world space, we project a point from world space into screen space.
For each cell, I take its four corner points and then project each one from world space into view space and then into projection space. This “flattens” that point into a 2D point that represents the pixel that point would be drawn as on the screen.
If any of these four points are inside the screen coordinates (which for Planitia is 0, 0 to 800, 600) then at least part of the cell is visible and the cell should be drawn. If all four of the points are outside the screen coordinates then the cell is not visible and should not be drawn.
The function I use for this is D3DXVec3Project(); it makes this procedure very easy.
Again, I’ve drawn the camera back in this video so that you can see how the visible area moves with the camera.
Only cells that pass both tests have their indices added to the index buffer, and thus it is the index buffer that limits how many triangles are drawn. The final result? We only draw what can be seen – and the game runs a whole lot faster.
Old Man River
And now for the last bit – the water.
Planitia’s water is its own heightfield. It uses the same vertex structure and FVF as the terrain. It’s a four-vertex, two-triangle heightfield and I don’t use triangle flipping on it. It’s pretty darn simple.
On the other hand, I do use an index buffer for it so I can do the same visibility tricks I do for the rest of the terrain.
The heightfield is updated fifteen times a second. During this update new heights are calculated based on a formula that changes over time, thus the heightfield seems to undulate. Yes, I could have used a vertex shader, but please recall what I said at the beginning about limiting the technologies I’m using.
While an undulating heightfield is nice, if the texture doesn’t animate the water can look more like blue slime. Populous: The Beginning has this problem.
So the second trick is to get the water texture to animate, and that is all done with the UV coordinates. I am not using a UV transformation matrix like I did for the lava, because that transformation matrix is applied to every UV coordinate identically and I needed to be able to customize them. So the UV coordinates are all individually calculated. And then hand-dipped in Bolivia chocolate before being delivered in a collectible tin.
The first thing we do is to simply add the current game time in seconds to all the UV coordinates. That gets the water moving.
The second thing we do is to add a very little bit of the camera’s movement to the UV coordinates. This is subtle but works really well, especially if your water texture incorporates reflected sky. Basically it makes it look like the reflected sky is moving at a different rate than the water, which it would be in reality. In the following movie, look at the edges to see the effect most clearly.
Now for the really clever bit. I add the same offset that I’m using to make the water undulate to the UV coordinate for that vertex. That is, if my undulation function says that the vertex is .015 above the normal height, I add .015 to the UV coordinates of that vertex. This has the effect of making the texture seem to squash and stretch as it moves. I think this does more to actually sell the idea that the water is flowing than anything else.
Now for one more thing. I actually add the height of each vertex in the terrain heightfield to the UV coordinates in the water heightfield. This has the effect of making the water “bunch up” around the land.
I could probably improve the water if I added another heightfield on top of the existing one, moving faster and in a different direction. If I did that, I would probably move the camera movement to the top heightfield, since it represents reflection movement. I may do this at some point, but I think Planitia’s water looks good enough for now.
And I think that’s about it. Planitia will be released with full source code so there won’t be any mysteries about how I did anything. If you’ve read this and you’re trying to replicate something I’ve done and are having trouble, please feel free to contact me at anthony.salter@gmail.com. And good luck with your own 3D programming endeavors!
Hunkerin’ Down
You may have noticed the lack of posts. I’ve been handed a rather important task here at work and I’m spending pretty much all my effort on it. Which means that posting will probably be light and work on Planitia is going to move very slowly if at all.
Then once that’s done, the holidays will be starting…
I do have one thing that I’ll be posting soon – an article on what writing Planitia taught me about Direct3D programming. It’s mostly written, it just needs some example graphics. But once that’s out, expect the dearth of posts to continue. But I’ll do the best I can.
January 2025 S M T W T F S 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31