Unity Shader を用いたメッシュ頂点の編集 (URP 環境)
Unity シェーダーの勉強.
おそらくこれからは URP 環境での開発が一般的になると思われるので, URP 環境での書き方について調べた.
Shader Graph を使えばもっと楽だとは思うが WebGL などへの応用性を考えて HLSL を用いた記述について勉強する.
本項ではメッシュ頂点座標の編集までの部分についてまとめる.
検証環境
- Windows 11 Home
- Unity 6000.0.50f1
- Universal Render Pipline
各シェーダーの違いについて
シェーダーについて種類と簡単な違いについては以下の通り
Lit 系
- 様々なライティングに反応してリアルなレンダリングする
- Unlit と比べ負荷が高い
- Baked Lit を用いるとライティングがベイクされるため比較的負荷が低い
Unlit
- ライティングがない (光の影響を反映しない) シェーダー
- 負荷が軽い
カスタムシェーダー記述についての基礎
- 公式ドキュメント を基にカスタムシェーダーを書く
テンプレートの作成
- 2025 年 6 月現在では
Create -> Shader
からは URP 用のテンプレートを作ることは出来なさそうなので, .shader ファイルを作成して公式ドキュメントから以下をコピペする ($FILENAME
をファイル名に置換)
// このシェーダーはコード内に事前定義されている色でメッシュ形状を塗りつぶします。 Shader "$FILENAME/URPUnlitShaderBasic" { // Unity シェーダーのプロパティブロック。この例では出力の色がフラグメントシェーダーの // コード内に事前定義されているため、このブロックは空です。 Properties { } // シェーダーのコードが含まれる SubShader ブロック。 SubShader { // SubShader Tags では SubShader ブロックまたはパスが実行されるタイミングと条件を // 定義します。 Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline" } Pass { // HLSL コードブロック。Unity SRP では HLSL 言語を使用します。 HLSLPROGRAM // この行では頂点シェーダーの名前を定義します。 #pragma vertex vert // この行ではフラグメントシェーダーの名前を定義します。 #pragma fragment frag // Core.hlsl ファイルには、よく使用される HLSL マクロおよび関数の // 定義が含まれ、その他の HLSL ファイル (Common.hlsl、 // SpaceTransforms.hlsl など) への #include 参照も含まれています。 #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" // この構造体定義では構造体に含まれる変数を定義します。 // この例では Attributes 構造体を頂点シェーダーの入力構造体として // 使用しています。 struct Attributes { // positionOS 変数にはオブジェクト空間内での頂点位置が // 含まれます。 float4 positionOS : POSITION; }; struct Varyings { // この構造体内の位置には SV_POSITION セマンティクスが必要です。 float4 positionHCS : SV_POSITION; }; // Varyings 構造体内に定義されたプロパティを含む頂点シェーダーの // 定義。vert 関数の型は戻り値の型 (構造体) に一致させる // 必要があります。 Varyings vert(Attributes IN) { // Varyings 構造体での出力オブジェクト (OUT) の宣言。 Varyings OUT; // TransformObjectToHClip 関数は頂点位置をオブジェクト空間から // 同種のクリップスペースに変換します。 OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz); // 出力を返します。 return OUT; } // フラグメントシェーダーの定義。 half4 frag() : SV_Target { // 色変数を定義して返します。 half4 customColor = half4(0.5, 0, 0, 1); return customColor; } ENDHLSL } } }
- 基本的に内容はすべて
SubShader
ブロックに記述する - 複数書いたとしても実行時は Unity が自動的に動作環境と互換性のある
SubShader
を一つ選んでくれる Tags
ブロックの"RenderPipeline"
で使用するレンダーパイプラインを指定するため, ここが URP になっていることを確認する (空だと Builtin Render Pipline となる)Pass
ブロック内に HLSL (High Level Shading Language) を記述する- HLSL は Microsoft 開発の DirectX 系で使われるシェーダー言語で, GLSL (WebGL 等のシェーダー言語) と同じような役割のもの
頂点シェーダーとフラグメントシェーダーの役割
- 頂点シェーダーは対象の各頂点や法線などの座標について, スクリーン上への座標変換処理を行う
- フラグメントシェーダーは各ピクセルの描画について処理を行うシェーダー
- フラグメントシェーダーは相対的に重い処理を行うためコーディングに注意した方がよいらしい
頂点シェーダーの編集
- 上記テンプレートでは頂点シェーダーにおいて
TransformObjectToHClip
関数にそのまま渡している (この関数は頂点のオブジェクト座標からスクリーン座標への変換を行う) TransformObjectToHClip
関数に与えるオブジェクト座標の値を編集することで頂点の動きを与えることが可能
// vertex shader Varyings vert(Attributes IN) { // output object Varyings OUT; // position change with noise float3 outPos = IN.positionOS.xyz; /* 任意の頂点座標操作 */ // to clip space OUT.positionHCS = TransformObjectToHClip(outPos); return OUT; }
(例) 各頂点座標にランダムな揺らぎを与える
- 頂点座標操作を行うシェーダーの例として, 各頂点の座標に時間変化するランダムな揺らぎを与えるシェーダーを作成する
ノイズ関数の定義
- Unity のシェーダーではデフォルトで疑似乱数生成の関数は用意されていない
- シェーダーグラフで扱われるランダムノードは ドキュメント によると frac, sin, dot 関数を用いて作成されている
- 今回は疑似乱数生成用の関数として以下を定義する
inline float randomNoise(float2 uv, float time) { return frac(sin(dot(uv + time, float2(12.9898, 78.233))) * 43758.5453); }
- 引数として 2 次元の数と時間の変数を受け取る
時間の取得
- Unity のシェーダーにおいてはいくつかの変数が定義なしで使用可能 (参照 公式ドキュメント)
_Time
はシーンがロードされてからの時間を 4 種類のスケールで返す- 今回は早いスケールで変化を加えたかったため
_Time.x
を使用
頂点シェーダーの書き換え
- 先ほど定義したノイズ関数を用いて各頂点の座標を変換するコードを追加する
// vertex shader Varyings vert(Attributes IN) { // output object Varyings OUT; // position change with noise float3 outPos = IN.positionOS.xyz; float scale = 0.1; outPos.x += randomNoise(IN.positionOS.yz, _Time.x) * scale; outPos.y += randomNoise(IN.positionOS.xz, _Time.x) * scale; outPos.z += randomNoise(IN.positionOS.xy, _Time.x) * scale; // to clip space OUT.positionHCS = TransformObjectToHClip(outPos); return OUT; }
シェーダーをマテリアルに適用
Create>Material
で作ったマテリアルのシェーダーについて, 自作のシェーダーを適用- 3D の球に上記マテリアルを適用すると揺らぎが確認できる
サンプルコード
- 今回使用したサンプルコードは以下の通り
Shader "VertNoiseShader/URPUnlitShaderBasic" { Properties { } SubShader { Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline" } Pass { HLSLPROGRAM #pragma vertex vert #pragma fragment frag #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" // float4 _Time; // unity defined time function // user defined random function inline float randomNoise(float2 uv, float time) { return frac(sin(dot(uv + time, float2(12.9898, 78.233))) * 43758.5453); } struct Attributes { // vertices position of object space float4 positionOS : POSITION; }; struct Varyings { // vertices position of screen space float4 positionHCS : SV_POSITION; }; // vertex shader Varyings vert(Attributes IN) { // output object Varyings OUT; // position change with noise float3 outPos = IN.positionOS.xyz; float scale = 0.1; outPos.x += randomNoise(IN.positionOS.yz, _Time.x) * scale; outPos.y += randomNoise(IN.positionOS.xz, _Time.x) * scale; outPos.z += randomNoise(IN.positionOS.xy, _Time.x) * scale; // to clip space OUT.positionHCS = TransformObjectToHClip(outPos); return OUT; } // fragment shader half4 frag() : SV_Target { // return color half4 customColor = half4(0.5, 0, 0, 1); return customColor; } ENDHLSL } } }