Multiple fragment outputs

Hey!

As I continue studying shaders I’m trying to convert a shader from some dialect (probably GLSL?) to Unity. It seems that in Unity fragment shaders can only output one value - usually fixed4 - in most examples I’ve seen.

void frag (
  in float2 texcoord : TEXCOORD0,
  out float4 col : COLOR0,
  out float4 col1 : COLOR1 ) {
}

My question: is there a workaround in Unity Shaderlab/CG code for this kind of fragment shader that outputs multiple values? Is there a way to output multiple (color) values?

Yes you can render into multiple targets, up to 4 in Unity.

This is a script I use for multitarget blitting in Unity. Its the Middle function that you will most likely will need to use.

using UnityEngine;
using System.Collections;

/// <summary>
/// A helper class for render textures related functions
/// </summary>
static public class RTUtility
{

    /// <summary>
    /// Replicates Unity's Graphics.Blit function.
    /// </summary>
    /// <param name="des">The destination render texture.</param>
    /// <param name="mat">The amterial to use</param>
    /// <param name="pass">Which pass of the materials shader to use.</param>
    static public void Blit(RenderTexture des, Material mat, int pass = 0)
    {
        RenderTexture oldRT = RenderTexture.active;
      
        Graphics.SetRenderTarget(des);

        GL.Clear(true, true, Color.clear);

        GL.PushMatrix();
        GL.LoadOrtho();
      
        mat.SetPass(pass);

        GL.Begin(GL.QUADS);
        GL.TexCoord2(0.0f, 0.0f); GL.Vertex3(0.0f, 0.0f, 0.1f);
        GL.TexCoord2(1.0f, 0.0f); GL.Vertex3(1.0f, 0.0f, 0.1f);
        GL.TexCoord2(1.0f, 1.0f); GL.Vertex3(1.0f, 1.0f, 0.1f);
        GL.TexCoord2(0.0f, 1.0f); GL.Vertex3(0.0f, 1.0f, 0.1f);
        GL.End();

        GL.PopMatrix();

        RenderTexture.active = oldRT;
    }

    /// <summary>
    /// Renders into a array of render textures using multi-target blit.
    /// Up to 4 render targets are supported in Unity but some GPU's can
    /// support up to 8 so this may change in the future. You MUST set up
    /// the materials shader correctly for multitarget blit for this to work.
    /// </summary>
    /// <param name="des">The destination render textures.</param>
    /// <param name="mat">The amterial to use</param>
    /// <param name="pass">Which pass of the materials shader to use.</param>
    static public void MultiTargetBlit(RenderTexture[] des, Material mat, int pass = 0)
    {
        RenderBuffer[] rb = new RenderBuffer[des.Length];

        // Set targets needs the color buffers so make a array from
        // each textures buffer.
        for(int i = 0; i < des.Length; i++)
            rb[i] = des[i].colorBuffer;

        //Set the targets to render into.
        //Will use the depth buffer of the
        //first render texture provided.
        Graphics.SetRenderTarget(rb, des[0].depthBuffer);

        GL.Clear(true, true, Color.clear);

        GL.PushMatrix();
        GL.LoadOrtho();
      
        mat.SetPass(pass);

        GL.Begin(GL.QUADS);
        GL.TexCoord2(0.0f, 0.0f); GL.Vertex3(0.0f, 0.0f, 0.1f);
        GL.TexCoord2(1.0f, 0.0f); GL.Vertex3(1.0f, 0.0f, 0.1f);
        GL.TexCoord2(1.0f, 1.0f); GL.Vertex3(1.0f, 1.0f, 0.1f);
        GL.TexCoord2(0.0f, 1.0f); GL.Vertex3(0.0f, 1.0f, 0.1f);
        GL.End();

        GL.PopMatrix();

    }

    /// <summary>
    /// Renders into a array of render buffers using multi-target blit
    /// and the provided depth buffer for depth testing.
    /// Up to 4 render targets are supported in Unity but some GPU's can
    /// support up to 8 so this may change in the future. You MUST set up
    /// the materials shader correctly for multitarget blit for this to work.
    /// </summary>
    /// <param name="des_rb">The destination render buffers to use.</param>
    /// <param name="des_db">The destination depth buffer to use.</param>
    /// <param name="mat">The material to use</param>
    /// <param name="pass">Which pass of the materials shader to use.</param>
    static public void MultiTargetBlit(RenderBuffer[] des_rb, RenderBuffer des_db, Material mat, int pass = 0)
    {

        //Set the targets to render into.
        //Will use the depth buffer provided, des_db.
        Graphics.SetRenderTarget(des_rb, des_db);

        GL.Clear(true, true, Color.clear);

        GL.PushMatrix();
        GL.LoadOrtho();
      
        mat.SetPass(pass);

        GL.Begin(GL.QUADS);
        GL.TexCoord2(0.0f, 0.0f); GL.Vertex3(0.0f, 0.0f, 0.1f);
        GL.TexCoord2(1.0f, 0.0f); GL.Vertex3(1.0f, 0.0f, 0.1f);
        GL.TexCoord2(1.0f, 1.0f); GL.Vertex3(1.0f, 1.0f, 0.1f);
        GL.TexCoord2(0.0f, 1.0f); GL.Vertex3(0.0f, 1.0f, 0.1f);
        GL.End();

        GL.PopMatrix();

    }


}

and the shader…

Shader "MultitargetBlit"
{
    SubShader
    {
        Pass
        {
            ZTest Always Cull Off ZWrite Off
              Fog { Mode off }

            CGPROGRAM
            #include "UnityCG.cginc"
            #pragma vertex vert
            #pragma fragment frag
          
            struct v2f
            {
                float4  pos : SV_POSITION;
                float2  uv : TEXCOORD0;
            };
          
            struct f2a
            {
                float4 col0 : COLOR0;
                float4 col1 : COLOR1;
                float4 col2 : COLOR2;
                float4 col3 : COLOR3;
            };

            v2f vert(appdata_base v)
            {
                v2f OUT;
                OUT.pos = mul(UNITY_MATRIX_MVP, v.vertex);
                OUT.uv = v.texcoord.xy;
                return OUT;
            }
          
          
            f2a frag(v2f IN)
            {
              
                f2a OUT;
              
                OUT.col0 = someColor0;
                OUT.col1 = someColor1;
                OUT.col2 = someColor2;
                OUT.col3 = someColor3;
              
                return OUT;
            }
          
            ENDCG

        }
    }
}
1 Like

Perfect! Thank you very much!