I'm trying to do position updates for particles being rendered as points using webgl. I've asked some questions before about the same project I'm playing around with here and here which lead me a fair bit on the way. Unfortunately, most of the answers use twgl which, to me, takes a lot of shortcuts which I have a hard time understanding (so I didn't want to just try to copy it either but start with the basics).
Basically, I'm trying to render to a texture with one framebuffer + program and then use this texture in another program.
I don't know if I'm failing to render to the posTexture, of if the posTexture gets successfully rendered and that it fails to load into the renderProgram afterwards (since both are happening in the 'blackbox' GPU).
I made a snippet here without the renderFramebuffer (it simply renders directly to canvas instead) to show the problem. The core of the problem is at the end of the javascript bit, the rest is setup (which may be related):
function initShaderProgram(gl, vShader, fShader) {
const shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vShader);
gl.attachShader(shaderProgram, fShader);
gl.linkProgram(shaderProgram);
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
throw new Error('Unable to initiate webgl shaders. Breaking.');
}
return shaderProgram;
}
function loadShader(gl, type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
let err = gl.getShaderInfoLog(shader);
gl.deleteShader(shader);
throw new Error(`Unable to compile shaders. ${err}`);
}
return shader;
}
const c = document.getElementById("c");
const gl = c.getContext('webgl2');
const amParticles = 1;
if (gl === null || gl === undefined) {
throw new Error('Unable to initiate webgl context. Breaking.');
}
// Extensions used for anti aliasing in rendering dots
let ext = gl.getExtension('EXT_color_buffer_float');
if (!ext) {
throw new Error("need EXT_color_buffer_float");
}
ext = gl.getExtension('EXT_float_blend');
if (!ext) {
throw new Error("need EXT_float_blend");
}
// Setup programs
const VsPos = document.getElementById("posVs").textContent;
const FsPos = document.getElementById("posFs").textContent;
const VsRender = document.getElementById("renderVs").textContent;
const FsRender = document.getElementById("renderFs").textContent;
const vShaderRender = loadShader(gl,
gl.VERTEX_SHADER, VsRender);
const vShaderPosUpd = loadShader(gl,
gl.VERTEX_SHADER, VsPos);
const fShaderRender = loadShader(gl,
gl.FRAGMENT_SHADER, FsRender);
const fShaderPosUpd = loadShader(gl,
gl.FRAGMENT_SHADER, FsPos);
// Setup shader
const renderProgram = initShaderProgram(gl,
vShaderRender, fShaderRender);
const posProgram = initShaderProgram(gl,
vShaderPosUpd, fShaderPosUpd);
// Setup global GL settings
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
// Blending to allow opacity (probably unrelated)
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
// Setup posTexture to render new positions to
let posTexture, posFrameBuffer; {
posTexture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, posTexture);
// Make texture non-mips
gl.texParameteri(gl.TEXTURE_2D,
gl.TEXTURE_WRAP_S,
gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D,
gl.TEXTURE_WRAP_T,
gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D,
gl.TEXTURE_MIN_FILTER,
gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D,
gl.TEXTURE_MAG_FILTER,
gl.NEAREST);
const level = 0;
const internalFormat = gl.RGBA32F;
const border = 0;
const format = gl.RGBA;
const type = gl.FLOAT;
// Example position pre-render
const data = new Float32Array([.5, .5, 0, 0]);
// height = 1, amount pixels = width, rgba = position
gl.texImage2D(gl.TEXTURE_2D,
level,
internalFormat,
amParticles,
1,
border,
format,
type,
data);
// Pos framebuffer
posFrameBuffer = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER,
posFrameBuffer);
// Bind it to posTexture
gl.framebufferTexture2D(gl.FRAMEBUFFER,
gl.COLOR_ATTACHMENT0,
gl.TEXTURE_2D,
posTexture,
level);
if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) !==
gl.FRAMEBUFFER_COMPLETE) {
console.error(`Something went wrong with setting up the the posFrameBuffer. Status: ${
gl.checkFramebufferStatus(gl.FRAMEBUFFER)}`);
}
}
gl.useProgram(posProgram);
gl.bindFramebuffer(gl.FRAMEBUFFER, posFrameBuffer);
gl.viewport(0, 0, amParticles, 1);
// Now (it should be?) drawing new positions to
// texture posTexture
gl.drawArrays(gl.POINTS, 0, amParticles);
// Set new posTexture to texture unit 1
gl.activeTexture(gl.TEXTURE1);
gl.bindTexture(gl.TEXTURE_2D, posTexture);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.useProgram(renderProgram);
// Set uniform location to texture unit 1
const loc = gl.getUniformLocation(renderProgram, "t0_pos_tex");
gl.uniform1i(loc, 1);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
// Should draw with new position
gl.drawArrays(gl.POINTS, 0, amParticles);
#c {
width: 400px;
height: 200px;
}
.hide {
display: none;
}
<canvas id="c"></canvas>
<p>
If the circle is at the <b>left</b> side of the canvas, rendered by the posProgram, the experiment is successfull.
</p>
<div id="posFs" class="hide"># version 300 es
#define M_PI 3.1415927
precision highp float;
out vec4 outColor;
// Only renders one particle for the sake
// of the example to a predetermined position
void main() {
// New position to render to (should appear
// top-left ish)
float new_x = -.5;
float new_y = .5;
outColor = vec4(new_x, new_y, 0., 1.);
}
</div>
<div id="posVs" class="hide">#version 300 es
// Does nothing since the fragment shader sets
// the new position depending on the pixel
// which indicates which index of the texture
// = index of the new positions to update
void main() {}
</div>
<div id="renderVs" class="hide"># version 300 es
#define M_PI 3.1415927
uniform sampler2D t0_pos_tex;
out vec4 color;
void main() {
vec4 t0_pos = texelFetch(t0_pos_tex, ivec2(gl_VertexID, 0), 0);
gl_Position = vec4(t0_pos.x, t0_pos.y, 0., 1.);
color = vec4(1., 1., 1., 1.);
gl_PointSize = 50.0;
}
</div>
<div id="renderFs" class="hide"># version 300 es
precision highp float;
in vec4 color;
out vec4 outColor;
// Turns point into a circle and adds
// antialiasing to make it smoothly round
void main() {
float r = 0.0, delta = 0.0, alpha = 1.0;
vec2 cxy = 2.0 * gl_PointCoord - 1.0;
r = dot(cxy, cxy);
delta = fwidth(r);
alpha = 1.0 - smoothstep(1.0 - delta, 1.0 + delta, r);
outColor = color * alpha;
}
</div>