Bizarre behaviour of Graphics.Blit and custom GL blit

I’m getting some strange behaviour from Graphics.Blit which I can’t understand, I’m surely missing something here but I can’t wrap my head around it. Basically, what I want is:

  • Mix a pair of textures to a render texture (compositedRT), using a material (compositorMaterial).
  • Output that render texture to the screen.

All the textures have been created correctly and have the correct data, and the materials already have the textures correctly assigned to their properties. For testing, the shaders output some colors that allow me to know what is going on. So this is what I did:

Graphics.Blit(null, compositedRT, compositorMaterial);
Graphics.Blit(null, null, textureOutputMaterial);

However, the first blit doesn’t output anything to compositedRT, I mean, it doesn’t even output the color that the shader from compositorMaterial must, I only get a black texture.

So then I thought about making my own blit method, like this:

public static class GraphicsUtils
{
    public static void Blit(RenderTexture destination, Material material, int materialPass = 0)
    {
        if (material.SetPass(materialPass))
        {
            // This is always reached.
            Graphics.SetRenderTarget(destination);

            GL.PushMatrix();
            GL.LoadOrtho();
           
            GL.Begin(GL.QUADS);
            {
                Vector3 coords = new Vector3(0, 0, 0);
                GL.TexCoord(coords);
                GL.Vertex(coords);
               
                coords = new Vector3(1, 0, 0);
                GL.TexCoord(coords);
                GL.Vertex(coords);
               
                coords = new Vector3(1, 1, 0);
                GL.TexCoord(coords);
                GL.Vertex(coords);
               
                coords = new Vector3(0, 1, 0);
                GL.TexCoord(coords);
                GL.Vertex(coords);
            }
            GL.End();

            GL.PopMatrix();
        }
    }
}

And then I tried to do this:

GraphicsUtils.Blit(compositedRT, compositorMaterial);
GraphicsUtils.Blit(null, textureOutputMaterial);

With this I got the first blit correct, but the second acted as if the input texture was completely black, though the output was correct for a black texture.

So when I use the custom blit for the first pass and the bult-in one for the second one, I get the desired result… But it’s odder than that, because if for the second pass I repeat the custom blit, I get the correct result:

GraphicsUtils.Blit(compositedRT, compositorMaterial);
GraphicsUtils.Blit(null, textureOutputMaterial);
GraphicsUtils.Blit(null, textureOutputMaterial);

What’s going on?

PS: It doesn’t matter if in any of the passes I output to a render texture or the screen, the effect is the same.

I have found something, though it doesn’t explain all: since it’s a VR project, I was creating the compositedRT texture using vrUsage = VRTextureUsage.TwoEyes, which probably I shouldn’t do; if I remove it, Graphics.Blit works for both passes, though my custom blit only works for the first one and not for the second, except if I do the second twice.

In your Graphics.Blit snippet, you don’t have any source texture set. What texture are you trying to copy? Graphics.Blit will set the _MainTex property of the material to point to the source texture.

-sam

The textures are already set to the corresponding material properties.

Yeah but Graphics.Blit will set whatever source texture you specify on the _MainTex property on the material. If you aren’t using _MainTex then I suppose that’s fine but if you are Graphics.Blit will override it with null.

-sam

It would seem that Graphics.Blit would override the texture, but I checked it and when passing null it doesn’t actually change _MainTex.

The problem lies in back-face culling. Changing the order of the quad’s vertices fixes the code which you posted. It would also be fixed by adding “Cull Off” in the shader which might be a better idea since Unity’s documentation states that “The culling rules may be different depending on which graphics API”.
Unity - Scripting API: GL.Begin (unity3d.com)

I ended up with the following code for blit:

        RenderTexture oldRT = RenderTexture.active;
        Graphics.SetRenderTarget(destination);
        GL.PushMatrix();
        GL.LoadOrtho();
        material.SetPass(0);
        GL.Begin(GL.QUADS);
        {
            GL.TexCoord2(0, 1);
            GL.Vertex(new Vector3(0, 1, 0));

            GL.TexCoord2(1, 1);
            GL.Vertex(new Vector3(1, 1, 0));

            GL.TexCoord2(1, 0);
            GL.Vertex(new Vector3(1, 0, 0));

            GL.TexCoord2(0, 0);
            GL.Vertex(new Vector3(0, 0, 0));          
        }
        GL.End();
        GL.PopMatrix();
        RenderTexture.active = oldRT;