2

I have a 3D Webgl scene. I am using Regl http://regl.party/ . Which is WebGL. So I am essentially writing straight GLSL.

This is a game project. I have an array of 3D positions [[x,y,z] ...] which are bullets, or projectiles. I want to draw these bullets as a simple cube, sphere, or particle. No requirement on the appearance.

How can I make shaders and a draw call for this without having to create a repeated duplicate set of geometry for the bullets?

Preferring an answer with a vert and frag shader example that demonstrates the expected data input and can be reverse engineered to handle the CPU binding layer

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
kevzettler
  • 4,783
  • 15
  • 58
  • 103

4 Answers4

3

You create an regl command which encapsulates a bunch of data. You can then call it with an object.

Each uniform can take an optional function to supply its value. That function is passed a regl context as the first argument and then the object you passed as the second argument so you can call it multiple times with a different object to draw the same thing (same vertices, same shader) somewhere else.

var regl = createREGL()

const objects = [];
const numObjects = 100;
for (let i = 0; i < numObjects; ++i) {
  objects.push({
    x: rand(-1, 1),
    y: rand(-1, 1),
    speed: rand(.5, 1.5),
    direction: rand(0, Math.PI * 2),
    color: [rand(0, 1), rand(0, 1), rand(0, 1), 1],
  });
}

function rand(min, max) {
  return Math.random() * (max - min) + min;
}

const starPositions = [[0, 0, 0]];
const starElements = [];
const numPoints = 5;
for (let i = 0; i < numPoints; ++i) {
  for (let j = 0; j < 2; ++j) {
    const a = (i * 2 + j) / (numPoints * 2) * Math.PI * 2;
    const r = 0.5 + j * 0.5;
    starPositions.push([
      Math.sin(a) * r,
      Math.cos(a) * r,
      0,
    ]);
  }
  starElements.push([
    0, 1 + i * 2, 1 + i * 2 + 1,
  ]);
}

const drawStar = regl({
  frag: `
  precision mediump float;
  uniform vec4 color;
  void main () {
    gl_FragColor = color;
  }`,
  vert: `
  precision mediump float;
  attribute vec3 position;
  uniform mat4 mat;
  void main() {
    gl_Position = mat * vec4(position, 1);
  }`,
  attributes: {
    position: starPositions,
  },
  elements: starElements,
  uniforms: {
    mat: (ctx, props) => {
      const {viewportWidth, viewportHeight} = ctx;
      const {x, y} = props;
      const aspect = viewportWidth / viewportHeight;
      return [.1 / aspect, 0, 0, 0,
              0, .1, 0, 0,
              0, 0, 0, 0,
              x, y, 0, 1];
    },
    color: (ctx, props) => props.color,
  }
})

regl.frame(function () {
  regl.clear({
    color: [0, 0, 0, 1]
  });
  objects.forEach((o) => {
    o.direction += rand(-0.1, 0.1);
    o.x += Math.cos(o.direction) * o.speed * 0.01;
    o.y += Math.sin(o.direction) * o.speed * 0.01;
    o.x  = (o.x + 3) % 2 - 1;
    o.y  = (o.y + 3) % 2 - 1;
    drawStar(o);
  });
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/regl/1.3.11/regl.min.js"></script>
gman
  • 100,619
  • 31
  • 269
  • 393
2

You can draw all of the bullets as point sprites, in which case you just need to provide the position and size of each bullet and draw them as GL_POINTS. Each “point” is rasterized to a square based on the output of your vertex shader (which runs once per point). Your fragment shader is called for each fragment in that square, and can color the fragment however it wants—with a flat color, by sampling a texture, or however else you want.

Or you can provide a single model for all bullets, a separate transform for each bullet, and draw them as instanced GL_TRIANGLES or GL_TRIANGLE_STRIP or whatever. Read about instancing on the OpenGL wiki.

rob mayoff
  • 375,296
  • 67
  • 796
  • 848
  • Can you expand on the particle sprite suggestion? doesn't that require more data to make them appear more than just pixels? does it need a texture? – kevzettler Jan 12 '19 at 17:29
2

Not a WebGL coder so read with prejudice...

  1. Encode the vertexes in a texture

    beware of clamping use texture format that does not clamp to <0.0,+1.0> like GL_LUMINANCE32F_ARB or use vertexes in that range only. To check for clamping use:

  2. Render single rectangle covering whole screen

    and use the texture from #1 as input. This will ensure that a fragment shader is called for each pixel of the screen/view exactly once.

  3. Inside fragment shader read the texture and check the distance of a fragment to your vertexes

    based on it render your stuff or dicard() fragment... spheres are easy, but boxes and other shapes might be complicated to render based on the distance of vertex especially if they can be arbitrary oriented (which need additional info in the input texture).

    To ease up this you can prerender them into some texture and use the distance as texture coordinates ...

This answer of mine is using this technique:

Spektre
  • 49,595
  • 11
  • 110
  • 380
2

You can sometimes get away with using GL_POINTS with a large gl_PointSize and a customized fragment shader. An example shown here using distance to point center for fragment alpha. (You could also just as well sample a texture)

The support for large point sizes might be limited though, so check that before deciding on this route.

var canvas = document.getElementById('cvs');
gl = canvas.getContext('webgl'); 

var vertices = [
  -0.5, 0.75,0.0,
   0.0, 0.5, 0.0,
  -0.75,0.25,0.0, 
];

var vertex_buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
gl.bindBuffer(gl.ARRAY_BUFFER, null);

var vertCode =
  `attribute vec3 coord;
   void main(void) {
     gl_Position = vec4(coord, 1.0);
     gl_PointSize = 50.0;
   }`;

var vertShader = gl.createShader(gl.VERTEX_SHADER);  
gl.shaderSource(vertShader, vertCode);
gl.compileShader(vertShader);

var fragCode =
  `void main(void) {
     mediump float ds = distance(gl_PointCoord.xy, vec2(0.5,0.5))*2.0;
     mediump vec4 fg_color=vec4(0.0, 0.0, 0.0,1.0- ds);     
     gl_FragColor = fg_color;
  }`;
var fragShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragShader, fragCode);
gl.compileShader(fragShader);

var shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertShader); 
gl.attachShader(shaderProgram, fragShader);


gl.linkProgram(shaderProgram);
gl.useProgram(shaderProgram);

gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);

var coord = gl.getAttribLocation(shaderProgram, "coord");

gl.vertexAttribPointer(coord, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(coord);

gl.viewport(0,0,canvas.width,canvas.height);
gl.drawArrays(gl.POINTS, 0, 3);
<!doctype html>
<html>
   <body>
      <canvas width = "400" height = "400" id = "cvs"></canvas>
   </body>
</html>
visibleman
  • 3,175
  • 1
  • 14
  • 27