rn.log

備忘録など

【シェーダーグラフメモ その21】近未来感のあるリングを作る

以下のような回転リングを作るシェーダーグラフです。
f:id:r-ngtm:20181209230230g:plain

回転リングについて

上記の回転リングですが、立方体の周りに2枚の板を配置しています。
これら2枚の板にシェーダーグラフを適用することで、立方体の周りを回転するようなリングを作ることができます。
f:id:r-ngtm:20181209230537p:plain

ノード全体

極座標を利用して円弧を作り、距離に応じたランダムな速さで円弧を回転させています。
f:id:r-ngtm:20181209230717p:plain

ノード解説

ノード解説 (左側)

極座標から距離と角度を取り出しています。
f:id:r-ngtm:20181209231344p:plain

ノード解説(上側)

距離を利用して、波紋のような模様を作っています。
f:id:r-ngtm:20181210001042p:plain

ノード解説(下側)

時間と距離を掛け合わせたもの角度に足して、外側ほど速く回転する円弧を作っています。
また、距離に応じたノイズを角度に足すことで円弧をランダムにズラしています。
f:id:r-ngtm:20181210001149p:plain

ノード解説(右側)

円弧と波紋を乗算して、円弧の間にすき間が空いたような見た目にしています。 f:id:r-ngtm:20181210001410p:plain

【シェーダーグラフメモ その20】マインクラフトの土ブロック

マインクラフトの土ブロックっぽいものをシェーダーグラフで作ってみました。
f:id:r-ngtm:20181208112329p:plain

ノード全体

草ドットと土ドットを作り、これをLerpで混ぜることでマインクラフトの土ブロックのような絵を作っています。
f:id:r-ngtm:20181208113227p:plain

ノード解説

ノード解説(上部分)

f:id:r-ngtm:20181208113318p:plain

ノード解説(下部分)

f:id:r-ngtm:20181208113344p:plain

ノード解説(右側)

f:id:r-ngtm:20181208113532p:plain

【シェーダーグラフメモ その19】キラキラする模様をつくる

以下のようなきらきらした模様をつくるシェーダーグラフの作り方を解説します。
f:id:r-ngtm:20181207093029g:plain

ノード完成図

f:id:r-ngtm:20181207233141g:plain f:id:r-ngtm:20181208004618p:plain

キラキラ模様を作ってみる

STEP1 : 距離を使って円を描画してみる

まずは以下のような円を作ってみます。
f:id:r-ngtm:20181207233551p:plain

f:id:r-ngtm:20181208004851p:plain

x2 + y2 < A を満たす領域が1.0になります(白くなります)

STEP2 : 距離を使って4角星を描画してみる

Powerノードの2.0の部分を0.75にすると、4角星っぽくなります。
f:id:r-ngtm:20181207234234p:plain

f:id:r-ngtm:20181207234144p:plain

STEP3 : 4角星を並べる

UVノードにMultiplyノードとFractionノードを繋げると、UV座標の0~1の繰り返しを複数個つくることができます。
f:id:r-ngtm:20181207234628p:plain
f:id:r-ngtm:20181207234809p:plain

4角星も複数並ぶようになります。
f:id:r-ngtm:20181207234842p:plain

STEP4 : 4角星を動かしてキラキラさせる

Powerノードの0.75(下図の赤く囲った部分)に変更を加え、時間を乗算します。
f:id:r-ngtm:20181207235159p:plain

具体的には、0.75の部分を以下のようにつなぎ変えます。
f:id:r-ngtm:20181207235245p:plain


4角星が動いてキラキラする感じになります。
f:id:r-ngtm:20181207235347g:plain

STEP5 :時間にランダムを加える

Posterizeノードで解像度を下げたUVを用いてノイズを作り、これを時間に足します。
ここではPosterizeのStepに4を指定しました。
f:id:r-ngtm:20181208002440p:plain

動きにバラつきが生まれました。
f:id:r-ngtm:20181208002556g:plain

STEP6: その他調整を加えて完成

f:id:r-ngtm:20181207093029g:plain:w256
f:id:r-ngtm:20181208004618p:plain

