Search Unity

Question Setting buffer data from inside fragment shader to be read from C# script (URP)

Discussion in 'Shaders' started by SpikeEscape, Jan 23, 2022.

  1. SpikeEscape

    SpikeEscape

    Joined:
    Mar 14, 2019
    Posts:
    5
    Hello all, I've been researching this for days now and the consensus seems to be that it is possible but can be very slow, however in all my attempts I have been unable to read any data at all from the CPU side of things.

    My end goal is to return a float either per instance or per pixel of an instance which indicated whether or not it is in shadow from a particular light, I have the calculation working from running AdditionalLightRealtimeShadow() which returns 0 when in light and 1 when in shadow.

    This works perfectly on the GPU and I am able to adjust say the color of the material based on whether or not the certain pixel is in light, but I want to pass this data to the CPU to apply gameplay logic when a certain percentage of the object is hit by light.

    I have tried creating a StructuredBuffer in the shader and setting a ComputeBuffer in c#, setting it with a Material.SetBuffer() call, after more research I found that Material.SetBuffer actually does not read from the GPU. I then looked into ComputeShaders which are be able to read from the GPU however I would need to pass my data from my fragment shader to the compute shader, and maybe I am not understanding correctly but my research has led me to think that in order to pass that data it would have to go through the CPU anyways. (Which I am struggling to do in the first place)

    Another work around I have heard of is to create a custom rendertexture and draw the frag shader to it, and then read the rendertexture from the cpu with .GetPixels(), I'd like to avoid this as my shader is coupled with my custom lighting shader to access the lighting data, and I would like to keep it on the main renderer.

    Does anyone know of the right way to reference a buffer in frag shaders from C#?
    Or any other work around to pass data from the GPU to CPU? I have also of the idea of using
    Stream Output Compatibility and I'm curious if that is a rabbit hole worth going down.
     
  2. cgDoofus

    cgDoofus

    Joined:
    Jun 15, 2021
    Posts:
    48
    I'm not sure if fragment shaders can actually write to buffers but in compute your buffer has to be a RWStructuredBuffer as opposed to StructuredBuffer if you want to read this data on the CPU side, and in C# you should call GetData() to populate an array with what's in the gpu buffer.
     
  3. Invertex

    Invertex

    Joined:
    Nov 7, 2013
    Posts:
    1,550
    You can write to a buffer from a fragment shader, but like @cgDoofus says, it needs to be a RWStructuredBuffer or RW buffer in general. And from C# you need to bind a buffer to it like you do with a compute shader, except instead use the .SetBuffer that exists on the target material, like
    myMat.SetBuffer("_shadowPixelsBuffer", shadowPixelsBuffer);


    You also need to specify a binding register to use in your shader, for example using the first register:
    uniform RWStructuredBuffer<uint> _shadowPixelsBuffer : register(u1);


    And in your C# code, when you bind the buffer here, you also need to indicate the register number and set the third parameter true to indicate binding to the fragment stage:
    Graphics.SetRandomWriteTarget(1, shadowPixelsBuffer, true);


    You'll probably just want each pixel to do an
    InterlockedAdd()
    on a
    uint
    in that buffer and then you'll know how many shadow pixels accumulated.
     
    Last edited: Jan 24, 2022
    NagiX and SpikeEscape like this.