Skip to content

第12回 頂点アニメーション

前回: 第11回 PBRマテリアル

今回の授業内容

動的に変形したグリッドメッシュ動的な変形を加えたグリッドメッシュ

  • 頂点シェーダーとフラグメントシェーダーの役割を区別する
  • 頂点位置を時間で変化させ、メッシュを動かす
  • UVまたは頂点カラーで動かす範囲を制御する
  • Object SpaceとWorld Spaceを目的に応じて使い分ける
  • 風で揺れる旗または草のマテリアルを制作する

頂点シェーダーによる変形

頂点シェーダーとフラグメントシェーダー

第1回で、シェーダーには頂点シェーダーとフラグメントシェーダーがあることに触れました

ポリゴンの描画は大きく分けて2段階で行われます
頂点を画面上のピクセルへ変換する処理を担当するのが頂点シェーダー、ピクセルごとの情報を計算する処理を担当するのがフラグメントシェーダーです
この2段階の処理で、三角形の集合体であるポリゴンモデルが画面上に描画されます

本来のシェーダープログラミングでは頂点処理とフラグメント処理は明確に分かれていますが、Shader Graph は頂点シェーダーとフラグメントシェーダーの違いを意識せずに作業できるよう設計されています
この授業でも両者の使い分けを扱ってきませんでした

実はここまでの授業で扱っていたのは、スキンメッシュを除いて主にフラグメントシェーダーです
今回は頂点シェーダーを扱いた頂点操作で、動的にモデルを変形させる方法を学びます

頂点シェーダーの主な役割は、メッシュを構成する頂点の位置、法線、接線を計算して確定することです
この計算中に三角関数で作った波やノイズで作った揺れを頂点位置に干渉させれば、メッシュの形を動的に変化させることができます
旗、草、波、呼吸、膨張、振動など、規則的な動きの表現に適しています

変形に必要な頂点数

分割数が異なるグリッドメッシュの比較分割の例
ただし細かくするほど処理が重くなるため、適切な分割数を選ぶ必要がある

頂点シェーダーが操作できるのは、メッシュに存在する頂点だけです
4頂点しかない平面では、中央部分を曲げられません
縦横に分割された平面なら、各頂点を少しずつ異なる位置へ動かせます

頂点数を増やせば結果も滑らかになりますが、それだけ処理とメモリの負荷も増えます
必要な品質を満たす分割数を選択する必要があります

INFO

動的にポリゴンを分割して頂点数を増やすテッセレーションという手法もあります
テッセレーションを利用できれば、元のメッシュを変更せずに頂点密度を高め、より滑らかな変形を表現できます
ただ、今回使用しているURPのShader Graphでは利用できません
今回はメッシュを細分化せずにそのまま使用します

PositionブロックへObject Spaceの座標を渡す

元の頂点座標に変位を加えるのが頂点アニメーションの基本です

Object Spaceで頂点を変位させる
Position (Object)
元の頂点位置を渡す
Vertex Position
Offset (Object)
変位を加える
Vertex Position

URP Lit Shader GraphのVertex ContextにあるPositionブロックは、Object Spaceの頂点位置を受け取ります
World SpaceやView Spaceの座標ではありませんので注意してください

周期的な揺れを作る

Sineで往復運動を作る

Sineは、サインカーブを計算するノードです
入力に応じて-1から1の間を繰り返します

このSineTimeを入力すると、時間とともに正負へ往復する値になります

w(t)=sin(tSpeed)Amplitude
プロパティ役割
Speed揺れが繰り返す速さ
Amplitude頂点が移動する最大量
Direction頂点を動かす方向
周期的な変位を作る
Time × Speed
周期的な値へ変換する
Sine
Sine
揺れ幅を調整する
Sine × Amplitude
Sine × Amplitude
移動方向を与える
× Direction
× Direction
頂点へ加える変位にする
Offset

このままでは全頂点が同じ量だけ動くため、メッシュ全体が平行移動してしまいます
「曲がる」動きにするには、場所ごとに変位量を変えます

UVで根元を固定する

例えば旗や草を揺らす際は、取り付け部分や根元は固定し、先端にいくほど大きく動かすと自然な表現になります
UVが根元で0、先端で1になるメッシュなら、その値を変位のマスクとして利用できます

text
揺れ × UV.y

根元 UV.y = 0  -> 動かない
中央 UV.y = 0.5 -> 半分動く
先端 UV.y = 1  -> 最大まで動く

例えば横方向を固定したい場合は、UVの縦成分を使います
あるいは逆に、縦方向を固定したい場合は、UVの横成分を使います

使う値はUV以外でも構いません
地面から生える草ならば、ObjectのY座標を使って根元を固定するのも悪くない選択です