【シェーダーグラフメモ その18】レイマーチングで遊んでみた

レイマーチング(ray marching)と呼ばれる手法を用いて以下のような絵を作ってみました。
f:id:r-ngtm:20181204230928g:plain

目次

サンプル

github.com

レイマーチングのざっくりとした解説

カメラ位置、カメラ向き、UV座標からレイを求め、レイがオブジェクトとぶつかるかどうかを判定します。
オブジェクトにレイがぶつかった場合は色を付ける、そうでない場合は色をつけないという描画を行います。
f:id:r-ngtm:20181204232139p:plain
※ここでいうカメラはレイマーチング計算に利用する視点のことを指しており、Unityのシーンカメラとは別のものです

f:id:r-ngtm:20181204232123p:plain:w240

準備 : レイマーチングを行うカスタムノードの作成

シェーダーグラフには、シェーダーコードをノード化してシェーダーグラフ上で使えるようにするカスタムノードという機能が用意されています。
blogs.unity3d.com

レイマーチングはforループを使った複雑な処理を必要とするため、カスタムノードを利用したシェーダーコードで実装します。

レイマーチングを行うカスタムノード

今回はレイマーチングで球をたくさん描画するカスタムノードを作成してみました。
f:id:r-ngtm:20181204234203p:plain

以下のC#スクリプトをUnityプロジェクトに入れることで、上記のカスタムノードが使えるようになります。

using System.Reflection;
using UnityEditor.ShaderGraph;
using UnityEngine;

/// <summary>
/// レイマーチングで球をたくさん表示するカスタムノード
/// </summary>
[Title ("Raymarching", "Raymarch Sphere")]
public class RaymarchingSphereNode : CodeFunctionNode {
    public RaymarchingSphereNode () {
        name = "Raymarching(Sphere)";
    }

    protected override MethodInfo GetFunctionToConvert () {
        return GetType ().GetMethod ("RaymarchingNode_Function",
            BindingFlags.Static | BindingFlags.NonPublic);
    }

