はじめに
[前回のノート](/articles/テクスチャマッピング(貼図)-webgl ノート 8/) の後、WebGL の最も基礎的な部分を終えました。今回のノートでは GLSL ES 構文を全面的に紹介し、比較的独立しています。その後、行列行列行列(変換、視点、照明、複雑なモデルの制御など、すべて行列を扱っています)
一.概要
GLSL ES は GLSL(OpenGL シェーダー言語)を基に、一部の機能を削除・簡素化して形成されたもので、ターゲットプラットフォームは民生用電子製品や組み込み機器です。例えばスマートフォン、ゲーム機など。ES バージョンは主にハードウェアの電力消費を低減し、パフォーマンスオーバーヘッドを削減しました
P.S. 実際、WebGL は GLSL ES のすべての特性をサポートしているわけではなく、GLSL ES 1.00 バージョンのサブセットをサポートしています
二.基本構文規則
-
大文字小文字を区別
-
文末には必ず セミコロンが必要
-
main 関数から実行開始
-
関数宣言では戻り値の型を省略できない(戻り値がない場合は void、C 言語では省略可能ですが、ここでは不可)
-
コメント構文は C 言語と同じ(単行//、多行/**/)
三.変数と基本データ型
###1.基本データ型
2 種類の基本データ型のみサポート:
-
数値型:整数、浮動小数点数
-
真偽型:true と false の 2 つの真偽定数
注意:文字列はサポートされていません
###2.変数
- 変数宣言
C 言語と同じく、型 + 変数名。変数命名規則も同じ。基本型は int、float、bool のみ
- 型変換
暗黙の 型変換はなく、3f のような型サフィックスもサポートされていません が、型変換関数を提供:int()、float()、bool()。これらは他の 2 つの基本データ型のみを受け付けます
- 演算子
ビット演算はサポートされていません。他は C 言語と同じ。三項選択もサポート。論理 AND(&&)と論理 OR(||)にもショートサーキット特性があります
- スコープ
C 言語と同じ。関数内部で宣言するのはローカル変数、外部で宣言するのはグローバル変数
四.複雑なデータ型
###1.ベクトル(vec)
2、3、4 次元ベクトルをサポート。成分のデータ型により 3 種類に分類:
-
vec2、vec3、vec4:成分は浮動小数点数
-
ivec2、ivec3、ivec4:成分は整数
-
bvec2、bvec3、bvec4:成分は真偽値
コンストラクタ名 は型名と同じ。例えば vec4(1.0) は 4 次元ベクトル [1.0, 1.0, 1.0, 1.0] を返します。このように 1 つのパラメータのみを渡す場合、すべての成分にその値を代入します。必要なパラメータ数より少ないが 1 つより多いパラメータを渡すと、エラー になります。例えば、vec4(1.0) と vec4(1.0, 1.0, 1.0, 1.0) は問題ありませんが、vec4(1.0, 1.0) と vec4(1.0, 1.0, 1.0) はエラーになります
さらに、ベクトルを渡して新しいベクトルを構築したり、既存のベクトルを組み合わせて新しいベクトルを作成できます。例えば:
vec3 v3 = vec3(0.0, 0.5, 1.0); // [0.0, 0.5, 1.0]
vec2 v2 = vec2(v3); // [0.0, 0.5]、v3 の最初の 2 つの成分を截取
vec4 v4 = vec4(v2, vec4(1.5)); // [0.0, 0.5, 1.5, 1.5]、v2 と新しいベクトル [1.5, 1.5, 1.5, 1.5] を組合せ
要するに、パラメータは基本値または他のベクトルから来れますが、パラメータ数が不足していて 1 つで��い場合はエラー になります
ベクトルの成分にアクセスするには 2 つの方法があります:
v4 = vec4(1, 2, 3, 4);
// .成分名
v4.x, v4.y, v4.z, v4.w // 同次座標
v4.r, v4.g, v4.b, v4.a // 色値
v4.s, v4.t, v4.p, v4.q // テクスチャ座標
// [] 演算子
v4[0], v4[1], v4[2], v4[3]
ドット成分名方式は意味を追加するためだけで、角括弧演算子と等価です。別名と理解できます。例えば v4[0] の別名は v4.x、v4.r、v4.s です。さらに面白いのは、組合せて使用できることです。例えば v4.xz は 2 次元ベクトルを返しますが、この場合成分名を混在できません(v4.sz は不正)
注意:角括弧中の値は定数インデックス値 でなければなりません。整数リテラル、const 修飾の変数、ループインデックス(フロー制御部分で後述)、またはこれら 3 つで構成される式
###2.行列(mat)
2、3、4 次元正方行列 のみをサポート。浮動小数点数型成分のみサポート:mat2、mat3、mat4
特に注意:行列要素は列優先順序 です。例えば:
mat4 m4 = mat4(
1, 2, 3, 4,
5, 6, 7, 8,
9, 10, 11, 12,
13, 14, 15, 16
);
// 生成される行列は:
1 5 9 13
2 6 10 14
3 7 11 15
4 8 12 16
想像と大きく異なるかもしれませんが、確かにそうです。同様に、行列のコンストラクタも他のベクトルまたは行列を受け付けられます。パラメータがどこから来ても、最終的にこれらの数は列優先順序 で行列を構築します。例えば:
vec2 v2_1 = vec2(1, 2);
vec2 v2_2 = vec2(3, 4);
mat2 m2 = mat2(v2_1, v2_2);
// 生成される行列は:
1 3
2 4
同様に、パラメータが不足していてパラメータ数が 1 つでない場合はエラー になります
行列要素にアクセスするには通常、角括弧演算子を使用します。例えば:
m4[0] // 第 1 列要素、4 次元ベクトル
m4[0][1] // 第 1 列第 2 行の要素、基本値
m4[0].y // 同上
注意:同様に、角括弧中の値も定数インデックス値 でなければなりません
###3.構造体(struct)
C 言語の構造体に類似。例えば:
// カスタム構造体型を宣言
struct light {
vec4 color;
vec3 pos;
};
// 構造体型変数を宣言
light l1, l2; // C 言語の struct light l1, l2; と等価
// C 言語のように構造体を宣言する同時に構造体型の変数を宣言することも可能
struct light {
vec4 color;
vec3 pos;
} l3;
構造体を宣言すると同名コンストラクタ が自動生成されます。パラメータ順序は構造体中のメンバー順序と一致する必要があります。例えば:
light l4 = light(vec4(1.0), vec3(0.0));
ドット演算子で構造体変数のメンバーに直接アクセスできます。構造体自体は代入(=)と 2 つの比較演算子(==、!=)のみをサポート。2 つの構造体メンバーと順序が同じなら、等しいと見なされます
###4.配列(xArray)
1 次元配列 のみをサポート。pop、push などの操作はサポートされていません。配列宣言方式は C 言語と同じ。例えば:
float a[10];
vec4 arr[3];
同様に、角括弧中の値は定数インデックス値 のみ。しかも配列は宣言と同時に初期化できません。各要素に明示的に代入する必要があります
###5.サンプラー(sampler)
サンプラー変数を通じてテクスチャにアクセスできます。サンプラー変数は 2 種類のみ:sampler2D と samplerCube。しかもサンプラー変数はのみuniform 変数です。例えば:
uniform sampler2D u_Sampler;
サンプラー変数に代入できる唯一の値はテクスチャユニット番号 です。例えば gl.uniformi(u_Sampler, 0) はテクスチャユニット番号 0 をシェーダーに渡します。そのためサンプラー変数は数が限られています。フラグメントシェーダーでは最大 8 個、頂点シェーダーにはサンプラー変数がありません
さらに、=、==、!= 以外、サンプラー変数は演算子として演算に参加できません
五.ベクトル演算と行列演算
ベクトルと行列は比較演算子の == と != のみをサポート。演算代入(+=, -=, *=, /=)操作はベクトルと行列に対して実際には各成分に対して演算代入を行います
###1.ベクトルと浮動小数点数演算
v2 + f; // v2[0] + f
// v2[1] + f
###2.ベクトル演算
v2_1 + v2_2; // v2_1[0] + v2_2[0]
// v2_1[1] + v2_2[1]
###3.行列と浮動小数点数演算
m2 + f; // m2[0] + f
// m2[1] + f
// m2[2] + f
// m2[3] + f
###4.行列右乗ベクトル
m3 * v3; // m3[0][0] * v3[0] + m3[1][0] * v3[1] + m3[2][0] * v3[2]
// m3[0][1] * v3[0] + m3[1][1] * v3[1] + m3[2][1] * v3[2]
// m3[0][2] * v3[0] + m3[1][2] * v3[1] + m3[2][2] * v3[2]
###5.行列左乗ベクトル
v3 * m3; // v3[0] * m3[0][0] + v3[1] * m3[0][1] + v3[2] * m3[0][2]
// v3[0] * m3[1][0] + v3[1] * m3[1][1] + v3[2] * m3[1][2]
// v3[0] * m3[2][0] + v3[1] * m3[2][1] + v3[2] * m3[2][2]
###6.行列乗行列
m3a * m3b; // m3a[0][0] * m3b[0][0] + m3a[1][0] * m3b[0][1] + m3a[2][0] * m3b[0][2]
// m3a[0][0] * m3b[1][0] + m3a[1][0] * m3b[1][1] + m3a[2][0] * m3b[1][2]
// m3a[0][0] * m3b[2][0] + m3a[1][0] * m3b[2][1] + m3a[2][0] * m3b[2][2]
// m3a[0][1] * m3b[0][0] + m3a[1][1] * m3b[0][1] + m3a[2][1] * m3b[0][2]
// m3a[0][1] * m3b[1][0] + m3a[1][1] * m3b[1][1] + m3a[2][1] * m3b[1][2]
// m3a[0][1] * m3b[2][0] + m3a[1][1] * m3b[2][1] + m3a[2][1] * m3b[2][2]
// m3a[0][2] * m3b[0][0] + m3a[1][2] * m3b[0][1] + m3a[2][2] * m3b[0][2]
// m3a[0][2] * m3b[1][0] + m3a[1][2] * m3b[1][1] + m3a[2][2] * m3b[1][2]
// m3a[0][2] * m3b[2][0] + m3a[1][2] * m3b[2][1] + m3a[2][2] * m3b[2][2]
六.フロー制御
###1.分岐
if-else 構造の用法は C 言語と js と同じですが、switch 文はありません
###2.ループ
for ループのみ をサポート。しかものみ 初期化式(for(;;) 中最初のセミコロンの前の位置)でループ変数を定義できます。例えば:
for (int i = 0; i < 10; i++) {
//...
}
1 つの ループ変数のみを許可。しかもループ変数はのみint または float。しかも条件式(for(;;) 中 2 つのセミコロンの間の位置)は必ず ループ変数と整数定数の比較。しかもループ本体内部で、ループ変数は代入できません
制限が多いのは、コンパイラが for ループをインライン展開できるようにするためです
continue、break の用法は js と同じ。さらに、discard があり、フラグメントシェーダーでのみ使用可能。現在のフラグメントを放棄し、直接次のフラグメントを処理することを表します
七.関数
C 言語と基本的に同じですが、配列を返せません。カスタム構造体を返す場合、構造体メンバー中に配列を含められません。例えば:
float luma(vec4 color) {
return 0.2126 * color.r + 0.7162 * color.g + 0.0722 * color.b;
}
同様に、先に宣言し、後で呼び出す必要があります。否则呼び出す前に関数シグネチャを宣言する必要があります。例えば:
float luma(vec4); // 関数シグネチャを宣言
void main() {
luma(color);
}
float luma...
さらに、再帰は許可されていません。この制限もコンパイラが関数をインライン展開できるようにするためです
GLSL ES にはいくつかのパラメータ限定字があります:
in 値渡し、省略可能、デフォルトは値渡し
const in 値渡し、関数内部でパラメータを修改できません
out アドレス渡し
inout アドレス渡し、渡されるパラメータはすでに初期化されている必要があります
八.記憶限定字
attribute、uniform、varying。違いは以下の通り:
- attribute
のみ 頂点シェーダー中に出現可能。のみ グローバル変数。型はのみfloat または成分が float のベクトルと行列。WebGL 環境は少なくとも 8 個の attribute 変数をサポート。頂点ごとの情報を表すために使用
- uniform
のみ グローバル変数。頂点シェーダーとフラグメントシェーダーで使用可能。配列と構造体以外の任意の型。WebGL 環境は少なくともフラグメントシェーダー中に 16 個の uniform 変数、頂点シェーダー中は 128 個をサポート。各頂点、各フラグメントが共用するデータを表すために使用
特殊的:頂点シェーダーとフラグメントシェーダー中に同名の uniform 変数を宣言した場合、2 つのシェーダーで共有されます
- varying
のみ グローバル変数。型はのみfloat または成分が float のベクトルと行列。WebGL 環境は少なくとも 8 個の varying 変数をサポート。頂点シェーダーからフラグメントシェーダーにデータを渡すために使用。但し直接渡すのではなく、補間 プロセスがあります
さらに、const 限定字もありますが、あまり使用されません
九.精度限定字
精度限定字を導入するのはシェーダープログラムの実行効率を向上し、メモリ消費を削減するためです。一般に中精度を採用:
#ifdef GL_ES
precision mediump float; // highp と lowp も可能
#endif
この文は後で遭遇するすべての精度を宣言していない浮動小数点数に中精度を使用することを表します。float 型のみデフォルト精度が設定されていないため、シェーダーソースプログラム中で必ず float のデフォルト精度を設定してから float 型を使用する必要があります
フラグメントシェーダーが高精度をサポートするかどうかはデバイスのサポートが必要です。マクロをチェックして検出可能:GL_FRAGMENT_PRECISION_HIGH
十.プリプロセス指令
C 言語に類似。常用的な 3 種類のプリプロセス指令は以下の通り:
// 1
#if 条件式
条件式が真なら、この部分を実行
#endif
// 2
#ifdef マクロ
マクロが存在すれば、この部分を実行
#endif
// 3
#ifndef マクロ
マクロが存在しなければ、この部分を実行
#endif
// マクロを定義
#define マクロ名 マクロ内容
// マクロ定義を解除
#undef マクロ名
有用なのは:#version 101 は GLSL ES1.01 バージョンの使用を指定可能。この指令は必ずシェーダーのトップにあり、前には空白またはコメントのみ可能
参考資料
- 《WebGL プログラミングガイド》
コメントはまだありません