フラグメントシェーダーでレイキャスティングを行っています。この目的のためにフルスクリーンクワッドを描画するいくつかの方法を考えることができます。投影行列を単位行列に設定してクリップ空間にクワッドを描画するか、ジオメトリシェーダーを使用してポイントを三角ストリップに変換します。前者は、OpenGL3.2で非推奨の即時モードを使用します。後者は私が目新しさから使用しますが、それでもポイントを引くために即時モードを使用します。
頂点属性をそれぞれ-1/1に設定して、クワッドを作成する2つの三角形を送信できます。
頂点/フラグメントシェーダーでそれらに行列を掛ける必要はありません。
ここにいくつかのコードサンプルがありますが、それは単純です:)
頂点シェーダー:
const vec2 madd=vec2(0.5,0.5);
attribute vec2 vertexIn;
varying vec2 textureCoord;
void main() {
textureCoord = vertexIn.xy*madd+madd; // scale vertex attribute to [0-1] range
gl_Position = vec4(vertexIn.xy,0.0,1.0);
}
フラグメントシェーダー:
varying vec2 textureCoord;
void main() {
vec4 color1 = texture2D(t,textureCoord);
gl_FragColor = color1;
}
フルスクリーンのクアッドジオメトリシェーダーを出力するには、次の方法を使用できます。
#version 330 core
layout(points) in;
layout(triangle_strip, max_vertices = 4) out;
out vec2 texcoord;
void main()
{
gl_Position = vec4( 1.0, 1.0, 0.5, 1.0 );
texcoord = vec2( 1.0, 1.0 );
EmitVertex();
gl_Position = vec4(-1.0, 1.0, 0.5, 1.0 );
texcoord = vec2( 0.0, 1.0 );
EmitVertex();
gl_Position = vec4( 1.0,-1.0, 0.5, 1.0 );
texcoord = vec2( 1.0, 0.0 );
EmitVertex();
gl_Position = vec4(-1.0,-1.0, 0.5, 1.0 );
texcoord = vec2( 0.0, 0.0 );
EmitVertex();
EndPrimitive();
}
頂点シェーダーは空です:
#version 330 core
void main()
{
}
このシェーダーを使用するには、空のVBOでダミーの描画コマンドを使用できます。
glDrawArrays(GL_POINTS, 0, 1);
ジオメトリシェーダー、VBO、またはメモリを使用する必要はまったくありません。
頂点シェーダーはクワッドを生成できます。
layout(location = 0) out vec2 uv;
void main()
{
float x = float(((uint(gl_VertexID) + 2u) / 3u)%2u);
float y = float(((uint(gl_VertexID) + 1u) / 3u)%2u);
gl_Position = vec4(-1.0f + x*2.0f, -1.0f+y*2.0f, 0.0f, 1.0f);
uv = vec2(x, y);
}
空のVAOをバインドします。 6つの頂点のドローコールを送信します。
Chistophe Riccioによるヒント:
私がビデオで説明した理由から、大きな三角形の方が効率的です。
以下は、fboテクスチャを画面に配置されたクワッドに描画するクラスの描画関数からのものです。
Gl.glUseProgram(shad);
Gl.glBindBuffer(Gl.GL_ARRAY_BUFFER, vbo);
Gl.glEnableVertexAttribArray(0);
Gl.glEnableVertexAttribArray(1);
Gl.glVertexAttribPointer(0, 3, Gl.GL_FLOAT, Gl.GL_FALSE, 0, voff);
Gl.glVertexAttribPointer(1, 2, Gl.GL_FLOAT, Gl.GL_FALSE, 0, coff);
Gl.glActiveTexture(Gl.GL_TEXTURE0);
Gl.glBindTexture(Gl.GL_TEXTURE_2D, fboc);
Gl.glUniform1i(tileLoc, 0);
Gl.glDrawArrays(Gl.GL_QUADS, 0, 4);
Gl.glBindTexture(Gl.GL_TEXTURE_2D, 0);
Gl.glBindBuffer(Gl.GL_ARRAY_BUFFER, 0);
Gl.glUseProgram(0);
実際のクワッド自体と座標は次の場所から取得されます。
private float[] v=new float[]{ -1.0f, -1.0f, 0.0f,
1.0f, -1.0f, 0.0f,
1.0f, 1.0f, 0.0f,
-1.0f, 1.0f, 0.0f,
0.0f, 0.0f,
1.0f, 0.0f,
1.0f, 1.0f,
0.0f, 1.0f
};
私があなたに任せるvboのバインディングとセットアップ。
頂点シェーダー:
#version 330
layout(location = 0) in vec3 pos;
layout(location = 1) in vec2 coord;
out vec2 coords;
void main() {
coords=coord.st;
gl_Position=vec4(pos, 1.0);
}
位置は生であるため、つまり、どの行列も乗算されないため、クワッドの-1、-1 :: 1、1がビューポートに収まります。 openGL.orgの彼の投稿のいずれかにリンクされているAlfonseのチュートリアルを探してください。
最も効率的なアプローチは、単一の「フルスクリーン」三角形を描画することであると主張します。三角形が全画面をカバーするには、実際のビューポートよりも大きくする必要があります。 NDC(およびクリップスペース、w=1
を設定した場合)では、ビューポートは常に[-1,1]
正方形になります。三角形がこの領域を完全にカバーするには、2つの辺がビューポートの長方形の2倍の長さである必要があります。これにより、3番目の辺がビューポートのエッジと交差するため、たとえば次の座標を使用できます(反時計回りの順序):(-1,-1)
、(3,-1)
、(-1,3)
。
また、texcoordsについて心配する必要はありません。表示されているビューポート全体で通常の正規化された[0,1]
範囲を取得するには、頂点に対応するtexcoordを2倍大きくする必要があります。重心補間では、クワッドを使用する場合とまったく同じ結果がビューポートピクセルに生成されます。 。
もちろん、このアプローチは、 demanzeの答え で提案されているように、属性のないレンダリングと組み合わせることができます。
out vec2 texcoords; // texcoords are in the normalized [0,1] range for the viewport-filling quad part of the triangle
void main() {
vec2 vertices[3]=vec2[3](vec2(-1,-1), vec2(3,-1), vec2(-1, 3));
gl_Position = vec4(vertices[gl_VertexID],0,1);
textcoords = 0.5 * gl_Position.xy + vec2(0.5);
}
なぜ単一の三角形がより効率的になるのですか?
これは、保存された頂点シェーダーの呼び出しが1つあり、フロントエンドで処理する三角形が1つ少ないnotです。単一の三角形を使用することの最も重要な効果は、フラグメントshderの呼び出しが少ないことです)
実際のGPUは、プリミティブの1つのピクセルがそのようなブロックに入るとすぐに、2x2ピクセルサイズのブロックのフラグメントシェーダーを呼び出します。これは、 ウィンドウ空間微分関数 を計算するために必要です(これらは、テクスチャサンプリングにも暗黙的に必要です。 この質問 を参照してください)。
プリミティブがそのブロック内の4ピクセルすべてをカバーしていない場合、残りのフラグメントシェーダー呼び出しは(微分計算のデータを提供することを除いて)有用な作業を行わず、いわゆるヘルパー呼び出し( gl_HelperInvocation
GLSL関数を介して照会することもできます 。詳細については、 Fabian "ryg" Giesenのブログ記事 も参照してください。
2つの三角形でクワッドをレンダリングすると、両方に1つのエッジがビューポートを斜めに横切るようになり、両方の三角形で、エッジで多くの役に立たないヘルパー呼び出しが生成されます。完全に正方形のビューポート(アスペクト比1)の場合、効果は最悪になります。単一の三角形を描画する場合、そのような対角線のエッジはありません(ビューポートの外側にあり、ラスタライザーにはまったく関係ありません)。したがって、追加のヘルパー呼び出しはありません。
三角形がビューポートの境界を越えて伸びている場合、それはクリップされて実際にmoreがGPUで動作するのではないでしょうか?
グラフィックスパイプライン(またはGL仕様))に関する教科書の資料を読むと、その印象を受けるかもしれませんが、実際のGPUはGuardのようないくつかの異なるアプローチを使用します-バンドクリッピング。ここでは詳しく説明しません(それ自体がトピックになります。 Fabian "ryg" Giesenのすばらしいブログ記事 詳細をご覧ください)、ただし、一般的な考え方では、ラスタライザーは、ビューポート(またはシザーレク)内のピクセルに対してのみフラグメントを生成します。完全にビューポート内にあるかどうかに関係なく、次の両方があれば、単純に大きな三角形をスローできます。 true:
a)三角形は、2Dの上部/下部/左側/右側のクリッピング平面のみを拡張します(z次元の近く/遠くのものとは対照的)
b)実際の頂点座標(およびラスタライザーがそれらに対して実行する可能性のあるすべての中間計算結果)は、GPUのハードウェアラスタライザーが使用する内部データ形式で表すことができます。ラスタライザーは実装固有の幅の固定小数点データ型を使用しますが、頂点座標は32ビットの単精度浮動小数点数です。 (これが基本的にガードバンドのサイズを定義するものです)
私たちのtiranlgeはビューポートよりも3倍大きいだけなので、クリップする必要はまったくないと確信できます。
しかし、それだけの価値はありますか?
まあ、フラグメントシェーダーの呼び出しによる節約は現実的ですが(特に複雑なフラグメントシェーダーがある場合)、実際のシナリオでは全体的な効果はほとんど測定できない可能性があります。一方、アプローチはフルスクリーンクワッドを使用するよりも複雑ではなく、少ないデータを使用するため、大きな違いがない場合でも、効果はありません。痛いので、なぜ使用しないのですか?
このアプローチは、フルスクリーンの長方形だけでなく、あらゆる種類の軸に沿った長方形に使用できますか?
理論的には、これをはさみテストと組み合わせて、任意の軸に沿った長方形を描くことができます(そして、はさみテストは、最初に生成されるフラグメントを制限するだけなので、実際の「テスト」ではないため、非常に効率的です。 "フラグメントを破棄するHWで)。ただし、これには、描画する各長方形のはさみパラメータを変更する必要があります。これは、多くの状態変更を意味し、描画呼び出しごとに1つの長方形に制限されるため、ほとんどのシナリオではそうすることはお勧めできません。
これは demanze の答えに似ていますが、理解しやすいと思います。また、これはTRIANGLE_STRIPを使用して4つの頂点でのみ描画されます。
#version 300 es
out vec2 textureCoords;
void main() {
const vec2 positions[4] = vec2[](
vec2(-1, -1),
vec2(+1, -1),
vec2(-1, +1),
vec2(+1, +1)
);
const vec2 coords[4] = vec2[](
vec2(0, 0),
vec2(1, 0),
vec2(0, 1),
vec2(1, 1)
);
textureCoords = coords[gl_VertexID];
gl_Position = vec4(positions[gl_VertexID], 0.0, 1.0);
}