Search Unity

Using RWTexture2D<float4> in vertex fragment shaders

Discussion in 'Shaders' started by Tudor, May 17, 2018.

  1. Tudor

    Tudor

    Joined:
    Sep 27, 2012
    Posts:
    107
    A while ago I was successfully able to implement `RWStructuredBuffer`s in vert frag shaders with `#pragma target 5.0` and `#pragma only_renderers d3d11`. It took some stumbling to discover you need `RWStructuredBuffer<MyStruct> myBuff : register(u1)` and then in C# `Graphics.SetRandomWriteTarget(1, myComputeBuffer, true);` etc..

    But I can't get `RWTexture2D<float4>` to work and there's no way to figure out what's wrong unless you magically already know.
    In C# I have:
    Code (CSharp):
    1.  
    2. m_paintAccumulationRT = new RenderTexture(rtWH_x, rtWH_y, 24, RenderTextureFormat.ARGB32);// Must be ARGB32 but will get automagically converted to float or float4 or int or half, from your shader code declaration.
    3. m_paintAccumulationRT.name = _MainTexInternal;
    4. m_paintAccumulationRT.enableRandomWrite = true;
    5. m_paintAccumulationRT.Create();
    6.  
    Then when I render, also C#:
    Code (CSharp):
    1.  
    2. m_paintAccumulatorCB.DrawMesh(m_testMeshToDraw.GetComponent<SkinnedMeshRenderer>().sharedMesh,
    3.                              Matrix4x4.TRS(m_testMeshToDraw.position, m_testMeshToDraw.rotation, m_testMeshToDraw.localScale),
    4.                              m_2DSprayAccumulator_mat,
    5.                              0,
    6.                              0);
    7.  
    Then my vertex fragment CG shader code:
    Code (CSharp):
    1.  
    2. uniform RWTexture2D<float4> _MainTexInternal;// : register(u2);
    3. //...
    4. #pragma target 5.0
    5. #pragma only_renderers d3d11
    6. //...
    7. float4 frag(v2f i) : SV_Target
    8. {
    9.         int coordx = int((mainUV.x)*2048);
    10.         int coordy = int((mainUV.y)*2048);
    11.         int2 mainUVRWT = int2(coordx,coordy);
    12.  
    13.         float4 prevColor = _MainTexInternal[mainUVRWT];// Doesn't work, but maybe it's not meant to be read this way.
    14. //...
    15. _MainTexInternal[mainUVRWT] += outColor;
    16. return _MainTexInternal[mainUVRWT]; // THIS WORKS, but is reset every frame (ie no "paint accumulation" is happening despite the +=)
    17. }
    The thing is, if I look at my `m_paintAccumulationRT` in C# / unity, no matter what I try in the shader or c#, I only see black, or sometimes white.

    Also note that I cannot use Blit. Don't recommend Blit. I need to write to a RenderTexture from a Mesh in the world (inlcuding backfaces). I also know I can use compute shaders, no need to recommend those.

    Has someone ever figured this out in unity? Does anyone have an actual working example? Or I'll just give up and use RWStructuredBuffers.
     
    Last edited: May 25, 2018
  2. Tudor

    Tudor

    Joined:
    Sep 27, 2012
    Posts:
    107
    I couldn't get the commandBuffer.DrawMesh to behave right, it was flickery and artifacty.

    But one solution, in which you can write to RWTextures from a vert frag shader in a simple material on a game object, no command buffers, is like this:

    Code (CSharp):
    1.  
    2. m_paintAccumulationRT = new RenderTexture(rtWH_x, rtWH_y, 24, RenderTextureFormat.ARGB32);// Must be ARGB32 but will get automagically converted to float or float4 or int or half, from your shader code declaration.
    3. m_paintAccumulationRT.name = _MainTexInternal;
    4. m_paintAccumulationRT.enableRandomWrite = true;
    5. m_paintAccumulationRT.Create();
    6.  
    7. m_PaintAccumulator_mat.SetTexture(_MainTexInternal, m_paintAccumulationRT);
    8. Graphics.ClearRandomWriteTargets();
    9. Graphics.SetRandomWriteTarget(2, m_paintAccumulationRT);//with `, true);` it doesn't take RTs
    10. // this is the Setup function, no need to do this every frame
    11.  
    The SetRandomWriteTarget function does not take RenderTargetIdentifiers, and the 3rd bool parameter hacks mentioned here https://forum.unity.com/threads/how...ss-compute-buffer-from-a-pixel-shader.403783/ are no longer valid (at least not for RenderTextures).
    The number 2 must match the uav number you defined in the shader `uniform RWTexture2D<float4> _MainTexInternal : register(u2);`

    That's it. You should now be able to read and write to RWTextures from your shader. Both read and write syntax looks like this: `_MainTexInternal[int2(int((mainUV.x)*2048),int((mainUV.y)*2048))]`.
     
  3. Tudor

    Tudor

    Joined:
    Sep 27, 2012
    Posts:
    107
    Note:
    - People and docs say that in Compute shaders it seems you can't read from UAV `RWTexture`s (only write).
    - In Vert Frag shaders with pragma 5.0, I tested and you CAN read from `RWTexture`. But you still read it in an unfiltered way, with `int,int` indexes. Had trouble with reading from the RT back in the C#/unity side though; the rt shows as black.

    So it seems the proper way to read from a `RWTexture` in the shader is to declare a separate regular texture in your shader, and from C# bind the same RenderTexture to both the `RWTexture` slot and the regular `Texture2D`. Then you can sample from the `Texture2D` in as you do normally, with UVs.

    source example: https://forum.unity.com/threads/converting-a-shadertoy-multipass-shader-to-unity-hlsl.418238/
     
    Last edited: Feb 5, 2019