シェーダーを使って二値化を再実装しました
Boltzmanです. 魚眼でやりたいAR, 4回目です.
少しですが進捗が出たので進捗報告です.
以前二値化を実装した際にあまりに重かったので, サークル仲間の助言に従ってシェーダーを使って実装しなおすことにしました.
使ったシェーダーはCompute Shaderです. Standerd Surface Shader, Unlit Shader, Image Effect Shader, Compute Shaderのうちどのシェーダーを使うのか選ぶ時点でかなり難航しました. 様々なサイトをめぐって情報をあさって, 最終的には消去法で選択したような記憶があります.
選んだシェーダーがあっていたのかはわかりませんが, とりあえず実装はできました.
以下にコードを記します. まずはCompute Shaderのほうから
<br /> Texture2D<float4> Input;<br /> float1 thresh;<br /> RWTexture2D<float4> Result;</p> <p>[numthreads(8,8,1)]<br /> void CSMain (uint3 id : SV_DispatchThreadID)<br /> {<br /> //二値化<br /> Result[id.xy] = lerp(float4(1, 1, 1, 0), float4(0, 0, 0, 0),<br /> ceil( clamp( -(Input[id.xy].r+Input[id.xy].g+Input[id.xy].b)/3 + thresh, 0, 1) ));<br /> }<br />
(注:ここからなぜか口調が変わりますが書いている人は同じです. 本来なら直すべきですが, どうしても気力がわかないということと, 内容に影響はないということからそのままにします. 申し訳ない. )
今見返すとlerpの1番目と2番目の引数がfloat4であるのに対して3番目の引数がfloat1になっているのでどうして動いているのか不思議である. (自動的に変換しているのだろうか).
(2016/3/5追記:こちらのページに”スカラー昇格”なる記述があり, それによるものかと考えられます)
まずuint3 idは三つの符号なし整数をパラメーターとして持つ変数であるが, このような変数はx,y,zやr,g,bなどを用いて要素を取り出すことができる.(詳細)
これから, Input[id.xy]ではid.xyによってテクスチャの座標を指定して, その座標の色を求めている.(たぶん)
Input[id.xy]の値はfloat4でrgbaらしいので, rgbの和を取って3で割って濃度値を算出し, threshからその値を引くと, threshより大きい値の時は結果が負, threshより小さな値の時は結果が正になる. これをclampすると, 負の値は0に, 正の値は0より大きく1以下の値になる. さらにceilによってこれを0と1に二分する. lerpで線形補間すると, 0のときは後ろの値, 1の時は前の値が採用され, 画像を二値化することができる.
と, 私は思っているのだが, 実際どうなってコードがうまくいっている(ように見える)のかはわからない.
次にC#スクリプト
<br /> using UnityEngine;<br /> using System.Collections;</p> <p>public class Test : MonoBehaviour {<br /> public ComputeShader cs;<br /> public int width = 1080;<br /> public int height = 720;<br /> public int FPS = 30;<br /> [Range(0, 1)]<br /> public float thresh = 0.5f;</p> <p> private RenderTexture text;<br /> private WebCamTexture wtex;</p> <p> // Use this for initialization<br /> void Start () {<br /> //WebCam準備<br /> var devices = WebCamTexture.devices;<br /> if (devices.Length > 0)<br /> {<br /> wtex = new WebCamTexture(width, height, FPS);<br /> wtex.Play();<br /> }<br /> else<br /> {<br /> Debug.LogError("Could not Find WebCam.");<br /> return;<br /> }<br /> text = new RenderTexture(wtex.width, wtex.height, 0);<br /> text.enableRandomWrite = true;<br /> text.Create();<br /> transform.localScale = new Vector3(width, 1, height);<br /> transform.GetComponent<Renderer>().material.mainTexture = text;<br /> cs.SetTexture(0, "Input", wtex);<br /> cs.SetFloat("thresh", thresh);<br /> cs.SetTexture(0, "Result", text);<br /> }</p> <p> // Update is called once per frame<br /> void Update () {<br /> if (!SystemInfo.supportsComputeShaders)<br /> {<br /> Debug.LogError("Computer Shader is not Supported.");<br /> return;<br /> }<br /> cs.Dispatch(0, width, height, 1);<br /> }<br /> }<br />
cs.Set~で値を設定する. WebcamTextureを入力画像として渡し, 結果をRenderTextureで受け取っている.
cs.DispatchでCompute Shaderを実行する. (Compute Shaderの詳細)
ちゃんとRandomWriteをtrueにしておくことが重要らしいとどこかで読んだ.
(2016/02/29追記:ソースを発見したので載せます->詳細)
途中まで実装したのが今月の頭くらいで, 完成したのが2,3日前とずいぶん間が空いてしまったために, どこから引っ張ってきた情報なのかわからず大変忍びない.
やっていて感じたのは, シェーダー関係は情報を探すのが大変だということ.
シェーダーその物もそうだが, CgやHLSLの書き方も公式のリファレンスに見つけられただけでCやC++、C#と比べるとずっと調べにくかったように思う.
まあ私の見落としが多いだけなのだろうが…
前回のコードが大津の手法まで実装したものであったため, 正確な比較ではありませんが, 前回のコードのMonoBehaviourが300ms以上かかっていたのに対し, 今回のコードではCompute ShaderのDispatshは0.01ms以下となっています. 目に見えてわかるほど大きく改善しました.
次回は(必要があれば)大津の手法をシェーダーで実装することを目標にします.
Posted on: 2016年2月27日, by : Lait-au-Cafe