知識0からのUnityShader勉強

知識0からのUnityShader勉強

UnityのShaderをメインとして、0から学んでいくブログです。

【UnityShader】モデルのポリゴンを点で出す #24

前回の成果

ステンシルバッファが理解できた。

soramamenatan.hatenablog.com

今回やること

ポリゴンを点で表現しようと思います。

nn-hokuson.hatenablog.com

事前準備

Sceneにモデルを配置してください。

f:id:soramamenatan:20190925144451p:plain

自分が使用しているモデルはおもちゃラボ様の下準備の項目にあるものです。

nn-hokuson.hatenablog.com

ソースコード

using UnityEngine;

public class MeshTopologyPoints : MonoBehaviour {
    void Start() {
        MeshFilter meshFilter = GetComponent<MeshFilter>();
        meshFilter.mesh.SetIndices(meshFilter.mesh.GetIndices(0), MeshTopology.Points, 0);
    }
}

Meshのindexを色々変更しているように見えます。

Meshとは

ポリゴン(三角形)の集まりで、3Dモデルの表面の形を表しているものです。

メッシュが所有している情報は以下の通りです。

情報名 意味
頂点 ポリゴンを構成する三角形のVector3型のList
三角形のリスト 3つの頂点リストを保持、座標ではなく頂点の配列に対するインデックス
テクスチャのUV座標 テクスチャと頂点の位置関係を保持する配列
サブメッシュ 1つのメッシュは複数のサブメッシュから構成されており、GetTrianglesで取得することができる。
マテリアルが複数の場合はサブメッシュも複数ある

indie-du.com

サブメッシュは、マテリアルのことです。
例えば、同じオブジェクトにマテリアルが3つアタッチされていたらサブメッシュの数は3つとなります。
今回は1つです。

オブジェクトのMeshを見てみる

UnityのSceneのタブの"Shaded"となっているところを"Wireframe"に変更してみてください。

f:id:soramamenatan:20190925184832p:plain

そうするとこのようにワイヤーフレームで描画され、メッシュをみることができます。
f:id:soramamenatan:20190925190109p:plain

MeshFilter

これはMeshのデータをMeshRendererに渡すクラスです。

MeshFilter.mesh.SetIndices()

Meshを再構築する関数です。
引数は、

// 第一引数 : indexの配列
// 第二引数 : Meshのトポロジー
// 第三引数 : SubMeshのindex
meshFilter.mesh.SetIndices(int[] indices, MeshTopology topology, int submesh)

となっています。

MeshFilter.mesh.GetIndices()

// 第一引数 : SubMeshのindex
MeshFilter.mesh.GetIndices(int submesh)

これで各頂点のindexが取得できます。

f:id:soramamenatan:20190927152918g:plain

その30 気になる頂点インデックスの意義:より引用

例えば上の図のような四角形があった場合、

// [0, 1, 2, 0, 2, 3]が入る
int[] indices = MeshFilter.mesh.GetIndices(0);

となります。

MeshTopology

そもそもトポロジーとは、ポリゴンの分割の仕方という意味です。
今回はPointsを指定していますが、他にも何種類かあるので紹介します。

トポロジー 意味
Traiangles 三角形から構成されるメッシュ
Quads 四角形から構成されるメッシュ
Lines 線から構成されるメッシュ
LineStrip 線分から構成されるメッシュ
Points 点から構成されるメッシュ

それぞれの違いは下記の画像がわかりやすいです。

f:id:soramamenatan:20190927155139p:plain

Unity の PrimitiveType は3つある。GL.TRIANGLE_STRIP,MeshTopology.LineStrip,PrimitiveType.Quad | whaison.jugem.jp:より引用

結果

ドラゴンが頂点で表示されれば成功です。

f:id:soramamenatan:20190927160436p:plain

他のトポロジーを指定してみた

Traiangles

f:id:soramamenatan:20190927160818p:plain

普段の描画方法と変わらないので変化なし。

Lines

f:id:soramamenatan:20190927160839p:plain

線で描画されているのでスカスカ
囚われてるドラゴンみたいで少しかっこいい。

LineStrip

f:id:soramamenatan:20190927160842p:plain

線同士が繋がっているのでLineより密度が高い。

Quads

f:id:soramamenatan:20190927160823p:plain

Lineと同じような描画方法だけど、勝手に補完してくれてるからかLineより密度高い。

頂点の色を変える

次に頂点の色を変えるShaderを書きます。

