I am looking for an alternative to the slow SetAlphamaps for realtime terrain editing/painting. I’ve heard that you can use CopyActiveRenderTextureToTexture to paint on the terrain. But there are no examples of how to implement this. Any help?
I have exactly the problem that setAlphaMap is very very slow (around 10ms per call). In my game, there are many sequences where heavy editing is done on the terrain (when earthwork is done…), and this makes the framerate drop significantly.
A first step is consolidating all the setAlphaMaps into a single call per frame (though the zone passed may be bigger). But ideally, there would be other tricks to go fast.
Editing performance is highly dependent on the size of the heightmap. So try decreasing the terrain resolution and using a few tiles of terrain. I use 512x512 and when editing an area at the junction of 4 terrains at once, function call is about 7 ms. When editing in the center of one tile terrain, respectively, about 2ms. If you have a mobile game, you may need an even lower heightmap resolution, then 512.
Coloring also adds its own overhead and depends on the number of layers on the current terrain. I keep 8 layers, which is enough for most types of games. 4 layers would be faster, but too little variety.
Over the past months, I haven’t found a more streamlined way to edit terrain. Probably not worth trying to look for.
I also use MicroSplat shader. There is a “Custom Splatmaps” function that allows you to manually manipulate layer textures. And remove the UpdateMaterial overhead, which becomes a big problem with a large number of terrain tiles (more than 100). However, I need to check this, I will need to edit the plugin code. But it should work.
I’d have a go at modifying the Terrain alphamaps on the GPU. You can use the PaintContext and TerrainPaintUtility APIs to modify subregions of the different Terrain textures.
Let’s say you want to modify a region starting at X,Y in world space using one Terrain Layer at a time. You can do something like this:
var terrainLayer = GetDesiredTerrainLayer(); // this is the terrain layer you want to "paint" onto the Terrain
var material = GetMaterial(); // the material/shader you are going to use to modify the terrain texture
var uv = ConvertWorldPosToLocalTerrainPos(terrain, x, y); // you'll want to do this yourself. if you're using physics raycasts, you can use the texcoord value from RaycastHit.texcoord as your uv value (we do this for the painting tools)
var xform = TerrainPaintUtility.CalculateBrushTransform(terrain, uv, sizeOfRegion, 0f); // 0f is for rotation. probably won't need any rotation. sizeOfRegion is going to be the world-space width and height of the area you want to modify. the function will convert that to local terrain units and figure stuff out for ya
var ctx = TerrainPaintUtility.BeginPaintTexture(terrain, xform.GetBrushXYBounds(), terrainLayer, 0);
TerrainPaintUtility.SetupTerrainToolMaterialProperties(ctx, xform, material);
Graphics.Blit(ctx.sourceRenderTexture, ctx.destinationRenderTexture, material); // actually modify the texture on the GPU
TerrainPaintUtility.EndPaintTexture(ctx, "end paint"); // second param won't be used at runtime (afaik). it's just for tracking undo in editor
and your shader should follow this structure
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "TerrainTool.cginc"
struct appdata_t
{
float4 vertex : POSITION;
float2 pcUV : TEXCOORD0; // pcUV stands for PaintContext UV
};
struct v2f {
float4 vertex : SV_POSITION;
float2 pcUV : TEXCOORD0;
};
v2f vert(appdata_t v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.pcUV = v.pcUV;
return o;
}
float4 frag(v2f i) : SV_Target
{
float2 uv = PaintContextUVToBrushUV(i.pcUV); // gets you the correct uv into the source and destination rendertexture based on properties set by TerrainPaintUtility.SetupTerrainToolMaterialProperties(...). useful if you want to sample an additional mask for your terrain modification
float weight = tex2D(_MainTex, i.pcUV).r; // get the current splat weight for the Terrain Layer
// do something to the splat weight
return weight; // unity will re-normalize splat weights for you when you call EndPaintTexture
}
If you want to modify multiple splats at a time, I’d use the CopyActiveRenderTextureToTexture APIs to get your subregion of the splatmap(s) and modify those on the GPU (less setup for this i think). You’ll want to handle normalization of the splat weights once you’re done modifying the texture