はなちるのマイノート

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

【Unity】ComputeShaderでHSV変換(HSV->RGB,RGB->HSV)をしてみる【Q5】

HSV変換とは

RGBは赤・緑・青で色を表現していましたが、HSVは色相・彩度・明度で色を表現したものです。

RGB -> HSV の変換式はこちら。

f:id:hanaaaaaachiru:20200205175837p:plain

HSV -> RGB の変換式はこれ。

f:id:hanaaaaaachiru:20200205180229p:plain

コード

今回はRGB -> HSV,Hを反転(180加算),HSV -> RGBをすることを目的としています。

ComputeShaderはこちら。

#pragma kernel InvertHue

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

[numthreads(8,8,1)]
void InvertHue (uint3 id : SV_DispatchThreadID)
{
    //----------------
    //-  RGB -> HSV  -
    //----------------
    float h,s,v;
    float r = Texture[id.xy].x;
    float g = Texture[id.xy].y;
    float b = Texture[id.xy].z;
    float maxValue = max(r, max(g, b));
    float minValue = min(r, min(g, b));

    if(maxValue == minValue){
        h = 0;
    } else if(minValue == b){
        h = 60 * (g - r) / (maxValue - minValue) + 60;
    } else if(minValue == r){
        h = 60 * (b - g) / (maxValue - minValue) + 180;
    } else if(minValue == g){
        h = 60 * (r - b) / (maxValue - minValue) + 300;
    }

    s = maxValue - minValue;
    v = maxValue;

    //-----------------
    //- 色相を反転させる -
    //-----------------
    h = fmod(h + 180, 360);

    //----------------
    //-  HSV -> RGB  -
    //----------------
    float c = s;
    float h2 = h / 60;
    float x = c * (1 - abs(fmod(h2, 2) - 1));

    r = v - c;
    g = v - c;
    b = v - c;

    if(h2 < 1){
        r += c;
        g += x;
    } else if(h2 < 2){
        r += x;
        g += c;
    } else if(h2 < 3){
        g += c;
        b += x;
    } else if(h2 < 4){
        g += x;
        b += c;
    } else if(h2 < 5){
        r += x;
        b += c;
    } else if(h2 < 6){
        r += c;
        b += x;
    }
    
    Result[id.xy] = float4(r, g, b,1);
}


C#のコードはこちら。

using UnityEngine;
using UnityEngine.UI;

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

    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の初期化
        var result = new RenderTexture(_tex.width, _tex.height, 0, RenderTextureFormat.ARGB32);
        result.enableRandomWrite = true;
        result.Create();

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

        // 一つのグループの中に何個のスレッドがあるか
        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;
    }
}

さいごに

実装は数式通りにすればいいので簡単でしたが、if分が多くなってしまったのでパフォーマンス的にあんまりよくないのかもしれません。

またcomputeShaderhlslを使っているらしいのですが、おそらくメソッドからメソッドを呼ぶこともできますよね。

そこでGPU側のコードもみやすくするために分割した方が良さそうな気もします。

とりあえず今回はこれくらいで。

ではまた。