6
6

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で深度値をテクスチャに書き出す&読み出す

Posted at

WebGLでシャドウマッピング被写界深度フィルターを実装する場合に、深度値をテクスチャに書き出したものが必要になるようです。

WebGL2からはフレームバッファに使う深度バッファをカラーバッファと同じようにテクスチャを使用できるようになり、これによりカラーバッファと深度バッファを同時に取得できるようになったようなので試してみました。
(正確にはWebGL1でも拡張機能WEBGL_depth_textureを有効にすることで深度バッファにテクスチャを利用できるようですが、以下で説明する方法と同じようにできるかはわかりません。)


サンプルでは以下のように左が手前、右が後ろになるように2つの三角形を描画しています。

Sample01_ColorTexture.png

このシーンの深度値を表示すると以下のようになります。
深度値の範囲は[0, 1]で手前が0(黒)、奥が1(白)になります

Sample01_DepthTexture.png

サンプルコード全体はこちらから、実際に動作しているものはこちらから確認できます。

処理の内容は、次のようなよくあるポストエフェクトの流れです。

  1. 深度バッファをテクスチャにしたフレームバッファを作成する
  2. 1で作成したフレームバッファに三角形をオフスクリーンレンダリングする
  3. canvas全体を覆うポリゴンに深度テクスチャをそのままレンダリングする

ソースコード全体は長くWebGLの定型的な処理も多いので、深度値をテクスチャに書き込む&読み込むことに関係があるところだけを抜粋して解説していきます。

まず、フレームバッファを作成している箇所です。
gl.DEPTH_ATTACHMENTで設定している深度バッファをカラーバッファと同じようにテクスチャにしています。

// creates framebuffer
const frameBuffer = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
const colorTexture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, colorTexture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, colorTexture, 0);
const depthTexture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, depthTexture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT16, width, height, 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_SHORT, null);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, depthTexture, 0);

depthTextureを作成する際のgl.texImage2Dの第7引数はgl.DEPTH_COMPONENTに、第3引数と第8引数のフォーマットの設定には適切な組み合わせを用いる必要があります。(この記事が参考になります。)
例えば、以下のようにしても深度バッファとして使用することができます。このあたりは必要な精度に応じて組み合わせを選択するのだと思います。

gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT32F, width, height, 0, gl.DEPTH_COMPONENT, gl.FLOAT, null);

gl.texParameteriでフィルター設定にgl.NEARESTを使用していますが、これはこうしないとテクスチャを参照する際にエラーがでるためです。おそらく後述するようにシェーダーでtexelFetch関数を用いてテクスチャをサンプリングしているためだと思います。

次に作成したフレームバッファに2つの三角形をオフスクリーンレンダリングします。
レンダリング前に深度値を1.0で初期化しています。

// renders triangles
gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
gl.clearColor(0.5, 0.5, 0.5, 1.0);
gl.clearDepth(1.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.useProgram(triangleProgram);
gl.bindVertexArray(triangleVertexArray);
gl.uniform3fv(triangleUniformLocations['u_offset'], [-0.3, 0.0, -0.5]);
gl.drawArrays(gl.TRIANGLES, 0, 3);
gl.uniform3fv(triangleUniformLocations['u_offset'], [0.3, 0.0, 0.5]);
gl.drawArrays(gl.TRIANGLES, 0, 3);

オフスクリーンレンダリングした結果のデプステクスチャをuniformでシェーダーに渡して画面全体の覆うポリゴンにレンダリングします。

// renders a depth or color texture
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.useProgram(quadProgram);
gl.bindVertexArray(quadVertexArray);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, colorTexture);
gl.uniform1i(quadUniformLocations['u_colorTexture'], 0);
gl.activeTexture(gl.TEXTURE1);
gl.bindTexture(gl.TEXTURE_2D, depthTexture);
gl.uniform1i(quadUniformLocations['u_depthTexture'], 1);
gl.uniform1i(quadUniformLocations['u_showDepth'], showDepth.checked);
gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);

フラグメントシェーダーは以下のようになっています。
GLSL ES 3.0から使えるtexelFetchで対応した座標をテクスチャからサンプルして、そのまま出力しています。
u_showDepthでカラーテクスチャを使うか、デプステクスチャを使うかを変更できるようにしています。

# version 300 es
precision highp float;

out vec4 out_color;
uniform sampler2D u_colorTexture;
uniform sampler2D u_depthTexture;
uniform bool u_showDepth;

void main(void) {
  if (u_showDepth) {
    float d = texelFetch(u_depthTexture, ivec2(gl_FragCoord.xy), 0).r;
    out_color = vec4(vec3(d, d, d), 1.0);
  } else {
    vec3 c = texelFetch(u_colorTexture, ivec2(gl_FragCoord.xy), 0).rgb;
    out_color = vec4(c, 1.0);
  }
}

次に以下のように透視プロジェクション行列を使用している場合を考えたいと思います。
このシーンでは左から右に段々と奥になるように三角形を配置しています。
ソースコードはこちらから、実際に動作しているものはこちらから確認できます。

Sample02_ColorTexture.png

このシーンの深度値をさきほどと同じ方法でそのまま表示すると、次のようなほとんど真っ白な画面が表示されます。

Sample02_DepthTexture.png

これは透視プロジェクション行列を用いた変換では深度値を非線形に保存しているからです。
(これは手前側ほど精度を高く深度値を保存するためです。)
このままではいろいろと使用しづらいと思うので、この記事を参考に線形に変換してみます。

具体的には以下のような変換をフラグメントシェーダーで行います。
u_nearu_farにはそれぞれカメラのニアークリップとファークリップの値となっています。

# version 300 es
precision highp float;

out vec4 out_color;
uniform sampler2D u_colorTexture;
uniform sampler2D u_depthTexture;
uniform bool u_showDepth;
uniform bool u_linearDepth;
uniform float u_near;
uniform float u_far;

void main(void) {
  if (u_showDepth) {
    float d = texelFetch(u_depthTexture, ivec2(gl_FragCoord.xy), 0).r;
    if (u_linearDepth) {
      d = (2.0 * u_near) / (u_far + u_near - d * (u_far - u_near));
    }
    out_color = vec4(vec3(d, d, d), 1.0);
  } else {
    vec3 c = texelFetch(u_colorTexture, ivec2(gl_FragCoord.xy), 0).rgb;
    out_color = vec4(c, 1.0);
  }
}

線形変換すると、深度値は以下のように表示されます。

Sample02_DepthTexture_Linear.png

6
6
0

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
6
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?