7
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

【WebGL2】gl_FragDepthを使ってフラグメントシェーダーで深度を書き込む

Last updated at Posted at 2019-04-14

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.xyzgl_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$より大きいところは青色で表示されています。
2d.png


次に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値の値に応じて赤と青がまだらに表示されます。
3d.png


gl_FragDephを使ってフラグメントシェーダーで深度を書き込む方法を紹介しました。具体的に何に使えるかというと難しいですが、WebGLで複雑なことをする場合に必要になるかもしれません。個人的には、ポリゴンベースの3Dオブジェクトとオブジェクトスペースレイマーチングで描画した3Dオブジェクトを組み合わせるときや、Deferred Renderingで深度値を操作するときに使用できるかもしれないと考えています。

7
2
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?