LoginSignup
31
29

More than 5 years have passed since last update.

Unity でShaderの勉強 その3

Posted at

Shader楽しい

今日はvertexshaderartというサイトを見つけた。
なかなか面白い。
https://www.vertexshaderart.com/

法線方向に膨らませるShader

法線
Shader "Custom/Test1" {
    Properties {
        _Scale ("hoge",Range(0,0.1)) = 0.01
    }
    SubShader {
        Pass {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            uniform float _Scale;

            struct v2f {
                half3 worldNormal : NORMAL;
                float4 pos :SV_POSITION;
            };

            v2f vert(float4 pos : POSITION , float3 normal : NORMAL) {
                v2f o;
                o.pos = mul(UNITY_MATRIX_MVP , pos);
                o.worldNormal = UnityObjectToWorldNormal(normal);
                o.pos += float4(o.worldNormal.xyz * _Scale ,0);
                return o;
            }


            float4 frag(v2f i) : SV_TARGET {
                fixed4 c = 0;
                c.rgb = i.worldNormal * 0.5 + 0.5;
                return c;
            }

            ENDCG
        }
    }
}

UnityObjectToWorldNormalでnormalをWorldに変換している。
あとはそれをVertex positionに加算してあげるだけ。

ちょっとふとったw
スクリーンショット 2016-09-21 1.49.22.png

描画順序を変えてみる

透過処理を作りたかったのだけど、まずは描画順序について理解しないと実装できそうになかったのでやってみます。
Test1 Shaderを参考にTest2 Shaderを作成。

Test1Shader
Shader "Custom/Test1" {
    Properties {
        _MainColor("Main Color",Color) = (1,1,1,1)
    }

    SubShader {
        Tags { "Queue" = "Geometry+1" "RenderType" = "Opaque"}
        ZTest Always
        ZWrite On
        Pass {
            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag

            uniform float4 _MainColor;

            float4 vert(float4 pos : POSITION) : SV_POSITION {
                return mul(UNITY_MATRIX_MVP,pos);
            }


            float4 frag () : SV_TARGET {
                return _MainColor;
            }
            ENDCG
        }
    }
}
Test2Shader
Shader "Custom/Test2" {
    Properties {
        _MainColor("Color" ,Color) = (1,1,1,1)
    }

    SubShader {
        Tags { "Queue" = "Geometry" "RenderType" = "Opaque"}

        ZTest LEqual
        ZWrite On
        Pass {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            uniform float4 _MainColor;

            float4 vert(float4 pos : POSITION) : SV_POSITION {
                return mul(UNITY_MATRIX_MVP,pos);
            }

            float4 frag() : SV_TARGET {
                return _MainColor;
            }
            ENDCG
        }
    }
}

2つのCubeにTest1とTest2をつける。

白CubeがTest1 (最前面に描画させたい)
赤CubeがTest2 (普通に描画される)
スクリーンショット 2016-09-22 11.53.00.png

向きを変えてみてみる1
スクリーンショット 2016-09-22 11.54.49.png

向きを変えてみてみる2
スクリーンショット 2016-09-22 11.54.57.png

白Cubeと赤Cubeが重なっている場合は、必ず白Cubeの方が描画されるようになりました。

ポイントはこの3つですTags , ZTest , ZWrite

ポイント箇所
        Tags { "Queue" = "Geometry+1" "RenderType" = "Opaque"}
        ZTest Always
        ZWrite On

ZWriteはデフォルトでONなので今回は書かなくても問題ないです。
1つずつ見ていきます。

Tags - Queue について

QueueにはGeometry + 1を指定しています。 (Test2だったらGeometryだけ)
Queueは描画の実行順序を指定できます。
GeometryはShader側が用意した定数で2000が入っています。
Geometry + 1で2000 + 1 になるので、実際Queueには2001の値が入ることになります。
GPUはQueueの入力値をもとにfragmentを実行していきます。低い数値のやつから実行を開始し、そいつの処理が終われば次に数値の低いやつを実行していきます。
そのため、Queueの入力値が高いやつのほうが後から描画されます。

Queue設定のためにShaderが用意してくれた定数はいくつかあります。

公式によると

