Welcome to our fourth Development Blog post.
Tank extraction and shovel digging are amongst of main features available in Tank Mechanic Simulator game. Very important is detecting proper places to situate extraction components. Let’s discuss about technical aspects behind this idea.
For terrain rendering, is often used a technique called texture splatting. This is a method for combining different input textures (two or more), to aim a produce more natural-looking surface. It is important to allow seamless and smooth integration between selected textures, so exist quite a few algorithms for texture maps blending.
To define opacity, Unity auto-generate alpha maps for all layers currently painted with Terrain Component tool. Let’s imagine, every pixel in picture on the right side is calculated from weights of corresponded input layers. Upper part is covered by field ground and down part by forest ground, respectively. Single splatmap can be treated as two-dimensional array of floats (in that case, weights). Top left corner has value 1.0 for field ground layer and 0.0 for forest ground layer. Down right corner has 0.0 (field) and 1.0 (forest). In the center, we can see smooth border between layers, so these values have range between 0.0 and 1.0 for each splatmap, and have influence on how much source pixels from each texture are visible. In default, final color is computed by built-in terrain standard shader (see TerrainSplatmapCommon.cginc).
To read weights, we can use TerrainData.GetAlphamaps function. It returns three-dimensional array – the first two dimensions represent x and y coordinates on the map, while the third denotes the splatmap texture to which the alphamap is applied. To decide, whether player can dig in certain place, we have to determine which textures allow to start proces. For example, terrain chunk under player has five splatmaps. Our algorithm returns splatmap name which has the highest weight in current coordinates. Next, we compare this name with list of splatmap names which don’t match to start digging process. Depends on result, player can make a cavity (with proper animation and particle effect) in the ground or not.
Excavation process is far more complicated than shovel digging detection. In the previous case, we simply take a geometrical point. It’s enough generalization, because hole made by shovel is rather small, relative to terrain size. Extraction area is much more wide. We have to treat excavation place as two-dimensional structure. Otherwise, it can lead to situation where calculatedpoint (center of circle), inform us that we can start extraction here (for example it detects sand texture), despite edges lie on restricted splatmaps (mountain rocks, river etc.). It’s bad.
As we said, it’s time to test two 2D objects (excavation place defined by radius and terrain surface). The easiest solution is probably, simply convert our basic detection algorithm. Treat circle as large set of points and compare values in loop. But it is expensive operation. Better idea is following: use HLSL Compute Shader, send splatmap data to buffer allocated in GPU memory and calculate parallely.
Screen shows map with selected area where we expect to find a tank. With in-game debug cheat tool we can visualise our compute buffer (top left window). Yellow rectangle shape indicates current terrain chunk. Red pixels symbolize non eligible parts of ground, black pixels allows to make extraction. On the second picture is rendered excavation circle which is not intersect restricted area. On the third picture, circle intersects restricted area (in this case, fragment of road – bright red colour), so the terrain is not fit to excavation.
HLSL buffers are singledimensional, so it is good to remap between 1D and 2D spaces. To make new virtual UV normalized coordinates, where index to current thread we store inside uint2 id : SV_DispatchThreadID and passed alphamap resolution, we use:
float2 uv = float2 (id.x/(float)resolutionX,id.y/(float)resolutionY);
Then code for procedural circle (with center c and radius r):
float circle(float2 p, float2 c, float r)
return step(length(p-c)-r, 0.0);
If point p inside circle, function returns 1 else 0. Compare result then store output value in buffer: image[id.y*resolutionY+id.x]. Now we need to count values with 1, to block extraction even if single red bright pixel inside circle appear. We call InterlockedAdd function, that performs a guaranteed atomic add of value to the destination resource variable.