In this implementation, vertex colors are directly assigned in C# during mesh generation
(GenerateVertexColors inside ProceduralSurface). This means the vertex buffer already contains a per-vertex Color attribute when passed to the GPU.
Thanks to this, in Shader Graph I can simply sample the Vertex Color node instead of recomputing terrain classification in the shader. This offers two major benefits:
- Performance — all classification (height / slope / noise / latitude / blend) is done once on the CPU in jobs, rather than per-pixel on the GPU.
- Flexibility — color rules can be authored freely and are directly baked into the mesh.
The logic is rule-driven: each ColorRule defines:
-
the condition (Height / Slope / Noise / Latitude / Blend);
-
two possible colors (color1,
color2);
- a blending curve or interpolation strategy.
▼ Click to see the detailed Pseudo-code
for each vertex v:
height = normalize(v.position.magnitude) // or y for plane
slope = angle(normal[v], up)
color = white
for each rule r in colorRules:
factor = r.Evaluate(height, slope, v.position, v.normal)
candidateColor = Lerp(r.color1, r.color2, factor)
color = Blend(color, candidateColor, r.weight, r.blendMode)
v.color = color
Define ranges of elevation that map to different biomes. Perfect for terrain-like distribution: e.g., ocean → beach → plain → mountain → snow. Preset Earth/Ice palettes are entirely generated by this rule.
factor = InverseLerp(minHeight, maxHeight, height);
factor = heightCurve.Evaluate(factor);
color = Lerp(color1, color2, factor);
Splits areas according to Perlin noise function. Produces patchy, irregular patterns (e.g., moss vs. soil, craters vs. flatlands). Reuses the same noise function as terrain generation, ensuring consistency. Used for Martian lowlands.
factor = PerlinNoise(position.x * noiseScale, position.z * noiseScale);
color = Lerp(color1, color2, factor);
Classifies areas by steepness angle. Useful for distinguishing cliffs vs. flat fields (e.g., add darker rock colors on slopes).
factor = InverseLerp(minSlope, maxSlope, slope);
color = Lerp(color1, color2, factor);
Computes latitude by normalizing Y-axis in [-1, 1]. Produces banded gradients (like polar caps, equatorial forests).
factor = InverseLerp(-1f, 1f, position.y);
color = Lerp(color1, color2, factor);
A flexible "catch-all" rule. Allows arbitrary mixing of two colors using a given blend mode (Add, Multiply, Overlay, Screen, SoftLight). Useful when combining multiple biome colors smoothly.
factor = 0.5f; // default blend
color = Blend(baseColor, Lerp(color1, color2, factor), weight, blendMode);
Material
In the shader graph, I implemented water material properties by detecting blue tones directly from the vertex colors.
The logic uses channel comparison to classify water regions and assign distinct surface properties.
- Use a Split node to separate the RGB channels of the vertex color.
- Compare the Blue channel against Red and Green to detect potential water areas.
- If Blue > (Red + Green), classify it as water and modify material properties:
- Metallic = 0.8 for a reflective water surface.
- Smoothness = 0.9 to achieve a glossy, fluid appearance.
- For non-water areas, default material parameters are preserved.
This approach produces visually distinct and physically plausible water regions that reflect light differently from the surrounding terrain.
▼ Click to see the implementation
PresetI also provided three classic color combinations that can be used directly.
Loading may take some time...