はなちるのマイノート

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

【Unity】ComputeShaderで減色処理(ポスタライズ)をしてみる【Q6】

はじめに

画像処理100本ノックの続き、やっていきましょう。

github.com

前回 -> 【Unity】ComputeShaderでHSV変換(HSV->RGB,RGB->HSV)をしてみる【Q5】 - はなちるのマイノート

次回 -> 【Unity】ComputeShaderで平均プーリングをする【Q7】 - はなちるのマイノート


今回は減色処理(ポスタライズ)について取り組んでいきましょう。

f:id:hanaaaaaachiru:20200205212759p:plain

減色処理とは

減色処理とは色の要素の階調数を減らす処理のことです。

例えばRGBでは赤を0~255の256階調ですが、それを4階調なんかに減らすということですね。

今回の減らす方法は次の式です。

if(val < 63){
    val = 32;
}else if(val < 127){
    val = 96;
}else if(val < 191){
    val = 160;
}else if(val < 255){
    val = 224;
}

0~62,63~126,128~190,191~255の4つに分けて、その平均値を値とします。(平均値と少しずれていますが、100本ノックのお題の方を表記しています)

これをR,G,Bそれぞれに適応するというわけですね。

コード

一応R,G,Bそれぞれを自由に何階調に減らすかを決められるようにしてみました。

100本ノックの解答例はRGBが0~255で表されますが、Unityでは0~1になっているところとStepが固定であるところに注意してください。

#pragma kernel Posterize

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

[numthreads(8,8,1)]
void Posterize (uint3 id : SV_DispatchThreadID)
{
    float r = clamp(floor(Texture[id.xy].x * Step.x) / Step.x + (1.0 / (Step.x * 2)), 0, 1);
    float g = clamp(floor(Texture[id.xy].y * Step.y) / Step.y + (1.0 / (Step.y * 2)), 0, 1);
    float b = clamp(floor(Texture[id.xy].z * Step.z) / Step.z + (1.0 / (Step.z * 2)), 0, 1);
    Result[id.xy] = float4(r, g, b, 1);
}


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

using UnityEngine;
using UnityEngine.UI;

public class Posterize : 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();

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

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

        // RGBそれぞれを何階調にするか
        int[] Step = new int[] { 4, 4, 4 };
        _computeShader.SetInts("Step", Step);

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

        // テクスチャを適応
        _renderer.texture = result;
    }
}

さいごに

少しずつ飽きてきたような飽きてきてないような感じですね。

また今回HLSLの組み込み関数を2つ使いましたが、こちらのまとめサイトがとても参考になるので良かったらみてみてください。

hlslref.wiki.fc2.com

ではまた。