はじめに
ポイントスプライトをやります。これは点描画でテクスチャを使うものです。詳しくはいつも本当にお世話になっているwgldさんのこちらの記事にあるような感じです:
ポイントスプライト
点の位置に正方形が表示されるのですが、そこにテクスチャ画像をはめ込むものです。
単に表示するだけでは面白くないので、以前書いたコードを改変します。理由はめんどくさいからです。
p5.jsのフレームバッファを使ってGPGPUで点描画を実行する
コード全文
フレームバッファに関する説明は割愛します。今回は表示方法をいじるだけです。display部分以外の解説はしません。
point sprite
// VTFできましたね
// bindTextures()ですね
// baseMaterialShader()の方はこれをやってるんですよね
// 必須ですね
let fbo0, fbo1;
let dataBuf, indexBuf;
let updateShader;
let displayShader;
const TEX_SIZE = 40;
let textGr;
const inputVS =
`#version 300 es
in float aIndex;
in vec4 aData;
out vec4 vData;
uniform vec2 uSize;
void main(){
float x = mod(aIndex, uSize.x);
float y = floor(aIndex/uSize.y);
vec2 uv = (vec2(x,y)+0.5)/uSize;
uv = 2.0*uv - 1.0;
vData = aData;
gl_Position = vec4(uv, 0.0, 1.0);
gl_PointSize = 1.0;
}
`;
const inputFS =
`#version 300 es
precision highp float;
in vec4 vData;
out vec4 color;
void main(){
color = vData;
}
`;
const updateVS =
`#version 300 es
in float aIndex;
out vec2 vUv;
uniform vec2 uSize;
void main(){
// 位置を取得して送るだけ
float x = mod(aIndex, uSize.x);
float y = floor(aIndex/uSize.y);
vec2 uv = (vec2(x,y)+0.5)/uSize;
vUv = uv;
// y座標は逆にする。お約束。
gl_Position = vec4(uv.x*2.0-1.0, 1.0-uv.y*2.0, 0.0, 1.0);
gl_PointSize = 1.0;
}
`;
const updateFS =
`#version 300 es
precision highp float;
in vec2 vUv;
out vec4 finalData;
uniform sampler2D uData;
void main(){
vec4 data = texture(uData, vUv);
vec2 pos = data.xy;
vec2 vel = data.zw;
if(pos.x + vel.x > 1.0 || pos.x + vel.x < -1.0) vel.x *= -1.0;
if(pos.y + vel.y > 1.0 || pos.y + vel.y < -1.0) vel.y *= -1.0;
pos += vel;
finalData = vec4(pos, vel);
}
`;
const displayVS =
`#version 300 es
in float aIndex;
uniform sampler2D uTex;
uniform vec2 uSize;
void main(){
float x = mod(aIndex, uSize.x);
float y = floor(aIndex/uSize.y);
vec2 uv = (vec2(x,y)+0.5)/uSize;
vec2 p = texture(uTex, uv).xy;
gl_Position = vec4(p, 0.0, 1.0);
gl_PointSize = 32.0;
}
`;
const displayFS =
`#version 300 es
precision highp float;
out vec4 color;
uniform sampler2D uTextGr;
void main(){
vec2 p = gl_PointCoord.xy;
color = texture(uTextGr, p) * vec4(vec3(1.0), 0.0);
}
`;
function setup() {
createCanvas(windowWidth, windowHeight, WEBGL);
const _gl = this._renderer;
const gl = _gl.GL;
const iArray = [];
const fArray = [];
for(let k=0; k<TEX_SIZE*TEX_SIZE; k++){
iArray.push(k);
const direction = random(TAU);
const speedValue = random(0.003, 0.005);
fArray.push(
random(-0.95, 0.95), random(-0.95, 0.95),
speedValue*cos(direction),speedValue*sin(direction)
);
}
indexBuf = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, indexBuf);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(iArray), gl.STATIC_DRAW);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
dataBuf = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, dataBuf);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(fArray), gl.STATIC_DRAW);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
const inputShader = createShader(inputVS, inputFS);
fbo0 = createFramebuffer({
width:TEX_SIZE, height:TEX_SIZE, format:FLOAT, textureFiltering:NEAREST
});
fbo1 = createFramebuffer({
width:TEX_SIZE, height:TEX_SIZE, format:FLOAT, textureFiltering:NEAREST
});
// これでflip-flopできるはずです。
// input.
fbo0.begin();
shader(inputShader);
gl.bindBuffer(gl.ARRAY_BUFFER, indexBuf);
inputShader.enableAttrib(inputShader.attributes.aIndex, 1);
gl.bindBuffer(gl.ARRAY_BUFFER, dataBuf);
inputShader.enableAttrib(inputShader.attributes.aData, 4);
inputShader.setUniform("uSize",[TEX_SIZE, TEX_SIZE]);
_gl._setPointUniforms(inputShader);
gl.drawArrays(gl.POINTS, 0, TEX_SIZE*TEX_SIZE);
inputShader.unbindShader();
fbo0.end();
updateShader = createShader(updateVS, updateFS);
displayShader = createShader(displayVS, displayFS);
textGr = createGraphics(32,32);
textGr.textAlign(CENTER,CENTER).textSize(30);
textGr.noStroke();textGr.fill(60, 120, 180);
textGr.text("海",16,16);
}
function draw() {
const _gl = this._renderer;
const gl = _gl.GL;
// update.
fbo1.begin();
shader(updateShader);
gl.bindBuffer(gl.ARRAY_BUFFER, indexBuf);
updateShader.enableAttrib(updateShader.attributes.aIndex, 1);
updateShader.setUniform("uData", fbo0.color);
updateShader.setUniform("uSize",[TEX_SIZE, TEX_SIZE]);
updateShader.bindTextures();
gl.drawArrays(gl.POINTS, 0, TEX_SIZE*TEX_SIZE);
updateShader.unbindShader();
fbo1.end();
// flip-flop.
const tmp = fbo1;
fbo1 = fbo0;
fbo0 = tmp;
// display.
shader(displayShader);
gl.bindBuffer(gl.ARRAY_BUFFER, indexBuf);
displayShader.enableAttrib(displayShader.attributes.aIndex, 1);
displayShader.setUniform("uTex", fbo0.color);
displayShader.setUniform("uTextGr", textGr);
displayShader.setUniform("uSize",[TEX_SIZE, TEX_SIZE]);
// これに相当する処理でfillとstrokeはやってるんですよね。bindTextures()を。
_gl._setPointUniforms(displayShader);
// でもpointはやってないんですよね。それで失敗するわけですね。
// blend処理はupdateにも影響するので、描画時のみ有効化し使ったらオフにする
gl.enable(gl.BLEND);
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
background(0);
displayShader.bindTextures();
gl.drawArrays(gl.POINTS, 0, TEX_SIZE*TEX_SIZE);
displayShader.unbindShader();
gl.disable(gl.BLEND);
// performance check.
if(frameCount%10===0)console.log(frameRate());
}
実行結果:
パーティクルの個数は1600個です。多すぎるとあれなので制限しました。多くてもいいんですが。
テクスチャを用意する
普通にcreateGraphicsで作ればいいですね。今回は漢字の「海」を使いました。絵文字でもいいです。
textGr = createGraphics(32,32);
textGr.textAlign(CENTER,CENTER).textSize(30);
textGr.noStroke();textGr.fill(60, 120, 180);
textGr.text("海",16,16);
これをグローバルで用意しておきます。
ポイントスプライトで描画する
これをsetUniformで登録します。
displayShader.setUniform("uTex", fbo0.color);
displayShader.setUniform("uTextGr", textGr);
displayShader.setUniform("uSize",[TEX_SIZE, TEX_SIZE]);
これの後でbindTextures()を実行していますが、これをやらないとテクスチャが...ってもう使っていますね。そうです。これを呼ばないと駄目です。シェーダー内部では次のような処理をしています。
#version 300 es
in float aIndex;
uniform sampler2D uTex;
uniform vec2 uSize;
void main(){
float x = mod(aIndex, uSize.x);
float y = floor(aIndex/uSize.y);
vec2 uv = (vec2(x,y)+0.5)/uSize;
vec2 p = texture(uTex, uv).xy;
gl_Position = vec4(p, 0.0, 1.0);
gl_PointSize = 32.0;
}
#version 300 es
precision highp float;
out vec4 color;
uniform sampler2D uTextGr;
void main(){
vec2 p = gl_PointCoord.xy;
color = texture(uTextGr, p) * vec4(vec3(1.0), 0.0);
}
gl_PointSizeは32.0に指定します(小さいと見えないので)。これはpixelDensityに依存するので注意してください。今回は面倒くさいのでそのままやってます。気になる場合はサイズ調整のためにuniformを使ってください。具体的にはpixelDensityを掛ければいいです。
注意すべきはフラグメントシェーダです。gl_PointCoordは要するにテクスチャ座標のようなものです。実際にテクスチャ座標として使うことでテクスチャを付けることができます。
今回は普通のブレンドですがちょっとだけ工夫しています。
// blend処理はupdateにも影響するので、描画時のみ有効化し使ったらオフにする
gl.enable(gl.BLEND);
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
background(0);
displayShader.bindTextures();
gl.drawArrays(gl.POINTS, 0, TEX_SIZE*TEX_SIZE);
displayShader.unbindShader();
gl.disable(gl.BLEND);
ブレンド処理は通常のブレンドですが、フラグメントシェーダの出力時にalphaだけ0にすることで加算合成にしています。ここの数値をいじることで通常のブレンドと加算ブレンドを切り替えることができます。詳しくは、
アルファブレンドと加算合成を共存させる小技
を参照してください。このスケッチの場合は特に機能してないですね。気分です。
おわりに
ここまでお読みいただいてありがとうございました。
追記
前回の動的更新の記事:
p5.jsで点の位置を逐次更新しながら点を描画する
で、描画に使うシェーダを
#version 300 es
in vec2 aPosition;
void main(){
gl_Position = vec4(aPosition, 0.0, 1.0);
gl_PointSize = 32.0;
}
#version 300 es
precision highp float;
out vec4 color;
void main(){
vec2 p = gl_PointCoord.xy;
p = p*2.0-1.0;
float value = pow(1.0-length(p), 2.0);
color = vec4(0.3, 0.5, 0.7, 1.0) * vec4(vec3(value), 1.0);
}
にすることで見た目が変化します。なおスプライトの個数は3600個に抑えています。下のシェーダでvalueを1.0にすると:
このようになりますが、valueにすることで
このようになります。やってることはテクスチャ座標のvec2(0.5)との距離を調べているだけです。グロー円のような表現ができます。いろんな使い方ができます。