テクスチャアトラスでテクスチャブリーディングを回避する方法


46

私のゲームには、キューブで作られたMinecraftのような地形があります。ボクセルデータから頂点バッファーを生成し、さまざまなブロックの外観にテクスチャアトラスを使用します。

ボクセルベースの地形のテクスチャアトラス

問題は、離れた立方体のテクスチャがテクスチャアトラス内の隣接するタイルで補間されることです。その結果、キューブ間に間違った色の線が表示されます(グラフィカルな欠陥を確認するには、以下のスクリーンショットをフルサイズで表示する必要がある場合があります)。

間違った色の縞模様のある地形のスクリーンショット

今のところ、これらの補間設定を使用しますが、すべての組み合わせを試しましたが、GL_NEARESTミップマッピングがなくてもより良い結果は得られません。

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

また、テクスチャ座標にオフセットを追加してタイルのわずかに小さい領域を選択しようとしましたが、不要な効果はカメラまでの距離に依存するため、問題を完全に解決することはできません。とにかく遠い距離でストライプが発生します。

このテクスチャのにじみをどのように解決できますか?テクスチャアトラスの使用は一般的な手法であるため、一般的なアプローチがあるかもしれません。悲しいことに、コメントで説明されているいくつかの理由により、異なるテクスチャやテクスチャ配列に変更することはできません。


4
問題はミップマッピングにあります-最大のミップレベルではテクスチャ座標がうまく機能する可能性がありますが、さらに離れていくと、境界のタイルがブレンドされます。これと戦うには、ミップマップを使用しない(おそらく良い考えではない)、ガードゾーン(タイル間の領域)を拡大する、ミップレベルに基づいたシェーダーで動的にガードゾーンを拡大(トリッキー)、ミップマップの生成中に奇妙なことをする(できる)私はこの問題に自分で取り組んだことはありませんが、最初からやることがあります。=)
ヤリコンパ

残念ながらミップマッピングをオフにしても問題は解決しません。ミップマップがなければGL_LINEAR、同様の結果を与える線形補間を行うかGL_NEAREST、ブロック間のストライプをもたらす最も近いピクセルを選択します。最後に言及したオプションは減少しますが、ピクセルの欠陥を排除しません。
ダニジャー

7
1つの解決策は、ミップマップを生成した形式を使用することです。その後、自分でミップマップを作成し、エラーを修正できます。別の解決策は、テクスチャをサンプリングするときにフラグメントシェーダーで特定のミップマップレベルを強制することです。別の解決策は、ミップマップを最初のミップマップで埋めることです。つまり、テクスチャを作成するときに、同じデータを含む特定の画像にサブ画像を追加するだけです。
トルディン

1
以前にこの問題が発生しましたが、アトラスの異なるテクスチャー間に1-2ピクセルのパディングを追加することで解決しました。
チャックD

1
@Kylotan。問題は、ミップマップ、線形補間、または最も近いピクセル選択によって行われるかどうかに関係なく、テクスチャのサイズ変更についてです。
ダニジャー

回答:


34

さて、ここで2つの問題が発生していると思います。

最初の問題はミップマッピングにあります。一般に、すべてのサブテクスチャのサイズが正確に1x1ピクセルでない限り、テクスチャブリーディングが発生するため、単純にアトラスとミップマッピングを混在させたくありません。パディングを追加すると、単純に問題がより低いミップレベルに移動します。

経験則として、2Dでは、通常、アトラスを作成しますが、ミップマップは行いません。一方、ミップマップを行う場所である3Dの場合、アトラスは使用しません(実際、出血が問題にならないように肌を整えます)

しかし、私があなたのプログラムで現在行っていることは、おそらく正しいテクスチャ座標を使用していないということです。そのため、テクスチャがにじんでいます。

8x8テクスチャを想定しましょう。おそらく、(0,0)から(0.5、0.5)のUV座標を使用して、左上の4分の1を取得しています。

誤ったマッピング

そして問題は、u座標0.5で、サンプラーが左テクセルの半分と右テクセルの半分を使用して宛先フラグメントを補間することです。同じことがu座標0でも起こり、v座標でも同じです。これは、中心ではなくテクセル間の境界に対応しているためです。

代わりにすべきことは、各テクセルの中心をアドレス指定することです。この例では、これらは使用する座標です。

