はなちるのマイノート

Unityをメインとした技術ブログ。自分らしくまったりやっていきたいと思いますー!

【Unity】ComputeShaderでチャンネル変換(RGB -> BGR)をしてみた【Q1】

はじめに

今回はComputeShaderでチャンネル変換してみようという記事になります!

ComputeShaderとはGPUを計算のために使ってみようというもので、計算を爆速化できることが多いです。

最近画像処理に少しハマっていて、この100本ノックをComputeShaderで実装してみようかなと密かに計画しています。

github.com

次回 -> 【Unity】ComputeShaderでグレースケールをしてみた【Q2】 - はなちるのマイノート


今回はその第一問であるチャンネル変換というものをしてみようと思います。

f:id:hanaaaaaachiru:20200203212907p:plain

チャンネル変換

これは単純にRGBBGRの順番になるように入れ替えています

ちなみにRは赤,Gは緑,Bは青のことです。

OpenCVではこのBGRがよく使われるみたいですね。

コード

ComputeShaderはこんな感じ。

#pragma kernel RGB2BGR

RWTexture2D<float4> Result;
Texture2D<float4> Texture;

[numthreads(8,8,1)]
void RGB2BGR (uint3 id : SV_DispatchThreadID)
{
    Result[id.xy] = float4(Texture[id.xy].z, Texture[id.xy].y, Texture[id.xy].x, Texture[id.xy].w);
}


C#でのCPU側のコードはこちら。

using UnityEngine;
using UnityEngine.UI;

public class RGB2BGR : MonoBehaviour
{
    [SerializeField] private ComputeShader _computeShader;
    [SerializeField] private Texture2D _tex;
    [SerializeField] private RawImage _renderer;
    private RenderTexture _result;

    struct ThreadSize
    {
        public uint x;
        public uint y;
        public uint z;

        public ThreadSize(uint x, uint y, uint z)
        {
            this.x = x;
            this.y = y;
            this.z = z;
        }
    }

    private void Start()
    {
        if (!SystemInfo.supportsComputeShaders)
        {
            Debug.LogError("Comppute Shader is not support.");
            return;
        }

        // RenderTextueの初期化
        _result = new RenderTexture(_tex.width, _tex.height, 0, RenderTextureFormat.ARGB32);
        _result.enableRandomWrite = true;
        _result.Create();

        // RGB2BGRのカーネルインデックス(0)を取得
        var kernelIndex = _computeShader.FindKernel("RGB2BGR");

        // 一つのグループの中に何個のスレッドがあるか
        ThreadSize threadSize = new ThreadSize();
        _computeShader.GetKernelThreadGroupSizes(kernelIndex, out threadSize.x, out threadSize.y, out threadSize.z);

        // GPUにデータをコピーする
        _computeShader.SetTexture(kernelIndex, "Texture", _tex);
        _computeShader.SetTexture(kernelIndex, "Result", _result);

        // GPUの処理を実行する
        _computeShader.Dispatch(kernelIndex, _tex.width / (int)threadSize.x, _tex.height / (int)threadSize.y, (int)threadSize.z);

        // テクスチャを適応する
        _renderer.texture = _result;

    }

    private void OnDestroy()
    {
        _result = null;
    }
}

解説

初回というわけで少しComputeShaderについて説明を加えます。

GPUでどんなように処理されているかを簡単にするために、入力画像を512*512と仮定しましょう。

つまりピクセル(RGBA:float[4])が512*512個あるということです。


GPUは並列処理が得意なので、今回も並列処理を活用して高速化をはかっています。

この並列処理にはスレッドスレッドグループの二つがとても重要になっています。

スレッドを決まった単位でまとめたものをスレッドグループと呼び,以下の画像のような関係にあります。

f:id:hanaaaaaachiru:20200203015230p:plain
numthreads - Win32 apps | Microsoft Docs


今回の場合は、一つのピクセルの色の計算に一つのスレッドを用いていて、合計512*512個のスレッドを利用して並列で計算させています

それを一つのスレッドグループに8*8*1個のスレッド,64(=512/8)*64*1個のスレッドグループを用意して実現させているとうわけですね。

また実際の書き方は是非参考に貼ったリンクをみていただけると分かりやすいと思います。

さいごに

おそらく100問目まではやらないと思いますが、飽きるまではこれ関係の投稿が続くかもしれません。

もし興味がある方は是非お付き合いください。

ではまた。