そろそろShaderをやるパート16 ジオメトリーシェーダーで法線方向にポリゴンを移動させる


そろそろShaderをやります

そろそろShaderをやります。そろそろShaderをやりたいからです。
パート100までダラダラ頑張ります。10年かかってもいいのでやります。
100記事分くらい学べば私レベルの初心者でもまあまあ理解できるかなと思っています。

という感じでやってます。

※初心者がメモレベルで記録するので
 技術記事としてはお力になれないかもしれません。

下準備

下記参考
そろそろShaderをやるパート1 Unite 2017の動画を見る(基礎知識~フラグメントシェーダーで色を変える)

ジオメトリーシェーダー

ジオメトリーシェーダーは正しくメッシュのジオメトリーに干渉するシェーダーで、頂点シェーダーの後に実行され、フラグメントシェーダーの前に実行されます。
「ジオメトリーに干渉する」とは、メッシュが持つ頂点数を増減させたり、メッシュのトランスフォーム(位置、回転、スケール)を変えられるという事です。
頂点シェーダーも頂点の位置を変えたりできますが、頂点シェーダーにできてジオメトリーシェーダーにできない事は少ないので基本的には頂点シェーダーの拡張版だと考えていいと思います。
もちろん例外はあります。

【引用元】:Geometry Shaderの書き方

ポリゴン毎に処理できるってのがジオメトリーシェーダーの特徴のようです。

デモ

参考リンクのまんまです。
【参考リンク】:【Unity】カメラが近づくと弾けるポリゴン分解シェーダー

法線方向に各ポリゴンを移動させています。

わからない部分もありましたが、
今完全に理解しようとして足踏みするのも時間がもったいないので
わかっている部分までメモします。

Shaderサンプル

Shader "Custom/GeometryNormalMove"
{
    Properties
    {
        _Color ("Color", Color) = (1, 1, 1, 1)
        _PositionFactor("Position Factor", float) = 0.5
    }
    SubShader
    {
        Tags
        {
            "RenderType"="Opaque"
        }

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma geometry geom //ジオメトリシェーダーの関数がどれかGPUに教える
            #pragma fragment frag

            #include "UnityCG.cginc"

            fixed4 _Color;
            float _PositionFactor;

            //頂点シェーダーに渡ってくる頂点データ
            struct appdata
            {
                float4 vertex : POSITION;
            };

            //ジオメトリシェーダーからフラグメントシェーダーに渡すデータ
            struct g2f
            {
                float4 vertex : SV_POSITION;
            };

            //頂点シェーダー
            appdata vert(appdata v)
            {
                return v;
            }

            //ジオメトリシェーダー
            //引数のinputは文字通り頂点シェーダーからの入力
            //streamは参照渡しで次の処理に値を受け渡ししている TriangleStream<>で三角面を出力する
            [maxvertexcount(3)] //出力する頂点の最大数 正直よくわからない
            void geom(triangle appdata input[3], inout TriangleStream<g2f> stream)
            {
                // 法線を計算
                float3 vec1 = input[1].vertex - input[0].vertex;
                float3 vec2 = input[2].vertex - input[0].vertex;
                float3 normal = normalize(cross(vec1, vec2));

                [unroll] //繰り返す処理を畳み込んで最適化してる?
                for (int i = 0; i < 3; i++)
                {
                    appdata v = input[i];
                    g2f o;
                    //法線ベクトルに沿って頂点を移動
                    v.vertex.xyz += normal * (sin(_Time.w) + 0.5) * _PositionFactor;
                    o.vertex = UnityObjectToClipPos(v.vertex);
                    stream.Append(o);
                }
            }

            //フラグメントシェーダー
            fixed4 frag(g2f i) : SV_Target
            {
                return _Color;
            }
            ENDCG
        }
    }
}

maxvertexcount

これよくわかりませんでした。

inout ○○Stream

ジオメトリーシェーダーもこれまでの頂点シェーダー、フラグメントシェーダーと同様に
入力と出力でデータを次のステップに渡すみたいです。

ただ、他のシェーダーと違いreturnがありません。出力はどこ?ってなりました。

調べていくと引数のinout ○○Streamが出力であるとわかりました。
このinoutは"参照渡し"ってやつです。

ジオメトリーシェーダー内でinout修飾子が付いた変数を変更すれば、
元々の値にも変更が及びます。次のステップでその値を参照すれば
変更済みの値を渡すことができているということでしょう。

ちなみに、○○Streamの○○には出力されるオブジェクトの形を当てはめることができます。
TriangleStream型PointStream型LineStream型があり、
デモはそれぞれを試したものなります。

【参考リンク】:GLSLの関数の引数で指定するout修飾子とinout修飾子の違いについて
シェーダ内関数への参照渡し

ポリゴンの法線

下記箇所でポリゴンの法線を計算しています。

// 法線を計算
float3 vec1 = input[1].vertex - input[0].vertex;
float3 vec2 = input[2].vertex - input[0].vertex;
float3 normal = normalize(cross(vec1, vec2));

法線は外積(cross関数)を使えば簡単に計算することができます。

頂点シェーダーであれば隣接する頂点座標を知る術はありません(?)が、
ジオメトリーシェーダーであれば、法線の計算でinput[0]input[1]、しているように
隣接する頂点を活用できます。

このように、ポリゴン単位で処理が行われる特性を活かした計算が可能なようです。

参考リンク

ジオメトリシェーダ入門
Graphics Pipeline
Direct3D 10 Shader4.0 ループと最適化
Geometry Shader:Terrain with grass
【Unity】Geometry Shader(ジオメトリシェーダー)の超入門サンプル【初心者向け】