Shader "Unlit/gradationPoints" {
    Properties {
        _StartColor ("StartColor", Color) = (1, 1, 1, 1)
        _EndColor ("EndColor", Color) = (1, 1, 1, 1)
    }
    SubShader {
        Tags { "RenderType" = "Transparent" }
        Cull Off
        Blend SrcAlpha OneMinusSrcAlpha

        Pass {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            struct v2f {
                float4 pos : SV_POSITION;
                float3 texcoord : TEXCOORD0;
            };

            float4 _StartColor;
            float4 _EndColor;

            v2f vert(appdata_base v) {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.texcoord = mul(unity_ObjectToWorld, v.vertex).xyz;
                return o;
            }

            half4 frag(v2f i) : COLOR {
                float4 startColor = float4(_StartColor.r, _StartColor.g, _StartColor.b, _StartColor.a);
                float4 endColor = float4(_EndColor.r, _EndColor.g, _EndColor.b, _EndColor.a);
                return lerp(startColor, endColor, i.texcoord.y * 0.2f);
            }
            ENDCG
        }
    }
}

startColorとendColorを補完しているだけです。
propertyで値を設定するのを忘れないように気をつけてください。

復習

UnityObjectToClipPos

オブジェクト空間からカメラのクリップ座標へ頂点を変換しています。

o.texcoord = mul(unity_ObjectToWorld, v.vertex).xyz

オブジェクトの座標をワールド座標系に変換しています。
余談ですが、セマンティクスがTEXCOORD0なのはfloat3型なだけで、NORMALでも構いません。

結果

f:id:soramamenatan:20190927184058p:plain

ドラゴンがカラフルになりました。

法線方向に飛ばす

これだけだと物足りないので法線方向に飛ばしてみようと思います。

Script

using UnityEngine;

public class MeshTopologyPoints : MonoBehaviour {
        // 追加
    private Material _material;
    private float _normalPow;
    void Start() {
                _material = GetComponent<Renderer>().material;
                // ここまで
                MeshFilter meshFilter = GetComponent<MeshFilter>();
                meshFilter.mesh.SetIndices(meshFilter.mesh.GetIndices(0), MeshTopology.Points, 0);
    }
        // 追加
    void Update() {
        _normalPow += Time.deltaTime * 3;
        _material.SetFloat("_NormalPow", _normalPow);
    }
        // ここまで
}

こちらはShaderの_NormalPow変数を増やしているだけです。

Shader

Shader "Unlit/gradationPoints" {
    Properties {
        _StartColor ("StartColor", Color) = (1, 1, 1, 1)
        _EndColor ("EndColor", Color) = (1, 1, 1, 1)
        // 追加
        _NormalPow("NormalPow", float) = 0
        // ここまで
    }
    SubShader {
        Tags { "RenderType" = "Transparent" }
        Cull Off
        Blend SrcAlpha OneMinusSrcAlpha

        Pass {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            struct v2f {
                float4 pos : SV_POSITION;
                float3 texcoord : TEXCOORD0;
            };

            float4 _StartColor;
            float4 _EndColor;
            // 追加
            float _NormalPow;
            // ここまで

            v2f vert(appdata_base v) {
                v2f o;
                // 追加
                float3 normal = UnityObjectToWorldNormal(v.normal);
                o.pos = UnityObjectToClipPos(v.vertex) + float4(normal * _NormalPow, 0);
                // ここまで
                o.texcoord = mul(unity_ObjectToWorld, v.vertex).xyz;
                return o;
            }

            half4 frag(v2f i) : COLOR {
                float4 startColor = float4(_StartColor.r, _StartColor.g, _StartColor.b, _StartColor.a);
                float4 endColor = float4(_EndColor.r, _EndColor.g, _EndColor.b, _EndColor.a);
                return lerp(startColor, endColor, i.texcoord.y * 0.2f);
            }
            ENDCG
        }
    }
}

法線の処理を追加しました。

UnityObjectToWorldNormal

ワールド空間の正規化された法線です。

ベクトルの正規化とは、ベクトルの方向は維持しつつ大きさを「1」にする事を指します。

3Dを基礎から勉強する 正規化 - デジタル・デザイン・ラボラトリーな日々:より引用

float4(normal * _NormalPow, 0)

o.pos = UnityObjectToClipPos(v.vertex) + float4(normal * _NormalPow, 0);

上の式の

float4(normal * _NormalPow, 0)

この部分で少し詰まりました。
なんで0なの???

0の理由

少し考えれば当然で、

UnityObjectToClipPos(v.vertex)

上の式の部分でクリップ座標を出しているのですが、

 {
\begin{bmatrix}
Tx \\\ 
Ty \\\
Tz \\\
1
\end{bmatrix}
}

※TはPosition

答えがこうなります。
0以外を足してしまうと1が変わってしまい、座標の計算が間違ってしまうので0を足しています。

結果

f:id:soramamenatan:20190927192757g:plain

このように弾けるような演出ができれば成功です。

今回はここで終わりです。
ここまでご視聴ありがとうございました。