Background は 1000、Geometry は 2000、AlphaTest は 2450、Transparent は 3000、そして Overlay は 4000 です。

とのこと。
Background => Skyboxとかの絶対背景になるやつ用。
Geometry => 不透明なやつ全般(Queueを省略するとGeometryが設定される)
AlphaTest => 不透明なやつ
Transparent => 不透明なやつ
Overlay => オーバーレイエフェクトのやつ(レンズフレアとか)

AlphaTestとTransparentの違いがいまいちわからないな

Transparentについて公式では

アルファブレンディングするもの(すなわち、デプスバッファに書き込みしないシェーダー)はすべて、ここにあるべきです(ガラス、パーティクルエフェクト)。

と書いているのでAlphaTestだとデプスバッファに書き込むということなのだろうか。。。?

Tags - RenderType について

レンダリングタイプをGPUに伝えるものらしい。
不透明ならOpaque、透明ならTransparent、とか。
よく分からない。
QueueでTransparentを指定してRenderTypeでOpaqueを指定するとどうなるんだろう。
Queue => 描画順序を決める
RenderType => レンダリングの種類を決める
Queueで描画順序を遅くしてもRenderTypeでOpaque(不透明)としてレンダリングされるから、不透明に描画されるんだろうな。。。

ZTest Always について

ZTestにはデプステストをする方法を指定します。
デプステストとは、スクリーンの同じピクセル位置に描画された別のfragmentがある場合、最終的にどっちをスクリーンに描画するのか決めるテストのこと。
ZTestを指定しなかった場合、LEqual(すでに描画されているオブジェクトと距離が等しいか、より近い場合に描画します。それより遠い場合はオブジェクトで隠します)が指定される(公式サイトより)

ZTest Alwaysを指定することで常にデプステストに勝利する(必ずそいつのfragmentがスクリーンに描画される)ようになる。

ZTest Alwaysを持つfragmentが複数あった場合はどうなるんだろう。。。
試しにTes2シェーダーのZTestをLEqualからAlwaysに変更した後、結果を見てみると・・・
スクリーンショット 2016-09-22 12.53.04.png
白Cubeの方が描画されていることがわかる。

白Cube Queue => Geometry + 1 = 2001
赤Cube Queue => Geometry = 2000
なのでZTestが同じだった場合はQueueが大きいやつのほうが描画させるらしい。

Queueが大きいやつのほうがZTest LEqualを指定されていて、
Queueが小さいやつのほうがZTest Alwaysを指定されていた場合はどうなるんだろう。
Queue大きいやつ => 白Cube
Queue小さいやつ => 赤Cube
とする。
先に赤Cubeの方がfragment処理が行われデプスバッファに書き込まれる。
次いで白Cubeのfragment処理が行われ、デプスバッファに書き込まれる。この時すでに赤Cubeのfragment情報がデプスバッファにあるのでデプステストが行われ、買った方のfragment情報がデプスバッファに残る。
デプステストの方法は白がLEqual,赤がAlwaysなので常に赤が勝つと思う。

やってみたところ、白Cubeの方がカメラに近い場合は、普通に白Cubeが描画された。
???常に赤Cubeの方が手前にくると思ったのに。。。
スクリーンショット 2016-09-22 13.02.04.png
スクリーンショット 2016-09-22 13.02.14.png

なんでなんだろう。。。
後から描画する(Queueの値が大きい)やつのほうが挑戦権をもってるから、そいつのZTest手法でデプステストが行われたということなのだろうか?もしそうだったら確かにZTestは白CubeのLEqualで行われるので、カメラにより近い白Cubeの方がデプステストに勝って描画されることになる。
もしそうならZTest Alwaysは自分より小さいQueueを持っているやつには絶対勝つ、という意味しかないのだろう。
デプステストに超絶対勝つシェーダーを作りたいなら
1: Queueを大きくする (でも不透明だったらAlphaTestやTransparent層まで伸ばしたくないな。こういう時にOverlayを使うのか?いやOverlayはレンズフレアみたいなエフェクト処理に使うものだと思うし・・・)
2: ステンシル?というやつを使う?
3: その他
うーん、どれだろう。

ZWrite On について

