How should I approach this procedural texture generation?

hey there! I’m designing a procedural world generator with a first person controller. to generate the texture, I’m sampling the noise height at each vertex point and assigning a color to its corresponding pixel on the texture from its UV coordinate. the problem is, I get one discrete value (one pixel position) for each vertex but I don’t have any logic to fill in the pixels, so it ends up looking like this:

how should I go about implementing the colors in between each point? ideally the algorithm would lerp the color between points of different “terrain type,” but at this point just filling in with a solid block works too lol. thanks!

Why not just assign that color to the vertex and use a shader that uses the vertex colors? Better yet, why not just do the color assignment in the vertex shader itself?

1 Like

thank you for the suggestions! before deciding on this weird texture method, I actually tried doing the color assignment in a vertex shader (finding the magnitude of the vertex’s xyz, determining color, applying it to the vertex) and doing a vert/frag shader, and the issue was that the edges between terrain segments look choppy as all hell, like this:

I would then suggest trying to output the local position of the vertex to the fragment shader, then do the color lookup there. Specifically you’ll want to pass the full float3 (or half3) position and do the length() in the fragment shader.

damn, I must be doing something wrong then! that’s exactly what I did initially - I interpreted each vert’s xyz with length() and assigned the v.color property there, and that didn’t work either. same choppiness, super super weird. how do people manage to write complex shaders? hahaha

is it maybe something to do with my camera? i honestly have no idea where to go from where

The suggestion of bgolus should work fine. Don’t use any vertex color property. Output the color from the fragment shader as result. Maybe you can show your current shader code?

It’s still very blocky! Here’s the code:

Shader "Planet" {

    Properties {
        _MyTexture ("My texture", 2D) = "white" {}
        _WaterColor ("Water Color (RGBA)", Color) = (0.0,0.0,1.0,1.0)
        _LandColor ("Land Color (RGBA)", Color) = (0.0,1.0,0.0,1.0)
    }
   
    SubShader {
        Tags {
            "Queue" = "Geometry"
            "RenderType" = "Opaque"
        }

        Pass {
            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag

            float4 _WaterColor;
            float4 _LandColor;

            struct appdata {
                 float4 vertex : POSITION;
                 fixed4 color : COLOR;
            };

            struct v2f {
                float4 pos : SV_POSITION;
                float4 color : COLOR;
            };

            v2f vert(appdata v) {
                v2f o;
                o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
                o.color = length(v.vertex.xyz) > 11.8738 ? _LandColor : _WaterColor;
                return o;
            }

            fixed4 frag(v2f output) : SV_Target {
                return output.color;
            }
            ENDCG
        }
    }

}

That’s doing the calculation in the vertex shader, which is what I suggested not doing. Try:

Shader "Planet" {

    Properties {
        _MyTexture ("My texture", 2D) = "white" {}
        _WaterColor ("Water Color (RGBA)", Color) = (0.0,0.0,1.0,1.0)
        _LandColor ("Land Color (RGBA)", Color) = (0.0,1.0,0.0,1.0)
    }
 
    SubShader {
        Tags {
            "Queue" = "Geometry"
            "RenderType" = "Opaque"
        }

        Pass {
            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag

            float4 _WaterColor;
            float4 _LandColor;

            struct appdata {
                 float4 vertex : POSITION;
                 fixed4 color : COLOR;
            };

            struct v2f {
                float4 pos : SV_POSITION;
                float3 vertex : TEXCOORD0;
            };

            v2f vert(appdata v) {
                v2f o;
                o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
                o.vertex = v.vertex.xyz;
                return o;
            }

            fixed4 frag(v2f i) : SV_Target {
                return length(i.vertex.xyz) > 11.8738 ? _LandColor : _WaterColor;
            }
            ENDCG
        }
    }

}

You can even do some fancy shader stuff to get an anti-aliased line using:

fixed4 frag(v2f i) : SV_Target {
  float dist = length(i.vertex.xyz);
  float distHalfDeriv = fwidth(dist) * 0.5;
  fixed landMask = smoothstep(11.8738 - distHalfDeriv, 11.8738 + distHalfDeriv, dist);
  return lerp(_WaterColor, _LandColor, landMask);
}

you’re a fucking hero. I thought you meant doing the calculations in the mesh code (I already do, so I was confused) and then passing it into the vert shader. I didn’t realize you meant doing the calc in the frag shader!! thank you so much!

the smooth fancy addon doesn’t really change anything, so I’ll get to tweaking. hah