Search Unity

Question Which specific textures are available for CopyActiveRenderTextureToTexture?

Discussion in 'World Building' started by VitruvianStickFigure, Jan 24, 2023.

  1. VitruvianStickFigure

    VitruvianStickFigure

    Joined:
    Jun 28, 2017
    Posts:
    38
    I'm working on a game which programmatically generates its terrain. This occurs much faster on the GPU than the CPU (around 20s of wait time is down to milliseconds—if that—on my hardware). So, I am using a compute shader to generate the terrain, and eventually hopefully all textured values on it.

    I would like to extend this method to several other phases of terrain generation, such as alpha mapping of the different layers. It looks as though I can do this with CopyActiveRenderTextureToTexture, which appears to be able to go beyond mere height maps.

    Unfortunately, it also requires both the texture's name and its index, which I'm not sure I have access to. This function appears to be exposed to the end users, but I'm having an awful time finding anything out about its intended use, or how to find out what the relevant texture names are with the current Unity LTS.

    Can anyone inform me of how this is meant to be used? Thank you so much.

    ADDENDUM: I've since discovered TerrainData.AlphamapTextureName, which seems to apply, but I'm not entirely sure what it means to me that this is a static field...
     
    Last edited: Jan 24, 2023
  2. VitruvianStickFigure

    VitruvianStickFigure

    Joined:
    Jun 28, 2017
    Posts:
    38
    As an update for clarity, I'm looking at the exposed source and am currently using this iteration:

    Code (CSharp):
    1. data.CopyActiveRenderTextureToTexture(
    2.             TerrainData.AlphamapTextureName, 0,
    3.             new RectInt(0, 0, data.alphamapResolution, data.alphamapResolution),
    4.             new Vector2Int(0, 0),
    5.             false);
    I still need to poke around with my compute shader to ensure that it's doing its job properly, but it seems to be working.

    However, I'm still scratching my head on the relevance of TerrainData.AlphamapTextureName (Are there alternatives? Can this vary from version to version, or platform to platform?), but I am finally making some noticeable progress.

    Input from someone more familiar with the API is still much appreciated.
     
  3. VitruvianStickFigure

    VitruvianStickFigure

    Joined:
    Jun 28, 2017
    Posts:
    38
    As a further update on progress, here is my current code:

    Code (CSharp):
    1.     public void Produce()
    2.     {
    3.         TerrainData data = GetComponent<Terrain>().terrainData;
    4.        
    5.         if(renderTexture == null) {
    6.             //the compute shader works in blocks of 8x8, and classically we must overshoot (at worst) to ensure that all
    7.             //relevant coordinates are filled. So, offset to ((int)(val/8) * 8) + 8.
    8.             int res = (int)(data.alphamapResolution/8) * 8 + 8;
    9.             renderTexture = new RenderTexture(res, res, 32, RenderTextureFormat.RFloat);
    10.             renderTexture.enableRandomWrite = true;
    11.             renderTexture.Create();
    12.         }
    13.        
    14.         splatter.SetTexture(0, "Result", renderTexture);
    15.         splatter.SetFloat("u_time", 0f); //assume constant time for this shader, as we aren't updating it
    16.         splatter.SetFloats("u_resolution", data.alphamapResolution, data.alphamapResolution);
    17.        
    18.         splatter.Dispatch(0, renderTexture.width/8, renderTexture.height/8, 1);
    19.        
    20.         RenderTexture.active = renderTexture;
    21.         data.CopyActiveRenderTextureToTexture(
    22.             TerrainData.AlphamapTextureName, 0,
    23.             new RectInt(0, 0, data.alphamapResolution, data.alphamapResolution),
    24.             new Vector2Int(0, 0),
    25.             false);
    26.         RenderTexture.active = null;
    27.        
    28.     }
    29.  
    My test compute shader (splatter) boils down to this:

    Code (CSharp):
    1. #pragma kernel CSMain
    2. #pragma warning (disable : 3571)
    3. #define PI 3.14159265359
    4. #define TWO_PI 6.28318530718
    5.  
    6. uniform float2 u_resolution;
    7. uniform float u_time;
    8.  
    9. RWTexture2D<float> Result;
    10.  
    11. [numthreads(8,8,1)]
    12. void CSMain (uint3 id : SV_DispatchThreadID)
    13. {
    14.     Result[id.xy] = (sin((id.x/360.0) * PI * 12.0) + 1.0) / 2.0;
    15. }
    16.  
    The result, given that grass is on alpha-map layer zero, is attached. I think I can work what it should actually depend on into my compute shaders...

    Where methodology goes I'm still scratching my head, but this is a great start.
     

    Attached Files:

  4. VitruvianStickFigure

    VitruvianStickFigure

    Joined:
    Jun 28, 2017
    Posts:
    38
    As an additional update, through consideration and experimentation, I've determined that (for the URP) Unity keeps one channel of the alphamap texture for each terrain texture. I'm guessing that additional terrain textures beyond four create new textures, as the documentation has mentioned that each additional group of four requires an additional render pass.

    They appear to be in ARGB32 order.

    My terrain layering compute shader is thus:

    Code (CSharp):
    1. #pragma kernel CSMain
    2. #pragma warning (disable : 3571)
    3. #define PI 3.14159265359
    4. #define TWO_PI 6.28318530718
    5.  
    6. uniform float2 u_resolution;
    7. uniform float u_time;
    8.  
    9. uniform float u_critical;
    10.  
    11. //Enumeration
    12. //
    13. //1 : Greater Than (>)
    14. //2 : Less Than (<)
    15. uniform int u_operator = 1;
    16.  
    17. RWTexture2D<float> Steepness;
    18. RWTexture2D<float4> Result;
    19.  
    20. [numthreads(8,8,1)]
    21. void CSMain (uint3 id : SV_DispatchThreadID)
    22. {
    23.     bool critical = false;
    24.     if(u_operator == 1) {
    25.         critical = Steepness[id.xy] > u_critical;
    26.     } else if(u_operator == 2) {
    27.         critical = Steepness[id.xy] <= u_critical;
    28.     }
    29.     Result[id.xy] = float4(
    30.         critical ? 1.0 : 0.0,
    31.         critical ? 0.0 : 1.0,
    32.         0.0,
    33.         0.0);
    34. }
    35.  
    Details on assigning it to the terrain are the same as above.

    This results in a sharp but otherwise very nice transition between textures, based on steepness. (Steepness itself was calculated in my heightmap compute shader, for further reference.) You can see the result in my attached jpeg. Popping a smoothstep in there somewhere could improve it even further.

    I suppose this post mostly exists to help other people out who have the same problem in the future; but if anyone knows anything about `TerrainData.AlphamapTextureName`, and why it has to be explicit, I would love to hear about it!
     

    Attached Files:

  5. VitruvianStickFigure

    VitruvianStickFigure

    Joined:
    Jun 28, 2017
    Posts:
    38
    As an unexpected update—and I really don't know how I missed this the first time—I've discovered that we also have the static field TerrainData.HolesTextureName.

    As long as you remember to appropriately use TextureData's holesResolution (as opposed to heightmapResolution or alphamapResolution) the procedure is much the same. My test class (in C#) is as follows.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class TerrainHolesTest : MonoBehaviour
    6. {
    7.     private RenderTexture renderTexture;
    8.     public ComputeShader splatter;
    9.    
    10.     public void Produce(RenderTexture heights, RenderTexture steepness)
    11.     {
    12.         TerrainData data = GetComponent<Terrain>().terrainData;
    13.        
    14.         if(renderTexture == null) {
    15.             //the compute shader works in blocks of 8x8, and classically we must overshoot (at worst) to ensure that all
    16.             //relevant coordinates are filled. So, offset to ((int)(val/8) * 8) + 8.
    17.             int res = (int)(data.holesResolution/8) * 8 + 8;
    18.             renderTexture = new RenderTexture(res, res, 32, RenderTextureFormat.RFloat); //RenderTextureFormat.ARGB32); // Terrain.heightmapRenderTextureFormat); //24);
    19.             renderTexture.enableRandomWrite = true;
    20.             renderTexture.Create();
    21.         }
    22.        
    23.         splatter.SetTexture(0, "Result", renderTexture);
    24.         splatter.SetFloat("u_time", 0f);
    25.         splatter.SetFloats("u_resolution", data.holesResolution, data.holesResolution);
    26.         splatter.SetFloat("u_critical", 0.25f);
    27.         splatter.Dispatch(0, renderTexture.width/8, renderTexture.height/8, 1);
    28.        
    29.         //And, copy the result over.
    30.         RenderTexture.active = renderTexture;
    31.         data.CopyActiveRenderTextureToTexture(
    32.             TerrainData.HolesTextureName, 0,
    33.             new RectInt(0, 0, data.alphamapResolution, data.alphamapResolution),
    34.             new Vector2Int(0, 0),
    35.             false);
    36.         RenderTexture.active = null;
    37.        
    38.     }
    39.  
    40. }
    41.  
    Put simply, it provides the basic data necessary, with u_critical in this case being the seam point for a checkerboard. The HLSL/Cg looks like this.

    Code (CSharp):
    1. #pragma kernel CSMain
    2.  
    3. uniform float2 u_resolution;
    4. uniform float u_time;
    5.  
    6. uniform float u_critical;
    7.  
    8. // Create a RenderTexture with enableRandomWrite flag and set it
    9. // with cs.SetTexture
    10. RWTexture2D<float> Result;
    11.  
    12. [numthreads(8,8,1)]
    13. void CSMain (uint3 id : SV_DispatchThreadID)
    14. {
    15.     float2 st = id.xy / u_resolution.xy;
    16.    
    17.     st *= 1.0/u_critical;
    18.     st = frac(st);
    19.     st *= 2.0;
    20.     Result[id.xy] = (st.x > 1.0) ^ (st.y > 1.0);
    21. }
    22.  
    The result is a hole pattern, properly applied to the terrain, which cuts checkerboards out of my terrain. The result is visible in the attached JPEG.

    I guess I've clarified quite a bit for myself, through a lot of digging and experimentation! But hopefully, if someone else finds themselves in the same quandary, they can use this for something of a walkthrough.
     

    Attached Files: