In this MP you will
This MP is elective, with no core components. It assumes you have already completed the Spheres MP.
You will submit a webpage that has
Submit an HTML file and any number of js, css, and glsl files. No json or image files are permitted for this assignment. Do not include spaces in file names as the submission server does not process them well.
You are welcome to use a JavaScript math library, such as the one used in in-class examples or others you might know.
Add the following to your HTML after the canvas element but still inside the body.
<div id="fps" style="position:fixed; bottom:1ex; right:1ex; display:table;"></div>
<form class="controls" action="javascript:void(0);" style="position:fixed; top:1ex; left:1ex; display:table;">
<label>Spheres: <input id="spheres" type="number" value="50" style="width:5em;"/></label>
<input id="submit" type="submit" value="Restart simulation"/>
</form>
Every frame, set document.querySelector('#fps').innerHTML = fps.toFixed(1)
,
where fps
is the current frame rate (i.e., 1/dt
if dt
is the number of seconds since the last frame).
Listen for clicks of the submit button and restart the simulation with the requested number of spheres. Also restart the simulation every 15 seconds, as you did for the Spheres MP.
Let the user specify the number of spheres to use in the animation.
Randomly allocate sphere sizes so that the largest has 5× the radius of the smallest and the full set of spheres when at rest will fill the bottom of the cube several spheres deep. One way to do this is
= (Math.random()+.25) * (0.75/n**(1/3)) radius
where n
is the number of spheres.
Have sphere mass be proportional to volume, which is proportional to the cube of radius. Thus, when a large and small sphere collide the small one should usually do most of the moving out of the way.
One major source of inefficiency in the Spheres MP is the number of times the CPU speaks to the GPU. In my reference implementation, that was 3n+5 calls per frame. Each such call requires the GPU to wait for the last one to complete, meaning most of the GPU’s possible parallelism is ineffective.
To fix this, change how spheres are drawn.
Remove the sphere geometry and gl.drawElements
and
instead use gl.drawArrays
with mode
gl.POINTS
.
Have three attribute arrays: one of sphere centers, one of sphere radii, and one of sphere colors.
Each frame, re-send the sphere centers buffer using
gl.DYNAMIC_DRAW
instead of
gl.STATIC_DRAW
.
In the vertex shader, compute the on-screen radius and store it
in gl_PointSize
uniform float
;
you can get it as gl.canvas.width
or gl.canvas.height
.proj[0][0]
for x or proj[1][1]
for y, where proj
is your
projection matrix.1/gl_Position.w
.In the fragment shader, render the square point as a lit sphere
using gl_PointCoord
gl_PointCoord
is a vec2
ranging from (0,0) in the top-left corner to (1,1) in the bottom rightif (nxylength > 1) discard;
).
Note: if
s that have discard;
as their sole
statement are handled efficiently by the GPU and are an exception to our
course’s usual prohibition on warp-breaking if
s.If done correctly, this should result in a fixed number of GPU invocations per frame (10 in my reference implementation) with much better runtime efficiency. It should also create perfectly-circular, perfectly-smooth spheres with no polygonal approximation.
Another major source of inefficiency is collision detection. Most implementations of the Spheres MP detects collisions by checking every pair of spheres every frame, for a O(n^2) runtime. We need to do much better to scale up to thousands of spheres.
Because our spheres are relatively similar in size and spread out so as not to overlap, we achieve O(n) instead using the following method:
There are other tricks that can make this even faster, but it provides a significant asymptotic speedup as-is.
If done correctly, doubling the number of spheres should halve the frames per second.
On both your development machine and when submitted to the submission server and then viewed by clicking the HTML link, the resulting animation should show perfectly circular spheres moving within an invisible cube and colliding with one another. Frames per second should scale linearly with number of spheres and be able to render 1000 spheres at reasonable speed. One example motion might be the following: