Exophysics Field Manual

Click here to improve this text.

Introduction

Exophysics is a particle simulator in HTML + JavaScript + WebGL2 along with GLSL Shaders. It is not physically correct, and that's not the intention. On the contrary, exophysics invites you to explore the possibilities of universes that are not physically correct (according to your universe).

Note that WebGL2 requires a modern browser and a modern graphics card to run. Check here whether WebGL2 works for you. Safari tends to have problems, older phones seem to struggle, while older PCs & laptops with a recent Firefox or Chromium seem fine.

If you experience any bugs, please open an issue on Codeberg.

Overview

Let's dive into the source code. Technically, there is not "the one" source code of exophysics. Each universe is self-contained in its own HTML file, with no external dependencies. This is necessary to ensure that it will still work when you get completely lost in the labyrinth of dimensions, outside of space and time, all alone in between the manifold realms of the multiverse, with no WIFI, no access to content delivery networks, and a completely dissolved software ecosystem.

As a result, the code of exophysics will naturally diverge. There is still a basic common structure though, as can be seen in this architecture diagram:

The main 5 parts of the program are the following, which will be explained in more detail in the next chapters.

  1. The HTML scaffolding
  2. The "parameters" object, for tweaking basic settings
  3. The vertex shader code that describes the laws of physics
  4. The fragment shader code that specifies the color of each particle
  5. The JavaScript code that takes care of the uninteresting plumbing

HTML

HTML is a human-era "mark-up language" that describes websites through a sequence of magic words and symbols. E.g. if you write "<title>Exophysics</title>" in a certain section of the file, it will set the title of the page to "Exophysics". The magic rune "<script>" allows you to embed fragments in other languages like JavaScript and GLSL within the file.

The HTML scaffold can typically be ignored. It serves mainly as a container for the other components, some HTML meta information, as well as a <canvas>, into which WebGL will render the universe.

Parameters

"parameters" is a JavaScript variable that's defined in a separate block, right before the Vertex Shader, for your convenience. It defines a few basic options of Exophysics like the Particle Limit.

Vertex Shader

The vertex shader is the core of exophysics which defines the laws of physics and determines the unfolding of the universe as time progresses.

Here is a good tutorial on the fundamentals of vertex shaders.

The vertex shader is written in the programming language GLSL ("OpenGL Shading Language"), a C-like language which is compiled to something that runs directly on the graphics card. It receives a set of data as the input, and computes the position of a vertex, e.g. a corner of a triangle, or the coordinates of a point. The strength of vertex shaders is that they run in parallel. If you want to compute the positions of 2000 points, it's run on 2000 (if available) processing units of your graphics card at the same time, making it fast enough for 3D games or a simulation like exophysics.

For each particle in the exophysics universe, one vertex shader is run on one processing unit of your GPU with an individual set of input data. The vertex shader then calculates the next position (and any other state information) of the particle and then makes it available to the JavaScript part through a mechanism called "transform feedback". The javascript code then feeds back the output as input to the vertex shader when the next frame is drawn, creating an infinite loop in which the universe unfolds.

The vertex shader varies strongly between individual universes. It is therefore recommended to comment it extensively. There are stories aplenty of adventurers entering a universe, getting entirely lost in a labyrinth of code, and never finding a way back home.

Changing physical constants

One interesting part of the vertex shader is the definitions of the physical constants. This might look like:

const float floorGravity = 0.0;
const float particleGravity = 0.0;
const float flavorAttraction = -0.05;
const float numFlavors = 3.0;
const float bounceDampeningFactor = 0.99;
const float maxV = 0.01;
const float maxAccel = 0.01;
...

Changing physical laws

The vertex shader also defines the laws of physics, for example, the gravitational attraction between particles:

