(Because of course I find it immediately after posting this question)
I figured I would leave this up as interim documentation while these macros aren’t in the Unity docs. Also, I am much more familiar with D3D and OGL renderers than with Vulkan or Metal, although the usage of the macros should be the same (their implementations will differ). I’m developing primarily for DX11 on an NVidia card, but hopefully (most of) this will apply no matter what renderer you’re using. I’m also leaving this question open in case anyone with more knowledge or a different renderer has something to point out.
UNITY_READ_FRAMEBUFFER_INPUT(x) is defined in HLSLSupport.cginc, along with:
(Where idx is the ID of the input (not the name) of the input)
I’m not quite sure why I thought it would be using the name of the variable, I guess I was tired. These macros take the index of the input as it is in the input array.
The actual read macro is somewhat misrepresented in the docs, as it actually has 2 inputs:
Vulkan and metal additionally have
_MS versions of all of these macros for multisampling. Vulkan/Metal’s
UNITY_READ_FRAMEBUFFER_INPUT_MS(idx, sampleIdx, v2fname) also takes a 3rd parameter to specify which sample to read. These do not exist on D3D or other renderers.
D3D, OpenGL, etc. use the general fallback for obtaining renderpass inputs. The Declare macros all declare a sampler-less Texture2D object, and the Read macro performs Texture2D.Load().
This is an important distinction when working with RenderPasses and Framebuffers, vs RenderTextures!
Most of the time, texture data is obtained via Texture2D.Sample(float2 uv), which is fed UV Coordinates in the [0-1] range.
Texture2D.Load(int3 coords) is different. It returns exact pixel data, given Pixel Coordinates in the [0-texsize] range, and doesn’t perform any filtering or access modification.
These coordinates can be obtained via the
SV_Position (frag shader) semantics, or, assuming you know the texture size (_ScreenParams.xy usually), UV coordinates can just be multiplied by that size and cast to an int.
(On a slightly unrelated note, it might be a good idea to use Load in fullscreen effects anyway, if you don’t actually need sampling, since its a fair bit faster than Sample() and consists only of a single fetch from memory, while Sample() does a lot more.)
There is an additional restriction to using these macros:
The rendering results of previous subpasses are only available within the same screen-space pixel coordinate
This means no convolutions, no refraction or distortion, nothing but the previous value of the pixel that you are on. Accessing other pixels is entirely dependent on renderer, hardware, stupid things like per-pixel race conditions, etc., and you probably won’t get anything at all. This means that pretty much all of the ‘interesting’ postFX will require you to bind your RenderTargetAttachment to a RenderTexture first, and things like this should probably be given a bit of distance from your SRP anyway. Deferred lighting, megatexturing, etc. can still be done.
This is by no means an exhaustive documentation of this macro, and I’m sure I made some mistakes, so please leave a comment or edit if you have more info or I made an error.
Good luck and Happy rendering!