ZWriteはOn/Offの設定ができます。Onにすることでデプスバッファにfragment情報が書き込まれます。(ZWriteはデフォルトでON)
主に不透明なやつはOnにして、透明なやつはOffにするらしいです。
OffにするとZTestを指定する意味がないのだろうか。

Test1とTest2両方のZWriteをOffにしてみた。
ZWriteをオフにしたことによってデブスバッファによる調整が行われなくなるはずなので、Queueの描画順序によって最終的に描画されるものが決定されるはずだ。
白Cubeの方がQueueが大きいから白Cubeの方が常に描画されるはずだ。

結果:白Cubeが常に描画された
スクリーンショット 2016-09-22 19.09.55.png
Test1とTest2両方ともZTestにはLEqualを指定している。ZTestが動いていれば白CubeのZTestは負けるはずなので赤Cubeが描画されるはずである。なのに白Cubeが描画されたということはZTestは動いていない??

Test1をOffに、Test2をOnにしてみた。
最終的な描画がQueueだけで決まるならまたしても白Cubeが常に描画されるはずだ。

結果:白Cubeも赤Cubeも距離に応じて描画された
スクリーンショット 2016-09-22 19.26.20.png
スクリーンショット 2016-09-22 19.26.28.png
うーん、なんでだろう。
実行順序としては
1: 赤Cubeのfragment処理が行われ、デプスバッファに情報が記録される
2: 白Cubeのfragment処理が行われ、スクリーンに?書き込まれる????
3: デプステスト?によってデプスバッファに情報がある赤Cubeが描画される???
4: 赤Cubeの方が手前にくる???
なんか違うな。

Wikiを読んでみた

まず、画面(レンダーターゲット)内において、あるピクセルに物体(の一部)を描画する際に、物体表面の深度(奥行き)を保存しておく。ここでは例えば最も手前の深度値をゼロと定め、奥に進むにつれて深度値が大きくなるものと定める(このルールは処理系によって異なる)。次に同じピクセルに物体を描画することになった場合、前回保存した深度の値と今回描画する物体の深度の値の大きさを比較する(この比較のことを、Zテストと呼ぶこともある)。もし今回の深度の方が大きければ、つまり奥にあれば、手前の描画済み物体に隠されて見えないので、新たにピクセルへ描画処理を行なわずに済むことになる。このため、手

デプステストって全てのfragment処理が終わった後に一斉にやるものだと思ってたけど違うのか。
1つのfragment処理 => スクリーンに書き込む (+ デプスバッファに書き込む指定があればそこにも書き込む) => 別のfragment処理
という感じかな。

もっと詳しく見てみれば、
1つのfragment処理 => fragment情報が決まる => 塗られるピクセル位置のデプスバッファを見に行く => すでにfragment情報があればZTestを行う
*=> (ZTestに勝った) => スクリーンに書き込む (+ デプスバッファに書き込む指定があればそこにも書き込む) => 別のfragment処理
*
=> (ZTestに負けた) そのfragmentは死亡。次のfragment処理へ
ということなのだろうか。

ポイントとしては、
1: ZTestは常に行なわれている
2: ZWriteがOffだとしてもデプスバッファは必ず見る。
3: ZWriteがOffだとしても、(ZTestに勝てば!)スクリーン??にfragment情報が書き込まれる
4: ZTestは挑戦権をもっているやつの手法で行う(後から描画される方=Queueが大きい方)
ということだろうか。

上記があっているなら、Test1をOffに、Test2をOnにした時に距離に応じて相応にCubeが描画されたことにもなっとくできる。
処理順序はこうなるはず。
1: 赤Cubeのfragment処理が行われる
2: 赤Cubeはデプスバッファを確認する(しかしデプスバッファには何もない!ZTestするまでもなく勝利)
3: 赤Cubeはfragment処理をデプスバッファとスクリーン??に書き込む
4: 赤Cubeの次のfragment処理を行う
~~~~
5: 赤Cube終わり
6: 白Cubeのfragment処理が行われる
7: 白Cubeはデプスバッファを確認する(赤Cubeとピクセル位置が重なっていれば)ZTest勝負を行う
8a: ZTest! (白Cubeは赤Cubeより奥にある場合) 白Cubeは負ける。そのfragmentは死んだ
8b: ZTest! (白Cubeは赤Cubeより手前にある場合) 白Cubeは勝った。fragment情報をスクリーン??に書き込む
9: 白Cubeは次のfragment処理を行う
~~~~
10: 白Cube終わり

