シェーダー

目次

はじめに

 今回はhgimg4のシェーダーに手を出してみました。 シェーディング言語、前からやってみたいと思ってたんですよね。

 シェーダーと言うのは、物体がどのように画面上に表示されるかを記述したプログラムです。 シェーダー次第で物体の色や質感が変わってきます。 このシェーダーと呼ばれるプログラムは「シェーディング言語(シェーダー言語)」で記述されていて、レンダリングする際にGPUで実行されます。

 シェーディング言語は、言語の分類を示すもので具体的な言語名を持つものは複数あります。例えばDirectXのHSLS、OpenGLのGLSL、PlayStationのPSSLなどがあります。 いずれもUnityで使えるらしいですね。

 hgimg4では、OpenGLシェーディング言語「GLSL」が採用されています。 hgimg4はGameplay3Dをベースに、Gameplay3DはOpenGLをベースに作られているためこの選択になったのだろうと思います。 GLSLの言語文法はC言語がベースとなっていて、C言語がわかる人ならあまり違和感なく読むことができます。 ポインタ型が存在しないので簡単そうです。 といはいえHSP3だけしか使ったことがないユーザーには見慣れない書き方があるので、ハードルが高そうですね。

 C言語に似ているとは言え、初めて触れる言語なので基本的な部分を調べてみました。 なお、ここには調査した際のメモを記載しています。間違った解釈を記載している可能性があります。 情報を鵜呑みにせず、リンク先や実際にプログラムを動かしてみてご確認ください。

参考になりそうな資料

 関連する資料の場所を調べてみました。ざっと目を通すとGLSLの雰囲気がわかってくると思います。

GamePlay

GamePlay
GamePlay 2D/3Dのサイト
GamePlay Wiki
Gameplay3Dのwiki。基本的な説明やチュートリアルなど。
gameplay3d GitHub
gameplay3dのGitHub。ダウンロードはこちらから。
gameplay3d - Game Framework
gameplay3dのクラスのリファレンス。

OpenGL

シェーディング言語 - Wikipedia
とりあえず「シェーディング言語」についてWikipedia。
OpenGLシェーディング言語 - Wikipedia
日本語版Wikipediaは歴史や概要に重点が置かれているが、英語版は言語の仕様について説明がされているので学習者向き。
OpenGL Wiki
用語の説明などが詳細。
クロノス・グループクロノス・グループ(日本)
OpenGLなどの開発元。日本語サイトはニュースと本家へのリンクだけっぽい。本家だとマニュアルとか置いてるようです。(但し探すのが大変)

GLSL

GLSL - Wikipedia
GLSLに付いて、基本的な文法の説明やシェーダーの例も記載されています。
WebGL 1.0 API Quick Reference Card
GLSL公式チートシート。英語なので、ダウンロードしたPDFをファイルごとGoogle翻訳に突っ込んで置くと読みやすくなります。
GLSL (OpenGL ES2.0)リファレンス
データ型、スウィズル演算子、ビルトイン関数、ビルトイン変数と定数

GLSL 解説サイト

第一回 WebGLスクール 「WebGLの概念」
wgld.orgの管理人であるdoxasさん主催のWebGLスクールの内容。全12回の講座。
[連載]やってみれば超簡単! WebGL と GLSL で始める、はじめてのシェーダコーディング(1)
全10回の講座。サンプルを自分で専用のエディタを使って実行しながら確認できる。GLSL Editor
WebGL 学習に役立つサイト 2016 年度版
人によってはここ見とけば十分なのかもしれない。
GLSL で暖を取るための準備をしよう! GLSL お役立ちマニュアル
Webでの利用例が紹介されています。
GLSL Shaderを扱う際の基本的な用語のメモ
用語について。
7日間でマスターするUnityシェーダ入門
Unityで学ぶシェーダー。
wgld.org WebGL contents
前述したサイトは入門や概要的な位置づけ、部分的に掘り下げたものが多いですが、ここは広範囲にわたって詳細に解説してくれているので、かゆいところに手が届きそう。

