MRが楽しい

MRやVRについて学習したことを書き残す

Unityで2枚のTexture2Dの画像を重ね合わせて合成する

本日は Unity の技術調査枠です。
Unityで2枚のTexture2Dの画像を重ね合わせて合成する方法を記事にします。

前提条件

以下の前回記事の続きです。
bluebirdofoz.hatenablog.com

透過画像の準備

今回は前回記事で撮影した UI を除いたキャプチャ画像に以下のような背景を透過したロゴ画像を重ね合わせてみます。

初めに重ね合わせするロゴ画像を Unity に画像を取り込みます。

透過部分を有効にするには Inspector ビューから以下の設定を行います。
・[Texture Type]を[Sprite(2D and UI)]に変更する
・[Alpha Is Transparency]にチェックを入れる

2枚のTexture2Dを重ね合わせて合成する

以下のように同じサイズのTexture2Dであればアルファ値を考慮して各Pixelを計算することで画像を合成することができます。

        var pixels = screenShot.GetPixels();
        var blendPixels = resizedTexture.GetPixels();
        for (int i = 0; i < pixels.Length; i++)
        {
            var pixel = pixels[i];
            var blendPixel = blendPixels[i];
            var baseAlpha = 1.0f - blendPixel.a;
            var blendAlpha = blendPixel.a;
            var r = pixel.r * baseAlpha + blendPixel.r * blendAlpha;
            var g = pixel.g * baseAlpha + blendPixel.g * blendAlpha;
            var b = pixel.b * baseAlpha + blendPixel.b * blendAlpha;
            var a = 1.0f;
            pixels[i] = new Color(r, g, b, a);
        }
        screenShot.SetPixels(pixels);
        screenShot.Apply();

キャプチャ画像とロゴ画像を重ね合わせて1枚の画像を生成して出力する以下のスクリプトを作成しました。
ロゴ画像は合成する前にキャプチャ画像とサイズを合わせるため、リサイズを行っています。
・TextureBlendTest.cs

using System;
using System.IO;
using System.Threading;
using Cysharp.Threading.Tasks;
using UnityEngine;

public class TextureBlendTest : MonoBehaviour
{
    [SerializeField]
    private Texture2D blendTexture;
    
    private Camera captureCamera;

    /// <summary>
    /// キャプチャの実行関数
    /// </summary>
    public void Capture()
    {
        CaptureScreenshotByRenderTexture();
    }
    
    private void Start()
    {
        captureCamera = Camera.main;
    }

    private void CaptureScreenshotByRenderTexture()
    {
        var ct = this.GetCancellationTokenOnDestroy();
        CaptureScreenshotByRenderTextureAsync(ct).Forget();
    }

    private async UniTask CaptureScreenshotByRenderTextureAsync(CancellationToken ct)
    {
        // 任意のフレームの描画処理が終わるまで待機する
        await UniTask.WaitForEndOfFrame(ct);

        // Cameraの描画領域をRenderTextureとして取り出す
        var rt = new RenderTexture(captureCamera.pixelWidth, captureCamera.pixelHeight, 24);
        var prev = captureCamera.targetTexture;
        captureCamera.targetTexture = rt;
        captureCamera.Render();
        captureCamera.targetTexture = prev;
        RenderTexture.active = rt;

        var screenShot = new Texture2D(
            captureCamera.pixelWidth,
            captureCamera.pixelHeight,
            TextureFormat.RGB24,
            false);
        
        screenShot.ReadPixels(new Rect(0, 0, screenShot.width, screenShot.height), 0, 0);
        screenShot.Apply();
        
        // キャプチャ画像に合わせてリサイズした合成用画像を作成する
        Texture2D copyTexture = new Texture2D(
            blendTexture.width,
            blendTexture.height,
            blendTexture.format,
            blendTexture.mipmapCount,
            false);
        Graphics.CopyTexture(blendTexture, copyTexture);
        
        // リサイズ後のサイズを持つRenderTextureを作成して書き込む
        var resizedRenderTexture = RenderTexture.GetTemporary(screenShot.width, screenShot.height);
        Graphics.Blit(copyTexture, resizedRenderTexture);

        // リサイズ後のサイズを持つTexture2Dを作成してRenderTextureから書き込む
        var preRT = RenderTexture.active;
        RenderTexture.active = resizedRenderTexture;
        var resizedTexture = new Texture2D(screenShot.width, screenShot.height);
        resizedTexture.ReadPixels(new Rect(0, 0, screenShot.width, screenShot.height), 0, 0);
        resizedTexture.Apply();
        RenderTexture.active = preRT;

        // 使い終わったRenderTextureを解放する
        RenderTexture.ReleaseTemporary(resizedRenderTexture);
        
        // 2枚の画像を透過を考慮して合成する
        var pixels = screenShot.GetPixels();
        var blendPixels = resizedTexture.GetPixels();
        for (int i = 0; i < pixels.Length; i++)
        {
            var pixel = pixels[i];
            var blendPixel = blendPixels[i];
            var baseAlpha = 1.0f - blendPixel.a;
            var blendAlpha = blendPixel.a;
            var r = pixel.r * baseAlpha + blendPixel.r * blendAlpha;
            var g = pixel.g * baseAlpha + blendPixel.g * blendAlpha;
            var b = pixel.b * baseAlpha + blendPixel.b * blendAlpha;
            var a = 1.0f;
            pixels[i] = new Color(r, g, b, a);
        }
        screenShot.SetPixels(pixels);
        screenShot.Apply();

        // 合成したTexture2DをPNGに変換して保存する
        var date = DateTime.Now.ToString("yyyyMMdd");
        string filename = $"blendCapture_{date}.png";
        var png = screenShot.EncodeToPNG();
        string path = Path.Combine(Application.persistentDataPath,filename);
        File.WriteAllBytes(path, png);

        Debug.Log($"Capture File : {path}");
    }
}

出力された画像は以下のようになります。
UI のボタンが含まれていないキャプチャ画像にロゴ画像が重ね合わせて合成されています。

上記スクリプトではロゴ画像をキャプチャ画像に合わせてサイズ変更していますが、アスペクト比は考慮していません。
アスペクト比がロゴ画像と異なる場合はロゴが変形してしまう点に注意が必要です。

参考ページ

light11.hatenadiary.com