Unityを使ってシェーダーを作る方法を学ぶ連載。前回まで「サーフェースシェーダー」を扱ってきたが、今回は「頂点・フラグメントシェーダー」を扱う。
Unityを使ってシェーダーを作る方法を学ぶ連載「Unityで始めるシェーダー入門」。連載第1回ではシェーダーの概要と作り始めるまでの環境構築を紹介した。
前回までは、「サーフェースシェーダー」を扱ってきたが、今回は「頂点・フラグメントシェーダー」を扱う。コード自体も少し複雑になるが、頂点・フラグメントシェーダーは、今回のみの使用であるため、ここでしっかりと理解していただきたい。
本稿で作成するシェーダーでは、最終的に図1のような処理が適用される。
頂点・フラグメントシェーダーでは、頂点シェーダーで頂点の位置情報を加工した後、フラグメントシェーダーで最終的なピクセルの色を決定する。ライティングの計算を行う必要がないものや、より高度なエフェクト、表現を作成したい場合に使用する。
頂点シェーダーでは、3次元の描画はポリゴンの表示が基本になっている。また、ポリゴンは一般的に三角形の面をつなぎ合わせたものが使用されている。三角形は頂点の位置が決まれば自動的に面の位置も形も決まるため、実際に計算するのは頂点の位置だけでいいことになる。これが「頂点シェーダー」だ。
頂点シェーダーの役割は「モデルの頂点の位置を、画面上の位置に変換すること」だ。頂点の位置は、「モデルの位置」「モーションによる変形」「カメラの位置」などで変化する。これらが「画面上ではどの位置に頂点が来るか」を計算するのが頂点シェーダーの基本的な処理となるのだ。
次に、フラグメントシェーダーとは、「色を計算する」ことだ。主に「テクスチャ(画像)からテクスチャ座標の位置にある色を持ってくる」という処理になるが、他にも、独自の色を設定したり、陰の部分を暗くしたり、半透明にしたりといった、さまざまな処理が行える。フラグメントシェーダーは、DirectXでは「ピクセルシェーダー」とも呼ばれる。
詳細については、Unityのマニュアルを参考にしてほしい。
Unityメニューから新しいScene画面を開く。まずは、いつものように「Shaders」フォルダ内に「VertexFragmentShader」というシェーダーを作るが、今回は前回までのようなサーフェースシェーダーの作り方と少し異なる。前回までは「Standard Suface Shader」と選択していたが、今回はShadersフォルダの上でマウスの右クリックをして「Create」→「Shader」→「Unlit Shader」と選択する(図2)。
次に、「Materials」フォルダの中に「VertexFragmentMaterial」を作成する。この作成手順は前回までと同じだ。
Shadersフォルダに作成した、VertexFragmentShaderファイルをダブルクリックすると、エディタが起動してコードが表示される。不要なコードを削除して、必要なコードを多少追加して整形したのが、リスト1だ。
Shader "Unlit/VertexFragmentShader" { Properties { _Color("Color", Color) = (1,0,0,1) _MainTex ("Texture", 2D) = "white" {} } SubShader { Tags { "RenderType"="Opaque" } LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; sampler2D _MainTex; fixed4 _Color; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = v.uv; return o; } fixed4 frag (v2f i) : SV_Target { fixed4 col = tex2D(_MainTex, i.uv); eturn col*_Color; } ENDCG } } }
今回筆者が行ったのは、5行目と33行目と46行目を少し変更しただけだ。必要なコードのほとんどはUnlitシェーダーを作成すると自動的に記述される。自分で意識的に書く必要はないが、その役割を知っておくことは重要だ。以降、コードの中身を解説する。
3〜7行目のPropertiesの中に、5行目の「_Color("Color", Color) = (1,0,0,1)」を追加した。これにより、カラーピッカーから色の選択が可能になる。6行目の「_MainTex ("Texture", 2D) = "white" {}」は2D型でテクスチャを選択する項目が表示される。
12行目の「Pass」ブロックは、Unlitで作成すると自動的に追加される。このブロック内には、ゲームオブジェクトのジオメトリを1回レンダリングする処理を記述する。
15行目の「#pragma vertex vert」は、頂点シェーダーとして「ヴェール関数」(vert)を使用するという意味だ。16行目の「#pragma fragment frag」は、フラグメントシェーダーとして「フラグメント関数」(frag)を使用するという意味だ。vertとfrag関数は、この後で定義している。
18行目では、「UnityCG.cginc」をインクルードしている。これをインクルードすると、法線/テクスチャ座標/頂点シェーダー入力などのヘルパー関数を利用できる。ここでは、37行目の「float4 UnityObjectToClipPos(float3 pos)」関数を使うためにインクルードしている。
20〜24行目では、「appdata」構造体を定義している。この中では、Unityから受け取る値を記述する。22行目は、頂点の位置としてvertexを定義している。23行目は、テクスチャの座標としてuvを定義している。
25〜29行目では「v2f」構造体を宣言している。この中では、フラグメントシェーダーで使用する値を記述している。v2fとは「Vertex To Fragment」の略だ。頂点シェーダーからフラグメントシェーダーに複数の値を渡す際には、このように構造体を定義してそれを利用する。
v2f構造体内の27行目は、23行目と同じく、テクスチャの座標として「uv」を定義している。27〜28行目では、UV座標(テクスチャ座標)と、その座標の頂点のスクリーン座標をフラグメントシェーダーに渡していることになる。
22行目では「POSITION」、28行目では「SV_POSITION」と定義している。POSITIONは、あらゆる任意の座標情報を示すための「セマンティクス」で、SV_POSITIONは「システム上で扱われる任意ではない座標情報である」ことを示すために使用されるという違いがある。
セマンティクスとは、シェーダー入力またはシェーダー出力に付加されている文字列で、パラメーターの使用目的に関する情報を伝達する。「vertexシェーダーの入力(関数の引数)は全てセマンティクスを持たねばならない」とされている。
セマンティクスの一覧は表1のようになる。
セマンティクス | 意味 | 型 |
---|---|---|
POSITION[n] | オブジェクト空間内の頂点位置 | float4 |
NORMAL[n] | 法線ベクトル | float4 |
TEXCOORD[n] | テクスチャ座標 | float4 |
TANGENT[n] | 接線 | float4 |
COLOR[n] | ディフューズ カラーとスペキュラ カラー | float4 |
参考 |
29行目では、ピクセル座標を受け取るvertexを定義している。
31行目では、テクスチャ型として「_MainText」を宣言している。必ず、3〜7行目のPropertiesで宣言したのと同じ名称でないといけない。プロパティで受け取ったデータをfrag関数内で使うための定義だ。
32行目では、RGBAの4原色としてfixed4型の_Colorを宣言している。プロパティで受け取ったデータをfrag関数内で使うための定義だ。
34〜40行目では、25〜29行目で定義しているv2f構造体のvert関数を定義している(リスト2)。引数にappdata構造体のvを指定している。vert関数ではフラグメントシェーダーで必要なパラメーターを返す。少なくともピクセル座標を出力する必要がある。
v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = v.uv; return o; }
リスト2の3行目では、v2f構造体として「o」を宣言している。
4行目では、ローカル座標をカメラから見た座標に変換している。5〜6行目では、4行目で取得した情報を、v2f構造体に詰め込まれてフラグメント関数(frag)に送られる。
リスト1の42〜46行目ではリスト3のようにfrag関数を定義している。引数にv2f構造体の「i」を指定している。
fixed4 frag (v2f i) : SV_Target { fixed4 col = tex2D(_MainTex, i.uv); return col*_Color; }
frag関数では最終的なピクセルの色を返す。ここでは「tex2D」関数で、インスペクターで指定した色をそのまま返している。フラグメントシェーダーは、カラーを出力し「SV_Target」セマンティクスを持つ。「ピクセルの色」という意味だ。ここでも、SV_Targetを指定して色を返すことを宣言している。
リスト3の3行目で、インスペクター内のテクスチャに指定したテクスチャを出力している。
リスト3の4行目では、3行目で取得した変数colの値(テクスチャ)に、指定した色を乗算して返している。
VertexFragmentMaterialインスペクターを表示させてみよう。Materialsフォルダ内のVertexFragmentMaterialを選択して、インスペクターを表示させると、図3のように表示される。
図3のShaderの箇所を見ると、今までのStandard Suface Shaderは「Custom」グループにシェーダーが入っていたが、今回の頂点・フラグメントシェーダーでは「Unlit」というグループの中に入っているのが分かる。
今回は3Dキャラクターの身体の一部分である衣装の色を変えてみる。そのためには各身体の部位に分かれたテクスチャが必要になる。そこで、Asset Storeから「Soldiers Pack」という無料のアセットをインポートしてほしい。
インポートした「Soldier Pack」に含まれるファイルから「Assets」→「SoldiersPack」→「ArmyOne」フォルダにある「Army 01.FBX」をScene画面上にドラッグ&ドロップする。カメラやキャラクターの位置を調整して図4のような表示にしてほしい。
VertexFragmentMaterialインスペクターを設定しよう。「Color」には「Green」を選択。テクスチャには「Select」ボタンをクリックして、「Select Texture」から「Head_Hair_ArmyCapColor」(兵士のヘルメット)を指定する(図5)。
このVertexFragmentMaterialを、Scene画面にある兵士の頭にドラッグ&ドロップしてみよう。図6のように表示されれば成功だ。
今回は頂点・フラグメントシェーダーについて解説した。
一番理解が難しいのはv2f構造体のvert関数で、フラグメントシェーダーで必要なパラメーターを返すところではないかと思う。理解するには、パラメーターのコードを変えたりするなど経験を積んでいく以外に方法はないのではないだろうか。本稿を参考にして、いろいろと試してみることをお勧めする。
次回は「サーフェースシェーダーのリムライティング」について解説する。お楽しみに。
薬師寺 国安(やくしじ くにやす) / 薬師寺国安事務所
薬師寺国安事務所代表。Visual Basicプログラミングと、マイクロソフト系の技術をテーマとした、書籍や記事の執筆を行う。
1950年生まれ。事務系のサラリーマンだった40歳から趣味でプログラミングを始め、1996年より独学でActiveXに取り組む。
1997年に薬師寺聖とコラボレーション・ユニット「PROJECT KySS」を結成。
2003年よりフリーになり、PROJECT KySSの活動に本格的に参加。.NETやRIAに関する書籍や記事を多数執筆する傍ら、受託案件のプログラミングも手掛ける。
Windows Phoneアプリ開発を経て、現在はWindowsストアアプリを多数公開中。
Microsoft MVP for Development Platforms - Client App Dev (Oct 2003-Sep 2012)。
Microsoft MVP for Development Platforms - Windows Phone Development(Oct 2012-Sep 2013)。
Microsoft MVP for Development Platforms - Client Development(Oct 2013-Sep 2014)。
Microsoft MVP for Development Platforms-Windows Platform Development(Oct 2014-Sep 2015)。
Copyright © ITmedia, Inc. All Rights Reserved.