バーテックスシェーダーとフラグメントシェーダー

 hgimg4で任意に指定できるシェーダーは、2種類で構成されています。 バーテックスシェーダーフラグメントシェーダーです。 (.materialファイルについては今回触れません。)

バーテックスシェーダー(*.vert)
頂点シェーダーとも言われるもの。 立体物を構成する面の頂点の位置を計算する。 上手に使うと、3Dモデルの頂点を移動したりすることもできます。
フラグメントシェーダー(*.frag)
テクスチャや光源など「様々な情報を元に実際の色を決定する」もの。 上手に使うと、テクスチャを移動したりできます。

 3Dモデルが画面上に表示されるまでのおおまかな流れは次の図のようになります。 3Dモデルデータは、各シェーダーに定義された計算を経て画面上に表示されています。

3Dモデル → Vertex → Frag → 出力
            頂点      色

 頂点の情報が無いと色は決められない、色が決まらないと画面にも出せない、なんとなく納得できますね。 なおこれらをhgimg4でシェーダーを指定する場合は、gpusershader命令を使用します。

変数

 文法はC言語に似ていると言っておきながら、変数ではvec3やビルトイン変数、uniformなど独特のものがいくつかあるのでなれるまで大変です。 代表的なものだけを簡単に整理してみました。

記述・用語 意味
const 定数
例:
const vec3 POSITION = vec3(0.0, 0.0, 0.0);
#define マクロ
例:
#define white vec4(1.0)
attribute アプリケーションから送らてきたデータを参照するための変数。 頂点ごとに異なる情報を渡すのに使う。
例:
attribute vec4 color;
uniform アプリケーションから値を受け取るための変数に使用する。 全ての頂点に対して一律に処理される情報を渡す際に使う。 HSP3からgpmatprm系命令で値を渡すことができます。 サンプルでは、変数名の頭に「u_」がついている。(u_xxxxxx)
例:
uniform mat4 mvpMatrix;
varying シェーダ間(頂点シェーダとフラグメントシェーダ)でデータをやり取りするための変数 サンプルでは、変数名の頭に「v_」がついている。(v_xxxxxx)
例:
varying vec4 vColor;
精度修飾子 どのくらいの精度でデータを扱うかと指定する。
低 < low < mediump < highp < 高
とりあえず、これはあまり気にする必要はなさそうです。
precision宣言 精度修飾子を使用するために頭につける宣言です。
例:
precision mediump float;

 この他にも、宣言無しで使用できる組み込み変数「ビルトイン変数」というものもあります。 ビルトイン変数は接頭辞がついていて「gl_xxxxxxxx」のような変数名です。 シェーダーの外で宣言されたグローバル変数のような感じのものです。 代表例を書き出してみます。

gl_Position vec4 [入力]頂点シェーダで、頂点の座標を代入するための変数(必須)
gl_PointSizefloat[入力]頂点シェーダで描く点の大きさを指定するのに使う
gl_FragColorvec4 [入力]フラグメントシェーダで色を出力するための変数
gl_FragCoordvec4 [出力]フラグメントシェーダで処理ピクセルの座標を読み取るのに使う

 他にもありますが、上記のどれかのリンク先を参照してください。

付属のシェーダー

 sample\hgimg4\res\shaders フォルダには、たくさんのシェーダー(vert/frag)が保存されいます。 シェーダーの動作結果は、サンプルから確認することができます。

customshader.hsp
tamane3.hsp
posteffect.hsp(mod_posteffect.asから呼び出し)
light_test5eff.hsp(mod_posteffect.asから呼び出し)

 どのシェーダーがどんな機能があるのかわかりにくいので、表にしてみました。 サンプルにないものについては、ファイル名から組み合わせを予想して書いています。 すぐに調査が面倒になってきたので空欄ご容赦を。

