【ShaderGraph】ベジェ曲線を波打たせてみる
以下のような表現を作ってみました。
実装
以下のような実装になっています。
- 多角形(水色)に内接するベジェ曲線を作成 (緑色)
- ベジェ曲線からメッシュを生成
- メッシュ頂点を法線方向に波打たせる
メッシュに持たせる情報
今回の表現でメッシュに以下のような情報を持たせています。
情報 | 詳細 |
---|---|
頂点カラー | 波や着色に利用するグラデーション |
法線情報 | 頂点が動く方向のグラデーション |
頂点カラー
メッシュには以下のような頂点カラーを持たせています。
チャンネル | 情報 |
---|---|
Rチャンネル | 波打たせたい方向のグラデーション (0~1) |
Gチャンネル | 着色に利用するグラデーション(0~1) |
法線情報
メッシュは内側から外側へ波打たせる際、法線を利用しています。
法線は以下のようになっています。
法線ベクトル(水色)
実装01 : ベジェ曲線の生成
多角形に内接する曲線をベジェ曲線を使って作ります。
多角形に内接するベジェ曲線の作り方
多角形の1つの頂点と、両隣の辺の中点を制御点としてベジェ曲線を生成します。
上記の処理をすべての頂点に関して実行すると、多角形に内接するベジェ曲線になります。
ベジェ曲線の座標計算
制御点をP1, P2, P3としてベジェ曲線上の座標を取得するコードです。
/// <summary>
/// P1, P2 ,P3を制御点とするベジエ曲線上の点を取得 (tは0~1の値)
/// </summary>
public static Vector3 Bezier3(Vector3 p1, Vector3 p2, Vector3 p3, float t)
{
return t * t * (p1 - 2f * p2 + p3)
+ t * 2f * (- p1 + p2)
+ p1;
}
法線の計算
メッシュを波打たせる際、法線ベクトルも必要になるので実装します。
法線ベクトル(水色)
ベジエ曲線の接線方向のベクトルと(0,0,1)との外積を計算することで法線ベクトルが求まります。
/// <summary>
/// P1, P2 ,P3を制御点とするベジエ曲線上の点を取得 (tは0~1の値)
/// </summary>
public static Vector3 Bezier3Normal(Vector3 p1, Vector3 p2, Vector3 p3, float t)
{
float dt = 1e-8f;
// ベジエ曲線の近い2点をとる
Vector3 b1, b2;
if (t < dt / 2f)
{
b1 = Bezier3(p1, p2, p3, 0f);
b2 = Bezier3(p1, p2, p3, dt);
}
else if (t > 1f - dt / 2f)
{
b1 = Bezier3(p1, p2, p3, 1f - dt);
b2 = Bezier3(p1, p2, p3, 1f);
}
else
{
b1 = Bezier3(p1, p2, p3, t - dt / 2f);
b2 = Bezier3(p1, p2, p3, t + dt / 2f);
}
// 接線方向ベクトル
var v = Vector3.Normalize(b2 - b1);
// 法線方向ベクトル
var n = Vector3.Cross(v, new Vector3(0, 0, 1)).normalized;
return n;
}
結果
ベジェ曲線を使うことで、多角形から曲線を作ることができます。
実装02 : 曲線のメッシュ化
ベジェ曲線からメッシュを生成します。
ベジエ曲線のメッシュ化
曲線をメッシュ化するためには、曲線を三角形の組み合わせで表現する必要があります。
考え方
曲線の中心点と、曲線上の2点を利用して三角形を作ります。
これを繰り返すことで、曲線を三角形の組み合わせで表現することができます。
メッシュ化の実装
CenterPolygonMeshGenerator
というクラスを作成し、その中でメッシュ化を行うようにしました。
Mesh mesh = new Mesh();
CenterPolygonMeshGenerator.CreateMesh(mesh, vertices, normals);
CenterPolygonMeshGeneratorの実装
using UnityEngine;
public static class CenterPolygonMeshGenerator
{
public static void CreateMesh(Mesh mesh, Vector3[] vertices, Vector3[] normals)
{
int vertexCount = vertices.Length;
var center = GetCenter(vertices);
var meshVertices = new Vector3[1 + vertexCount];
var meshTriangles = new int[3 * vertexCount];
var meshColors = new Color[meshVertices.Length];
var meshNormals = new Vector3[meshVertices.Length];
var meshUV = new Vector2[meshVertices.Length];
// 頂点座標
meshVertices[vertexCount] = center;
for (int i = 0; i < vertexCount; i++)
{
meshVertices[i] = vertices[i];
}
// 法線
meshNormals[vertexCount] = Vector3.zero; // 中心は波打たせないので法線ゼロ
for (int i = 0; i < vertexCount; i++)
{
meshNormals[i] = normals[i];
}
// 頂点カラー
meshColors[vertexCount] = Color.black;
for (int i = 0; i < vertexCount; i++)
{
float t = (float)i / (vertexCount);
meshColors[i] = new Color(t, 1f, 0f, 1f);
}
// UV
meshUV[vertexCount] = Vector2.zero;
for (int i = 0; i < vertexCount; i++)
{
float t = (float)i / (vertexCount - 1);
meshUV[i] = new Vector2(t, 1);
}
// 頂点インデックス
int ti = 0;
for (int i = 0; i < vertexCount; i++)
{
meshTriangles[ti] = vertexCount;
meshTriangles[ti + 1] = (i + 1) % vertexCount;
meshTriangles[ti + 2] = i;
ti += 3;
}
// メッシュ更新
mesh.triangles = null;
mesh.vertices = meshVertices;
mesh.colors = meshColors;
mesh.uv = meshUV;
mesh.normals = meshNormals;
mesh.triangles = meshTriangles;
}
/// <summary>
/// 中心の計算
/// </summary>
private static Vector3 GetCenter(Vector3[] vertices)
{
int vertexCount = vertices.Length;
var sum = Vector3.zero;
for (int i = 0; i < vertexCount; i++)
{
sum += vertices[i];
}
return sum / vertexCount;
}
}
実装03 : 頂点アニメーションの実装(ShaderGraph)
頂点アニメーションの実装
以下のような頂点が波打つアニメーションを実装します。
頂点アニメーションの実装の全体は以下のようになります。
頂点Sineアニメーション
メッシュの頂点カラーのRチャンネルには、メッシュの外周に沿って0~1のグラデーションを設定しています。
これを利用して、外周に沿った頂点アニメーションをつけます。
波の周波数を乗算
周波数というパラメータを用意して、メッシュの外周に沿って配置したい波の個数をコントロールします。
ShaderGraph
これを実現するノードは以下のようになります。 Constantノード(TAU)を利用することで、2πという数値が取れます。
数式
数式であらわすと以下のようになります。
2π乗算する理由
ある値xが0~2πの値をとったときに sin(x)はちょうど一つの波を描きます。
グラフの横軸は入力x, グラフのタテ軸は出力yをあらわしています。
今回、Sineへの入力となる頂点カラーは0~1の範囲をとるため、これを2π倍して0~2πとなるようにします。
シェーダーグラフでは、2πという数値はConstantノード(TAU)で取得できるので、これを利用して以下のようなノードを組みます。
頂点を法線方向に移動させる
Sine波とメッシュ法線を乗算し、これを頂点座標に足すことで頂点が押し出されます。
周波数5のSineで頂点押し出し
Sine波を移動させる
sinの入力に時間を足すことで、波が動くようになります。
数式であらわすと以下のようになります。
結果
頂点が波打つようになります。
実装04 : 頂点カラーを利用した着色 (ShaderGraph)
頂点カラーのGチャンネルには、内側から外側へ向かうグラデーションが設定されています。
これを利用して、色を付けます。
ShaderGraphの実装は以下のようになっています。
- グラデーションとLerpを利用して2色を混ぜて内側のグラデーションを作る
- グラデーションとStepを利用して、輪郭部分を白くしたマスクを取り出す
- 1.の2色グラデーションと2.のマスクを重ねる
参考 : グラデーションを使った着色は以下のページでも解説しています。
1) 2色グラデーションの作成
以下のノードでは、グラデーションを元にして2色をつけています。
2) 輪郭抽出
グラデーションにStepを適用し、輪郭線マスクを作成します。
3) 2色グラデーションと輪郭の合成
1)で作成したグラデーションと、2)で作成した輪郭線マスクをLerpで合成します。
結果
色味などを調整します。
ShaderGraph全体
ShaderGraphの全体の実装は以下のようになっています。
- 頂点カラー(Rチャンネル)と法線、Sineを利用した頂点アニメーション
- 頂点カラー(Gチャンネル)を利用した着色
シェーダープロパティ
シェーダーのプロパティは以下のようなものを実装しています。
プロパティ名 | 詳細 |
---|---|
Color | ポリゴン全体に乗算する色 |
Extrusion | 波のゆれ幅(頂点押し出し量) |
Frequency | 波の周波数 |
Fill Color 01 | グラデーションの内側の色 |
Fill Color 02 | グラデーションの外側の色 |
Edge Color | 輪郭線の色 |
Edge Width | 輪郭線の太さ |
マテリアル設定
マテリアルは以下のように設定しています。
Discussion