import GLWrap from './GLWrap'; // WebGL 2.0 sample - http://webglsamples.org/WebGL2Samples/#transform_feedback_separated_2 export default GLWrap('WebGL2 - Transform feedback', async (gl) => { const POSITION_LOCATION = 0; const VELOCITY_LOCATION = 1; const SPAWNTIME_LOCATION = 2; const LIFETIME_LOCATION = 3; const ID_LOCATION = 4; const NUM_LOCATIONS = 5; const NUM_PARTICLES = 1000; const ACCELERATION = -1.0; const vertexSource = `#version 300 es #define POSITION_LOCATION ${POSITION_LOCATION} #define VELOCITY_LOCATION ${VELOCITY_LOCATION} #define SPAWNTIME_LOCATION ${SPAWNTIME_LOCATION} #define LIFETIME_LOCATION ${LIFETIME_LOCATION} #define ID_LOCATION ${ID_LOCATION} precision highp float; precision highp int; precision highp sampler3D; uniform float u_time; uniform vec2 u_acceleration; layout(location = POSITION_LOCATION) in vec2 a_position; layout(location = VELOCITY_LOCATION) in vec2 a_velocity; layout(location = SPAWNTIME_LOCATION) in float a_spawntime; layout(location = LIFETIME_LOCATION) in float a_lifetime; layout(location = ID_LOCATION) in float a_ID; out vec2 v_position; out vec2 v_velocity; out float v_spawntime; out float v_lifetime; float rand(vec2 co){ return fract(sin(dot(co.xy, vec2(12.9898, 78.233))) * 43758.5453); } void main() { if (a_spawntime == 0.0 || (u_time - a_spawntime > a_lifetime) || a_position.y < -0.5) { // Generate a new particle v_position = vec2(0.0, 0.0); v_velocity = vec2(rand(vec2(a_ID, 0.0)) - 0.5, rand(vec2(a_ID, a_ID))); v_spawntime = u_time; v_lifetime = 5000.0; } else { v_velocity = a_velocity + 0.01 * u_acceleration; v_position = a_position + 0.01 * v_velocity; v_spawntime = a_spawntime; v_lifetime = a_lifetime; } gl_Position = vec4(v_position, 0.0, 1.0); gl_PointSize = 2.0; } `; const fragmentSource = `#version 300 es precision highp float; precision highp int; uniform vec4 u_color; out vec4 color; void main() { color = u_color; } `; const vert = gl.createShader(gl.VERTEX_SHADER)!; gl.shaderSource(vert, vertexSource); gl.compileShader(vert); const frag = gl.createShader(gl.FRAGMENT_SHADER)!; gl.shaderSource(frag, fragmentSource); gl.compileShader(frag); const program = gl.createProgram()!; gl.attachShader(program, vert); gl.attachShader(program, frag); const appStartTime = Date.now(); let currentSourceIdx = 0; // Get varyings and link program const varyings = ['v_position', 'v_velocity', 'v_spawntime', 'v_lifetime']; gl.transformFeedbackVaryings(program, varyings, gl.SEPARATE_ATTRIBS); gl.linkProgram(program); // Get uniform locations for the draw program const drawTimeLocation = gl.getUniformLocation(program, 'u_time'); const drawAccelerationLocation = gl.getUniformLocation(program, 'u_acceleration'); const drawColorLocation = gl.getUniformLocation(program, 'u_color'); // Initialize particle data const particlePositions = new Float32Array(NUM_PARTICLES * 2); const particleVelocities = new Float32Array(NUM_PARTICLES * 2); const particleSpawntime = new Float32Array(NUM_PARTICLES); const particleLifetime = new Float32Array(NUM_PARTICLES); const particleIDs = new Float32Array(NUM_PARTICLES); for (let p = 0; p < NUM_PARTICLES; ++p) { particlePositions[p * 2] = 0.0; particlePositions[p * 2 + 1] = 0.0; particleVelocities[p * 2] = 0.0; particleVelocities[p * 2 + 1] = 0.0; particleSpawntime[p] = 0.0; particleLifetime[p] = 0.0; particleIDs[p] = p; } // Init Vertex Arrays and Buffers const particleVAOs = [gl.createVertexArray(), gl.createVertexArray()]; // Transform feedback objects track output buffer state const particleTransformFeedbacks = [gl.createTransformFeedback(), gl.createTransformFeedback()]; const particleVBOs = new Array(particleVAOs.length); for (let i = 0; i < particleVAOs.length; ++i) { particleVBOs[i] = new Array(NUM_LOCATIONS); // Set up input gl.bindVertexArray(particleVAOs[i]!); particleVBOs[i][POSITION_LOCATION] = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, particleVBOs[i][POSITION_LOCATION]); gl.bufferData(gl.ARRAY_BUFFER, particlePositions, gl.STREAM_COPY); gl.vertexAttribPointer(POSITION_LOCATION, 2, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(POSITION_LOCATION); particleVBOs[i][VELOCITY_LOCATION] = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, particleVBOs[i][VELOCITY_LOCATION]); gl.bufferData(gl.ARRAY_BUFFER, particleVelocities, gl.STREAM_COPY); gl.vertexAttribPointer(VELOCITY_LOCATION, 2, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(VELOCITY_LOCATION); particleVBOs[i][SPAWNTIME_LOCATION] = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, particleVBOs[i][SPAWNTIME_LOCATION]); gl.bufferData(gl.ARRAY_BUFFER, particleSpawntime, gl.STREAM_COPY); gl.vertexAttribPointer(SPAWNTIME_LOCATION, 1, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(SPAWNTIME_LOCATION); particleVBOs[i][LIFETIME_LOCATION] = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, particleVBOs[i][LIFETIME_LOCATION]); gl.bufferData(gl.ARRAY_BUFFER, particleLifetime, gl.STREAM_COPY); gl.vertexAttribPointer(LIFETIME_LOCATION, 1, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(LIFETIME_LOCATION); particleVBOs[i][ID_LOCATION] = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, particleVBOs[i][ID_LOCATION]); gl.bufferData(gl.ARRAY_BUFFER, particleIDs, gl.STATIC_READ); gl.vertexAttribPointer(ID_LOCATION, 1, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(ID_LOCATION); gl.bindBuffer(gl.ARRAY_BUFFER, null); // Set up output gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, particleTransformFeedbacks[i]); gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, particleVBOs[i][POSITION_LOCATION]); gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 1, particleVBOs[i][VELOCITY_LOCATION]); gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 2, particleVBOs[i][SPAWNTIME_LOCATION]); gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 3, particleVBOs[i][LIFETIME_LOCATION]); } gl.useProgram(program); gl.uniform4f(drawColorLocation, 0.0, 1.0, 1.0, 1.0); gl.uniform2f(drawAccelerationLocation, 0.0, ACCELERATION); gl.enable(gl.BLEND); gl.blendFunc(gl.SRC_ALPHA, gl.ONE); return { onTick() { const time = Date.now() - appStartTime; const destinationIdx = (currentSourceIdx + 1) % 2; // Clear color buffer gl.clearColor(0.0, 0.0, 0.0, 1.0); gl.clear(gl.COLOR_BUFFER_BIT); // Toggle source and destination VBO const sourceVAO = particleVAOs[currentSourceIdx]; const destinationTransformFeedback = particleTransformFeedbacks[destinationIdx]; gl.bindVertexArray(sourceVAO); gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, destinationTransformFeedback); // Set uniforms gl.uniform1f(drawTimeLocation, time); // Draw particles using transform feedback gl.beginTransformFeedback(gl.POINTS); gl.drawArrays(gl.POINTS, 0, NUM_PARTICLES); gl.endTransformFeedback(); gl.endFrameEXP(); // Ping pong the buffers currentSourceIdx = (currentSourceIdx + 1) % 2; }, }; });