正しいマッピング

この場合、常に各テクセルの中心をサンプリングし、出血しないはずです... mipmappingを使用しない限り、スケーリングされたサブテクスチャがきちんといないミップレベルで常に出血しますピクセルグリッド内に収まります。

一般化するには、特定のテクセルにアクセスする場合、座標は次のように計算されます。

function get_texel_coords(x, y, tex_width, tex_height)
    u = (x + 0.5) / tex_width
    v = (y + 0.5) / tex_height
    return u, v
end

これは「ハーフピクセル補正」と呼ばれます。興味がある場合は、ここに詳細な説明があります。


あなたの説明はとても有望です。残念ながら、現時点ではビデオカードを使用する必要があるため、まだ実装できません。修理されたカードが到着した場合、私はそうします。そして、タイルの境界を補間することなく、自分でアトラスのミップマップを計算します。
ダニジャー

@sharethis間違いなくアトラスが必要な場合は、サブテクスチャのサイズが2の累乗であることを確認してください。これにより、出血を最小のミップレベルに制限できます。そうすれば、実際に独自のミップマップを手作りする必要はありません。
パンダパジャマ

ただし、バイリニアフィルタリングはこれらの境界を越えてサンプリングできるため、PoTサイズのテクスチャでさえにじみが発生する可能性があります。(少なくとも私が見た実装では。)ハーフピクセル補正に関しては、この場合に適用すべきでしょうか?たとえば、彼の岩のテクスチャをマッピングしたい場合、U座標とV座標に沿って常に0.0〜0.25になりますか?
カイロタン

1
@Kylotanテクスチャサイズが均等で、すべてのサブテクスチャのサイズが均等で、座標が均等である場合、テクスチャサイズを半分にした場合のバイリニアフィルタリングはサブテクスチャ間で混合しません。2のべき乗で一般化します(アーティファクトが見られる場合は、おそらくバイリニアフィルタリングではなく、バイキュービックフィルタリングを使用しています)。0.25ピクセルの補正に関しては、0.25が右端のテクセルではなく、両方のサブテクスチャの中間点であるため、必要です。遠近感を出すために、あなたがラスタライザであると想像してください:テクスチャの色は(0.00、0.25)ですか?
パンダパジャマ

1
@sharethisあなたは助けることができるかもしれません、私が書いたこの他の答えをチェックアウトgamedev.stackexchange.com/questions/50023/...
パンダパジャマ

19

ミップマップをいじったり、テクスチャ座標ファズファクターを追加するよりもはるかに簡単なオプションの1つは、テクスチャ配列を使用することです。テクスチャ配列は3Dテクスチャに似ていますが、3次元でミップマッピングが行われないため、「サブテクスチャ」がすべて同じサイズのテクスチャアトラスに最適です。

http://www.opengl.org/wiki/Array_Texture


それらが利用できる場合、それらははるかに優れたソリューションです-あなたはアトラスで見逃しているテクスチャのラッピングのような他の機能も取得します。
ヤリコンパ

@JariKomppa。テクスチャ配列を使用したくないのは、その方法では地形用に追加のシェーダーが必要になるからです。私が書くエンジンは可能な限り一般的なものでなければならないので、この例外を作りたくありません。テクスチャ配列と単一テクスチャの両方を処理できる1つのシェーダーを作成する方法はありますか?
ダニジャー

8
@sharethisあなたが「一般」になりたいので、明白に優れた技術的ソリューションを却下することは失敗のレシピです。ゲームを作成する場合は、エンジンを作成しないでください。
サムホセバー

@SamHocevar。私はエンジンを書いています。私のプロジェクトは、コンポーネントが完全に分離された特定のアーキテクチャに関するものです。そのため、フォームコンポーネントは、標準のバッファと標準のテクスチャのみを提供するテレインコンポーネントを処理すべきではありません。
ダニジャー

1
@sharethis OK。質問の「ゲーム内」の部分に何らかの形で惑わされました。
サムホセバル

6

この問題に苦労した後、私は最終的に解決策を思いつきました。

テクスチャアトラスとミップマッピングの両方を使用するには、ダウンサンプリングを実行する必要があります。これは、OpenGLがアトラス内のタイルの境界を補間するためです。さらに、ミップマップ間を補間するとテクスチャのにじみが発生するため、適切なテクスチャパラメータを設定する必要がありました。

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

