How to correctly send a Texture2D generated at runtime to a shader?

Hello. So I’m generating terrain using Perlin Noise and tessellating it in a shader. I can correctly displace the mesh vertices on the CPU but tessellation is not increasing the mesh detail since the new vertices are interpolated from the old ones. I want to expand my approach to calculate the noise into a height map texture and sample it in my tessellation shader so that the new vertices can contribute to the detail of the mesh. But when I try to displace the vertices they all end up at the same y value of around 0.8 and the mesh is flat. There should be no issue with my shader code because when I use “SetTexture” with a Texture2D of normal image via the Inspector it works correctly. So there seems to be an issue with the GeneratedHeightMap itself. I have spent hours trying to find a solution.

Texture2D GeneratedHeightMap = new Texture2D((int)size, (int)size);

for (int x = 0; x < size; x++) {
    for (int y = 0; y < size; y++) {
        float2 uv = float2(x, y) / (float)size;
               
        float freq = frequency;

        float noise = 0f;
        float AmplitudeSum = 0f;
        float Amp = 1f;
        for (int o = 0; o < octaves; o++){
            noise += Amp * saturate(Mathf.PerlinNoise(uv.x * freq, uv.y * freq));

            AmplitudeSum += Amp;
            Amp *= persistence;
            freq *= lacunarity;
        }
        noise /= AmplitudeSum;


        GeneratedHeightMap.SetPixel(x, y, new Color(noise, noise, noise, 1f));
    }
}
this.GetComponent<Renderer>().material.SetTexture("_HeightMap", GeneratedHeightMap);

call GeneratedHeightmap.Apply after setting all your pixels, also .sharedMaterial is the reference to existing material (vs. .material which creates a new copy).

also, u will want to keep a reference to GeneratedHeightmap somewhere and call Destroy on it when you’re done with it later, to prevent memory leak / texture not being disposed.

Edit: lastly, if performance matters, take a look at GetRawTextureData (is much faster than SetPixel).

Thank you. I implemented what you said and it worked. I actually did it hours ago so I ended up changing my implementation quite a bit, e.g. I calculate the noise and set it to the texture directly using a ComputeShader (had to swap to a RenderTexture and set enableRandomWrite to true).

The tip for calling Destroy definitely helped me a lot. The code I showed you was just everything in my OnEnable method. At first I tried to use Destroy right after calling SetTexture but my program broke so I commented it out. Unity then proceeded to crash when I adjusted the settings too much. I only started Unity recently and I haven’t even thought about memory leaks lol. Keeping the references for dynamic memory in the class, allocating it in OnEnable then freeing/setting to null in OnDisable seems to work well.

Pretty far with the project now just trying to add support for shadows (the hard part right now is trying to make it work with my vertex displacement). Thanks again for the help :slight_smile:

1 Like