// Attraction of particles towards each other
if (particleGravity != 0.0) {
    vec4 particle_pos;
    vec4 center_of_mass;
    center_of_mass = vec4(0.0, 0.0, 0.0, 0.0);
    for (int i = 0; i < particleLimit; i++) {
        particle_pos = allStates[i];
        center_of_mass += particle_pos;
    }
    center_of_mass /= float(particleLimit);
    vec2 dist = center_of_mass.xy - state.xy;
    dist = (attractionRotationMatrix * dist);
    vec2 accel = dist * particleGravity / weight;
    if (length(accel) > maxAccel) {
        accel *= vec2(maxAccel / length(accel));
    }
    newV.xy += accel;
}

You can start by tweaking the physical constants first and see what happens, but don't be afraid to alter the laws of physics as well. Be creative, think outside the box, tinker, and don't be afraid to break things. Law #1 of exophysics ensures that no sentient being is harmed in the process. In this expedition, there is no right and wrong, and only if you're brave enough, you can get a glimpse of the unexplored, inifinite possibilities of exophysics.

Fragment Shader

The fragment shader is, like the vertex shader, written in GLSL. Fragment shaders are used to specify the color of a pixel. If you draw a triangle, for example, the vertex shader would be run 3 times (once per corner of the triangle), and the fragment shader would be run once for every pixel that the triangle occupies.

In the case of vanilla exophysics, the only thing that's drawn is points. For each point, the vertex shader is run once, and then the fragment shader is run once. So instead of passing a lot of data between individual shaders, the author decided to simply compute the color in the vertex shader and pass it directly to the fragment shader.

The fragment shader is therefore a stub, which only outputs what is passed as the input from the vertex shader.

See more info on fragment shaders in the WebGL2 Fundamentals Tutorial Page.

JavaScript

The JavaScript code contains functions for:

  1. initializing the WebGL2 state
  2. managing user interaction by registering event handlers for e.g. mouse clicks
  3. compiling and linking the vertex & fragment shader
  4. managing the buffers for sending and receiving variables from/to the vertex shader
  5. triggering the drawing of the WebGL image into the <canvas>

Unless you want to change any of the above (for example, pass extra information to the vertex shader), it will not be necessary to touch any JavaScript code.

Adding variables

These would be the steps to pass an extra variable from the JavaScript code to the vertex shader, if you ever need to:

  1. Add an "in" variable to the vertex shader, like "in float coolNumber;"
  2. Add the variable to "shaderInfo.attribs" in "initShaders"
  3. Add a buffer in the "initBuffers" function
    1. gl.bindBuffer(...);
    2. gl.bufferData(...);
    3. gl.vertexAttribPointer(...);
    4. gl.enableVertexAttribArray(...);
    5. Don't forget to add it to the returned object
  4. Set the data that the variable should receive in the "drawScene" function
    1. gl.bindBuffer(...);
    2. gl.bufferData(...);
  5. Now you just need to use the variable in the vertex shader. Error messages may appear if the variable is unused, due to aggressive optimization.

Adding uniforms

My advice is: DO NOT ADD UNIFORMS, since there's a uniform limit, and exophysics needs as many uniforms as possible for passing information about all particles to the shader through the "allStates" uniform. See also the section "Particle Limit".

Particle Limit

The particle limit is defined in the "parameters" object, as well as in the "particleLimit" constant of the vertex shader. As described in code comments, the number needs to be identical in both places, otherwise the universe will malfunction. The author did not find a way to have a single place for this parameter due to technical limitations.

In order for a particle to interact with all other particles, the state of each particle is passed to the vertex shader through a "uniform" variable, typically called "allStates". Unfortunately, the size of uniform variables is restricted (see MAX_VERTEX_UNIFORM_VECTORS). The limit depends on the device, with a minimum of 256 uniforms.

As a result, there will be some devices which have a maximum number of particles of 256, and others with 2048 or higher. Exophysics will automatically reduce the number of particles to match the device specifications. But since the universe works a little differently depending on the number of particles, it's generally a good idea to set a hard particle limit of 256, so the universe's properties will unfold identically on each device. Of course, if you want more, go for it.