Unity C# Terrain Generation Shaders Issue: Multiple Biomes and Splat Maps
I’m generating a Unity terrain with 6 biomes, each with a unique texture. Since Unity splat maps support only 4 textures per map, I’m using multiple splat maps to manage all 6 biomes. However, after generation, only one texture appears on the terrain instead of the distinct textures for all biomes.
Possible Issues:
Incorrect texture assignment.
Errors in splat map generation (e.g., weights ).
Shader setup issues.
Other logical or coding errors.
Code:
where i set the splat maps!
if (terrainDataThreadInfoQueue.Count > 0)
{
for (int i = 0; i < terrainDataThreadInfoQueue.Count; i++)
{
MapThreadInfo<DataStructure.TerrainData> threadInfo = terrainDataThreadInfoQueue.Dequeue();
Texture2D[] splatMap = null;
//Texture2D splatMap = new Texture2D(chunkSize, chunkSize);
if (terrainTextureBasedOnVoronoiPoints)
{
splatMap = SplatMapGenerator.GenerateSplatMaps(this, threadInfo.parameter.biomeMap);
// splatMap = SplatMapGenerator.GenerateSplatMapOutsideMainThread(this, threadInfo.parameter.biomeMap, splatMap);
}
threadInfo.parameter.splatMap = splatMap;
threadInfo.callback(threadInfo.parameter);
}
}
splat map function
public static Texture2D[] GenerateSplatMaps(TerrainGenerator terrainGenerator, Biome[,] biomeMap)
{
int chunkSize = terrainGenerator.ChunkSize;
int numBiomes = terrainGenerator.BiomeDefinitions.Length;
int numSplatMaps = Mathf.CeilToInt(numBiomes / 4f);
// Initialize the splat maps
Texture2D[] splatMaps = new Texture2D[numSplatMaps];
for (int i = 0; i < numSplatMaps; i++)
{
splatMaps[i] = new Texture2D(chunkSize, chunkSize, TextureFormat.RGBA32, false);
}
// Iterate over each point in the chunk
for (int y = 0; y < chunkSize; y++)
{
for (int x = 0; x < chunkSize; x++)
{
Biome biome = biomeMap[x, y];
int biomeIndex = Array.FindIndex(terrainGenerator.BiomeDefinitions, def => def.BiomePrefab.name == biome.name);
if (biomeIndex >= 0)
{
int splatMapIndex = biomeIndex / 4; // Determine which splat map to use
int channelIndex = biomeIndex % 4; // Determine which channel to modify
// Get the current color for all splat maps
Color[] colors = new Color[numSplatMaps];
for (int i = 0; i < numSplatMaps; i++)
colors[i] = splatMaps[i].GetPixel(x, y);
// Update the correct channel in the correct splat map
colors[splatMapIndex][channelIndex] = 1;
// Set the updated colors back to the splat maps
for (int i = 0; i < numSplatMaps; i++)
splatMaps[i].SetPixel(x, y, colors[i]);
}
}
}
// Apply changes to all splat maps
foreach (var splatMap in splatMaps)
{
splatMap.Apply();
}
return splatMaps;
}
where i assign the texture
public void AssignTexture(Texture2D[] splatMaps, TerrainGenerator terrainGenerator, MeshRenderer meshRenderer)
{
Shader shader = Shader.Find("Custom/TerrainSplatMapShaderHDRP");
if (shader == null)
{
Debug.LogError("Failed to find shader: Custom/TerrainSplatMapShaderHDRP");
return;
}
Material mat = new Material(shader);
int textureWidth = 1024;
int textureHeight = 1024;
TextureFormat format = TextureFormat.RGBA32;
// Create texture array for biomes
Texture2DArray textureArray = new Texture2DArray(textureWidth, textureHeight, terrainGenerator.BiomeDefinitions.Length, format, true);
for (int i = 0; i < terrainGenerator.BiomeDefinitions.Length; i++)
{
Texture2D standardizedTexture = StandardizeTexture(terrainGenerator.BiomeDefinitions[i].BiomePrefab.texture, textureWidth, textureHeight, format);
Graphics.CopyTexture(standardizedTexture, 0, 0, textureArray, i, 0);
}
textureArray.Apply();
// Create texture array for splat maps
Texture2DArray splatMapArray = new Texture2DArray(splatMaps[0].width, splatMaps[0].height, splatMaps.Length, format, false);
for (int i = 0; i < splatMaps.Length; i++)
{
Graphics.CopyTexture(splatMaps[i], 0, 0, splatMapArray, i, 0);
}
splatMapArray.Apply();
// Assign textures to material
mat.SetTexture("_TextureArray", textureArray);
mat.SetTexture("_SplatMaps", splatMapArray);
mat.SetInt("_TextureArrayLength", terrainGenerator.BiomeDefinitions.Length);
mat.SetInt("_SplatMapCount", splatMaps.Length);
meshRenderer.sharedMaterial = mat;
}
Texture2D StandardizeTexture(Texture2D sourceTexture, int width, int height, TextureFormat format)
{
// Create a temporary RenderTexture
RenderTexture renderTexture = RenderTexture.GetTemporary(width, height, 0);
RenderTexture.active = renderTexture;
// Blit the source texture onto the RenderTexture
Graphics.Blit(sourceTexture, renderTexture);
// Create a new Texture2D and read the pixels from the RenderTexture
Texture2D standardizedTexture = new Texture2D(width, height, format, true);
standardizedTexture.ReadPixels(new Rect(0, 0, width, height), 0, 0);
standardizedTexture.Apply();
// Release the RenderTexture
RenderTexture.ReleaseTemporary(renderTexture);
RenderTexture.active = null;
return standardizedTexture;
}
my shaders:
Shader "Custom/TerrainSplatMapShaderHDRP"
{
Properties
{
_TextureArray("Texture Array", 2DArray) = "" {}
_SplatMaps("Splat Maps", 2DArray) = "" {}
}
HLSLINCLUDE
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Material/Material.hlsl"
TEXTURE2D_ARRAY(_TextureArray);
SAMPLER(sampler_TextureArray);
TEXTURE2D_ARRAY(_SplatMaps);
SAMPLER(sampler_SplatMaps);
CBUFFER_START(UnityPerMaterial)
int _TextureArrayLength;
int _SplatMapCount;
CBUFFER_END
struct Attributes
{
float3 positionOS : POSITION;
float2 uv : TEXCOORD0;
};
struct Varyings
{
float4 positionCS : SV_POSITION;
float2 uv : TEXCOORD0;
};
Varyings Vert(Attributes input)
{
Varyings output;
output.positionCS = TransformObjectToHClip(input.positionOS);
output.uv = input.uv;
return output;
}
float4 Frag(Varyings input) : SV_Target
{
float4 color = float4(0, 0, 0, 0);
for (int i = 0; i < _SplatMapCount; i++)
{
// Sample the splat map array with proper arguments
float4 splatControl = SAMPLE_TEXTURE2D_ARRAY(_SplatMaps, sampler_SplatMaps, input.uv, i);
for (int j = 0; j < 4; j++)
{
int textureIndex = i * 4 + j;
if (textureIndex < _TextureArrayLength)
{
// Sample the texture array with proper arguments
color += splatControl[j] * SAMPLE_TEXTURE2D_ARRAY(_TextureArray, sampler_TextureArray, input.uv, textureIndex);
}
}
}
return color;
}
ENDHLSL
SubShader
{
Tags { "RenderPipeline"="HDRenderPipeline" }
Pass
{
Name "Forward"
Tags { "LightMode"="Forward" }
HLSLPROGRAM
#pragma vertex Vert
#pragma fragment Frag
ENDHLSL
}
}
Fallback "HDRenderPipeline/Unlit"
}