- Home /
Send float value from shader to script?
Can a shader give information to the CPU code? For example, a shader function returns some information which the shader itself can't do anything with, but would be valuable information in CPU code. Where do I store that value to have access to it from script?
Answer by Eudaimonium · Sep 06, 2015 at 09:59 PM
You cannot. Shaders are part of the graphics "pipeline", which means that you input the info to the GPU and it'll render stuff on screen.
It's a one-way ticket.
Problem is, you cannot simply return one float (even if you could return some data). Remember, shader program is executed once per every vertex of a game object + once for every pixel of game object displayed on screen.
So if your object covers half the screen on a fullHD resolution, a pixel shader would theoretically "return" ~1 million different float values.
You can actually return some values from GPU back to main memory, but it's special GPGPU computing area and not GPU accelerated graphics, which is what we're dealing with in a game engine.
Note: I do not know how ComputeShader works though.
Now, if you need that info for some debugging to find out what's going on in your shader code, best way is to actually render out the float values in some meaningful way. Suppose you want to find out if a particular pixel is too close to the camera, and you need to find out what's the depth value.
Simply have the pixel shader program write a red pixel if the value is too close, or return normal colored pixel if value is too far.
Pixels that are too close will be red. Shader debugging is often composed of such various hacks and workarounds.
If you absolutely must analize your shader input in total detail, you can write the needed values into seperate RenderTarget texture, and then read that texture using the CPU. Note: Reading the RenderTexture values via CPU is EXTREMELY slow and will kill performance. I recommend avoiding that.
Could you please give an example of RenderTarget texture approach? How to 1) write to it in the hlsl and 2) read it whole in the C#?
I'd really love to have the ability to send the data from shader to RA$$anonymous$$, however slow and hacky, to use at least once in editor, and your last paragraph is the most promising thing that I found!
Yeah, you can use the Texture2D.GetPixels method: https://docs.unity3d.com/ScriptReference/Texture2D.GetPixels.html
RenderTexture inherits from Texture2D so it also has the same method. There is also a way to do this asyncronously to avoid stalling the main thread but it's significantly more complex: https://unity.xiaomo.info/ScriptReference/Rendering.AsyncGPUReadbackRequest.html
So reading (receiving) on cpu is actually simple. What I didn't realise was that "sending" on GPU is actually rendering ( returning data as a color in fragment shader).
This is not ideal, because surface shaders, or the new shader graph do not let you control exactly what is returned from fragment shader. Now I am using Albedo to pass my value, and it sort of works: 0.5 on gpu -> .498 on cpu. But it is still scary that lighting can interfere with it.
EDIT: Also, looks like I get only 3 digits per color value this way :) - since with shader graph (and I imagine surface shaders too), the resulting fragment color value is truncated to [0, 1], and frag shaders there use half3, which allows only for 3 digits of precision
Anyway, here's the example for that, from this thread.
if (!TryGetComponent(out $$anonymous$$eshRenderer renderer))
{
return;
}
// all colors should be the same since you should return a constant debug value, so we need only one pixel/color
int width = 1;
int height = 1;
RenderTexture buffer = new RenderTexture(
width,
height,
0, // No depth/stencil buffer
RenderTextureFormat.ARGB32, // Standard colour format
RenderTextureReadWrite.Linear // No sRGB conversions
);
Graphics.Blit(null, buffer, renderer.shared$$anonymous$$aterial);
Texture2D outputTex = new Texture2D(width, height, TextureFormat.ARGB32, false);
RenderTexture.active = buffer; // If not using a scene camera
outputTex.ReadPixels(
new Rect(0, 0, width, height), // Capture the whole texture
0, 0, // Write starting at the top-left texel
false // No mipmaps
);
// this is what you return in your fragment shader
Debug.Log($"Shader Debug Color: {string.Join(",", outputTex.GetPixels()[0])}");
Then, each color of the array will hold the value returned by your fragment shader.
Your answer
Follow this Question
Related Questions
Shader Graph - How to draw Circle? 1 Answer
Shader - Render to Texture 0 Answers
How to locate the correct texture within a texture atlas using a shader? 1 Answer
Unity crashes when trying to run depth shader with color lerp. 1 Answer
I am Getting Error in shader how can i solve it,I am Getting error in Shader 0 Answers