今回は描画カラーをカラフルにしてみました。
いままでは全体が緑色っぽくてパッとしませんでしたが、カラフルにしたら急に見栄えが良くなりました。
次回までに、選択可能な描画パターンを10種類くらい用意しようと思います。
今回は描画カラーをカラフルにしてみました。
いままでは全体が緑色っぽくてパッとしませんでしたが、カラフルにしたら急に見栄えが良くなりました。
次回までに、選択可能な描画パターンを10種類くらい用意しようと思います。
今回は、前回決めた予定通り、”INFO 表示”として座標や拡大率を画面左上に表示する機能を追加しました。
拡大は、初期表示の全体が見える状態を1倍として、
1000000000000000000000000000倍
くらいまで可能です。(0が27個)
このくらいの倍率になってくると座標も細かくなり、
小数点以下が30桁くらい
必要になります。
今後の予定を決めました。このスケジュールに沿って開発を進め、
ゲームではなくてマンデルブロ集合描画アプリ(教育アプリ)になってしまいましたが
7~8月頃には新アプリをリリースしたいと思います。
作業計画
・INFO 表示 1W ~4/24
ITERATION
SCALE
REAL
IMAGE
・スライダーの数値指定 1W ~5/1
・SCALE/REAL/IMAGE の数字指定 1W
・NO LIMIT チェック 1D ~5/8
・描画パターンの指定(ダイアログ 10種類位) 2W ~5/22
・ライブラリの指定 (ダイアログ 30種類位) 2W ~6/5
・UIのスクロール 1W ~6/19
・iOS対応 2W ~7/3
・リリース作業 1W ~7/10
・minnano.appで公開 1W ~7/17
なんかゲームを作るつもりがマンデルブロ集合をWebGLで描画することになり、100日経ってもゲームのゲの字も見えない状況ですが、今回もマンデルブロ集合の描画精度を高めました。16バイト(doubleの2倍、floatの4倍)の精度での描画が成功しました。
ある程度負荷が高くなってきましたが、RTX3070なら何とかインタラクティブにリアルタイム描画できている感じです。
もうゲームはいったん置いておいて、マンデルブロ集合描画アプリを作りこんでAppleのアプリストアに並べる予定です。
今回は、WebGLのfloat型精度32bitの限界を超えるマンデルブロ集合の描画を行いました。
64bitの浮動小数点数相当の精度が出ています。これは、Javascriptの変数と同等になります。実はJavaScriptの変数はもれなく64bitの小数なんですよね。
いちおう60FPS出ていますが、RTX3070 が結構唸ってて描画中は200Wくらいの消費電力になっていました。
次は更に倍の128bitの精度でマンデルブロ集合を描画したいと思います。
128bitはJavaScriptの変数精度を超えるため、Javascript側でも調整が必要になってくると思います。
uvec4で8バイト符号なし整数を4つつなげて、最大
3.4028236692093846346337460743177e+38
の整数型を作ります。
符号は別途用意します。
で、このuvec4の足し算や引き算や掛け算を作りました。(引き算について、少しでも速度出したいので2の補数を利用していません)
/* [uvec4の足し算] */\
void vec4_add(in uvec4 a, in uvec4 b, out uvec4 res) {\
uint over;\
res.w = a.w + b.w;\
if (UINT_MAX - a.w < b.w) { over = 1u; } else { over = 0u; }\
res.z = a.z + b.z + over;\
if ((UINT_MAX - a.z < b.z + over) || (UINT_MAX - over < b.z)) { over = 1u; } else { over = 0u; }\
res.y = a.y + b.y + over;\
if ((UINT_MAX - a.y < b.y + over) || (UINT_MAX - over < b.y)) { over = 1u; } else { over = 0u; }\
res.x = a.x + b.x + over;\
if ((UINT_MAX - a.x < b.x + over) || (UINT_MAX - over < b.x)) { over = 1u; } else { over = 0u; }\
}\
\
/* [uvec4 x 2 の足し算] */\
void vec4_add2(in uvec4 a1, in uvec4 a2, in uvec4 b1, in uvec4 b2, out uvec4 res1, out uvec4 res2) {\
uint over;\
res1.w = a1.w + b1.w;\
if (UINT_MAX - a1.w < b1.w) { over = 1u; } else { over = 0u; }\
res1.z = a1.z + b1.z + over;\
if ((UINT_MAX - a1.z < b1.z + over) || (UINT_MAX - over < b1.z)) { over = 1u; } else { over = 0u; }\
res1.y = a1.y + b1.y + over;\
if ((UINT_MAX - a1.y < b1.y + over) || (UINT_MAX - over < b1.y)) { over = 1u; } else { over = 0u; }\
res1.x = a1.x + b1.x + over;\
if ((UINT_MAX - a1.x < b1.x + over) || (UINT_MAX - over < b1.x)) { over = 1u; } else { over = 0u; }\
\
res2.w = a2.w + b2.w + over;\
if ((UINT_MAX - a2.w < b2.w + over) || (UINT_MAX - over < b2.w)) { over = 1u; } else { over = 0u; }\
res2.z = a2.z + b2.z + over;\
if ((UINT_MAX - a2.z < b2.z + over) || (UINT_MAX - over < b2.z)) { over = 1u; } else { over = 0u; }\
res2.y = a2.y + b2.y + over;\
if ((UINT_MAX - a2.y < b2.y + over) || (UINT_MAX - over < b2.y)) { over = 1u; } else { over = 0u; }\
res2.x = a2.x + b2.x + over;\
if ((UINT_MAX - a2.x < b2.x + over) || (UINT_MAX - over < b2.x)) { over = 1u; } else { over = 0u; }\
}\
\
/* [uvec4の引き算] */\
/* a > b の前提 */\
void vec4_sub(in uvec4 a, in uvec4 b, out uvec4 res) {\
uint over;\
\
res.w = a.w - b.w;\
if (a.w < b.w) { over = 1u; } else { over = 0u; }\
res.z = a.z - b.z - over;\
if ((a.z < b.z + over) || (UINT_MAX - b.z < over)) { over = 1u; } else { over = 0u; }\
res.y = a.y - b.y - over;\
if ((a.y < b.y + over) || (UINT_MAX - b.y < over)) { over = 1u; } else { over = 0u; }\
res.x = a.x - b.x - over;\
}\
\
/* [uvec4 x 2 の引き算] */\
/* a > b の前提 */\
void vec4_sub2(in uvec4 a1, in uvec4 a2, in uvec4 b1, in uvec4 b2, out uvec4 res1, out uvec4 res2) {\
uint over;\
\
res1.w = a1.w - b1.w;\
if (a1.w < b1.w) { over = 1u; } else { over = 0u; }\
res1.z = a1.z - b1.z - over;\
if ((a1.z < b1.z + over) || (UINT_MAX - b1.z < over)) { over = 1u; } else { over = 0u; }\
res1.y = a1.y - b1.y - over;\
if ((a1.y < b1.y + over) || (UINT_MAX - b1.y < over)) { over = 1u; } else { over = 0u; }\
res1.x = a1.x - b1.x - over;\
if ((a1.x < b1.x + over) || (UINT_MAX - b1.x < over)) { over = 1u; } else { over = 0u; }\
\
res2.w = a2.w - b2.w - over;\
if ((a2.w < b2.w + over) || (UINT_MAX - b2.w < over)) { over = 1u; } else { over = 0u; }\
res2.z = a2.z - b2.z - over;\
if ((a2.z < b2.z + over) || (UINT_MAX - b2.z < over)) { over = 1u; } else { over = 0u; }\
res2.y = a2.y - b2.y - over;\
if ((a2.y < b2.y + over) || (UINT_MAX - b2.y < over)) { over = 1u; } else { over = 0u; }\
res2.x = a2.x - b2.x - over;\
if ((a2.x < b2.x + over) || (UINT_MAX - b2.x < over)) { over = 1u; } else { over = 0u; }\
}\
\
/* [uvec4 の左シフト] */\
/* shift < 128u */\
void vec4_shift_l(in uvec4 a, in uint shift, out uvec4 res1, out uvec4 res2) {\
if (shift < 32u) {\
res1.x = a.x << shift;\
res1.y = a.y << shift;\
res1.z = a.z << shift;\
res1.w = a.w << shift;\
res2.x = 0u;\
res2.y = 0u;\
res2.z = 0u;\
res2.w = 0u;\
if (0u < shift) {\
res1.x += a.y >> (32u - shift);\
res1.y += a.z >> (32u - shift);\
res1.z += a.w >> (32u - shift);\
res2.w += a.x >> (32u - shift);\
}\
} else if (shift < 64u) {\
res1.x = a.y << (shift - 32u);\
res1.y = a.z << (shift - 32u);\
res1.z = a.w << (shift - 32u);\
res1.w = 0u;\
res2.x = 0u;\
res2.y = 0u;\
res2.z = 0u;\
res2.w = (a.x << (shift - 32u));\
if (32u < shift) {\
res1.x += a.z >> (64u - shift);\
res1.y += a.w >> (64u - shift);\
res2.z += a.x >> (64u - shift);\
res2.w += a.y >> (64u - shift);\
}\
} else if (shift < 96u) {\
res1.x = a.z << (shift - 64u);\
res1.y = a.w << (shift - 64u);\
res1.z = 0u;\
res1.w = 0u;\
res2.x = 0u;\
res2.y = 0u;\
res2.z = a.x << (shift - 64u);\
res2.w = a.y << (shift - 64u);\
if (64u < shift) {\
res1.x += a.w >> (96u - shift);\
res2.y += a.x >> (96u - shift);\
res2.z += a.y >> (96u - shift);\
res2.w += a.z >> (96u - shift);\
}\
} else {\
res1.x = (a.w << (shift - 96u));\
res1.y = 0u;\
res1.z = 0u;\
res1.w = 0u;\
res2.x = 0u;\
res2.y = a.x << (shift - 96u);\
res2.z = a.y << (shift - 96u);\
res2.w = a.z << (shift - 96u);\
if (96u < shift) {\
res2.x += a.x >> (128u - shift);\
res2.y += a.y >> (128u - shift);\
res2.z += a.z >> (128u - shift);\
res2.w += a.w >> (128u - shift);\
}\
}\
}\
\
/* [uvec4 の右シフト] */\
/* shift < 128u */\
void vec4_shift_r(in uvec4 a1, in uvec4 a2, in uint shift, out uvec4 res1, out uvec4 res2) {\
if (shift < 32u) {\
res2.x = a2.x >> shift;\
res2.y = a2.y >> shift;\
res2.z = a2.z >> shift;\
res2.w = a2.w >> shift;\
res1.x = a1.x >> shift;\
res1.y = a1.y >> shift;\
res1.z = a1.z >> shift;\
res1.w = a1.w >> shift;\
if (0u < shift) {\
res2.y += a2.x << (32u - shift);\
res2.z += a2.y << (32u - shift);\
res2.w += a2.z << (32u - shift);\
res1.x += a2.w << (32u - shift);\
res1.y += a1.x << (32u - shift);\
res1.z += a1.y << (32u - shift);\
res1.w += a1.z << (32u - shift);\
}\
} else if (shift < 64u) {\
res2.x = 0u;\
res2.y = a2.x >> (shift - 32u);\
res2.z = a2.y >> (shift - 32u);\
res2.w = a2.z >> (shift - 32u);\
res1.x = a2.w >> (shift - 32u);\
res1.y = a1.x >> (shift - 32u);\
res1.z = a1.y >> (shift - 32u);\
res1.w = a1.z >> (shift - 32u);\
if (32u < shift) {\
res2.z += a2.x << (64u - shift);\
res2.w += a2.y << (64u - shift);\
res1.x += a2.z << (64u - shift);\
res1.y += a2.w << (64u - shift);\
res1.z += a1.x << (64u - shift);\
res1.w += a1.y << (64u - shift);\
}\
} else if (shift < 96u) {\
res2.x = 0u;\
res2.y = 0u;\
res2.z = a2.x >> (shift - 64u);\
res2.w = a2.y >> (shift - 64u);\
res1.x = a2.z >> (shift - 64u);\
res1.y = a2.w >> (shift - 64u);\
res1.z = a1.x >> (shift - 64u);\
res1.w = a1.y >> (shift - 64u);\
if (64u < shift) {\
res2.w += a2.x << (96u - shift);\
res1.x += a2.y << (96u - shift);\
res1.y += a2.z << (96u - shift);\
res1.z += a2.w << (96u - shift);\
res1.w += a1.x << (96u - shift);\
}\
} else {\
res2.x = 0u;\
res2.y = 0u;\
res2.z = 0u;\
res2.w = a2.x >> (shift - 96u);\
res1.x = a2.y >> (shift - 96u);\
res1.y = a2.z >> (shift - 96u);\
res1.z = a2.w >> (shift - 96u);\
res1.w = a1.x >> (shift - 96u);\
if (96u < shift) {\
res1.x += a2.x << (128u - shift);\
res1.y += a2.y << (128u - shift);\
res1.z += a2.z << (128u - shift);\
res1.w += a2.w << (128u - shift);\
}\
}\
}\
\
/* [uvec4 の乗算] */\
void vec4_mul(in uvec4 a, in uvec4 b, out uvec4 res1, out uvec4 res2) {\
uvec4 checkbit = uvec4(0u, 0u, 0u, 1u);\
uvec4 tmp;\
uvec4 a1 = a;\
uvec4 a2 = uvec4(0u, 0u, 0u, 0u);\
res1 = uvec4(0u, 0u, 0u, 0u);\
res2 = uvec4(0u, 0u, 0u, 0u);\
for (uint i = 0u; i < 128u; i++) {\
if (0u < ((b.x & checkbit.x) + (b.y & checkbit.y) + (b.z & checkbit.z) + (b.w & checkbit.w))) {\
vec4_add2(res1, res2, a1, a2, res1, res2);\
}\
vec4_shift_l(a, i + 1u, a1, a2);\
vec4_shift_l(checkbit, 1u, checkbit, tmp);\
}\
}\
int vec4_cmp(in uvec4 a, in uvec4 b) {\
if (a.x > b.x) {\
return 1;\
} else if (a.x < b.x) {\
return -1;\
}\
if (a.y > b.y) {\
return 1;\
} else if (a.y < b.y) {\
return -1;\
}\
if (a.z > b.z) {\
return 1;\
} else if (a.z < b.z) {\
return -1;\
}\
if (a.w > b.w) {\
return 1;\
} else if (a.w < b.w) {\
return -1;\
}\
return 0;\
}\
int vec4_cmp2(in uvec4 a1, in uvec4 a2, in uvec4 b1, in uvec4 b2) {\
if (a1.x > b1.x) {\
return 1;\
} else if (a1.x < b1.x) {\
return -1;\
}\
if (a1.y > b1.y) {\
return 1;\
} else if (a1.y < b1.y) {\
return -1;\
}\
if (a1.z > b1.z) {\
return 1;\
} else if (a1.z < b1.z) {\
return -1;\
}\
if (a1.w > b1.w) {\
return 1;\
} else if (a1.w < b1.w) {\
return -1;\
}\
if (a2.x > b2.x) {\
return 1;\
} else if (a2.x < b2.x) {\
return -1;\
}\
if (a2.y > b2.y) {\
return 1;\
} else if (a2.y < b2.y) {\
return -1;\
}\
if (a2.z > b2.z) {\
return 1;\
} else if (a2.z < b2.z) {\
return -1;\
}\
if (a2.w > b2.w) {\
return 1;\
} else if (a2.w < b2.w) {\
return -1;\
}\
return 0;\
}\
次回はこの型を使ってマンデルブロ集合を描くことに挑戦します。
今回は、GLSLでのfloat型の精度向上策を考案していました。
最初はGLSLで独自の高精度浮動小数型を自作しようとしたのですが、小数による演算はとても複雑で、小数点の位置を合わせたりオーバーフローチェックが大変だったり一筋縄ではいきそうにないことが分かりました。つまり
無理そうなことが判明
/(^o^)\オワタ
そこで無い頭を捻って、整数で扱えるような座標系に一旦変換することを考えました。
マンデルブロ集合はおおよそ実軸と虚軸が共に-2.0~2.0位の実数の範囲に収まります。
これを、(4バイト整数なら) -2.0相当を整数値-2147483648に、2.0相当を2147483647に一旦変換して、その系でマンデルブロ集合の計算をして、元の座標系に戻す等をします。
このようにすれば計算を整数として扱え、WebGL2.0で導入されたビット演算子も手伝って、巨大な数を扱える整数型を自作出来る気がします。
今回は、WebGL2.0のTransform Feedbackによりマンデルブロ集合をとりあえず描画することが出来ました。
数値が発散するまでの計算回数を元に適当に色付けしています。
シェーダは以下のようになりました。in_realやtf_realが実部、in_imgやtf_imgが虚部に関する計算値です。
#version 300 es
in vec3 aVertexPosition;
in float in_real;
in float in_img;
in float in_count;
out float tf_real;
out float tf_img;
out float tf_count;
uniform float uReset;
uniform float uCalcs;
uniform float uScale;
uniform float uBaseX;
uniform float uBaseY;
void main(void) {
float real = in_real;
float img = in_img;
float count = in_count;
for(float i = 0.0; i < uCalcs; i++){
if (0.0 < uReset && 0.0 == i) {
real = (aVertexPosition.x + uBaseX) * uScale;
img = (aVertexPosition.y + uBaseY) * uScale;
count = 0.0;
} else {
float a = real * real - img * img + (aVertexPosition.x + uBaseX) *uScale;
float b = 2.0 * real * img + (aVertexPosition.y + uBaseY) * uScale;
real = a;
img = b;
if (4.0 < a * a + b * b) {
count = count + 0.05;
} else {
count = count;
}
}
}
tf_real = real;
tf_img = img;
tf_count = count;
gl_Position = vec4(aVertexPosition, 1.0);
}
シェーダの呼出しコード(概略)は以下の通りです。入力と出力のVBOを描画ごとに入れ替えることでマンデルブロ集合の計算を進めています。
~略~
// --- Transform Feedback ---
gl.useProgram(this.shader_tf);
this.shader_tf.enableAttribute();
// [入力] ユニフォーム変数
gl.uniform1f(this.shader_tf["uReset"], bReset ? 1.0 : 0.0);
gl.uniform1f(this.shader_tf["uCalcs"], calcs);
gl.uniform1f(this.shader_tf["uScale"], scale);
gl.uniform1f(this.shader_tf["uBaseX"], baseX);
gl.uniform1f(this.shader_tf["uBaseY"], baseY);
// [入力] VBO のバインド
gl.bindBuffer(gl.ARRAY_BUFFER, this.vID);
gl.vertexAttribPointer(this.shader_tf["aVertexPosition"], this.v.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this.tf_turn ? this.tf_rID : this.tf_r2ID);
gl.vertexAttribPointer(this.shader_tf["in_real"], this.tf_turn ? this.tf_r.itemSize : this.tf_r2.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this.tf_turn ? this.tf_iID : this.tf_i2ID);
gl.vertexAttribPointer(this.shader_tf["in_img"], this.tf_turn ? this.tf_i.itemSize : this.tf_i2.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this.tf_turn ? this.tf_cID : this.tf_c2ID);
gl.vertexAttribPointer(this.shader_tf["in_count"], this.tf_turn ? this.tf_c.itemSize : this.tf_c2.itemSize, gl.FLOAT, false, 0, 0);
// [出力] 書き込み先の VBO をバインド
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, this.tf_turn ? this.tf_r2ID : this.tf_rID);
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 1, this.tf_turn ? this.tf_i2ID : this.tf_iID);
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 2, this.tf_turn ? this.tf_c2ID : this.tf_cID);
gl.enable(gl.RASTERIZER_DISCARD);
gl.beginTransformFeedback(gl.POINTS);
// 実行
gl.drawArrays(gl.POINTS, 0, this.points);
gl.disable(gl.RASTERIZER_DISCARD);
gl.endTransformFeedback();
this.shader_tf.disableAttribute();
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, null);
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 1, null);
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 2, null);
this.tf_turn = !this.tf_turn;
~略~
動画は画素ごとに1フレーム1回で計算した様子ですが、シェーダ内でループしてまとめて計算もできます。
4Kの解像度で1フレーム1万回の計算をした場合、RTX3070で約18FPSの結果となりました。
これは、 f(z) = z^2 + C の計算を、1秒間に3,840 * 2,160 * 10,000 * 18 = 1,492,992,000,000 回行っていることを意味します。
( -`ω-)キリッ
_人人人人人人_
> 意味します <
 ̄Y^Y^Y^Y^Y^Y ̄
今回は、マンデルブロ集合をTransform Feedbackで高速描画するための下準備を行いました。
このページでマンデルブロ集合を手軽に描画できるようにWebGL2を利用することとしました。
過去に作成したLIFEGAME LIGHTNINGはWebGL1.0で作成していましたが、このバージョンだとTransform Feedbackが利用できないため土台部分のコードをWebGL2.0で書き直し、その上でTransform Feedbackの基礎処理を実装しました。
中核となるコード(概略)は以下通りです。
// --- Transform Feedback ---
gl.useProgram(this.shader_tf);
this.shader_tf.enableAttribute();
// [入力] VBO のバインド
gl.bindBuffer(gl.ARRAY_BUFFER, this.vID);
gl.vertexAttribPointer(this.shader_tf["aVertexPosition"], this.v.itemSize, gl.FLOAT, false, 0, 0);
// [出力] 書き込み先の VBO をバインド
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, this.tf_rID);
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 1, this.tf_iID);
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 2, this.tf_cID);
gl.enable(gl.RASTERIZER_DISCARD);
gl.beginTransformFeedback(gl.POINTS);
// 実行
gl.drawArrays(gl.POINTS, 0, this.points);
gl.disable(gl.RASTERIZER_DISCARD);
gl.endTransformFeedback();
// テスト出力
//const view = new Float32Array(this.points);
//gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 2, this.tf_cID);
//gl.getBufferSubData(gl.TRANSFORM_FEEDBACK_BUFFER, 0, view);
//console.log(view);
this.shader_tf.disableAttribute();
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, null);
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 1, null);
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 2, null);
入力に初期座標を、出力にマンデルブロ集合の計算に必要となる複素数の計算結果「実部」と「虚部」と「計算回数」を設定しました。
Transform Feedback用の頂点シェーダはとりあえず以下のようにしました。
const POINT_VS_TF = '#version 300 es\n\
in vec3 aVertexPosition;\
\
out float tf_real;\
out float tf_img;\
out float tf_count;\
\
void main(void) {\
tf_real = 0.0;\
tf_img = 0.0;\
tf_count = 0.0 < aVertexPosition.x && 0.0 < aVertexPosition.y ? 1.0 : 0.0;\
gl_Position = vec4(aVertexPosition, 1.0);\
}';
描画用の頂点シェーダは以下のようにしました。
const POINT_VS = '#version 300 es\n\
in vec3 aVertexPosition;\
in vec3 aVertexColor;\
in float aVertexAlpha;\
in float tf_count;\
\
out vec4 vColor;\
\
uniform float uPointSize;\
\
uniform mat4 uMVMatrix;\
uniform mat4 uPMatrix;\
\
void main(void) {\
vColor = vec4(aVertexColor, aVertexAlpha * tf_count);\
gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);\
gl_PointSize = uPointSize;\
}';
描画用のフラグメントシェーダは以下のようにし、各点を円形のグラデーションで描画しています
const POINT_FS = '#version 300 es\n\
precision mediump float;\
\
in vec4 vColor;\
out vec4 outColor;\
\
void main(void) {\
vec3 target;\
target.xy = (gl_PointCoord - 0.5) * 2.0;\
float r2 = target.x * target.x + target.y * target.y;\
if (1.0 < r2) {\
discard;\
}\
outColor = vec4(vColor.r, vColor.g, vColor.b, vColor.a * 0.4 * (1.0 - r2));\
}';
テストで、Transform Feedback用の頂点シェーダでは、入力座標 x y が共に 0以上 ならば tf_count に 1.0 を出力するようにしています。
見事にシェーダでの計算結果が反映されました。
ちなみにフルスクリーン切り替え処理や、ちょっとした設定の切り替え機能(今のところFPSの表示/非表示だけ)も実装しました。今回は超がんばりました。
あとはTransform Feedback用の頂点シェーダに複素数の計算を記述し、描画用のシェーダで解像度を上げていけばマンデルブロ集合の画像が表示されるはずです。