Using RWTexture2D<float4> in vertex fragment shaders

A while ago I was successfully able to implement RWStructuredBuffers 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:

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.
m_paintAccumulationRT.name = _MainTexInternal;
m_paintAccumulationRT.enableRandomWrite = true;
m_paintAccumulationRT.Create();

Then when I render, also C#:

m_paintAccumulatorCB.DrawMesh(m_testMeshToDraw.GetComponent<SkinnedMeshRenderer>().sharedMesh,
                             Matrix4x4.TRS(m_testMeshToDraw.position, m_testMeshToDraw.rotation, m_testMeshToDraw.localScale),
                             m_2DSprayAccumulator_mat,
                             0,
                             0);

Then my vertex fragment CG shader code:

uniform RWTexture2D<float4> _MainTexInternal;// : register(u2);
//...
#pragma target 5.0
#pragma only_renderers d3d11
//...
float4 frag(v2f i) : SV_Target
{
        int coordx = int((mainUV.x)*2048);
        int coordy = int((mainUV.y)*2048);
        int2 mainUVRWT = int2(coordx,coordy);
 
        float4 prevColor = _MainTexInternal[mainUVRWT];// Doesn't work, but maybe it's not meant to be read this way.
//...
_MainTexInternal[mainUVRWT] += outColor;
return _MainTexInternal[mainUVRWT]; // THIS WORKS, but is reset every frame (ie no "paint accumulation" is happening despite the +=)
}

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.

4 Likes

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:

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.
m_paintAccumulationRT.name = _MainTexInternal;
m_paintAccumulationRT.enableRandomWrite = true;
m_paintAccumulationRT.Create();

m_PaintAccumulator_mat.SetTexture(_MainTexInternal, m_paintAccumulationRT);
Graphics.ClearRandomWriteTargets();
Graphics.SetRandomWriteTarget(2, m_paintAccumulationRT);//with `, true);` it doesn't take RTs
// this is the Setup function, no need to do this every frame

The SetRandomWriteTarget function does not take RenderTargetIdentifiers, and the 3rd bool parameter hacks mentioned here How to write to an unordered access compute buffer from a pixel shader? 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 Likes

Note:

  • People and docs say that in Compute shaders it seems you can’t read from UAV RWTextures (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: Converting a Shadertoy Multipass shader to Unity / HLSL ?

3 Likes