名称 バーテックスシェーダー フラグメントシェーダー
ぼかしフィルター sprite.vert p_blur.frag
ガウスぼかしフィルター(高品質) p_blur2.vert p_blur2.frag
グローフィルター sprite.vert p_bright.frag
コントラストフィルター sprite.vert p_contrast.frag
ブラウン管フィルター sprite.vert p_crtmonitor.frag
ブラウン管? sprite.vert p_crtmonitor2.frag
カットオフフィルター sprite.vert p_cutoff.frag
白黒フィルター sprite.vert p_grayscale.frag
モザイクフィルター sprite.vert p_mosaic.frag
古いフィルムフィルター sprite.vert p_oldfilm.frag
セピアフィルター sprite.vert p_sepia.frag
輪郭抽出フィルター sprite.vert p_sobel.frag
colored.vert
 ├skinning-none.vert
 └lighting.vert
colored.frag
 └lighting.frag
font.vert font.frag
simpletex.vert simpletex.frag
skybox.vert skybox.frag
sprite.vert sprite.frag
spritecol.vert spritecol.frag
terrain.vert
└lighting.vert
terrain.frag
 └lighting.frag
textured.vert
 └lighting.vert
textured.frag
 └lighting.frag
テクスチャーを白黒に変換 textured.vert textured_gray.frag
 └lighting.frag

 ファイル名が p_….vert/.frag となっているものについてはhgimg4で準備されたシェーダーで、その他は gameplay3d 付属のものっぽいです。 機能は調べきれていません。(.materialファイルについては今回は触れません。)

フラグメントシェーダー

 少しわかってきたので、試しにフラグメントシェーダーを読んでみます。
sprite.frag


	#if defined(OPENGL_ES) || defined(GL_ES)
	#ifdef GL_FRAGMENT_PRECISION_HIGH
	precision highp float;
	#else
	precision mediump float;
	#endif
	#endif

	///////////////////////////////////////////////////////////
	// Uniforms
	uniform sampler2D u_texture;

	///////////////////////////////////////////////////////////
	// Varyings
	varying vec2 v_texCoord;
	varying vec4 v_color;

	void main()
	{
	    gl_FragColor = v_color * texture2D(u_texture, v_texCoord);
	}

 フラグメントシェーダーの最初に書かれていることが多いこの部分について見てみます。


	#if defined(OPENGL_ES) || defined(GL_ES)
		#ifdef GL_FRAGMENT_PRECISION_HIGH
			precision highp float;
		#else
			precision mediump float;
		#endif
	#endif

 実行環境がOpenGL ESだった場合のときのための処理です。 OpenGL ESは、スマホなど組込みシステムで使用されている3D-CG用のAPIです。 デバイスによっては高精度highpのデータに対応していないことがあるため、環境が変わっても動作するように最初にこのように記述されているようです。

 フラグメントシェーダーでhighpがサポートされている場合は、floatはhighp(高精度)。 そうでない場合は、floatはmediump(中精度)の精度を使用します。

 低スペックスマホでの動作を考えなくていいなら書かなくても大丈夫ですね。


	// Uniforms
	…
	// Varyings
	…

v_~は、バーテックスシェーダーから引き継いだ変数。
u_~は、HSP3側から入力された変数。


	void main()
	{
	    …
	}

 ここがメインプログラム。実行されるのはこの部分です。 例では使用していませんが、関数を使う場合は、この行より上に書く必要があります。


	    gl_FragColor = v_color * texture2D(u_texture, v_texCoord);

 gl_FragColorに色情報を書き込んでいます。 テクスチャルックアップ関数 texture2D にテクスチャ(u_texture)とテクスチャ座標(v_texCoord)を与えて取り出した色の情報と、頂点の色情報とをかけ合わせて出力する色を決めているようです。

 u_textureはシェーダー内で宣言されていますが、値が代入されていません。 値の代入はhgimg4が勝手にやってくれているらしいので何もせず使用することができます。 実はこのような「定義済値」がいくつも出てくるのですが、すべてを説明している資料が見つからずサンプルの解読が捗っていません。 どこかに良い資料無いものですかね。

 さて次は…このサンプル以外でよく見かけるヘッダ部分。


	#ifndef DIRECTIONAL_LIGHT_COUNT
		#define DIRECTIONAL_LIGHT_COUNT 0
	#endif
	#ifndef SPOT_LIGHT_COUNT
		#define SPOT_LIGHT_COUNT 0
	#endif
	#ifndef POINT_LIGHT_COUNT
		#define POINT_LIGHT_COUNT 0
	#endif
	#if (DIRECTIONAL_LIGHT_COUNT > 0) || (POINT_LIGHT_COUNT > 0) || (SPOT_LIGHT_COUNT > 0)
		#define LIGHTING
	#endif

 ディクショナルライト、スポットライト、ポイントライトの個数が定義されていない場合は、0個として定義。 という感じで、ライト関係の宣言みたいですね。

