WebGL2ではgl_FragDepth
を使うとフラグメントシェーダーで深度を書き込めるらしいので試してみました。
サンプルはGitHubに置いておきました。
aadebdeb/Sample_WebGL2_FragDepth: Sample of Writing Depth in Fragment Shader by gl_FragDepth
まずはgl_Position
で設定される深度について確認しておきます。頂点シェーダーではgl_Position
にクリップ空間の座標を設定しています。その後WebGL側で、gl_Position.xyz
をgl_Position.w
を割ることで正規化デバイス座標に変換しています。この座標の値が$-1.0 \leq z \leq 1.0$にあるとレンダリング対象になります。$-1$が最も手前、$1$が最も奥にあるということになります。さらに、正規化デバイス座標はビューポート変換によりビューポート座標に変換されます。$z$の値は$0.5z + 0.5$という変換が行われるので、レンダリング対象の値の範囲は$0.0 \leq z \leq 1.0$になります。この値が深度として扱われます。
例えば以下のような頂点シェーダーでは、クリップ空間では$z = 0.0, w = 1.0$なため、正規化デバイス座標のz値は$z / w = 0.0 / 1.0 = 0.0$、ビューポート座標のz値つまり深度値は$0.5z + 0.5 = 0.5 \times 0.0 + 0.5 = 0.5$になります。
#version 300 es
layout (location = 0) in vec2 position;
out vec2 v_position;
void main(void) {
v_position = position;
gl_Position = vec4(position, 0.0, 1.0);
}
gl_FragDepth
には、ビューポート座標のz値を書き込みます。
それでは行列変換を用いない単純な2Dで試してみます。サンプルはこちらになります。今回は先ほどの頂点シェーダーで2つの同じサイズの四角形を同じ位置に重ねるように描画してます。1つの四角形はgl_FragDepth
で深度を設定する以下のフラグメントシェーダーを使い、もう1つの四角形はフラグメントシェーダーでは深度を設定しません。fbm
関数について補足すると、フラクタルブラウンモーションという$[0, 1]$の範囲のノイズを生成する関数です。
#version 300 es
precision highp float;
in vec2 v_position;
out vec4 o_color;
uniform vec3 u_color;
uniform float u_time;
float random(vec3 x){
return fract(sin(dot(x,vec3(12.9898, 78.233, 39.425))) * 43758.5453);
}
float valuenoise(vec3 x) {
vec3 i = floor(x);
vec3 f = fract(x);
vec3 u = f * f * (3.0 - 2.0 * f);
return
mix(
mix(
mix(random(i), random(i + vec3(1.0, 0.0, 0.0)), u.x),
mix(random(i + vec3(0.0, 1.0, 0.0)), random(i + vec3(1.0, 1.0, 0.0)), u.x),
u.y
),
mix(
mix(random(i + vec3(0.0, 0.0, 1.0)), random(i + vec3(1.0, 0.0, 1.0)), u.x),
mix(random(i + vec3(0.0, 1.0, 1.0)), random(i + vec3(1.0, 1.0, 1.0)), u.x),
u.y
),
u.z
);
}
float fbm(vec3 x) {
float sum = 0.0;
float amp = 0.5;
for (int i = 0; i < 5; i++) {
sum += amp * valuenoise(x);
amp *= 0.5;
x *= 2.01;
}
return sum;
}
void main(void) {
o_color = vec4(u_color, 1.0);
gl_FragDepth = fbm(vec3(v_position + vec2(100.0), u_time * 0.3));
}
結果は以下のようになります。青色の四角形はgl_FragDepth
で深度値を設定し、赤色の四角形はgl_FragDepth
で値を設定していません(つまり深度値はgl_Position
から求める値になります)。先に述べたように赤色の四角形の深度値は$0.5$になるので、fbm
関数の値が$0.5$より小さいところでは赤色で、$0.5$より大きいところは青色で表示されています。
次に3Dの場合です。サンプルはこちらになります。3Dでも2枚の同じ大きさの四角形を同じ位置に重ねています。頂点シェーダーでは以下のように行列で座標変換しています。
#version 300 es
layout (location = 0) in vec3 position;
out vec3 v_position;
uniform mat4 u_mvpMatrix;
void main(void) {
v_position = position;
gl_Position = u_mvpMatrix * vec4(position, 1.0);
}
gl_FragDepth
で深度値を書き込むフラグメントシェーダーは以下のようになります。fbm
関数でz値をずらした後に行列でクリップ空間に変換しています。gl_FragDepth
に書き込むのはビューポート座標のz値なので、wの値で割ってから$[0, 1]$の範囲に変換しています。
#version 300 es
precision highp float;
in vec3 v_position;
out vec4 o_color;
uniform mat4 u_mvpMatrix;
uniform vec3 u_color;
uniform float u_time;
// fbm関数については省略
void main(void) {
o_color = vec4(u_color, 1.0);
float offset = fbm(vec3(v_position.xy + vec2(100.0), u_time * 0.3)) * 2.0 - 1.0;
vec4 position = u_mvpMatrix * vec4(v_position + vec3(0.0, 0.0, offset), 1.0);
gl_FragDepth = (position.z / position.w) * 0.5 + 0.5;
}
結果は以下のようにz値の値に応じて赤と青がまだらに表示されます。
gl_FragDeph
を使ってフラグメントシェーダーで深度を書き込む方法を紹介しました。具体的に何に使えるかというと難しいですが、WebGLで複雑なことをする場合に必要になるかもしれません。個人的には、ポリゴンベースの3Dオブジェクトとオブジェクトスペースレイマーチングで描画した3Dオブジェクトを組み合わせるときや、Deferred Renderingで深度値を操作するときに使用できるかもしれないと考えています。