おお、それっぽい!
デプスバッファに書き込まない時にスクリーン??に書き込むというのはあっているのだろうか。
スクリーンってなんだ・・・。

試しにTest1のZTestをAlwaysにしてみたら常に白Cubeが描画されるようになった。
スクリーンショット 2016-09-22 19.57.04.png

気になるのは、(白CubeがZWriteがOffになっているので)白CubeがZTestに勝った後のピクセル位置のデプスバッファがどうなっているかだ。
考えられるのは、
1: 白CubeがZTestに勝った。デプスバッファには本当に何も書かない。=>赤Cubeのデプスバッファがそのまま残る
2: 白CubeがZTestに勝った。デプスバッファには何も書かない。デプスバッファは初期化じゃ => 赤Cubeのデプスバッファが削除されて空になる
3: その他
どれだろう??

調べるためにTest2をコピーしたTest3 Sahderを作成した。
Test1からTest3のTags情報をまとめると
Test1(白Cube) => 2001 ZWrite Off ZTest Always
Test2(赤Cube) => 2000 ZWrite On ZTest LEqual
Test3(青Cube) => 2002 ZWrite On ZTest LEqual
となっている。
Queueの順番とShader名が対応していないので若干ややこしくなってしまった・・・orz

Test3用に新しくCubeを作成し、青Cubeにした。
並べてみるとこんな感じ
スクリーンショット 2016-09-22 20.09.36.png

これをサイドから見た時にどうなるかを考えてみる。
レンダリングはQueueの順番によって行われるのでまずはQueue2000の赤Cubeからだ。

==赤Cubeここから==
1: 赤Cubeのfragment処理が行われる
2: 赤Cubeのデプスバッファチェック。何もない。ZTestには常に勝利だ。
3: 赤Cubeのデプスバッファに自分の情報を書き込む。スクリーンにも書き込む
4: 赤Cubeの次のfragment処理を行う
~~~ 全ての赤Cube fragment処理が終わる ~~~

==白Cubeここから==
5: 白Cubeのfragment処理が行われる
6: デプスバッファチェック
7a: デプスバッファに何もなかった。ZTestには勝利だ。
7b: デプスバッファに赤情報があった。しかし白CubeのZTestはAlwaysだ。故にZTestには常に勝つ。
8: (7a,7bどちらにせよZTestには勝ったので)白Cubeのfragment情報をスクリーンに書き込む
~~~ 全ての白Cube fragment処理が終わる ~~~

==青Cubeここから==
9: 青Cubeのfragment処理が行われる
10: デプスバッファチェック
11a: デプスバッファに何もなかった。ZTestには勝利だ。
11b: デプスバッファに何か発見!!!!?!???。どうやら赤Cubeのデプス情報のようだ。ZTestを仕掛けよう。
12a: (青Cubeは赤Cubeより手前にいる場合)。ZTestに勝った。スクリーン情報に青を書き込み、デプスバッファを更新しよう。
12b: (青Cubeは赤Cubeより奥にいる場合)。ZTestに負けたよ。そのfragmentは死んだ。
~~~ 全ての青Cube fragment処理が終わる ~~~

こんな感じだろう。
なんだかじゃんけんみたいだ。
赤Cube => 位置関係によっては青Cubeより強い時もある。白Cubeには負けるよ。
白Cube => 赤Cubeにつよい。青Cubeに弱い。
青Cube => 白Cubeにつよい。位置関係によっては赤Cubeより強い時もある。

実際にサイドから見てみた結果。
スクリーンショット 2016-09-22 20.23.40.png
最初に赤Cubeが書き込まれ、
次いで白CubeがZTestに勝って書き込まれ、
次いで青Cubeが赤Cubeより手前にあるのでZTestに勝つので、青Cubeが書き込まれる。
のだろう。

ポイントは
1: 白CubeがZTestに勝った後のデプスバッファは初期化等はされず、赤Cube情報が入ったままのデプスバッファのままであること。
2: デプスバッファに自分の情報を書き込まなかった白Cubeは、簡単に青Cubeに負けること(青Cubeは残っていた赤Cubeの情報を持つデプスバッファとしかZTestしてない)

