まんてらさんによるUnityシェーダー講座。内容は Unity上でのシェーダーが初心者の方 向けでした。
この記事の趣旨
「講演内容を見直したい」「もうちょっと詳しく知りたい」人向けです。
著者知識も混ぜつつ書きます。あと、分からなかったらマニュアル読めよ という暴力をふるう プログラマなので、ちまちまマニュアルのリンクを貼ります。
判りやすいのと正確さは別の話ですが、正確さに欠けていたら…マサカリください。私も知りたいです。
記事で書くこと
- Unityでのシェーダーの構造(ShaderLabをざっくりと)
- 頂点シェーダとフラグメントシェーダーで、モデルを1色に染める方法
記事で書かないこと
前提知識
- Unityとは
- Unityの基本的な使い方
- UnityのHierarchyについて
- コンピューターグラフィックスで使用される座標変換
そのほか
Properties
について (次回内容のはずなので書きません)- テクスチャの貼り方 (次回内容なので書きません)
- 開発環境の構築手順
- 講演内容の再現手順
- 宣伝
Unityシェーダー事始め
シェーダーは 画面に映像を出力する為の設計図です。本村・クリストファー・純也 さん曰く、料理のレシピに似ている
- https://cgworld.jp/feature/1607-gtmf2016.html
- カレーを作る手順は料理のレシピ
- コンピュータグラフィックを描く手順はシェーダー
- 正確には シェーダーを交えたレンダーパイプラインがレシピあたると思うがそんな細かいことはおいておこう
Unity の Matreialについて
https://docs.unity3d.com/Manual/class-Material.html
とりあえず、モデルにシェーダーを適用させるために必要なモジュールくらいでOK。
Shaderファイルについて
https://docs.unity3d.com/Manual/SL-Reference.html
Unityのシェーダーは「ShaderLab」言語で書かれています。
https://docs.unity3d.com/Manual/SL-Shader.html
ShaderLabはだいたい以下の形をとります。
Shader "MannteraSample" { SubShader { Pass { CGPROGRAM // Cg言語で書く ENDCG } } }
Shader
マテリアルの名前みたいなものです。アッパーキャメルで名前を付けると、大文字部分で区切るみたいです。これスラッシュじゃなかったっけ…? スラッシュで階層区切りができます。大文字区切りなんてないんや
e.g. `ManteraSample/Shader` だと、Materialは「ManteraSample > Shader」で選択可能。
なーんにも書かれていないシェーダーファイルは、「Not supported」から選択できます。
SubShader
https://docs.unity3d.com/Manual/SL-SubShader.html
Pass
をまとめている親玉です。
同じシェーダーでもGraphics の設定やデバイスによって分けたい時などに、SubShader
を複数作って分けることができます。
Pass
レンダーパイプライン内で実行される、シェーダ-プログラム本体です。 ここに、頂点シェーダーやフラグメントシェーダーを書きます。
Cgで書く - Cg、HLSLとは
まんてらさんはCgって言ってたけど、どっちでもいいっぽいです。
https://docs.unity3d.com/Manual/SL-ShaderPrograms.html
補足:
後、CG言語は古い言語だと言うのはUnity側も認識してるから、新しいグラフィクスパイプラインではHLSLに置き換わってるよー。
— まんてら@ゲーム作り系VTuber (@manntera) 2019年3月21日
まぁ、一応今のパイプラインでもHLSLPROGRAMで宣言すれば使える事は使えるけどね。
まじかー 書くならHLSLがよさそうですね。
Pass
内に書かれたマクロ CGPROGRAM
ENDCG
内で書ける、シェーダー言語の一種です。
OpenGL には GLSL 、DirectX には HLSL という言語が使われます。Cg はNVIDIAが開発したシェーダー言語だそうです(でも古いって聞いたゾ)。
シェーダーに入力する頂点データの宣言
まず、Vertex Shader に頂点データを流し込みます。
頂点データは複数の引数で受け取ることもできますが、長くなりがちなので 構造体 として宣言を行い、1つの構造体を引数として受け取る事が多いです。
今回は以下の形で受け取ります。
struct appdata { float4 vertex : POSITION; };
頂点の座標のみを受け取る構造体です。
float4
は4次元ベクトルであることを表します。 3次元ではない理由は、行列計算の都合があるためです。
(この辺のことは座標変換についての別記事を書く予定です)
POSITION
は頂点座標である事を表します。この部分は セマンティクス ( Semantics ) といいます。
セマンティクスは色々ありますが、今回は説明を省きます。
頂点シェーダーからの出力と、フラグメントシェーダーが受け取るデータの宣言
もう一つ、頂点シェーダーが出力し、フラグメントシェーダーが受け取る構造体も宣言します。
正確にはフラグメントシェーダーが受け取るかもしれないデータ、ですが。
struct v2f { float4 pos : SV_POSITION; };
SV_POSITION
は座標変換後の座標が入ります。
シェーダーのエントリーポイントを宣言
一つのシェーダーファイルに2つのシェーダーを収める為、宣言が必要です。
##pragma vertex vert ##pragma fragment frag
公式ドキュメント にある「HLSL snippets」を読むとわかる人は何となくつかめると思います。
##pragma vertex
が頂点シェーダーの宣言 (vert
がエントリーポイント)、##pragma fragment
がフラグメントシェーダーの宣言(frag
がエントリーポイント)です。
頂点シェーダー を書く
ここで、座標変換などを行います。
v2f vert(appdata v) { v2f _out = (v2f)0; _out.pos = UnityObjectToClipPos(v.vertex); return _out; }
appdata
、v2f
は先ほど宣言した構造体です。
UnityObjectToClipPos
関数は、ざっくりいうと3次元の情報である頂点座標を、2D画面につぶしてくれる関数です。
詳しい話は https://docs.unity3d.com/Manual/SL-BuiltinFunctions.html で、どうぞ。
フラグメントシェーダー を書く
ここで最終的な色を決めます。
float4 frag(v2f _in) : SV_Target { float4 col=(float4)0; col=float4(1,1,1,1); return col; }
SV_Target
は出力するデータを表すセマンティックスです。
今、これは単色(白色)を出力しています。
col=float4(1,1,1,1)
の部分を変えれば色が変わります。
講演はここまででした。
テクスチャ貼ったりするのは次回だそうです。