Custom shader with Megasplat wasn't working. Fix included.
Steps to reproduce:
- Download Mapmagic from Asset store
- Download Megasplat from Asset store
- Follow steps to add megasplat to terrain
- Resolve all "Fix" dialogs
- Add Clusters etc to Megasplat
- Add custom shader to MapMagic
- Add base layer and additional cluster layers
- Connect noise to additional cluster layer
Expected result: Terrain to show two textures based on noise.
Actual result: Terrain shows one texture
Problem: Source code in CustomShader for MegaSplat in asset wasn't correctly setting top and bottom id's and also setting a blend of zero.
Fix: Rewrote ProcessMegaSplat with what understanding I had of MegaSplat to correctly select topID and bottomID based on a rudimentary bubble sort like approach.
Also attempt to adjust sorting of textures to work with biomes.
As it loops through allLayers, based on the current layers input mask value and the previous input mask value I determine if the top layer ID should be replaced with the current layers cluster index. If so then the current topID becomes the bottomID.
For biomes, allLayers includes all cluster layers from all biomes. I multiply the layer mask value by the biome mask value.
Its not perfect but it appears to work.
public static void ProcessMegaSplat(CoordRect rect, Chunk.Results results, GeneratorsAsset gens, Chunk.Size terrainSize, Func<float,bool> stop = null)
{
#if __MEGASPLAT__
if (stop!=null && stop(0)) return;
//creating color arrays
Color[][] colors = new Color[2][];
colors[0] = new Color[MapMagic.instance.resolution * MapMagic.instance.resolution];
colors[1] = new Color[MapMagic.instance.resolution * MapMagic.instance.resolution];
//creating all and special layers/biomes lists
List<Layer> allLayers = new List<Layer>(); //all layers count = gen num * layers num in each gen (excluding empty biomes, matrices, etc)
List<Matrix> allMatrices = new List<Matrix>();
List<Matrix> allBiomeMasks = new List<Matrix>();
List<Matrix> specialWetnessMatrices = new List<Matrix>(); //special count = number of generators (excluding empty biomes only)
//List<Matrix> specialPuddlesMatrices = new List<Matrix>();
//List<Matrix> specialDampeningMatrices = new List<Matrix>();
//List<Matrix> specialBiomeMasks = new List<Matrix>();
//filling all layers/biomes
foreach (CustomShaderOutput gen in gens.GeneratorsOfType<CustomShaderOutput>(onlyEnabled: true, checkBiomes: true))
{
//loading biome matrix
Matrix biomeMask = null;
if (gen.biome != null)
{
object biomeMaskObj = gen.biome.mask.GetObject(results);
if (biomeMaskObj == null) continue; //adding nothing if biome has no mask
biomeMask = (Matrix)biomeMaskObj;
if (biomeMask == null) continue;
if (biomeMask.IsEmpty()) continue; //optimizing empty biomes
}
for (int i = 0; i < gen.baseLayers.Length; i++)
{
//reading output directly
Output output = gen.baseLayers[i].output;
if (stop!=null && stop(0)) return; //checking stop before reading output
if (!results.results.ContainsKey(output)) continue;
Matrix matrix = (Matrix)results.results[output];
if (matrix.IsEmpty()) continue;
if (textureList.clusters == null)
{
Debug.Log("CSO MegaSplat clusters are not assigned");
continue;
}
if (i >= textureList.clusters.Length)
{
Debug.LogError("Cluster out of range");
continue;
}
//adding to lists
allLayers.Add(gen.baseLayers[i]);
allMatrices.Add(matrix);
allBiomeMasks.Add(gen.biome == null ? null : biomeMask);
}
//adding special
/*
object wetnessObj = gen.wetnessIn.GetObject(results);
specialWetnessMatrices.Add( wetnessObj!=null? (Matrix)wetnessObj : null );
object puddlesObj = gen.puddlesIn.GetObject(results);
specialPuddlesMatrices.Add( puddlesObj!=null? (Matrix)puddlesObj : null );
object dampeingObj = gen.displaceDampenIn.GetObject(results);
specialDampeningMatrices.Add( dampeingObj!=null? (Matrix)dampeingObj : null );
specialBiomeMasks.Add(gen.biome == null ? null : biomeMask);
*/
}
//if no texture list found in any of generators - returning
if (textureList == null || allLayers.Count==0) return;
//processing
int allLayersCount = allLayers.Count;
int specialCount = specialWetnessMatrices.Count;
for (int x = 0; x<rect.size.x; x++)
for (int z = 0; z<rect.size.z; z++)
{
int pos = rect.GetPos(x + rect.offset.x, z + rect.offset.z);
// doesn't use height, normal, but I'm not sure how to get that here..
Vector3 worldPos = new Vector3(
1f * (x+rect.offset.x) / MapMagic.instance.resolution * rect.size.x,
0,
1f * (z+rect.offset.z) / MapMagic.instance.resolution * rect.size.z);
float heightRatio = results.heights!=null? results.heights.array[pos] : 0.5f; //0 is the bottom point, 1 is the maximum top
Vector3 normal = new Vector3(0,1,0);
// find highest two layers
int botIdx = 0;
int topIdx = 0;
float blend = 0;
int lastIndex = 0;
float lastVal = 0;
float biomeModifier = 1;
for (int i = 0; i < allLayersCount; i++)
{
biomeModifier = allBiomeMasks[i] != null ? allBiomeMasks[i].array[pos] : 1f;
if (i == 0)
{
lastIndex = topIdx = allLayers[i].index;
botIdx = allLayers[i].index;
lastVal = 1 * biomeModifier;
}
else
{
float val = (1 + allMatrices[i].array[pos] ) * biomeModifier;// Current layer input value.
//val *= biomeModifier;
if (val > lastVal)
{
botIdx = lastIndex;
topIdx = allLayers[i].index;
lastIndex = topIdx;
lastVal = val;
}
}
}
topIdx = textureList.clusters[topIdx].GetIndex(worldPos * clusterNoiseScale, normal, heightRatio);
botIdx = textureList.clusters[botIdx].GetIndex(worldPos * clusterNoiseScale, normal, heightRatio);
blend = 1 - lastVal ;
if (smoothFallof) blend = (Mathf.Sqrt(blend) * (1 - blend)) + blend * blend * blend;
colors[0][pos] = new Color(topIdx / 255f, botIdx / 255f, blend, 1.0f);
}
//pushing to apply
if (stop!=null && stop(0))
return;
results.apply.CheckAdd(typeof(CustomShaderOutput), colors, replace: true);
#endif
}