Monday, June 11, 2012

Level Editors and Splat Maps!

Got busy working on a level editor for Hazard Pay, as I was already reaching a point where I wanted to build up a test level for running around, and wanted to get some of the tool development out of the way.  I want to make this a user-friendly, intuitive editor as I'll likely bundle it with the game once it's all finished so people can easily (and immediately) start making custom maps for the game.  Us creative types just love mucking with other people's tools (see all BethSoft construction kits, for example).

This is additionally forcing me to define a level file, and solidify some of the framework for file loading, which is all good stuff.

I've also gotten to play with texture splatting in HLSL, which basically involves taking a base texture (the splat map) and converting, at each pixel, its RGBA values into alpha-blending values for up to 4 individual textures.  This allows for nice things like seamlessly blending from sand/rocks into grass, or whatever alien surfaces you might encounter ;)

WPF makes GUI editing incredibly quick (yay C#), even if I have had to do a little hacking to get button images to show up.  Apparently the way I put together the project (making the Windows Form a member of an XNA project, and not the other way around) screws up the ability for the Form to recognize resources it needs, even if they're a part of the project and the form has an associated .resx file.  Luckily, you can just jump into the designer code and manually point to image resources, so I end up with little gems like this in the form constructor:

//Begin button image hacks
this.raiseTerrainButton.Image = Image.FromFile(contentPath + "Icons\\RaiseTerrainIcon.png");
this.lowerTerrainButton.Image = Image.FromFile(contentPath + "Icons\\LowerTerrainIcon.png");
//End button image hacks

As far as splatting, once you've confirmed that your terrain textures are indeed loading (that was 2 hours of pointless debugging...) the following pixel shader function makes splatting a snap.  Just make sure you load up one Texture2D for the "splat map" (i.e. the texture whose color channels will dictate visibility of your palette textures) and up to 4 more for your terrain textures (in the example above, I'd loaded up a cartoony sand texture, a plain green texture, and a plain light-blue texture.)

float4 QuadPixelShader(MTVertexShaderOutput input) : COLOR0
 float4 splat = tex2D(TextureSampler0, input.BaseTextureCoords);

 float3 pixColor = float3(0,0,0);

 pixColor += tex2D(TexSampler1, input.TileTextureCoords).rgb * splat.r;
 pixColor += tex2D(TexSampler2, input.TileTextureCoords).rgb * splat.g;
 pixColor += tex2D(TexSampler3, input.TileTextureCoords).rgb * splat.b;
 //pixColor += tex2D(TexSampler4, input.TileTextureCoords).rgb * splat.a;

 pixColor.rgb *= saturate(input.LightingFactor) + xAmbient;
 float4 finalColor = float4(pixColor, 1);

 return finalColor;
(the 4th alpha channel was ignored for now, as I discovered my test splat map was set to full alpha, meaning I'd paint over everything with the 4th texture until I use a file that has varied alpha.  Paint order matters!  Also, that saturate line is for the normal-calculated shadows, you can ignore that if you're just looking for a simple splatting technique.  I defined a custome vertex structure that takes two texture coordinates, because I wanted my splat map to scale to the size of the terrain mesh, but still tile the paintable textures for better detail.  That's why there's two texcoord types in the sampler calls.)

The fun part of all this is that with those shaders and structures in place, once I get the brush tool working, I can re-paint a terrain mesh in real-time by having it alter the channels of the underlying splat map, then just roll that up with the level file during the save process.  Whee!



  1. hi brandon.
    im also working on a 3D-level editor in XNA. At first i had texture splatting with a colormap too, but now i changed it a bit. My vertices now have a Vector4 for textureWeights, so when i paint the textures onto the terrain, i dont have to redraw a colormap every frame. This is way more easy and faster, so you could think about that too.
    Great work so far, thumbs up, keep on coding.

    1. Thanks! I might just add that in, as I'm sure I could save lots of cycles, and entire sample pass (and be able to dynamically change the size of the terrain mesh) if I wasn't reliant on a matching colormap resource.

      Good luck with your editor!