フラグメントシェーダーの小技を集めてみました。
VanillaGL
で書き換えているくらい(驚きの見通しのよさ)
drawArrays
はVanillaGLProgram
のものではなく、WebGLRenderingContext
の素のdrawArrays
である点に注意
if
文が増えている: if (...) discard
length
すると、現在描画しているピクセルがポイントスプライトの中心からどれくらい離れているかが分かる
gl.getParameter(ALIASED_POINT_SIZE_RANGE)
で分かるが、GLの仕様ではサイズ1だけ書ければよいことになっているので、実はきちんと描画できないかもしれない
discard
ではなく、アルファを使っても見た目は同じにできる
VanillaGL.init
を少し拡張する
init(opt) { ... if (opt?.useDepth) { this.enable(this.DEPTH_TEST); this.clearMask |= this.DEPTH_BUFFER_BIT; } },
opt
引数を追加し、useDepth
がtrue
ならば、深度テストを有効にして、initCanvas
時に深度バッファもクリアするように設定
promise_all
もprepare
という別名を作ってある
promise_all
はPromise.all
に合わせて引数がプロミスの配列だが、prepare
はプロミスを直接指定する
const ready = prepare(...); ... ready.then((...) => { ... });のように使う(prepare→ready→then)
discard
による円とアルファによる円を描いてから、キャンバス全体を覆う四角形を描く
gl.depthFunc
で変えられる
discard
するとフラグメントシェーダーの呼び出しがなかったが如く、カラーバッファだけではなく、深度バッファへも出力が行われない
discard
した部分は、背後にあるものを後に描いても描画される
中を抜けば枠になる。
all(lessThan(abs(vTex - 0.5), vec2(0.45))))
vTex - 0.5
: テクスチャ座標vTex
の各成分から0.5を引く、結果は±0.5になる
abs(...)
: 絶対値をとる、結果はポリゴン中央の時が (0, 0) で一番外側はどちらに行っても0.5
vec2(0.45)
: x, yとも0.45のvec2
を作る
lessThan(...)
: vec2
の各成分を比較して、第1引数の方が小さければ対応する成分をtrue
としたbool
のvec2
、つまりbvec2
を返す
all(...)
: 引数のbvec2
の成分がすべてtrue
ならばtrue
VanillaGL.pers0
を定義している
VanillaMat4.pers0
を呼んでくれる
mode
でも指定できる
VanillaMat4
のtoRadian
をデフォルトから変更するとVanillaGLTouchDrag
で問題が起きるので修正
setupTouchDrag(canvas, ev => { Mat4.pushToRadian?.(); ... Mat4.popToRadian?.(); this.draw(); });
toRadian
がない古いVanillaMat4
でも動くようになっている
uBorder
は.xy
がボーダーの太さ(X方向・Y方向)で.zw
が角の半径(X方向・Y方向)
p = 0.5 - abs(vTex.xy - 0.5)
p.xy
のいずれかが半径より大きかった場合はボーダーの太さよりも大きい(内側にある)点をdiscard
(else
節)
p.xy
の両方が角の半径より小さければ角の部分を書く
z
、\(b\)をw
として、楕円より外側ならdiscard
z-x
、\(b\)をw-y
として、楕円より内側ならdiscard
p = uBorder.zw - p
r.xy = max(vec2(0), r.zw - r.xy)
r.xy
が内側、r.zw
が外側の半径になっている
p *= p
のように各成分を2乗しておけば、\(x^2b^2+y^2a^2\)はdot
関数(内積を求める関数)で求められる
テクスチャの角を落とすこともできる。
VanillaGL.loadTexture
は意外にも作ってなかったので作っておく(glUtil.loadTexture
を呼び出す)
setTexture
はsetUniform
と並べた時に第1引数がUniformの型を示す文字列の方がコード全体の座りがいいため、第1引数が文字列だった場合は第2引数と順序を逆にしてglUtil.setTexture
を呼び出す
gl-sample.css
に追い出した
.xy
から0.5を引くと、テクスチャ座標の原点が (0.5, 0.5) に移り、座標の範囲は (-0.5, -0.5)〜(+0.5, +0.5)になる
abs
をとると、マイナス側にはみ出した部分がX軸・Y軸と対称な位置に「折り返す」
max
で0と座標値の大きい方を取るので、座標が0未満になると0を返す、つまり、最小値が0
discard
すればよい
uRadius
の.a
成分が最終結果のアルファ成分に設定される 31 Aug 2024
.z
は使われていない
フラグメントシェーダーで円が描けるのだから、いわゆるラウンドキャップの直線(角が丸いやつ)が描けるのではないか?
uMat
は投影変換行列
uThickness
は.xy
にキャンパスサイズ(幅と高さ)を、.z
に線の太さを入れる
p1
とp2
は直線の始点と終点
uv
は直線および接合部のテクスチャ座標っぽいもので、どの位置の頂点かを示し、.z
が0ならば直線、1ならば接合部の頂点であることを示す
uv
で示し、p1
とp2
は12回すべて同じ値を指定し、uv
だけを8通り(指定する頂点数は12個だが、そのうち4個は重複しているので全部で8通り)指定する
uv.xyz
の指定の仕方は以下のようになる
直線部三角形1 | [ 0, 1, 0 ], [ 0, 0, 0 ], [ 1, 1, 0 ]
|
直線部三角形2 | [ 1, 1, 0 ], [ 0, 0, 0 ], [ 1, 0, 0 ]
|
接合部三角形1 | [ 0, 1, 1 ], [ 0, 0, 1 ], [ 1, 1, 1 ]
|
接合部三角形2 | [ 1, 1, 1 ], [ 0, 0, 1 ], [ 1, 0, 1 ]
|
p1
・p2
を投影変換行列で斉次座標に変換、さらに.w
で割ってスクリーン座標を求める
offset
はuv.xy
が (0, 0) - (1, 1) なのに対し、(-1, -1) - (1, 1) になる
k
は直線太さをキャンバスサイズで割ったもので、直線の太さをスクリーン座標系で表していることになる
k
が1になるが、スクリーン座標系は-1〜+1なので、実際には画面の半分にしかならず、直線の太さの半分に相当する
uv.z
が正の場合(通常は1を指定)は接合部なのでuv.xy
をテクスチャ座標としてそのまま流す
p1
とp2
のスクリーン座標が一致する場合は法線ベクトルの長さが0になるが、ゼロ除算を避けるため、長さの最小を1/1,000,000に押さえ込む
uv.x
によって始点と終点のどちらかを選び、uv.y
で直線の上側と下側のどちらの頂点なのかを決定し、k
をかけて始点もしくは終点に足すことで頂点座標を作る
k
は直線の太さの半分なので、上下に半分ずつ描くことで、できあがりの直線の幅は指定どおりになる
prepareRoundCapLine
が線を用意してくれる
pointlist
には点列を渡すが、次元は問わない
closed
はfalse
ならば点列は閉じていない、true
ならば点列は閉じている
drawRoundCapLine
で線を描く
attr
はprepareRoundCapLine
に渡した点列の型を頂点属性の形で示す
f3
の部分だけ指定する
p1:f3, p2:f3
のように展開される
f1
で、prepareRoundCapLine
で生成した直線を描画できる
prepareEquatorRoundCap
はprepareRoundCapLine
に2次元の点列を渡し、drawEquatorRoundCap
はdrawRoundCapLine
にf2
を渡している
ポリゴンの表と裏で表示を変えることができる。
VanillaMat4.pers
(クラスメソッド版)を呼び出す
w
とh
はthis.canvas
から自動で埋めてくれる
gl.TRIANGLES
の場合、の絵のように三角形の頂点を結び、それを視点から見た場合に反時計回りに見えるなら、ポリゴンの表が見えている
gl.TRIANGLE_STRIP
の場合、最初の三角形はgl.TRIANGLES
と同じになるが、ふたつ目の三角形は逆になる
gl.TRIANGLE_STRIP
の場合、最初の三角形は反時計回りに見える場合が表、次の三角形は時計回りに見える場合が表、その次の三角形は反時計回りに見える場合が表、のように、表に見える回転方向が交互に入れ替わる
gl_FrontFacing
で分かる
true
ならば表が見えている、false
ならば裏が見えている
if
は避けるべきと言われている
gl_FrontFacing
をfloat
にキャストすると0か1になるため、mix
関数の第3引数に渡せば、表が見えている時に第2引数の値が、裏が見えている時に第1引数の値が得られる
true
だが、隣のユニットではfalse
といった場合を考えると、実際にはジャンプできないことが分かる
gl_FrontFacing
で分岐して1回実行するよりも、これまた説明していないが、カリングを設定して裏と表を別々のシェーダーで描画した方が性能が出るんじゃなかろうか(やってないからじっさいのところはわかんない)
09 Sep 2024: apply
をaffect
に変更
20 Aug 2024: 本編
15 Aug 2024: 予告編
Copyright (C) 2024 akamoz.jp