#はじめに
テッセレーションシェーダに関する日本語の記述サイトがほぼなかったので
備忘録もかねて記述していこうとおもいます。主に基礎的な部分なので、曲面どうのこうのということは今回は見送ります。
#テッセレーションシェーダ
opengl4.0では、新たにテッセレーションシェーダを扱うことができるようになっている。
ポリゴンを自動で分割してくれる便利なシェーダだ。現状4角形と3角形でできることを確認しているけど今回は主に3角形での説明にする。
テッセレーションシェーダの主な役割はこんな感じ
テッセレーション制御シェーダ:1パッチの分割数を決める。
テッセレーション評価シェーダ:分割したポリゴンの頂点位置等を決定する
このように分割してくれたりする。
シェーダの処理される順番としては、
1.バーテックスシェーダ
2.テッセレーション制御シェーダ
--------ポリゴン分割----------
3.テッセレーション評価シェーダ
4.ジオメトリシェーダ
5.フラグメントシェーダ
と、このような感じになっている。テッセレーション制御シェーダで指定した分割数が固定パイプラインで、分割されて、テッセレーション評価シェーダで分割後の形状を取得することができる。
##分割方法(テッセレーション制御シェーダ)
テッセレーションシェーダでは、gl_TessLevelOuterとgl_TessLevelInnerで、2種類の
方法で分割を行うことができる。
分割方法は3角形ポリゴンの場合はこのようになる
図3
参考ページ:http://stackoverflow.com/questions/24083656/tessellation-shader-
opengl
Outerでは、元ポリゴンの辺上に伸びるエッジが増えていることがわかると思う。
Innerでは、具体的な内容はわからないけれども、奇数と偶数で処理が分かれてる?ように思える。
少し踏み入った話になるが、三角形ポリゴンでは、Outerは3つ指定でき、
Outerを5、2、1と設定した場合はこのようになる。
図4
左側が5分割下が2分割右上は1のため分割されていないのがわかる。
まぁこんな変則的な分割が使えるのかはわからないけど。
##テッセレーション座標(テッセレーション評価シェーダ)
ここのシェーダでは、分割されたポリゴンが受け取れる。4角形ポリゴンを分割しても
3角形で出てくる。
テッセレーション評価シェーダでは、分割後の頂点が、UV座標で定義されている。
UV座標はgl_TessCoordで受け取れる。gl_TessCoord.xyzはUVWに対応付いている。
各UV座標はこんなかんじになっている。
図5
参考:http://stackoverflow.com/questions/28946396/how-the-gl-tesscoord-is-computed-during-the-tessellation
四角形でのZ(W)はオプション?らしい。まぁ使わなくても十分に処理できるので割愛。
先ほどの3角形ポリゴンで各gl_TessCoordの座標をグレースケールで表したらこんな感じ、値は0~1までで、黒ほど0で白ほど1
この辺りまで説明しておくと、後は自分でシェーダを扱えるようになると思う。
##実装方法
この節では、元形状を分割して描画するシェーダを示していく。分割結果が明示的にわかるように、エッジを描画した結果を示す。
###gl側での処理
元形状を分割しただけのシェーダを作成する前に、gl側で一つだけ設定しなければならないものがある。
テッセレーションを使う場合、描画できるポリゴンはGL_Patchesだけになる。ドローコールする
ときはここだけ注意しておこう。GL_Patches以外のポリゴンはドローが無視される。
あとはシェーダの処理について、
###頂点シェーダ
layout (location = 0) in vec3 position;
out vec3 vPosition;
void main(void)
{
gl_Position = vec4(position, 1.0);
vPosition = position;
}
頂点シェーダでは、頂点データを投げるだけ。
###テッセレーション制御シェーダ
layout(vertices = 3) out;
void main()
{
float Inner = 3;
float Outer = 3;
gl_out[gl_InvocationID].gl_Position
= gl_in[gl_InvocationID].gl_Position;
gl_TessLevelOuter[0] = float(Outer);
gl_TessLevelOuter[1] = float(Outer);
gl_TessLevelOuter[2] = float(Outer);
gl_TessLevelInner[0] = float(Inner);
}
ここで、layout(vertices=3)は、パッチの頂点数となる3角形パッチなら3。
四角形パッチなら(vertices=4)と設定すればいい。
gl_in[gl_InvocationID].gl_Positionでは、分割元のポリゴンの頂点情報である。
図1の頂点情報がこのなかに格納されている。
そして、gl_TessLevelOuterは、verticesで指定した数分設定する必要がある。
今回はvertices=3で設定しているため、[0],[1],[2]の3要素にOuterを設定している。
Innerは1つでもいい。リファレンスによるとOuterとInner共に要素の最大数は4らしい。
###テッセレーション評価シェーダ
layout(triangles, equal_spacing, ccw) in;
out vec3 tePatchDistance;
uniform mat4 MVP;
void main()
{
float u = gl_TessCoord.x;
float v = gl_TessCoord.y;
float w = gl_TessCoord.z;
vec3 p0 = gl_in[0].gl_Position.xyz;
vec3 p1 = gl_in[1].gl_Position.xyz;
vec3 p2 = gl_in[2].gl_Position.xyz;
tePatchDistance = gl_TessCoord;
gl_Position = MVP * vec4( u*p0 + v*p1 + w*p2, 1);
}
ここでは、分割したポリゴンの頂点毎に処理が行われる。(図2の各頂点)
ここでは、最初にlayout(triangles,equal_spacing,ccw)を設定している。
各意味合いは
triangles:テッセレーション制御シェーダで、何を使って分割したかを示す。
3角形パッチをテッセレーションしたならtriangles四角形パッチならquadsを指定する。
他にisolineもある。
equal_spacing:すべてのパッチの分割が等しい長さを持つようにテッセレーションを行う設定である。(その他:fractional_even_spacing、fractional_odd_spacing)
テッセレーション結果をccw:反時計回りか、時計回り化を示す。(時計回りcw、反時計回りccw)
また、今回はここで、頂点の描画位置を決定している。
###ジオメトリシェーダ
layout(triangles) in;
layout(triangle_strip, max_vertices = 3) out;
uniform mat3 NormalMatrix;
in vec3 tePatchDistance[3];
out vec3 gFacetNormal;
out vec3 gPatchDistance;
out vec3 gTriDistance;
void main()
{
vec3 A = (gl_in[1].gl_Position - gl_in[0].gl_Position).xyz;
vec3 B = (gl_in[2].gl_Position - gl_in[0].gl_Position).xyz;
gFacetNormal = NormalMatrix * normalize(cross(A, B));
gPatchDistance = tePatchDistance[0];
gTriDistance = vec3(1, 0, 0);
gl_Position = gl_in[0].gl_Position; EmitVertex();
gPatchDistance = tePatchDistance[1];
gTriDistance = vec3(0, 1, 0);
gl_Position = gl_in[1].gl_Position; EmitVertex();
gPatchDistance = tePatchDistance[2];
gTriDistance = vec3(0, 0, 1);
gl_Position = gl_in[2].gl_Position; EmitVertex();
EndPrimitive();
}
ここでは、分割後のポリゴンごとに、処理を行っている。
面の法線を算出して、フラグメントシェーダに値を渡しているだけである。
エッジの描画のために必要なデータもついでに渡している。
###フラグメントシェーダ
out vec4 FragColor;
in vec3 gFacetNormal;
in vec3 gTriDistance;
in vec3 gPatchDistance;
in float gPrimitive;
uniform vec3 LightPos;
float amplify(float d, float scale, float offset)
{
d = scale * d + offset;
d = clamp(d, 0, 1);
d = 1 - exp2(-2*d*d);
return d;
}
void main()
{
vec3 N = normalize(gFacetNormal);
vec3 L = LightPos;
float df = max(dot(N, L),0.0);
vec3 color = vec3(df);
float d1 = min(min(gTriDistance.x, gTriDistance.y), gTriDistance.z);
float d2 = min(min(gPatchDistance.x, gPatchDistance.y), gPatchDistance.z);
color = amplify(d1, 40, -0.5) * amplify(d2, 60, -0.5) * color;
FragColor = vec4(color, 1.0);
}
ここでは、描画処理を行っているだけである。エッジ
の描画のために、処理が多くあるが、
ほぼコピペしてもらったら動く段階なのでソースの説明は割愛。
こんな感じに分割される。
#終わりに
テッセレーションシェーダを使うと、ディスプレースメントマップなどが使えて、
簡単な形状で、複雑なモデルの描画を行えて面白いかと思います。
日本語の基礎的な解説サイトが見当たらなかったので、テッセレーションシェーダに取り組みたい
人の手助けになればと思います。
自分でも現状ここまでしか把握できていないため、間違い等あれば指摘のほうをしていただければと思います。