より細かく調整するのであれば、頂点カラーに固定度を塗ってマスクにしたり、テクスチャーを使って揺れ具合を塗り分けることもできます
商用ゲームの樹木などでは、根元は固定、枝は少し揺れ、葉は大きく揺れるように頂点カラーで塗り分けられていることがあります

場所ごとに位相をずらす

全頂点が同じタイミングで動くと、板のように見えます
頂点位置をSineの入力へ加えると、場所ごとに波のタイミングをずらせます

w(p,t)=sin(tSpeed+pFrequency)Amplitude

Frequencyを上げると、短い間隔で波が繰り返されます
高すぎる値は細かく震えて見え、メッシュの分割数が足りない場合は角ばります

場所ごとに位相をずらす
Time × Speed
時間による位相を渡す
Sineの入力
Position.x × Frequency
場所による位相を加える
Sineの入力
Sineの入力
周期的な値へ変換する
Sine
Sine
揺れ幅を調整する
× Amplitude
× Amplitude
根元の変位を抑える
× RootMask

揺れの方向の制御

Object SpaceとWorld Space

Object Spaceは、モデル自身の原点と軸を基準にした座標系です
World Spaceは、シーン全体の原点と軸を基準にした座標系です

風の方向や揺れの位相はWorld Spaceで指定できた方が扱いやすいですが、Shader GraphはPositionブロックにObject Spaceの座標を要求します
このため、Shader Graph内の計算はObject Spaceで行うか、World Spaceで計算した結果をObject Spaceに変換する必要があります

INFO

どちらでも結果は変わりませんが、Object Space→World Space→Object Spaceの変換は計算量が増えてしまいます
可能であればObject Spaceで計算する方が効率的です

風向きをObject Spaceへ変換する
Wind Direction (World)
World to ObjectのDirection変換
Transformノード
Transformノード
Object Spaceの方向を得る
Offset Direction (Object)
Offset Direction (Object)
頂点位置へ加算する
Position (Object)

方向を変換するためにはTransformノードのTypeはDirectionを選びます
Positionとして変換すると平行移動の成分まで含まれて意図しない値になってしまうので注意してください

ワールド位置で風の位相を作る

草原全体へ風が伝わる表現では、各オブジェクトのWorld PositionをSineの位相へ使います
オブジェクトごとに同じローカル座標を持っていても、ワールド上の場所が違えば異なるタイミングで揺れます

ワールド位置から風の位相を作る
dot(PositionWS.xz, WaveDirection.xz) × Frequency
場所による位相を渡す
Sineの入力
Time × Speed
時間による位相を加える
Sineの入力
Sineの入力
風の波を作る
Sine

この処理により、風の波が地面を移動するように見せられます
頂点の移動方向と、波が伝わる方向は別々のプロパティとして持たせると調整しやすくなります

応用:ノイズで規則性を崩す

Sineだけでは動きが規則的になってしまいます
第6回で扱ったNoiseを弱く混ぜると、風の強さやタイミングに変化を加えられます
ノイズを加える際は、UV座標とTimeを使って得た乱数をSineの位相へ加える方法が簡単です

Noiseを強くしすぎると細かい振動になり、草や布の重さを感じにくくなります
まずSineで大まかな動きを作り、弱いNoiseを加えて調整するのがおすすめです

法線方向に変形させる

先の例では風の方向を基準に頂点を動かしました
草がたなびくような表現には好適な方法です

続いてメッシュの法線方向を基準に動かすことで波の表現を実装します

変形後の描画を確認する

法線とライティング

法線を更新していない変形メッシュ法線を更新しない場合
変形後の法線を計算したメッシュ変形後の法線を計算した場合

頂点位置を動かしても法線は変化しません
メッシュ形状を変形させても法線が元の平面のままではライティングが不自然になります

変形後の法線は、変形後の面に沿う2つの方向から求めます
法線と求める座標から見て縦と横にずらした位置の変化を使い、外積を取ることで近似できます
外積は第7回で扱っています

変形後の法線を求める
Deformed Tangent
変形面の横方向を渡す
Cross Product
Deformed Bitangent
変形面の縦方向を渡す
Cross Product
Cross Product
外積を単位ベクトルにする
Normalize
Normalize
変形後の法線として使う
Normal

たとえば横方向へ波を作る場合は、横方向へ少し進んだ変位の変化を使って、変形後の横方向を近似できます
縦方向も同じように求め、2つの方向の外積を取ると、波で傾いた面に近い法線になります

text
元の位置 + 変位 = 変形後の位置
変形後の位置の横方向の変化 -> Deformed Tangent
変形後の位置の縦方向の変化 -> Deformed Bitangent
Cross Productで変形後の法線を作る

