textureとtexelFetchの比較

この記事は約6分で読めます。

当初の認識

textureの方はサブピクセル単位で座標を指定できるのに対して、texelFetchの方はピクセル単位でしか指定できません。なので、ばっちりピクセル単位でアクセスしたい時にはtexelFetch、ピクセル単位でないアクセスをするときはtextureを使うというぐらいの認識でした。ですが、思ったよりも違いがあったのでその違いをまとめておこうと思います。

範囲外へのアクセス

テクスチャのサイズがW×Hピクセルだった場合を考えます。texture関数でアクセスする場合はサイズによらず-1.0<=x<=+1.0、-1.0<=y<=+1.0の座標でテクスチャの範囲内にアクセスすることになり、texelFetch関数でアクセスする場合は0<=x<=W-1、0<=y<=H-1でテクスチャの範囲内にアクセスすることになります。範囲外にアクセスすることもできますが、そのときの挙動がtextureとtexelFetchで異なります。

今回、テクスチャの元画像として512×512pxの下記画像を使用します。

範囲外にアクセスした場合にテクスチャパターンが繰り返されるようにGL_REPEATを指定します。

  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

バーテックスシェーダとしては共通で下記を使用します。

#version 300 es

in vec2 position;

void main(void) {
  gl_Position = vec4(position.x, position.y, 0.0, 1.0);
}

ここで、フラグメントシェーダによりテクスチャを上下左右1個分はみ出すようにアクセスして描画する場合、textureを使用するのとtexelFetchを使用するときで結果が異なります。

まず、textureを使用する場合から見てみます。フラグメントシェーダは下記のようなコードになります。

#version 300 es

precision mediump float;

uniform float width, height;
uniform sampler2D sampler;

out vec4 color;

void main() {
  color = texture(sampler, (3.0 * gl_FragCoord.xy - vec2(width, height)) / vec2(width, height));
}

描画結果は以下のようにテクスチャパターンが繰り返されたものになります。

一方、texelFetchを使用する場合、フラグメントシェーダは下記のようになります。

#version 300 es

precision mediump float;

uniform float width, height;
uniform sampler2D sampler;

out vec4 color;

void main() {
  color = texelFetch(sampler, ivec2(3.0 * gl_FragCoord.xy - vec2(width, height)), 0);
}

こちらは中央に1回分だけテクスチャパターンが現れる描画となります。その他の部分は透明度も含めて0なので、大きさが分かるように外枠をつけて表示しています。

ということで、範囲外へのアクセスを行う場合はtextureでないといけないということが分かりました。

速度

前節で見たように範囲外にアクセスしたときにテクスチャの画素値が取得できるのはtextureだけでした。とすると、texelFetchはそのあたりの面倒を見てくれない分、速度が速いのではという気がしてきます。そこで、入力テクスチャと同じサイズの矩形にテクスチャを貼り付けるだけの場合で速度を比較してみました。なお、本記事ではIntel Core i5 1340P、Fedora 38での測定です。他の環境では異なる可能性もあります。

textureを使用する場合のフラグメントシェーダは下記のようにしています。

#version 300 es

precision mediump float;

uniform float width, height;
uniform sampler2D sampler;

out vec4 color;

void main() {
  color = texture(sampler, gl_FragCoord.xy / vec2(width, height));
}

texelFetchを使用する場合のフラグメントシェーダは下記のようにしています。

#version 300 es

precision mediump float;

uniform float width, height;
uniform sampler2D sampler;

out vec4 color;

void main() {
  color = texelFetch(sampler, ivec2(gl_FragCoord.xy), 0);
}

glDrawElementsによる描画とglReadPixelsによる描画結果の読み出しにかかった時間を各30回ずつ計測して見たところ下記のようになりました。

テクスチャへのアクセス時間
texture1.167msec
texelFetch1.207msec

予想に反してほとんど差がない結果となりました。ただ、このシェーダでは1画素あたり1回しかテクスチャにアクセスしないので、もう少しテクスチャにアクセスする場合も試してみます。シェーダのコードは割愛しますが、着目画素を中心とした21×21ピクセルを平均するフラグメントシェーダに変更した場合の処理時間は下記のようになりました。

テクスチャへのアクセス時間
texture5.974msec
texelFetch6.030msec

1画素あたり400回以上テクスチャにアクセスする場合でも差は生じませんでした。速度を考えて使い分ける必要はなさそうです。

まとめ

この記事では以下のことを確かめました。

  • textureは範囲外のアクセスに対応している
  • texelFetchは範囲外アクセスで画素値を取得できない
  • textureとtexelFetchに速度差はない(※環境によっては速度差があるかも)

この記事がご参考になれば幸いです。

コメント

タイトルとURLをコピーしました