Skip to content

Commit 177f83c

Browse files
author
Andrew Adamson
committed
Adding episode 24 Transform Feedback code
1 parent 3aaf875 commit 177f83c

File tree

2 files changed

+425
-0
lines changed

2 files changed

+425
-0
lines changed

24.transformfeedback.example.js

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
/*
2+
This is an example of a very basic particle system
3+
using transform feedback.
4+
5+
It's important to remember that not all particle
6+
systems *need* transform feedback. It's just a tool
7+
you can use when the number of particles grow very
8+
large. This animation could have been done entirely
9+
in JavaScript, but you would run out of CPU capcity
10+
and saturate your bandwidth on most hardware.
11+
*/
12+
13+
const vertexShaderSource = `#version 300 es
14+
#pragma vscode_glsllint_stage: vert
15+
16+
uniform float uRandom;
17+
18+
layout(location=0) in float aAge;
19+
layout(location=1) in float aLifespan;
20+
layout(location=2) in vec2 aPosition;
21+
layout(location=3) in vec2 aVelocity;
22+
23+
out float vAge;
24+
out float vLifespan;
25+
out vec2 vPosition;
26+
out vec2 vVelocity;
27+
out float vHealth;
28+
29+
/* From TheBookOfShaders, chapter 10. This is a slightly upscaled implementation
30+
of the algorithm:
31+
r = Math.cos(aReallyHugeNumber);
32+
except it attempts to avoid the concentration of values around 1 and 0 by
33+
multiplying by a very large irrational number and then discarding the result's
34+
integer component. Acceptable results. Other deterministic pseudo-random number
35+
algorithms are available (including random textures).
36+
*/
37+
float rand2(vec2 source)
38+
{
39+
return fract(sin(dot(source.xy, vec2(1.9898,1.2313))) * 42758.5453123);
40+
}
41+
42+
void main()
43+
{
44+
if (aAge == aLifespan)
45+
{
46+
float s = float(gl_VertexID);
47+
float r1 = rand2(vec2(s, uRandom));
48+
float r2 = rand2(vec2(r1, uRandom));
49+
float r3 = rand2(vec2(uRandom, r1 * uRandom));
50+
51+
vec2 direction = vec2(cos(r1 * 2.0 + .57), sin(r1 * 2.0 + .57)); // Unit vector, mostly pointing upward
52+
float energy = .2 + r2; // particles with very little energy will never be visible, so always give them something.
53+
vec2 scale = vec2(.05, .3); // direction*energy gives too strong a value, so we scale this to fit the screen better.
54+
55+
// use values above to calculate velocity
56+
vVelocity = direction * energy * scale;
57+
58+
// Particles will be emitted from below the frame
59+
vPosition = vec2(.5 - r1, -1.1);
60+
61+
vAge = -r3 * .01;
62+
vLifespan = aLifespan;
63+
}
64+
else
65+
{
66+
// Note that even values you **arn't** updating
67+
// must be assigned to the varying or else the
68+
// value will be 0 in the next draw call.
69+
vec2 gravity = vec2(0.0, -0.02);
70+
71+
vVelocity = aVelocity + gravity;
72+
vPosition = aPosition + vVelocity;
73+
vAge = min(aLifespan, aAge + .05);
74+
vLifespan = aLifespan;
75+
}
76+
77+
vHealth = 1.0 - (vAge / vLifespan);
78+
79+
gl_Position = vec4(vPosition, 0.0, 1.0);
80+
gl_PointSize = 5.0 * (1.0 - vAge);
81+
}`;
82+
83+
const fragmentShaderSource = `#version 300 es
84+
#pragma vscode_glsllint_stage: frag
85+
86+
precision mediump float;
87+
88+
in float vHealth;
89+
90+
out vec4 fragColor;
91+
92+
void main()
93+
{
94+
// Point primitives are considered to have a width and
95+
// height of 1 and the center is at (.5, .5). So if we
96+
// discard fragments beyond this distance, we get a
97+
// point primitive shaped like a disc.
98+
99+
float distanceFromPointCenter = distance(gl_PointCoord.xy, vec2(0.5));
100+
if (distanceFromPointCenter > .5) discard;
101+
102+
fragColor = vec4(.3, 0.4, 0.8, vHealth);
103+
}`;
104+
105+
const canvas = document.querySelector('canvas');
106+
const gl = canvas.getContext('webgl2');
107+
const program = gl.createProgram();
108+
109+
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
110+
gl.shaderSource(vertexShader, vertexShaderSource);
111+
gl.compileShader(vertexShader);
112+
gl.attachShader(program, vertexShader);
113+
114+
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
115+
gl.shaderSource(fragmentShader, fragmentShaderSource);
116+
gl.compileShader(fragmentShader);
117+
gl.attachShader(program, fragmentShader);
118+
119+
// This line tells WebGL that these four output varyings should
120+
// be recorded by transform feedback and that we're using a single
121+
// buffer to record them.
122+
gl.transformFeedbackVaryings(program, ['vAge', 'vLifespan', 'vPosition', 'vVelocity'], gl.INTERLEAVED_ATTRIBS);
123+
124+
gl.linkProgram(program);
125+
126+
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
127+
console.log(gl.getShaderInfoLog(vertexShader));
128+
console.log(gl.getShaderInfoLog(fragmentShader));
129+
console.log(gl.getProgramInfoLog(program));
130+
}
131+
132+
gl.useProgram(program);
133+
134+
// This is the number of primitives we will draw
135+
const COUNT = 1000000;
136+
137+
// Initial state of the input data. This "seeds" the
138+
// particle system for its first draw.
139+
let initialData = new Float32Array(COUNT * 6);
140+
for (let i = 0; i < COUNT * 6; i += 6) {
141+
const px = Math.random() * 2 - 1;
142+
const age = Math.random() * -3 + .75;
143+
const lifespan = Math.random() * 3 + 1;
144+
145+
initialData.set([
146+
age, // vAge
147+
lifespan, // vLifespan
148+
px, -1.1, // vPosition
149+
0,0, // vVelocity
150+
], i);
151+
152+
}
153+
154+
155+
// Describe our first buffer for when it is used a vertex buffer
156+
const buffer1 = gl.createBuffer();
157+
const vao1 = gl.createVertexArray();
158+
gl.bindVertexArray(vao1);
159+
gl.bindBuffer(gl.ARRAY_BUFFER, buffer1);
160+
gl.bufferData(gl.ARRAY_BUFFER, 6 * COUNT * 4, gl.DYNAMIC_COPY);
161+
gl.bufferSubData(gl.ARRAY_BUFFER, 0, initialData);
162+
gl.vertexAttribPointer(0, 1, gl.FLOAT, false, 24, 0);
163+
gl.vertexAttribPointer(1, 1, gl.FLOAT, false, 24, 4);
164+
gl.vertexAttribPointer(2, 2, gl.FLOAT, false, 24, 8);
165+
gl.vertexAttribPointer(3, 2, gl.FLOAT, false, 24, 16);
166+
gl.enableVertexAttribArray(0);
167+
gl.enableVertexAttribArray(1);
168+
gl.enableVertexAttribArray(2);
169+
gl.enableVertexAttribArray(3);
170+
171+
// Initial data is no longer needed, so we can clear it now.
172+
initialData = null;
173+
174+
// Buffer2 is identical but does not need initial data
175+
const buffer2 = gl.createBuffer();
176+
const vao2 = gl.createVertexArray();
177+
gl.bindVertexArray(vao2);
178+
gl.bindBuffer(gl.ARRAY_BUFFER, buffer2);
179+
gl.bufferData(gl.ARRAY_BUFFER, 6 * COUNT * 4, gl.DYNAMIC_COPY);
180+
gl.vertexAttribPointer(0, 1, gl.FLOAT, false, 24, 0);
181+
gl.vertexAttribPointer(1, 1, gl.FLOAT, false, 24, 4);
182+
gl.vertexAttribPointer(2, 2, gl.FLOAT, false, 24, 8);
183+
gl.vertexAttribPointer(3, 2, gl.FLOAT, false, 24, 16);
184+
gl.enableVertexAttribArray(0);
185+
gl.enableVertexAttribArray(1);
186+
gl.enableVertexAttribArray(2);
187+
gl.enableVertexAttribArray(3);
188+
189+
// Clean up after yourself
190+
gl.bindVertexArray(null);
191+
gl.bindBuffer(gl.ARRAY_BUFFER, null);
192+
193+
// This code should NOT be used, since we are using a single
194+
// draw call to both UPDATE our particle system and DRAW it.
195+
// gl.enable(gl.RASTERIZER_DISCARD);
196+
197+
198+
// We have two VAOs and two buffers, but one of each is
199+
// ever active at a time. These variables will make sure
200+
// of that.
201+
let vao = vao1;
202+
let buffer = buffer1;
203+
let time = 0;
204+
205+
const uRandomLocation = gl.getUniformLocation(program, 'uRandom');
206+
207+
// When we call `gl.clear(gl.COLOR_BUFFER_BIT)` WebGL will
208+
// use this color (100% black) as the background color.
209+
gl.clearColor(0,0,0,1);
210+
211+
const draw = () => {
212+
// schedule the next draw call
213+
requestAnimationFrame(draw);
214+
215+
// It often helps to send a single (or multiple) random
216+
// numbers into the vertex shader as a uniform.
217+
gl.uniform1f(uRandomLocation, Math.random());
218+
gl.clear(gl.COLOR_BUFFER_BIT);
219+
220+
// Bind one buffer to ARRAY_BUFFER and the other to TFB
221+
gl.bindVertexArray(vao);
222+
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, buffer);
223+
224+
// Perform transform feedback and the draw call
225+
gl.beginTransformFeedback(gl.POINTS);
226+
gl.drawArrays(gl.POINTS, 0, COUNT);
227+
gl.endTransformFeedback();
228+
229+
// Clean up after ourselves to avoid errors.
230+
gl.bindVertexArray(null);
231+
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, null);
232+
233+
// If we HAD skipped the rasterizer, we would have turned it
234+
// back on here too.
235+
// gl.disable(gl.RASTERIZER_DISCARD);
236+
237+
// Swap the VAOs and buffers
238+
if (vao === vao1) {
239+
vao = vao2;
240+
buffer = buffer1;
241+
} else {
242+
vao = vao1;
243+
buffer = buffer2;
244+
}
245+
};
246+
draw();

0 commit comments

Comments
 (0)