近似の細かさは実装によります
小さな揺れでは元の法線との差が目立ちにくい場合もありますが、たなびく旗や波打つ水面のように面の向きが大きく変わる表現では法線の計算を避けることはできません
また、メッシュの分割と粒度がそろっていない場合は変形後の法線が不自然に見えることがあります

Boundsとカリング

UnityはメッシュのBoundsを使い、カメラに映る可能性があるか判定します
頂点シェーダーで元のBoundsより外へ大きく動かすと、画面内にあるはずのメッシュが描画されないことがあります

問題が起きた場合は、次を確認します

  • 変位量が必要以上に大きくないか
  • 元メッシュのBoundsが変形後の範囲を含むか
  • 大量のオブジェクトでBoundsを広げすぎていないか

表示されないからといってBoundsを無制限に広げるのも問題です
実際に表示の必要がないのにカメラの描画範囲を超えて描画されることになり、パフォーマンスに悪影響を与えます

実習:風による頂点アニメーションを作る

実習1:草を風向きへ揺らす

  1. 根元から先端へUVが0から1になる草メッシュを用意する
  2. UVを使って根元を固定する
  3. Wind DirectionをWorld Spaceのプロパティとして用意する
  4. TransformノードでWorld SpaceからObject Spaceの方向へ変換する
  5. 変換した方向へ頂点を動かす
  6. 複数の草を回転させて配置し、同じ風向きへ揺れることを確認する

実習2:旗を揺らす

波の頂点変形を作るShader Graphシェーダーの例

  1. 縦横に分割されたPlaneを用意する
  2. URP Lit Shader Graphを作成する
  3. Vertex ContextへPositionブロックを追加する
  4. TimeMultiplySineで往復する値を作る
  5. UVの横方向をマスクにし、固定側を0にする
  6. 揺れをObject Spaceの一定方向へ加える
  7. SpeedAmplitudeFrequencyをプロパティにする

GameObjectを回転し、揺れの方向がモデルと一緒に回転することを確認します

実習3:風の波を移動させる

  1. PositionノードをWorld Spaceに設定する
  2. XZ成分とWave DirectionDot Productを計算する
  3. Frequencyを掛け、Time × Speedへ加える
  4. 結果をSineへ入力する
  5. 複数の草の間を波が移動して見えるか確認する
  6. 必要に応じて低周波のNoiseを加える

授業内課題

旗または草を題材に、根元を固定した頂点アニメーションを作成してください
小さな揺れで仕組みを確認する場合は、元の法線との差が目立たない範囲に変位を抑えます
大きく曲げる旗や布として見せる場合は、変形後の法線計算も完成条件に含めます

完成条件

  • Vertex ContextのPositionを変更している
  • 元の頂点位置へ変位を加えている
  • TimeSineを使って往復運動を作っている
  • UVまたは頂点カラーで固定部分と可動部分を分けている
  • SpeedAmplitudeFrequencyをマテリアルから調整できる
  • Object SpaceまたはWorld Spaceを選んだ理由を説明できる
  • 大きく変形するLit表現では、変形後の法線を計算してNormalへ反映している
  • 静止状態と動作状態を比較できる動画を提出する

発展条件

基本課題が完成した場合は、次のいずれかを追加します

  • World Spaceで複数オブジェクトの風向きを統一する
  • World Positionを使って風の波を伝える
  • 頂点カラーで揺れやすさを塗り分ける
  • Noiseで風の強弱を変化させる
  • 変形後の接線方向と従法線方向から、より安定した法線を近似する
  • C#またはMaterialPropertyBlockから風向きと強さを変更する

よくある問題

問題確認する場所
全頂点が原点へ集まる元のPositionへOffsetを加えているか
メッシュ全体が平行移動するUVまたは頂点カラーのマスク
曲がらず板のまま動くメッシュの分割数、位相に頂点位置を使っているか
固定したい側が動くUVの向き、One Minusの要否
回転すると風向きが変わるWorld方向をObject方向へ変換しているか
一部の角度で突然消えるBoundsと最大変位量
光の当たり方が不自然変位量、法線、非一様スケール

今回のまとめ

  • Vertex ContextのPositionを変更すると、GPU上でメッシュを変形できます
  • SineとTimeを使うと、繰り返す往復運動を作れます
  • UVや頂点カラーは、動かす範囲を制御するマスクとして利用できます
  • 頂点位置を位相へ加えると、メッシュに沿って波が伝わります
  • Object Spaceはモデルに固定された動き、World Spaceはシーンで共有する風に適しています
  • 頂点密度、変形後の法線、Boundsも変形品質と描画結果に影響します

発展資料

座標空間と行列を詳しく確認する場合は、次の資料を参照してください

参考資料