Search Unity

  1. Unity 2019.2 is now released.
    Dismiss Notice

Modifying RWTexture2D in Compute Shader

Discussion in 'Shaders' started by Subliminum, Feb 1, 2018.

  1. Subliminum

    Subliminum

    Joined:
    Nov 9, 2017
    Posts:
    98
    I am having trouble implementing a lerp between a RWtexture2D value and a static green float4(0,1,0,0) in a compute shader.
    This code is what i am trying to get working but any interpolation values other then 0 or 1 return a black texture. Can someone help me with this frustratingly simple seeming issue??
    As i see it this should make the texture slightly greener every dispatch until completely green?

    Code (CSharp):
    1.  
    2. RWTexture2D<float4> Result;
    3.  
    4. [numthreads(8,8,1)]
    5. void PhysicsUpdate(uint3 id : SV_DispatchThreadID)
    6. {
    7.     float4 newVector = lerp(Result[int2(id.x, id.y)], float4(0, 1, 0, 0), 0.01);
    8.    
    9.     Result[id.xy] = newVector;
    10. }
     
  2. Subliminum

    Subliminum

    Joined:
    Nov 9, 2017
    Posts:
    98
    It seems as though any operations regarding the Result[id.xy] float4 value will return a black texture. Could it be how ive set up the RWTexture2D perhaps?

    Code (CSharp):
    1.  public ComputeShader calculationEngine;
    2. public Texture2D initialInput;
    3. public RenderTexture texture;
    4.  
    5. private int physicsSimID;
    6.  
    7. public RenderTexture getSimulatedTexture
    8. {
    9.     get { return texture; }
    10. }
    11.  
    12. void Start()
    13. {
    14.         physicsSimID = calculationEngine.FindKernel("PhysicsUpdate");
    15.         int overwrite = calculationEngine.FindKernel("FlashInput");
    16.         texture = new RenderTexture(initialInput.width, initialInput.height, 24);
    17.         texture.wrapMode = TextureWrapMode.Repeat;
    18.         texture.enableRandomWrite = true;
    19.         texture.Create();
    20.  
    21.         //Assign shader variables
    22.         calculationEngine.SetFloat("Width", texture.width);
    23.         calculationEngine.SetFloat("Height", texture.height);
    24.  
    25.         calculationEngine.SetTexture(physicsSimID, "Result", texture);
    26.         calculationEngine.SetTexture(overwrite, "Input", initialInput);
    27.         calculationEngine.SetTexture(overwrite, "Result", texture);
    28.  
    29.         calculationEngine.Dispatch(overwrite, texture.width / 8, texture.height / 8, 1);
    30.         simulatedMaterial.SetTexture("_DeformationMap", texture);
    31.  
    32.     }
    33.  
    34.     void FixedUpdate()
    35.     {
    36.         calculationEngine.SetFloat("ElapsedTime", Time.fixedDeltaTime);
    37.         calculationEngine.Dispatch(physicsSimID, texture.width / 8, texture.height / 8, 1);
    38.     }
    If there's any compute shader genius' out there i could really use some assistance on this.
     
  3. scrawk

    scrawk

    Joined:
    Nov 22, 2012
    Posts:
    765
    I presume you expected your render texture to slowly fade to green as a check your code is working.

    The code you provided does work for me.

    Heres the code I used to check it. Its basically the same as yours but I prefer to just display the render texture so I changed the alpha to 1 on your lerp color.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class Test : MonoBehaviour
    6. {
    7.  
    8.     public ComputeShader calculationEngine;
    9.     public RenderTexture texture;
    10.  
    11.     private int physicsSimID;
    12.  
    13.     public RenderTexture getSimulatedTexture
    14.     {
    15.         get { return texture; }
    16.     }
    17.  
    18.     void Start()
    19.     {
    20.         physicsSimID = calculationEngine.FindKernel("PhysicsUpdate");
    21.         texture = new RenderTexture(256, 256, 24);
    22.         texture.wrapMode = TextureWrapMode.Repeat;
    23.         texture.enableRandomWrite = true;
    24.         texture.Create();
    25.  
    26.         calculationEngine.SetTexture(physicsSimID, "Result", texture);
    27.  
    28.     }
    29.  
    30.     void FixedUpdate()
    31.     {
    32.         calculationEngine.SetFloat("ElapsedTime", Time.fixedDeltaTime);
    33.         calculationEngine.Dispatch(physicsSimID, texture.width / 8, texture.height / 8, 1);
    34.     }
    35.  
    36.     void OnGUI()
    37.     {
    38.         GUI.DrawTexture(new Rect(0, 0, 256, 256), texture);
    39.     }
    40.  
    41. }
    And the shader,

    Code (CSharp):
    1. #pragma kernel PhysicsUpdate
    2.  
    3. RWTexture2D<float4> Result;
    4.  
    5. [numthreads(8, 8, 1)]
    6. void PhysicsUpdate(uint3 id : SV_DispatchThreadID)
    7. {
    8.     float4 newVector = lerp(Result[int2(id.x, id.y)], float4(0, 1, 0, 1), 0.01);
    9.  
    10.     Result[id.xy] = newVector;
    11. }

    If it still does not work for you then it might be because your reading and writing to/from the same texture which can be a problem in some cases.
     
  4. Subliminum

    Subliminum

    Joined:
    Nov 9, 2017
    Posts:
    98
    hmmm could it be that i have two kernels accessing the Result texture? They have been both set as follows
    Code (CSharp):
    1.         calculationEngine.SetTexture(physicsSimID, "Result", texture);
    2.         calculationEngine.SetTexture(overwrite, "Input", initialInput);
    3.         calculationEngine.SetTexture(overwrite, "Result", texture);
    Assuming it is just the read/write issue and not related to multiple kernels how would i go about fixing this or is there a work around that you know of?
    Thanks
     
  5. scrawk

    scrawk

    Joined:
    Nov 22, 2012
    Posts:
    765
    Might be the multiple kernels with same texture name. Can’t remember if that’s allowed. I normally have seperate texture names for each kernel.

    Try using a seperate texture name for the other kernel. You can still bind the same texture to it.

    In some graphics api’s reading and writing to the same texture in the same shader is undefined and its up to the vendor to decide how it’s handled meaning it works on some GPU’s but not others. That’s the case with OpenGL anyway. Not sure if it’s ok in direct compute but it’s generally not a great idea.

    If your writing a simulation you will most likely need to sample the neighbour pixels at some point and that’s definitely going to cause a issue if you read write to same texture as it won’t be thread safe. (Thread group execution order is undefined).

    You will have to use two texture and ping/pong between them, read from one and write into the other. Then in the next frame do the opposite.
     
  6. Subliminum

    Subliminum

    Joined:
    Nov 9, 2017
    Posts:
    98
    That does make alot of sense since i was following a tutorial and this technique worked in that but not for me. I guess the graphics card will be the problem but id like to try the names also. If that doesnt work i suppose structured buffers would be the way to go?
     
  7. scrawk

    scrawk

    Joined:
    Nov 22, 2012
    Posts:
    765
    A structured buffer might work. Not sure but I think the read/write rules are better defined and is allowed but dont quote me on that.

    If your going to need to try and access the neighbor values in your simulation you will still get a race condition (ie wont be thread safe) so you will still need to ping/pong.

    Heres some examples if you need it. All the GPU ones use this method, some in normal shader, some in compute shaders and some with textures and some with buffers.
     
  8. Subliminum

    Subliminum

    Joined:
    Nov 9, 2017
    Posts:
    98
    I dont need to access neighbour values just to turn each one progressively greener. Awesome link ill have a look into these and see if i cant come up with an efficient solution haha.
    Thanks heaps for your advice and time Scrawk!
     
  9. Subliminum

    Subliminum

    Joined:
    Nov 9, 2017
    Posts:
    98
    So ive looked through the examples in that link and i cant seem to find any compute shaders doing the "ping-pong" technique amongst them? Forgive me if ive missed it but could you chuck up some psuedo code for this? Im not sure how to switch between the RWTexture2Ds in the compute shader?
     
  10. scrawk

    scrawk

    Joined:
    Nov 22, 2012
    Posts:
    765
    Its all done in the script not the shader. This is a example from the 3D fluid sim which uses buffers. Look in the FireFluidSim or SmokeFluidSim scripts.

    Notice the buffers are declared as arrays and have a size of 2. READ is 0, and WRITE is 1. Both are const.

    Code (CSharp):
    1. ComputeBuffer[] m_density

    In the shader theres a read and write buffer. You only ever read from one and write to the other.

    Code (CSharp):
    1.          
    2. m_applyImpulse.SetBuffer(0, "_Read", buffer[READ]);
    3. m_applyImpulse.SetBuffer(0, "_Write", buffer[WRITE]);
    4.  
    And the shader....

    Code (CSharp):
    1.  
    2. #define NUM_THREADS 8
    3.  
    4. float4 Size;
    5.  
    6. StructuredBuffer<float> _Read;
    7. RWStructuredBuffer<float> _Write;
    8.  
    9. #pragma kernel GaussImpulse
    10.  
    11. [numthreads(NUM_THREADS,NUM_THREADS,NUM_THREADS)]
    12. void GaussImpulse(uint3 id : SV_DispatchThreadID)
    13. {
    14.     float amount = whaterever;
    15.              
    16.     int idx = id.x + id.y*_Size.x + id.z*_Size.x*_Size.y;
    17.  
    18.     _Write[idx] = _Read[idx] + amount;
    19. }
    After the shader runs you swap the buffers in the array so the next frame reads from the one that was previously written to.

    Code (CSharp):
    1.  
    2. m_applyImpulse.Dispatch(0, (int)m_size.x/NUM_THREADS, (int)m_size.y/NUM_THREADS, (int)m_size.z/NUM_THREADS);
    3.          
    4.  Swap(buffer);
    5.  
    And swap is like so...

    Code (CSharp):
    1.      
    2. void Swap(ComputeBuffer[] buffer)
    3. {
    4.   ComputeBuffer tmp = buffer[READ];
    5.   buffer[READ] = buffer[WRITE];
    6.   buffer[WRITE] = tmp;
    7. }
    8.  
    Its the swapping that the name ping/ponging comes from.

    Not saying you have to do it like that but its always been a flexible design for myself.
     
    Subliminum likes this.
  11. Subliminum

    Subliminum

    Joined:
    Nov 9, 2017
    Posts:
    98
    Thanks a lot for the added explanation I think i have a much better idea now haha. Ill see if i cant get this method working to see what performance is like. Cheers :)
     
  12. moreilly

    moreilly

    Joined:
    Aug 2, 2018
    Posts:
    3
    Thanks a lot for this information, you save my day!
     
    Subliminum likes this.