Reputation: 313
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>
Upvotes: 1
Views: 237
Reputation:
Your posVS
vertex shader does nothing so nothing will be rendered by the fragment shader. In order to render something the vertex shader must either generate a point by setting gl_Position
and gl_PointSize
OR it must generate a line by being called twice and setting gl_Position
to different values each time or a triangle by being called 3 times and setting gl_Position
to different values each time so that's the first issue.
changed it to this
void main() {
// draw a single pixel
gl_PointSize = 1.0;
// in the center of the viewport
gl_Position = vec4(0, 0, 0, 1);
}
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
void main() {
// draw a single pixel
gl_PointSize = 1.0;
// in the center of the viewport
gl_Position = vec4(0, 0, 0, 1);
}
</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>
But, I suggest you spend a couple of minutes to try to understand the example linked. Yes it uses TWGL because the point of explaining how to do particles does not also want to be a tutorial on the entirely of WebGL. It should be pretty obvious what twgl.createTexture
does just by looking at the inputs. Similarly with, twgl.createFramebufferInfo
and twgl.createBufferInfoFromArrays
if not obvious are probably just a few seconds away from understanding. twgl.setBuffersAndAttributes
and twgl.setUniforms
do exactly what they say. If you've done either of those things manually in webgl it should be pretty clear what it means to "set buffers and attributes" and to "set uniforms". All that's left is twgl.drawBufferInfo
In any case, it's going to be slower computing the new particle positions using gl.POINTS
, one point per particle, rather than drawing a quad with N pixels, one for pixel for each point. Drawing the particles you might use gl.POINTS
but not updating the positions.
One other note: like you print the shader info log when compiling fails you probably want to print the program info log when linking fails. There are plenty of errors that only happen during linking.
Upvotes: 1