反対側のサイドから見てみた結果。
スクリーンショット 2016-09-22 20.28.11.png
うーん。ごちゃごちゃしてて見にくい。。。
赤Cube => 青Cubeに勝ってる(距離的に)。白Cubeには負けた。
青Cube => 白Cubeには勝って、赤Cubeには負けた。
白Cube => 赤Cubeには勝った。青には負けた。

白Cubeは赤Cubeに勝っているが、デプスバッファが白Cubeのものに更新されなかったため、
青Cubeは赤Cube情報とZTestバトルすることになったため負けた。
という感じか。

実際にスクリーンに映っているfragment情報とデプスバッファの情報が一致しないと見た目的におかしくなることがわかった。
ZTestとZWrite大切!!!!

ちょいと長くなってしまったけどZTest,ZWrite周りはこんな感じでいいか。

透過させたい(まずは失敗してみる)

アルファ付きのTextureを使いたいのでCubeにユニティちゃんのイラストを貼り付けて透過処理を実装してみたいと思います。

使用するイラスト(アルファ付きpng)
portrait_kohaku_05.png

今まで通りにTextureをuvに沿って貼り付けるShaderを書いてみた。

今まで見たいな
Shader "Custom/Test1" {
    Properties {
        _MainTex("main", 2D) = "white" {}
    }
    SubShader {
        Pass {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag


            uniform sampler2D _MainTex;

            struct v2f {
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD0;
            };

            v2f vert(float4 pos : POSITION , float2 uv : TEXCOORD0) {
                v2f o;
                o.pos = mul(UNITY_MATRIX_MVP,pos);
                o.uv = uv;

                return o;
            }


            float4 frag(v2f i) :COLOR {
                return tex2D(_MainTex,i.uv);
            }


            ENDCG
        }
    }
}

結果
黒 => rgbが000になってる。透過されていないからalphaは1
スクリーンショット 2016-09-21 2.01.51.png

よく分からないなりに調べてみると、
alphaを使うためにはやらなきゃいけないことがあるそうです。
ブレンドとか処理順序とかの指定

透過させたい

透過するぞ
Shader "Custom/Test1" {
    Properties {
        _MainTex("main", 2D) = "white" {}
    }
    SubShader {
        Pass {
            Tags { "RenderType" = "Transparent" "Queue" = "Transparent"}
            Blend SrcAlpha OneMinusSrcAlpha
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag


            uniform sampler2D _MainTex;

            struct v2f {
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD0;
            };

            v2f vert(float4 pos : POSITION , float2 uv : TEXCOORD0) {
                v2f o;
                o.pos = mul(UNITY_MATRIX_MVP,pos);
                o.uv = uv;

                return o;
            }


            float4 frag(v2f i) :COLOR {
                return tex2D(_MainTex,i.uv);
            }


            ENDCG
        }
    }
}

結果
スクリーンショット 2016-09-21 2.07.04.png
レンダリングの順番?とBlendを指定しただけで透過処理が行われるようになった。

Tags { "RenderType" = "Transparent" "Queue" = "Transparent"}

こちらをコメントアウトしても透過処理は行われた。描画順序を指定しているから透過処理に影響があるかと思ったけどどうなんだろう。。。

Blend SrcAlpha OneMinusSrcAlpha

これをコメントアウトすると透過処理が行われなくなる。Blendを指定することでアルファ処理が行われるようだ。Blendを明示しない場合はBlend Off(透過処理なし)が入るそうだ。

Blendの問題点

さっきのCubeを複製して並べたところ正常に描画されていないことがわかった。
scene.unity - ShaderTesta - PC, Mac & Linux Standalone <OpenGL 4.1> 2016-09-21 02-12-08.jpg
奥のCubeの描画が途切れている。手前のCubeが奥のCubeを遮って描画しているみたいだ。
たぶんだけど、ZTest?ZWhite?とかそういうのが間違っているのだと思う。
床はちゃんと見えてるから不思議だ。

ライセンス

この作品はユニティちゃんライセンス条項の元に提供されています
imageLicenseLogo-2.png

ユニティちゃんに幸あれ

31
29
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
31
29