🎆

Unity 背景を画像にする

2022/11/12に公開

Summary

Unityで背景を一枚の画像にする方法のメモです。

開発環境

Unity 2021.3.6f1
Windows 10

この記事の更新履歴

2022-11-13:コード修正
2022-11-12:新規作成

RendererFeature の追加

テクスチャを描画するRendererFeatureを追加します。

using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

public class BackGroundRendererFeature : ScriptableRendererFeature
{
    class CustomRenderPass : ScriptableRenderPass
    {
        Material material;
        Mesh mesh;

        public Texture mainTexture;
        public Color mainColor = Color.white;
        public float offsetU = 0;
        public float offsetV = 0;
        public float scaleU = 1.0f;
        public float scaleV = 1.0f;

        public CustomRenderPass(RenderPassEvent renderPassEvent)
        {
            this.material = new Material(Shader.Find("Hidden/BackGround"));
            this.renderPassEvent = renderPassEvent;
            this.profilingSampler = new ProfilingSampler("BackGroundRenderer");

            // 描画するメッシュを作成します。
            // このメッシュは画面全体を覆うものですが、位置調整はシェーダー側でやっているので頂点位置に意味はありません。
            mesh = new Mesh();
            mesh.vertices = new Vector3[]
            {
                new Vector3(0, 0, 0),
                new Vector3(1, 0, 0),
                new Vector3(0, 1f, 0),
                new Vector3(1f, 1f, 0),
            };
            mesh.uv = new Vector2[]
            {
                new Vector2(0, 1f),
                new Vector2(1f, 1f),
                new Vector2(0, 0f),
                new Vector2(1f, 0f),
            };
            mesh.triangles = new int[]
            {
                0, 1, 2,
                1, 2, 3,
            };
        }

        // Here you can implement the rendering logic.
        // Use <c>ScriptableRenderContext</c> to issue drawing commands or execute command buffers
        // https://docs.unity3d.com/ScriptReference/Rendering.ScriptableRenderContext.html
        // You don't have to call ScriptableRenderContext.submit, the render pipeline will call it at specific points in the pipeline.
        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            // Setting Material
            material.SetTexture("_MainTex", mainTexture);
            material.SetColor("_MainColor", mainColor);
            material.SetVector("_MainTex_ST", new Vector4(scaleU, scaleV, offsetU, offsetV));

            if(renderingData.cameraData.camera.aspect > 1.0f)
            {
                material.EnableKeyword("_ASPECTBASIS_HORIAZONTAL");
                material.DisableKeyword("_ASPECTBASIS_VERTICAL");
            }
            else
            {
                material.EnableKeyword("_ASPECTBASIS_VERTICAL");
                material.DisableKeyword("_ASPECTBASIS_HORIAZONTAL");
            }

            var cmd = CommandBufferPool.Get();
            using(new ProfilingScope(cmd, profilingSampler))
            {
                cmd.DrawMesh(mesh, Matrix4x4.identity, material);
            }
            context.ExecuteCommandBuffer(cmd);
            CommandBufferPool.Release(cmd);
        }
    }

    public Texture mainTexture;
    public Color mainColor = Color.white;
    public float offsetU = 0;
    public float offsetV = 0;
    public float scaleU = 1.0f;
    public float scaleV = 1.0f;

    public RenderPassEvent renderPassEvent = RenderPassEvent.AfterRenderingOpaques;

    CustomRenderPass m_ScriptablePass;

    /// <inheritdoc/>
    public override void Create()
    {
        m_ScriptablePass = new CustomRenderPass(renderPassEvent);
    }

    // Here you can inject one or multiple render passes in the renderer.
    // This method is called when setting up the renderer once per-camera.
    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        m_ScriptablePass.mainTexture = mainTexture;
        m_ScriptablePass.mainColor = mainColor;
        m_ScriptablePass.offsetU = offsetU;
        m_ScriptablePass.offsetV = offsetV;
        m_ScriptablePass.scaleU = scaleU;
        m_ScriptablePass.scaleV = scaleV;

        renderer.EnqueuePass(m_ScriptablePass);
    }
}

こちらのRendererFeatureをRendererに追加すると以下のようになります。

RendererFeature

Shader を作成する

Shaderは以下の通り

Shader "Hidden/BackGround"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _MainColor("Color", Color) = (1,1,1,1)
        [KeywordEnum(HORIAZONTAL, VERTICAL)]_AspectBasis("Aspect basis", Int) = 0
    }
    SubShader
    {
        Tags { "Queue" = "Background" "RenderType" = "Background" "RenderPipeline" = "UniversalPipeline" }
        // No culling or depth
        Cull Off
        ZWrite Off

        Pass
        {
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma shader_feature _ASPECTBASIS_HORIAZONTAL _ASPECTBASIS_VERTICAL

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

            TEXTURE2D(_MainTex);
            float4 _MainTex_ST;
            SAMPLER(sampler_MainTex);
            half4 _MainColor;

            struct Attributes
            {
                float4 positionOS   : POSITION;
                uint vertexID       : SV_VertexID;
                float2 texcoord0    : TEXCOORD0;
            };

            struct Varyings
            {
                float4 positionHCS  : SV_POSITION;
                float2 uv           : TEXCOORD0;
            };

            Varyings vert (Attributes IN)
            {
                Varyings OUTPUT;

                // copy from "com.unity.render-pipelines.core/Runtime/Utilityes/Blit.hlsl"
#if SHADER_API_GLES
                float4 pos = IN.positionOS;
                float2 uv = IN.texcoord0;
#else
                float4 pos = GetFullScreenTriangleVertexPosition(IN.vertexID, UNITY_RAW_FAR_CLIP_VALUE);
                float2 uv = GetFullScreenTriangleTexCoord(IN.vertexID);
#endif

#ifdef _ASPECTBASIS_HORIAZONTAL
                float aspect = (_ScreenParams.y / _ScreenParams.x);
                uv.y = (uv.y * aspect) + (abs(aspect - 1) * 0.5);
#elif _ASPECTBASIS_VERTICAL
                float aspect = (_ScreenParams.x / _ScreenParams.y);
                uv.x = (uv.x * aspect) + (abs(aspect - 1) * 0.5);
#endif

                OUTPUT.positionHCS = pos;
                OUTPUT.uv = TRANSFORM_TEX(uv, _MainTex);
                return OUTPUT;
            }

            half4 frag (Varyings IN) : SV_Target
            {
                half4 col = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, IN.uv);
                col *= _MainColor;
                return col;
            }
            ENDHLSL
        }
    }
}

vert 関数でメッシュが画面全体を覆うように位置調整しているところが重要な点です。詳しくはcom.unity.render-pipelines.core/ShaderLibrary/Common.hlslを参照してください。

Vertex Index から頂点位置を計算しているところ

GetFullScreenTriangleVertexPosition はVertex Index から画面全体を覆うような頂点位置を算出する関数です。実装詳細はこちら
UNITY_RAW_FAR_CLIP_VALUEはクリッピングされた領域の最遠値を表しています。

GitHubで編集を提案

Discussion