    public override void GenerateNodeFunction (FunctionRegistry registry, GraphContext graphContext, GenerationMode generationMode) {
        registry.ProvideFunction ("distance_func", s => s.Append (@"
            // 距離関数: 点pから球オブジェクトまでの距離を求める
            #define INTERVAL interval
            float distance_func(float3 p, float size, float interval) {
                p = frac(p / INTERVAL) * INTERVAL - INTERVAL / 2.0; // -INTERVAL/2.0 ~ +INTERVAL/2.0 の繰り返しを作る
                return length(p) - size;
            }
        "));
        
        registry.ProvideFunction ("getNormal", s => s.Append (@"
            // 法線の計算
            float3 getNormal(float3 p, float size, float interval) {
                float2 e = float2(0.0001, 0.0);
                return normalize(float3(
                    distance_func(p + e.xyy, size, interval) - distance_func(p - e.xyy, size, interval),
                    distance_func(p + e.yxy, size, interval) - distance_func(p - e.yxy, size, interval),
                    distance_func(p + e.yyx, size, interval) - distance_func(p - e.yyx, size, interval)
                ));
            }
        "));


        base.GenerateNodeFunction (registry, graphContext, generationMode);
    }

    static string RaymarchingNode_Function (
        [Slot (0, Binding.MeshUV0)] Vector2 UV, 
        [Slot (1, Binding.None, 0f, 0f, 4f, 0f)] Vector3 CameraPos, // カメラ位置
        [Slot (2, Binding.None, 0f, 0f, -1f, 0f)] Vector3 CameraDir, // カメラの向きベクトル
        [Slot (3, Binding.None, 0f, 1f, 0f, 0f)] Vector3 CameraUp,  // カメラの上方向ベクトル
        [Slot (4, Binding.None, 1f, 0f, 0f, 0f)] Vector1 ObjectSize, // 球のサイズ
        [Slot (5, Binding.None, 2f, 0f, 0f, 0f)] Vector1 ObjectInterval, // 球の配置間隔
        [Slot (6, Binding.None, 32f, 0f, 0f, 0f)] Vector1 RaymarchLoop, // レイマーチングのループ回数(この数を大きくすると遠くまで描画されるようになりますが重くなります)
        [Slot (10, Binding.None)] out Vector1 Hit, // レイがオブジェクトにぶつかったら1.0, ぶつからなかったら0.0
        [Slot (11, Binding.None)] out Vector1 Distance, // レイマーチングでレイが進んだ距離
        [Slot (12, Binding.None)] out Vector3 Normal  // オブジェクト上の法線
    ) {
        Normal = Vector3.zero;
        return @"{
                #define MAX_REPEAT 100

                float2 p = UV - 0.5;

                // カメラに関する情報(Position, Direction, Up)
                #define cPos CameraPos
                #define cDir normalize(CameraDir)
                #define cUp normalize(CameraUp)
                #define cSide normalize(cross(cUp, cDir))

                // レイマーチング
                float3 ray = normalize(p.x * cSide + p.y * cUp + 1.0 * cDir); // レイの向きベクトル
                float3 rPos = cPos; // レイ位置
                float rLength = 0.0;// レイが進む長さ
                float dist = 0.0; // レイとオブジェクト間の距離
                for (int i = 0; i < min(RaymarchLoop, MAX_REPEAT); i++)
                {
                    dist = distance_func(rPos, ObjectSize, ObjectInterval); // レイ位置からオブジェクトまでの距離を求める
                    rLength += dist; // 距離を足す(レイを進める)
                    rPos = cPos + ray * rLength; // レイ位置の更新
                }

                Hit = step(dist, 0.01); // レイがオブジェクトにある程度近かったら1.0を出力、それ以外は0.0を出力
                Distance = rLength; // レイが進んだ距離を出力
                Normal = saturate(getNormal(rPos, ObjectSize, ObjectInterval)); // レイの交点におけるオブジェクト上の法線を出力
            }";
    }
}

2018-12-05追記

カスタムノードを一部修正しました。
修正前 : #define cSide normalize(cross(cDir, cUp))
修正後 : #define cSide normalize(cross(cUp, cDir))

カスタムノードの詳細

このカスタムノードを利用することで、以下の数値が利用できるようになります。
・レイがオブジェクトに当たったかどうか(当たった場合は1.0、それ以外は0.0を出力)
・レイがオブジェクトに当たるまでに進んだ距離
・オブジェクト上の法線情報(影の計算などに利用できます)

f:id:r-ngtm:20181205000343p:plain

カスタムノードのメニュー位置

カスタムノードは「Raymarching/Raymarchi Sphere」にいます。
f:id:r-ngtm:20181205001955p:plain

とりあえずカスタムノードを使ってみる

レイマーチングのカメラ位置を動かしてみる

ノードを以下のようにつないでみます。
f:id:r-ngtm:20181205003206p:plain

以下のような絵が出力されました。
f:id:r-ngtm:20181205003534g:plain:w256

zのマイナス方向(画面の奥側)を向いたカメラがzのプラスの方向(画面の手前側)へ移動しているため、
球オブジェクトが画面の奥へ移動しているような動きをします。

カメラの向きを変えてみる

カメラ向き用のVector3を作成して、カスタムノードのカメラ向きとして入力します。
f:id:r-ngtm:20181205005134p:plain

マテリアル上からカメラ向きを変えられるようになりました。なかなか楽しいです。
f:id:r-ngtm:20181205005609g:plain:w420

レイマーチングで遊んでみた

今回、このレイマーチングノードを使って以下のようなシェーダーグラフを作ってみました。
f:id:r-ngtm:20181204230928g:plain

ノード全体

f:id:r-ngtm:20181205013138p:plain

ノード解説(左側)

レイマーチングのパラメータの指定をしています。
f:id:r-ngtm:20181205011922p:plain

ノード解説(右側)

レイがオブジェクトへぶつかるまでに進んだ距離を補正し、最後にSampleGradientで色を付けて画面出力しています。
f:id:r-ngtm:20181205014530p:plain

陰影を付けてみる

f:id:r-ngtm:20181205084156p:plain:w256

法線情報と光の内積をとることで、影を付けることができます。 DotProductノードを使うと内積を計算できます。 f:id:r-ngtm:20181205084220p:plain

シーンカメラとレイマーチングカメラを同期させてみる

Unityのシーンにあるカメラとレイマーチングのカメラを同期させる方法を解説します。
f:id:r-ngtm:20181205225055g:plain:w400 f:id:r-ngtm:20181205225134g:plain:w400

シェーダーグラフの設定

カメラの位置、向き、上方向の計3つのプロパティを追加し、レイマーチングノードへ入力します。
f:id:r-ngtm:20181205224342p:plain:w512

Referenceの部分は以下のように設定します。
f:id:r-ngtm:20181205224937p:plain

このReferenceはMaterialのSetVector(name, value)メソッドのnameに指定する名前となります。

        int _cameraUp = Shader.PropertyToID("_CameraUp");
        int _cameraDir = Shader.PropertyToID("_CameraDir");
        int _cameraPos = Shader.PropertyToID("_CameraPos");
        _material.SetVector(_cameraUp, transform.up);
        _material.SetVector(_cameraDir, transform.forward);
        _material.SetVector(_cameraPos, transform.position);

カメラ位置をシェーダーグラフへコピーするC#スクリプト

以下のスクリプトCameraShaderSync.csをUnityプロジェクトへ追加します。

using UnityEngine;

public class CameraShaderSync : MonoBehaviour
{
    [SerializeField] Material _material; // カメラと同期させる対象のマテリアル
    int _cameraUp;
    int _cameraDir;
    int _cameraPos;

    void Start()
    {
        _cameraUp = Shader.PropertyToID("_CameraUp");
        _cameraDir = Shader.PropertyToID("_CameraDir");
        _cameraPos = Shader.PropertyToID("_CameraPos");
    }

    void Update()
    {
        // 位置と向きをシェーダーに渡す
        _material.SetVector(_cameraUp, transform.up);
        _material.SetVector(_cameraDir, transform.forward);
        _material.SetVector(_cameraPos, transform.position);
    }
}


CameraShaderSyncコンポーネントをシーンカメラにアタッチし、Inspectorタブからレイマーチングのマテリアルを登録します。
f:id:r-ngtm:20181205225557p:plain

再生ボタンを押してカメラ同期

Unityの再生ボタンを押すと、シーンの位置と向きがレイマーチングのカメラ情報へ反映されるようになります。
f:id:r-ngtm:20181205225055g:plain:w400

C#スクリプト制御でカメラを動かしてみる

C#スクリプトを使ってカメラを動かしてみました。

f:id:r-ngtm:20181205232323g:plain

以下のC#スクリプトをカメラにアタッチして動かしています。

using UnityEngine;

[RequireComponent(typeof(Rigidbody))]
public class SetVelocity : MonoBehaviour
{
    [SerializeField] float m_AngularSpeed = -1f; // 角速度の係数
    [SerializeField] Vector3 m_AngularSpeeds = new Vector3(-0.3f, 0.4f, -0.3f);  // 角速度
    [SerializeField] float m_PositionSpeed = -1f; // 移動速度の係数
    [SerializeField] Vector3 m_PositionSpeeds = new Vector3(-7f, -7.5f, -0.2f); // 移動速度

    void Start()
    {
        var rigidbody = GetComponent<Rigidbody>();
        rigidbody.angularVelocity = m_AngularSpeed * m_AngularSpeeds;
        rigidbody.velocity = m_PositionSpeed * m_PositionSpeeds;
        Debug.Log(rigidbody.velocity);

    }
}

【シェーダーグラフメモ その17】3Dオブジェクトの中に液体が入っているような感じにするシェーダーグラフ

3Dオブジェクトを液体が入った感じにするシェーダーグラフです。
f:id:r-ngtm:20181202202939g:plain

ワールド座標ベースのサイン波で塗りつぶしており、オブジェクトを回転させても波の位置は固定されたままです。
f:id:r-ngtm:20181202205318g:plain

サンプル

ShaderGraphExample/Assets/ShaderGraphExample/17_Liquid at master · rngtm/ShaderGraphExample · GitHub

ノード全体

f:id:r-ngtm:20181202205825p:plain

Unlitマスターの設定

今回のシェーダーグラフではUnlitマスターノードを以下のように設定しています。 f:id:r-ngtm:20181202205152p:plain

【シェーダーグラフメモ その16】マグマの波紋を作ろう

マグマが広がるような表現を作る方法を解説します。

f:id:r-ngtm:20181202133634g:plain

サンプル

ShaderGraphExample/Assets/ShaderGraphExample/16_MagmaRipple at master · rngtm/ShaderGraphExample · GitHub

ざっと解説

座標(X, Y, Z)のXZ成分を取り出し、座標(0, 0)からの距離をとります。
この距離を使って波を作っています。
f:id:r-ngtm:20181202134840p:plain

シェーダーグラフを作る

STEP0 : 板オブジェクトとUnlitシェーダーグラフの準備

Unity標準のPlaneオブジェクトを作り、これにUnlitシェーダーグラフから作ったマテリアルを適用しておきます。
f:id:r-ngtm:20181202141017p:plain:w200 f:id:r-ngtm:20181202141156p:plain:h200

STEP1 : Distanceノードを使って原点(0,0)からの距離をとる

以下のようにノードをつなぎます。
f:id:r-ngtm:20181202140708p:plain

原点(0,0)からの距離が板の色に反映されるようになりました。
f:id:r-ngtm:20181202140825p:plain:w256

STEP2: 距離にSineノードを適用する

距離をSineノードにつなぎます。
f:id:r-ngtm:20181202141352p:plain

波紋っぽい色になりました。
f:id:r-ngtm:20181202141506p:plain:w256

STEP3: Sineの範囲を(-1,1)から(0,1)に変換する

Sineノードの出力は(-1,1)の範囲になっているため、Remapノードを使って範囲(-1, 1)を(0, 1)へ変換します。
後に頂点座標へ加算する時、負の値が含まれていると板が下へ凹んでしまうためこのような補正を入れています。
f:id:r-ngtm:20181202141958p:plain
f:id:r-ngtm:20181202142020p:plain:w256

STEP4: 波紋を時間で動かす

SubtractノードとTimeノードを使い、距離から時間を減算して波紋が広がるようにします。
f:id:r-ngtm:20181202142744p:plain
f:id:r-ngtm:20181202142814g:plain

STEP5: 波紋を使って頂点を動かす

波紋を頂点座標のY成分へ加算して頂点を動かします。
f:id:r-ngtm:20181202143634p:plain

板がY方向に動くようになりました。
f:id:r-ngtm:20181202143806g:plain

STEP6: smoothstepで波紋をとがらせる

smoothstepノードを使って波紋をとがらせます。
f:id:r-ngtm:20181202152144p:plain
f:id:r-ngtm:20181202152231g:plain

補足 : smoothstepの挙動について

f:id:r-ngtm:20181202154212p:plain:w200

smoothstepが返す値

smoothstepノードは以下のような値を返します。

  • In<Edge1の場合は0.0を返す
  • In>Edge2の場合は1.0を返す
  • Edge1 < In < Edge2の場合は0.0~1.0の間を滑らかに補間するような値を返す
    f:id:r-ngtm:20181202153235p:plain

グラフの描画にはiq氏のGraphToyを使わせていただきました。(http://www.iquilezles.org/apps/graphtoy/)

smoothstepをsinに対して使った場合

smoothstepをsin(x)に対して使用することで、sinカーブをとがらせることができます。
f:id:r-ngtm:20181202153849p:plain

今回はsin(x)が(0,1)の範囲に収まるように補正しているため、実際は以下のようなグラフを描きます。 f:id:r-ngtm:20181202153933p:plain

STEP7: 波の高さを調整する

Multiplyノードを使って波の高さを1.4倍にします。
f:id:r-ngtm:20181202155524p:plain

なんだか良い感じの形の波になってきました。
f:id:r-ngtm:20181202155613g:plain

STEP8: SampleGradientノードで色をつける

波紋(Vector1型の数値)をSampleGradientでカラーグラデーションに変換して色を付けます。
f:id:r-ngtm:20181202160009p:plain

マグマっぽくなりました。
f:id:r-ngtm:20181202160141g:plain

STEP9 : Smoothstepノードで色を絞る

波が立っていないところにも色がついてしまっていて残念な印象なので、SmoothStepノードでくっきりとさせたいと思います。
f:id:r-ngtm:20181202160728p:plain

以下のようにSmoothstepノードをつなぎます。
f:id:r-ngtm:20181202161521p:plain

色がくっきりとしました。
f:id:r-ngtm:20181202161539g:plain

変化が分かりにくいので、変更前と変更後を並べてみました。
f:id:r-ngtm:20181202161915p:plain

Step10 : ノイズで質感を加える

原点(0,0)からの距離を利用して波紋を作っていましたが、この距離にノイズを加えてみます。
今回はNoiseScaleに45.0を指定してみました。
f:id:r-ngtm:20181202162440p:plain f:id:r-ngtm:20181202162526g:plain

Step11 : 板オブジェクトを差し替える(完成)

25x25の頂点からなるサイズ10の板オブジェクトに差し替えると以下のような見た目になります。
f:id:r-ngtm:20181202163145g:plain

今回はC#スクリプトで板オブジェクトを生成してみました。

using UnityEngine;

/// <summary>
/// 板状のメッシュを作成C#スクリプト
/// </summary>
[RequireComponent(typeof(MeshFilter))]
[RequireComponent(typeof(MeshRenderer))]
public class GeneratePlane : MonoBehaviour
{
    [SerializeField] bool generateMeshOnUpdate = false; // 毎フレームメッシュ更新
    [SerializeField] int xCount = 25; // x方向の頂点数
    [SerializeField] int zCount = 25; // y方向の頂点数
    [SerializeField] float planeSize = 10f; // 板のサイズ
    Mesh mesh;

    void Start()
    {
        mesh = new Mesh();
        GetComponent<MeshFilter>().mesh = mesh;
        Create();
    }

    private void Update()
    {
        if (generateMeshOnUpdate)
        {
            Create();
        }
    }

    private void Create()
    {
        if (xCount < 2) xCount = 2;
        if (zCount < 2) zCount = 2;

        // 頂点座標(vertices)と法線(normals)作成
        Vector3[] vertices = new Vector3[xCount * zCount];
        Vector3[] normals = new Vector3[vertices.Length];
        int vi = 0;
        for (int z = 0; z < zCount; z++)
        {
            for (int x = 0; x < xCount; x++)
            {
                vertices[vi++] = new Vector3(
                    (float)x / (xCount - 1) - 0.5f, 
                    0f, 
                    (float)z / (zCount - 1) - 0.5f
                    ) * planeSize;
            }
        }
        for (int i = 0; i < vertices.Length; i++)
        {
            normals[i] = new Vector3(0f, 1f, 0f);
        }

        // 頂点インデックス(triangles)作成
        int[] triangles = new int[(xCount - 1) * (zCount - 1) * 6];
        int ti = 0;
        int triangleOffset = 0;
        for (int z = 0; z < zCount - 1; z++)
        {
            for (int x = 0; x < xCount - 1; x++)
            {
                triangles[ti++] = triangleOffset + xCount; // 2
                triangles[ti++] = triangleOffset + 1; // 1
                triangles[ti++] = triangleOffset + 0; // 0
                triangles[ti++] = triangleOffset + xCount + 1; // 3
                triangles[ti++] = triangleOffset + 1; // 1
                triangles[ti++] = triangleOffset + xCount; // 2
                triangleOffset++;
            }
            triangleOffset++;
        }

        // メッシュ更新
        mesh.triangles = null;
        mesh.vertices = vertices;
        mesh.normals = normals;
        mesh.triangles = triangles;
    }
}

【シェーダーグラフメモ その15】ドット絵風の海

ドット絵風の海を作るシェーダーグラフです。 f:id:r-ngtm:20181201210027g:plain

ノード解説

UV座標をPosterizeノードで解像度を落とし、Voronoiノードを使って模様を作っています。 f:id:r-ngtm:20181201212200p:plain

海の動きについて

VoronoiノードのAngleOffsetに時間を入力することで動きを作り出しています。 f:id:r-ngtm:20181201212538p:plain