そろそろShaderをやるパート21 Animator使ってジオメトリーシェーダーを制御

7 min読了の目安(約6700字TECH技術記事

そろそろShaderをやります

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

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

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

下準備

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

デモ

Animationで制御したプロパティーをShaderで利用することで
ジオメトリをアニメーションさせています。

その他の処理は下記の過去記事でまとめてます。

【過去記事リンク】
そろそろShaderをやるパート16 ジオメトリーシェーダーで法線方向にポリゴンを移動させる
そろそろShaderをやるパート17 ジオメトリーシェーダーでポリゴンの大きさを変える
そろそろShaderをやるパート18 ジオメトリーシェーダーでポリゴンごとに色を変える
そろそろShaderをやるパート19 ジオメトリーシェーダーでポリゴンを回転させる
そろそろShaderをやるパート20 ジオメトリーシェーダーでポリゴンの操作を組み合わせる

C#スクリプト

using UnityEngine;

/// <summary>
/// アニメーションで変化させた値をShaderで使ってみる
/// </summary>
[ExecuteAlways] //こいつ付けとけばEditorでプレビュー可能
public class AnimationUseToShader : MonoBehaviour
{
    // シェーダーで利用する値たち C#側ではAnimatorで変化させる
    public float gravityValue;
    public float positionValue;
    public float rotationValue;
    public float scaleValue;
    
    /// <summary>
    /// ジオメトリシェーダーを適用したオブジェクトのレンダラー
    /// </summary>
    [SerializeField] private Renderer _renderer;
    
    // Shader側に用意した定義済みの値を受け取る変数たち
    private string _gravityFactor = "_GravityFactor";
    private string _positionFactor = "_PositionFactor";
    private string _rotationFactor = "_RotationFactor";
    private string _scaleFactor = "_ScaleFactor";
    
    private Material _mat;

    void Start ()
    {
        //Editor上でマテリアルのインスタンスを作ろうとするとエラーが出るのでsharedMaterialを利用
        _mat = _renderer.sharedMaterial;
    }
    
    void Update()
    {
        //Shaderに値を渡す
        _mat.SetFloat(_gravityFactor, gravityValue);
        _mat.SetFloat(_positionFactor, positionValue);
        _mat.SetFloat(_rotationFactor, rotationValue);
        _mat.SetFloat(_scaleFactor, scaleValue);
    }
}

Shaderサンプル

Shader "Custom/GeometryAnimation"
{
    SubShader
    {
        Tags
        {
            "RenderType"="Opaque"
        }
        
        //両面描画
        Cull Off

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

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float3 localPos : TEXCOORD0;
            };

            //頂点シェーダー
            appdata vert(appdata v)
            {
                appdata o;
                o.localPos = v.vertex.xyz; //ジオメトリーシェーダーで頂点を動かす前に"描画しようとしているピクセル"のローカル座標を保持しておく
                return v;
            }

            struct g2f
            {
                float4 vertex : SV_POSITION;
                fixed4 color : COLOR;
            };

            //回転させる
            //pは回転させたい座標 angleは回転させる角度 axisはどの軸を元に回転させるか 
            float3 rotate(float3 p, float angle, float3 axis)
            {
                float3 a = normalize(axis);
                float s = sin(angle);
                float c = cos(angle);
                float r = 1.0 - c;
                float3x3 m = float3x3(
                    a.x * a.x * r + c, a.y * a.x * r + a.z * s, a.z * a.x * r - a.y * s,
                    a.x * a.y * r - a.z * s, a.y * a.y * r + c, a.z * a.y * r + a.x * s,
                    a.x * a.z * r + a.y * s, a.y * a.z * r - a.x * s, a.z * a.z * r + c
                );

                return mul(m, p);
            }

            //ランダムな値を返す
            float rand(float2 co)
            {
                return frac(sin(dot(co.xy, float2(12.9898, 78.233))) * 43758.5453);
            }

            //C#側から受け取る変数
            float _GravityFactor;
            float _PositionFactor;
            float _RotationFactor;
            float _ScaleFactor;

            // ジオメトリシェーダー
            [maxvertexcount(3)]
            void geom(triangle appdata input[3], uint pid : SV_PrimitiveID,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));

                //1枚のポリゴンの中心
                float3 center = (input[0].vertex + input[1].vertex + input[2].vertex) / 3;
                float random = 2.0 * rand(center.xy) - 0.5;
                float3 r3 = random.xxx;
              
                [unroll]
                for (int i = 0; i < 3; i++)
                {
                    appdata v = input[i];
                    g2f o;

                    //回転 アニメーション側で制御している値を計算に利用
                    v.vertex.xyz = center + rotate(v.vertex.xyz - center, (pid + _Time.y) * _RotationFactor, r3);
                    //スケール変更 アニメーション側で制御している値を計算に利用
                    v.vertex.xyz = center + (v.vertex.xyz - center) * (1.0 - _ScaleFactor);
                    //法線方向に移動 アニメーション側で制御している値を計算に利用
                    v.vertex.xyz += normal * _PositionFactor * abs(r3);

                    //アニメーション側で制御している値を計算に利用 Y座標に渡して重力っぽく見せる
                    v.vertex.y += _GravityFactor;
                    
                    o.vertex = UnityObjectToClipPos(v.vertex);
                    //ランダムな値
                    //シード値にワールド座標を利用すると移動するたびに色が変わってやかましいのでローカル座標を利用
                    float r = rand(v.localPos.xy);
                    float g = rand(v.localPos.xz);
                    float b = rand(v.localPos.yz);
                
                    o.color = fixed4(r,g,b,1);
                    stream.Append(o);
                }
            }

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

C#→Shaderという変数の受け渡しについては下記の参考リンクでまとめた通りで
大したことはしていません。

【参考リンク】:そろそろShaderをやるパート13 マウスのRayの座標をC#からShaderで受け取る

今回はAnimatorでタイミングを制御し、
AnimationでC#スクリプトで定義したプロパティーの値を変更しています。

語弊があるかもしれませんが、AnimatorAniamtionC#Shader
といった具合です。

下記のように動いているので、
トランジションを発火させるTriggerやBoolを定義すれば任意のタイミングで
アニメーションが可能です。

参考リンク

Raycastによるマウス座標との当たり判定
マテリアルのプロパティをスクリプトから変更【Unity】