定義済みの変数

 u_worldViewProjectionMatrix とかどこにも説明が見つけられなくて困りました。 (マテリアルファイル側でWORLD_VIEW_PROJECTION_MATRIXを代入してあるっぽいのですが、そもそもWORLD_VIEW_PROJECTION_MATRIXの資料が見つからない。今回は.materialファイルについては触れません。) 最終手段として、別のものを説明する資料から引っ張ってくることにしました。 よく使われる変数名は同じ意味で利用されるだろうというわけです。 正しくない記述があるかもしれませんが、メモ書きということでご容赦を。

参考資料
https://www.materialx.org/docs/api/_hw_shader_generator_8h_source.html

Vertex input variables :

変数名 説明
i_position vec3オブジェクト空間での頂点の位置
i_normal vec3オブジェクト空間における頂点の法線
i_tangent vec3オブジェクト空間における頂点の接線
i_bitangent vec3オブジェクト空間における頂点の2接線
i_texcoord_Nvec2N:番目のUVセットの頂点テクスチャ座標
i_color_N vec4N番目のカラーセット(RGBA)の頂点カラー

Uniform variables :

変数名 説明
u_worldMatrix mat4 ワールド変換ワールド変換
u_worldInverseMatrix mat4 ワールド変換、反転ワールド変換、反
u_worldTransposeMatrix mat4 ワールド変換、転置ワールド変換、転
u_worldInverseTransposeMatrix mat4 ワールド変換、反転および転置ワールド変換、反
u_viewMatrix mat4 ビュー変換ビュー変換
u_viewInverseMatrix mat4 ビュー変換、反転ビュー変換、反転
u_viewTransposeMatrix mat4 ビュー変換、転置ビュー変換、転置
u_viewInverseTransposeMatrix mat4 ビュー変換、反転および転置ビュー変換、反転
u_projectionMatrix mat4 投影変換投影変換
u_projectionInverseMatrix mat4 投影変換、反転投影変換、反転
u_projectionTransposeMatrix mat4 投影変換、転置投影変換、転置
u_projectionInverseTransposeMatrixmat4 投影変換、反転および転置投影変換、反転お
u_worldViewMatrix mat4 ワールドビュー変換ワールドビュー変
u_viewProjectionMatrix mat4 ビュー投影変換ビュー投影変換
u_worldViewProjectionMatrix mat4 ワールドビュー投影変換ワールドビュー投
u_viewPosition vec3 視点(カメラ)のワールド空間座標視点(カメラ)の
u_viewDirection vec3 視点(カメラ)のワールド空間方向視点(カメラ)の
u_frame float ホストアプリケーションによって定義された現在のフレーム番号ホストアプリケー
u_time float 現在の時刻(秒単位)現在の時刻(秒単
u_geomprop_<name> <type>指定された<type>のプロパティで、<name>はジオメトリ上の変数名。指定された<ty
u_numActiveLightSources int 現在アクティブな光源の数。 シェーダーでは、これは光源の最大許容数に対してクランプされていることに注意してください。 最大数は、生成オプションGenOptions.hwMaxActiveLightSourcesによって現在アクティブな
u_lightData[] struct アクティブな光源用のパラメータを保持する LightData 構造体の配列。LightData 構造体は、バウンド ライト シェーダーの要件に応じて動的に構築されます。アクティブな光源
u_envMatrix mat4 環境の回転行列。環境の回転行列。
u_envIrradiance sampler2D ディフューズ光 (アンビエントライト・環境光)に使用されるテクスチャのサンプラーです。ディフューズ光 (
u_envRadiance sampler2D スペキュラ光(鏡面光)に使用されるテクスチャのサンプラーです。スペキュラ光(鏡
u_envRadianceMips int スペキュラー環境テクスチャで使用されるミップマップの数。 スペキュラー環境
u_envRadianceSamples int スペキュラー環境照明にFiltered Importance Sampling(フィルタリングされた重要なサンプリング)を使用する場合に使用するサンプル。スペキュラー環境

まとめ

 以上、仕入れた情報を整理したメモ書きでした。 検証不十分な情報なので、あてにしないよう十分ご注意ください。 GLSLはこれから学習していきます!(多分!)

 もう少し整理してから、もう少しちゃんと理解してから公開しようと思ったんですが、情報を整理できなくなってきたので一旦書き出すことにしました。

列挙子

 追記。(2022/07/04)

 GLSLでシェーダーを書く際に使用できる列挙子の情報を見つけたので掲載。

gameplay::RenderStateクラスリファレンス(https://docs.huihoo.com/doxygen/gameplay3d/classgameplay_1_1_render_state.html)

マテリアルパラメータ用の組み込みの即時更新定数。いや定数ではないか。列挙子?

WORLD_MATRIXmat4ノードのワールドマトリックスをバインドします。
VIEW_MATRIXmat4ノードのシーンのアクティブなカメラのビュー変換行列をバインドします。
PROJECTION_MATRIXmat4ノードのシーンのアクティブなカメラの投影行列をバインドします。
WORLD_VIEW_MATRIXmat4ノードのWorldView変換行列をバインドします。
VIEW_PROJECTION_MATRIXmat4ノードのシーンのアクティブなカメラのViewProjection変換行列をバインドします。
WORLD_VIEW_PROJECTION_MATRIXmat4ノードのWorldViewProjection変換行列をバインドします。
INVERSE_TRANSPOSE_WORLD_MATRIXmat4ノードのInverseTransposeWorld変換行列をバインドします。
INVERSE_TRANSPOSE_WORLD_VIEW_MATRIXmat4ノードのInverseTransposeWorldView変換行列をバインドします。
CAMERA_WORLD_POSITIONvec3ノードのシーンのアクティブなカメラの位置(Vector3)をバインドします。
CAMERA_VIEW_POSITIONvec3ノードのシーンのアクティブなカメラのビュースペース位置(Vector3)をバインドします。
MATRIX_PALETTEノードのモデルにアタッチされたMeshSkinのマトリックスパレットをバインドします。
SCENE_AMBIENT_COLORvec3現在のシーンのアンビエントカラー(Vector3)をバインドします。

変換行列

 変換行列とにうのは、頂点の座標データに掛けて座標変換に使うやつの事です。 調べてみると整理された情報が見つかりました。(HLSLですが)

World ワールド変換行列 モデル座標系→ワールド座標系
View ビュー変換行列 ワールド座標系→ビュー座標系
Projection(射影) プロジェクション変換行列 ビュー座標系→クリップ座標系
WorldView ワールド変換行列×ビュー変換行列 モデル座標系→ワールド座標系→ビュー座標系
ViewProjection ビュー変換行列×プロジェクション変換行列 ワールド座標系→ビュー座標系→クリップ座標系
WorldViewProjection ワールド変換行列×ビュー変換行列×プロジェクション変換行列モデル座標系→ワールド座標系→ビュー座標系→クリップ座標系
InverseTransposeWorld ワールド変換行列の逆行列の転置行列
InverseTransposeWorldViewワールドビュー変換行列の逆行列の転置行列
Inverse~ 逆行列 変換前の状態に戻す行列
Transpose~ 転置行列
InverseTranspose~ 逆行列の転置行列

参考資料:MikuMikuEffectリファレンス 2-1. ジオメトリ変換
https://sites.google.com/site/mmereference/home/Annotations-and-Semantics-of-the-parameter/2-1-geometry-translation

 これらの変換を理解するには、座標系や空間に関する言葉の意味を知っている必要があります。

モデル座標系 3Dモデルのローカル座標を原点とした座標系のこと。
ワールド座標系3Dモデルを配置したりする際に使っているような、原点中心の座標のこと。
ビュー座標系(カメラ座標系)カメラを中心とした座標系。カメラとPC画面は通常は固定されているのでほぼ同じようなもの。奥行きがあるのが違いか。hgimg4の場合、カメラが向いている方向が-Z方向、右手方向が+X方向、上方向が+Y方向です。
プロジェクション座標系クリップ座標系。クリップ空間にある物体だけがカメラに写ったものとして表示されます。ここで平行投影(遠近感が出ない投影方法)や透視投影変換(遠近感が出る投影方法)が決定され、またそのパラメータが反映されます。
クリップ座標系 クリップ空間の座標系。プロジェクション座標系。
スクリーン座標系カメラの前にある仮想の平面で…(検索すると絵付きで解説しているところが見つかるので自分で調べてください。)…要するにPC画面の座標系です。

materialファイルで使用されている定数

 hgimg4のサンプルに含まれるmaterialファイルから定数というか、大文字+アンダースコアを抽出してみました。 サンプルを読み解く際に必要になりそうなのでまとめ。

 まずは前述したものです。

WORLD_VIEW_PROJECTION_MATRIX
INVERSE_TRANSPOSE_WORLD_VIEW_MATRIX
CAMERA_WORLD_POSITION
MATRIX_PALETTE

 どうやらテクスチャの品質に関する指定らしいもの。リンク先参照。
テクスチャの品質を指定する
https://wgld.org/d/webgl/w028.html

LINEAR
LINEAR_MIPMAP_LINEAR

 資料を見つけることが出来なかったので、そのうちなんとかしたい一覧です。 名前からなんとなく想像できそうな感じもします。

CLAMP
DIRECTIONAL_LIGHT_COUNT
REPEAT
SKINNING
SKINNING_JOINT_COUNT
SPECULAR
VERTEX_COLOR

materialファイルで使用されているuniform変数

 materialファイル内でuniform変数に代入が行われています。 おそらくここで代入された変数は、バーテックスシェーダーやフラグメントシェーダーで利用できるようになるのだと思います。 どんな変数が使用されているのか調べてみました。

samplerオブジェクト。テクスチャをどのようにサンプリングするかを定義しているようです。

u_diffuseTexture

数値を値を直接代入しているもの。

u_ambientColor
u_diffuseColor
u_specularExponent

列挙子を代入しているもの。 これらの命名規則は、大文字のスネークケースからキャメルケースに変えて、頭に「u_」を付けるようになっているようですね。

u_cameraPosition CAMERA_WORLD_POSITION
u_inverseTransposeWorldViewMatrixINVERSE_TRANSPOSE_WORLD_VIEW_MATRIX
u_matrixPalette MATRIX_PALETTE
u_worldViewProjectionMatrix WORLD_VIEW_PROJECTION_MATRIX

追記まとめ

 materialファイルまで捜索範囲を広げて資料を集めてみました。全てを把握できたわけではありませんが、なんとなく見えてきた気がします。

 しかし、適当に資料眺めれば分かるかなといろいろと調べていたのですが、そろそろ真面目に手を動かさないと前に進まないみたいです。

関連記事

  1. マテリアルの設定変更 materialファイルを書き換えてみる ZテストとZバッファ書き込み gpmatstate dep...