### Implementing a Particle System with WebGL2 and Transform Feedback
Robert Muth
(http://robert.muth.org)
#### NYC WebGL Developers Meetup
#### 2017/07/12
#### About Me
* Ph.D. CS. University of Arizona (Compilers)
* Day job: Google (Storage Infrastructure)
* Noteworthy past projects:
* [PNaCl](https://blog.chromium.org/2017/05/goodbye-pnacl-hello-webassembly.html) (Google)
* [Pin](https://software.intel.com/en-us/articles/pin-a-dynamic-binary-instrumentation-tool) (Intel)
* WebGL Hobbyist
#### Me and WebGL
* Got into 3D Graphics via [Eric Haines' Class at Udacity](https://www.udacity.com/course/interactive-3d-graphics--cs291)
* Maintain Dart based WebGL2 engine [ChronosGL](https://github.com/ChronosTeam/ChronosGL)
* Like to experiment with procedural generated animations
* My pieces [art.muth.org](http://art.muth.org)
* Pieces by others [arscalculanda.com](http://www.arscalculanda.com/about.html)
#### WebGL2
* Spec Finalized 2017/01
* Documentation is sparse but similar to OpenGL ES 3.0 (2012)
* Support
* Firefox since 51
* Chrome since 56
* Safari [behind flag](https://webkit.org/status/#)
* Edge [under consideration](https://developer.microsoft.com/en-us/microsoft-edge/platform/status/webgl20/)
* Even available on (Android) mobile browsers
* Useful: [WebGL Report](http://webglreport.com) and [WebGL Stats](https://webglstats.com)
### WebGL2 Features In This Presentation
* Shader language Version 3.00 es
* Vertex Array Objects (VAO)
* Transform Feedback
* (Point Sprites)
## [Particle Demo](http://chronosteam.github.io/ChronosGL/Examples/transform_feedback/transform_feedback.html)
#### (Inspired by [Helios](http://art.muth.org/helios.html))
[Dart code on github](http://github.com/ChronosTeam/ChronosGL/tree/master/example/transform_feedback)
#### Demo Take-Away
* Orbiting camera
* Lots of particles (ions) flying around
* Emitted from 5 sources (poles)
* Absorbed by 5 sinks (poles)
* Random initial direction
* Inspired by electrical fields
* Looks nice but bogus physics
* Much higher particle count with GPU
* while maintaining high FPS
## CPU-Side Implementation
Simulation Time-Step For Single Particle [Dart]
Vector3 Update(Vector3 pos, double dt,
List<Vector3> sources, List<Vector3> sinks) {
Vector3 force = new Vector3(0.0, 0.0, 0.0);
for (Vector3 pole in sources) { // pushing forces
Vector3 t = pos - pole;
final double dist = t.length;
if (dist <= kMinDistance) continue;
if (dist > kMaxDistance) {
return sources[RandInt(sources.length)] + RandVector3(0.35);
}
force += t / (dist * dist);
}
for (Vector3 pole in sinks) { // pulling forces
Vector3 t = pole - pos;
final double dist = t.length;
if (dist <= kMinDistance) {
return sources[RandInt(sources.length)] + RandVector3(0.35);
}
force += t / (dist * dist);
}
return pos + force.normalized() * dt; // bogus physics
}
### Point Sprites
* Ideal for particles
* No triangles: each object defined by single vec3
* No orientation - always face camera
* Predefined Vertex Shader outputs:
* ``gl_Position [vec3]`` : determines location
* ``gl_PointSize [float]`` : determines size
* Predefined Fragment Shader input:
* ``gl_PointCoord [vec2]`` : provides texture UVs
Point Sprites Texture Generation
var canvas = document.createElement('canvas');
canvas.width = 64;
canvas.height = 64;
var cx = canvas.width / 2;
var cy = canvas.height / 2;
var ctx = canvas.getContext("2d");
var gradient1 = ctx.createRadialGradient(cx, cy, 1, cx, cy, 22);
gradient1.addColorStop(0, 'gray');
gradient1.addColorStop(1, 'black');
ctx.fillStyle = gradient1;
ctx.fillRect(0, 0, canvas.width, canvas.height);
// see top image for intermediate result
var gradient2 = ctx.createRadialGradient(cx, cy, 1, cx, cy, 6);
gradient2.addColorStop(0, 'white');
gradient2.addColorStop(1, 'gray');
ctx.globalAlpha = 0.9;
ctx.fillStyle = gradient2;
ctx.arc(cx, cy, 4, 0, 2 * 3.145);
ctx.fill();
// see bottom image for final result
Simplifiction of Perspective • View • Model approach
Fragment Shader
#version 300 es
precision highp float;
out vec4 oFragColor;
uniform sampler2D uTexture;
void main(void) {
oFragColor = texture(uTexture, gl_PointCoord);
}
#### Animation Loop
* Update each particle position
* Collect all positions in ArrayBuffer
* Copy ArrayBuffer to attribute buffer (CPU → GPU)
* Standard call: ``gl.drawArrays(gl.POINTS, ...)``
Randomness in shaders is a bit painful
# Implementation Details
Compiling and Linking the Shaders [Dart]
Shader CompileShader(int type, String code) {
Shader out = gl.createShader(type);
gl.shaderSource(out, code);
gl.compileShader(out);
return out;
}
Program LinkProgram(String vertCode, String fragCode, List<String> transforms) {
Program out = gl.createProgram();
gl.attachShader(program, CompileShader(gl, gl.VERTEX_SHADER, vertCode);
gl.attachShader(program, CompileShader(gl, gl.FRAGMENT_SHADER, fragCode);
if (transforms.length > 0) {
gl.transformFeedbackVaryings(program, transforms, gl.SEPARATE_ATTRIBS);
}
gl.linkProgram(program);
return out;
}
Order of transforms matters!
Traditional WebGL Draw Setup
gl.useProgram(program);
for uni in uniforms: // UBO can simplify this
gl.uniform<uni-type>(<uni-location>, <uni-value>);
for att in attributes:
gl.enableVertexAttribArray(<att-location>);
for att in attributes:
gl.bindBuffer(gl.ARRAY_BUFFER, <att-buffer>);
gl.vertexAttribPointer(<att-location>, ...)
gl.drawArrays(gl.POINTS, ...);
for att in attributes:
gl.disableVertexAttribArray(<att-location>);
VAO Initialization + New Draw Setup
var vao = gl.createVertexArray();
gl.bindVertexArray(vao);
for att in attributes:
gl.enableVertexAttribArray(<att-location>);
for att in attributes:
gl.bindBuffer(gl.ARRAY_BUFFER, <att-buffer>);
gl.vertexAttribPointer(<att-location>, ...)
gl.bindVertexArray(null);
gl.useProgram(program);
for uni in uniforms: // UBO can simplify this
gl.uniform<uni-type>(<uni-location>, <uni-value>);
gl.bindVertexArray(vao);
gl.drawArrays(gl.POINTS, ...);