1
\$\begingroup\$

Context

I am writing a ScriptableImporter for a specialised image format (the specifics are not relevant). To do so, I need to semi-procedurally generate a Texture2D. My idea was to create a RenderTexture at import time, and call Graphics.Blit(source, destination, material) on it using a material whose shader that does the procedural part of the job.

I acknowledge this might be a bit of an XY problem, so I will also accept an answer that generally allows me to render a shader to a texture of a specified size, which is the actual use case.

I am using Unity 2022.2.19 and Universal Render Pipeline 14.0.7.

The problem

However, I don't seem to be able to use this technique. I created a trivial shader that simply outputs white everywhere, and called Blit with it, yet the texture is still completely black.

The code

Test shader

Shader "Hidden/My Cool Shader"
{
    Properties
    {
        _MainTex("InputTex", 2D) = "white" {}
     }

     SubShader
     {
        Blend One Zero

        Pass
        {
            Name "Blit"

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma target 3.0

            sampler2D _MainTex;
            
            struct appdata {
                float4 uv0: TEXCOORD0;
            };
            
            struct v2f {
                float4 uv0: TEXCOORD0;
            };
            
            v2f vert(appdata vertex) {
                v2f fragment;
                fragment.uv0 = vertex.uv0;
                return fragment;
            }

            float4 frag(v2f IN) : SV_Target
            {
                return float4(1,1,1,1);
            }
            ENDCG
        }
    }
}

Double-buffered helper

This is so I can render multiple times to the "same" texture, as I might

internal struct DoubleBufferedRenderTexture : IDisposable
{
    private RenderTexture _a;

    private RenderTexture _b;


    public DoubleBufferedRenderTexture(CustomRenderTexture source)
    {
        _a = source;
        _b = Object.Instantiate(source);
    }


    public void Blit(Material material)
    {
        Graphics.Blit(_a, _b, material);
        (_a, _b) = (_b, _a);
    }


    public RenderTexture Consume()
    {
        var result = _a;
        _a = null;
        return result;
    }


    public void Dispose()
    {
        Object.DestroyImmediate(_a);
        Object.DestroyImmediate(_b);
    }
}

The Blitting

var canvas = new DoubleBufferedRenderTexture(new RenderTexture(64, 64, GraphicsFormat.R32G32B32A32_SFloat, GraphicsFormat.None));
var brush = new Material(Shader.Find("Hidden/My Cool Shader"));

// Here I would set up the material properties when using an actual shader.

canvas.Blit(brush);
ctx.AddObjectToAsset("test", canvas.Consume());
canvas.Dispose();

What I've tried

  • Using a CommandBuffer instead of directly using Graphics.Blit;
  • Copying the RenderTexture to a plain Texture2D to save in the asset;
  • Skipping the double-buffering helper and ensure I'm just calling Blit once in a single texture.
\$\endgroup\$

0

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.