これらのパラメーターを使用すると、出血は発生しません。

カスタムミップマップを提供する際に注意しなければならないことが1つあります。各タイルが既に1x1である場合でもアトラスを縮小することは意味をなさないため、提供する内容に応じて最大ミップマップレベルを設定する必要があります。

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 7); // pick mipmap level 7 or lower

他のすべての回答と非常に役立つコメントをありがとう!ちなみに、私はまだ線形拡大縮小の使用方法を知りませんが、方法はないと思います。


あなたがそれを解決したことを知っているのは良いことです。私の代わりにこの答えを正しいものとしてマークする必要があります。
パンダパジャマ

ミップマッピングは、ダウンサンプリング専用の手法であり、アップサンプリングには意味がありません。アイデアは、小さな三角形を描画しようとすると、大きなピクセルを数ピクセル取り出すだけで大きなテクスチャをサンプリングするのに時間がかかるため、事前にダウンサンプリングして適切なミップレベルを使用するということです。小さな三角形を描きます。大きな三角形の場合、大きなミップレベルとは異なるものを使用しても意味がありません。そのため、次のようなことを行うのは誤りですglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_FILTER, GL_NEAREST_MIPMAP_LINEAR);
パンダパジャマ

@PandaPajama。あなたは正しいです、私は私の答えを修正しました。に設定GL_TEXTURE_MAX_FILTERするGL_NEAREST_MIPMAP_LINEARと、ミップマッピングの理由ではなく、補間の理由で出血が発生します。したがって、この場合はGL_LINEAR、とにかく最低のミップマップレベルが使用されるのと同等です。
ダニジャー

@danijar-自分でダウンサンプリングを実行する方法、およびダウンサンプリングされたアトラスをどこで(いつ)使用するかをOpenGLに伝える方法を詳しく説明できますか?
ティムケイン14

1
@TimKaneアトラスをタイルに分割します。ミップマップレベルごとに、双線形にタイルをダウンサンプリングし、それらを結合して、それをGPUにアップロードし、ミップマップレベルをパラメーターとして指定しglTexImage2D()ます。GPUは、画面上のオブジェクトのサイズに基づいて正しいレベルを自動的に選択します。
ダニジャー14

4

この問題はMSAAが原因のようです。参照してくださいこの回答リンク先の記事を

「MSAAを有効にすると、ピクセル領域の内側にあるが三角形領域の外側にあるサンプルに対してシェーダーを実行できるようになります」

解決策は、重心サンプリングを使用することです。頂点シェーダーでテクスチャ座標のラッピングを計算している場合、そのロジックをフラグメントシェーダーに移動すると問題が解決する可能性があります。


すでに別のコメントで書いたように、現時点では動作するビデオカードがないため、それが実際の問題であるかどうかを確認できません。できるだけ早くそうします。
ダニジャー

ハードウェアアンチエイリアシングを無効にしましたが、出血はまだ残っています。フラグメントシェーダーでテクスチャ検索を既に実行しています。
ダニジャー

1

これは古いトピックであり、このトピックと同様の問題がありました。

私は自分のテクスチャ座標をうまく持っていましたが、カメラの位置(要素の位置を変更)を次のように変更しました:

public static void tween(Camera cam, Direction dir, Vec2 buffer, Vec2 start, Vec2 end) {
    if(step > steps) {
        tweening = false;
        return;
    }

    final float alpha = step/steps;

    switch(dir) {
    case UP:
    case DOWN:
        buffer = new Vec2(start.getX(), Util.lerp(start.getY(), (float)end.getY(), alpha));
        cam.getPosition().set(buffer);
        break;
    case LEFT:
    case RIGHT:
        buffer = new Vec2(Util.lerp(start.getX(), end.getX(), alpha), start.getY());
        cam.getPosition().set(buffer);
        break;
    }

    step += 1;
}

全体の問題は、Util.lerp()がfloatまたはdoubleを返すことでした。これは、カメラがオフグリッドを変換し、テクスチャスニペットでXで0を超え、Yで0を超える奇妙な問題を引き起こしたため、悪かった。

TLDR:トゥイーンまたはフロートを使用するもの

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.