🌈

そろそろShaderをやるパート54 ワイヤフレームで描画する

2022/02/23に公開

そろそろShaderをやります

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

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

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

下準備

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

デモ

スクリプトからトポロジを変化させるパターン(手前)とジオメトリーシェーダーで指定するパターンを試しました。

Shaderサンプル

まずはShaderの方です。
以前ジオメトリーシェーダーについて記事を書いています。
【参考リンク】:そろそろShaderをやるパート16 ジオメトリーシェーダーで法線方向にポリゴンを移動させる

Shader "Custom/GeometryWireFrame"
{
    Properties
    {
        _Color ("Color", Color) = (1, 1, 1, 1)
    }
    SubShader
    {
        Tags
        {
            "RenderType"="Opaque"
        }

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma geometry geom
            #pragma fragment frag

            #include "UnityCG.cginc"

            fixed4 _Color;

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

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

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

            //ジオメトリシェーダー
            [maxvertexcount(3)] 
            void geom(triangle appdata input[3], inout LineStream<g2f> stream)
            {
                [unroll]
                for (int i = 0; i < 3; i++)
                {
                    appdata v = input[i];
                    g2f o;
                    o.vertex = UnityObjectToClipPos(v.vertex);
                    stream.Append(o);
                }
            }

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

先ほど、トポロジを変化させると書きましたが、
トポロジとは"ポリゴンの構成のこと"を指します。

inout LineStream<g2f> streamがそれにあたります。
LineStreamとすることで辺のトポロジを指定できます。

C#スクリプト

次にスクリプトで変更するパターンです。
変更させたいオブジェクトにアタッチします。

using UnityEngine;

/// <summary>
/// トポロジを変更する
/// </summary>
public class WireFrame : MonoBehaviour
{
    /// <summary>
    /// メッシュ
    /// </summary>
    private Mesh mesh;

    void Start()
    {
        mesh = GetComponent<MeshFilter>().mesh;

        if (mesh.GetTopology(0) == MeshTopology.Triangles)
        {
            // メッシュをワイヤーフレームで再構築する
            mesh.SetIndices(MakeIndices(mesh.triangles), MeshTopology.Lines, 0);
        }
    }

    /// <summary>
    /// 三角形の頂点の配列から、三角形の辺の頂点の配列を生成する
    /// </summary>
    private int[] MakeIndices(int[] triangles)
    {
        var indices = new int[2 * triangles.Length];
        var i = 0;
        for (int t = 0; t < triangles.Length; t += 3)
        {
            indices[i++] = triangles[t];
            indices[i++] = triangles[t + 1];
            indices[i++] = triangles[t + 1];
            indices[i++] = triangles[t + 2];
            indices[i++] = triangles[t + 2];
            indices[i++] = triangles[t];
        }

        return indices;
    }
}

過去の記事で頂点インデックスを定義し、そのインデックスを用いてメッシュを作るということをやりました。その記事の中でインデックスの配列の"順番を参照して面ができあがる"と書きました。

これと同様に、辺もインデックスの配列の順番を参照してできあがります。
ただし、そのまま三角形の頂点の配列を使用し、辺として適用すると必要なインデックスの数が足りなくなってしまいます。

例えば、単純な三角形のインデックスの配列は下記です。

var triangles = new List<int>
{
    1, 2, 3
};

三角形の場合、3つずつインデックスの配列を参照するのでこれで1つ面ができあがります。

次に、これを辺にした場合が下記です。

var edges = new List<int>
{
    1, 2, 
    2, 3,
    3, 1
};

本来は繋がっていますがわかりやすくするために辺を切り離したものが下記です。

それぞれの辺を表すために2つずつインデックスを使用しています。
これによりインデックスの配列の数が2倍になっていることがわかります。

それを行っているのが下記のMakeIndicesというわけです。

/// <summary>
/// 三角形の頂点の配列から、三角形の辺の頂点の配列を生成する
/// </summary>
private int[] MakeIndices(int[] triangles)
{
    var indices = new int[2 * triangles.Length];
    var i = 0;
    for (int t = 0; t < triangles.Length; t += 3)
    {
        indices[i++] = triangles[t];
        indices[i++] = triangles[t + 1];
        indices[i++] = triangles[t + 1];
        indices[i++] = triangles[t + 2];
        indices[i++] = triangles[t + 2];
        indices[i++] = triangles[t];
    }

    return indices;
}

参考リンク

UnityでありもののMeshをワイヤフレームで描画する
Mesh.SetTriangles
その6 ジオメトリシェーダの使い方
レンダリングパイプライン

Discussion