Drawing many shapes in WebGL

2020-01-26 09:56发布

问题:

I was reading tutorials from here.

<script class = "WebGL">
var gl;
function initGL() {
  // Get A WebGL context
  var canvas = document.getElementById("canvas");
  gl = getWebGLContext(canvas);
  if (!gl) {
    return;
  }
}
var positionLocation;
var resolutionLocation;
var colorLocation;
var translationLocation;
var rotationLocation;
var translation = [50,50];
var rotation = [0, 1];
var angle = 0;
function initShaders() {
  // setup GLSL program
  vertexShader = createShaderFromScriptElement(gl, "2d-vertex-shader");
  fragmentShader = createShaderFromScriptElement(gl, "2d-fragment-shader");
  program = createProgram(gl, [vertexShader, fragmentShader]);
  gl.useProgram(program);

  // look up where the vertex data needs to go.
  positionLocation = gl.getAttribLocation(program, "a_position");

  // lookup uniforms
  resolutionLocation = gl.getUniformLocation(program, "u_resolution");
  colorLocation = gl.getUniformLocation(program, "u_color");
  translationLocation = gl.getUniformLocation(program, "u_translation");
    rotationLocation = gl.getUniformLocation(program, "u_rotation");

  // set the resolution
  gl.uniform2f(resolutionLocation, canvas.width, canvas.height);
}
function initBuffers() {
  // Create a buffer.
  var buffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
  gl.enableVertexAttribArray(positionLocation);
  gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);

  // Set Geometry.
  setGeometry(gl);
}

function setColor(red, green, blue) {
    gl.uniform4f(colorLocation, red, green, blue, 1);
}
// Draw the scene.
function drawScene() {
    // Clear the canvas.
    gl.clear(gl.COLOR_BUFFER_BIT);

    // Set the translation.
    gl.uniform2fv(translationLocation, translation);
    // Set the rotation.
    gl.uniform2fv(rotationLocation, rotation);

    // Draw the geometry.
    gl.drawArrays(gl.TRIANGLES, 0, 6);
}


// Fill the buffer with the values that define a letter 'F'.
function setGeometry(gl) {
/*Assume size1 is declared*/
    var vertices = [
         -size1/2, -size1/2,
         -size1/2, size1/2,
         size1/2, size1/2,
         size1/2, size1/2,
         size1/2, -size1/2,
         -size1/2, -size1/2 ];
      gl.bufferData(
         gl.ARRAY_BUFFER,
         new Float32Array(vertices),
         gl.STATIC_DRAW);
}
function animate() {
    translation[0] += 0.01;
    translation[1] += 0.01;
    angle += 0.01;
    rotation[0] = Math.cos(angle);
    rotation[1] = Math.sin(angle);
}
function tick() {
    requestAnimFrame(tick);
    drawScene();
    animate();
}
function start() {

    initGL();
    initShaders();
    initBuffers();
    setColor(0.2, 0.5, 0.5);
    tick();
}

</script>

<!-- vertex shader -->
<script id="2d-vertex-shader" type="x-shader/x-vertex">
    attribute vec2 a_position;

    uniform vec2 u_resolution;
    uniform vec2 u_translation;
    uniform vec2 u_rotation;
    void main() {
        vec2 rotatedPosition = vec2(
        a_position.x * u_rotation.y + a_position.y * u_rotation.x,
        a_position.y * u_rotation.y - a_position.x * u_rotation.x);

       // Add in the translation.
       vec2 position = rotatedPosition + u_translation;

       // convert the position from pixels to 0.0 to 1.0
       vec2 zeroToOne = position / u_resolution;

       // convert from 0->1 to 0->2
       vec2 zeroToTwo = zeroToOne * 2.0;

       // convert from 0->2 to -1->+1 (clipspace)
       vec2 clipSpace = zeroToTwo - 1.0;

       gl_Position = vec4(clipSpace, 0, 1);
    }
</script>
<!-- fragment shader -->
<script id="2d-fragment-shader" type="x-shader/x-fragment">
    precision mediump float;

    uniform vec4 u_color;

    void main() {
       gl_FragColor = u_color;
    }
</script>

My WebGL program for 1 shape works something like this:

  1. Get a context (gl) from the canvas element.
  2. initialize buffers with the shape of my object
  3. drawScene() : a call to gl.drawArrays()
  4. If there is animation, an update function, which updates my shape's angles, positions, and then drawScene() both in tick(), so that it gets called repeatedly.

Now when I need more than 1 shape, should I fill the single buffer at once with many objects and then use it to later call drawScene() drawing all the objects at once [OR] should I repeated call the initBuffer and drawScene() from requestAnimFrame().

回答1:

In pseudo code

At init time

  • Get a context (gl) from the canvas element.
  • for each shader
    • create shader
    • look up attribute and uniform locations
  • for each shape
    • initialize buffers with the shape
  • for each texture
    • create textures and/or fill them with data.

At draw time

  • for each shape
    • if the last shader used is different than the shader needed for this shape call gl.useProgram
    • For each attribute needed by shader
      • call gl.enableVertexAttribArray, gl.bindBuffer and gl.vertexAttribPointer for each attribute needed by shape with the attribute locations for the current shader.
    • For each uniform needed by shader
      • call gl.uniformXXX with the desired values using the locations for the current shader
    • call gl.drawArrays or if the data is indexed called gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, bufferOfIndicesForCurrentShape) followed by gl.drawElements

Common Optimizations

1) Often you don't need to set every uniform. For example if you are drawing 10 shapes with the same shader and that shader takes a viewMatrix or cameraMatrix it's likely that viewMatrix uniform or cameraMatrix uniform is the same for every shape so just set it once.

2) You can often move the calls to gl.enableVertexAttribArray to initialization time.



回答2:

Having multiple meshes in one buffer (and rendering them with a single gl.drawArrays() as you're suggesting) yields better performance in complex scenes but obviously at that point you're not able to change shader uniforms (such as transformations) per mesh.

If you want to have the meshes running around independently, you'll have to render each one separately. You could still keep all the meshes in one buffer to avoid some overhead from gl.bindBuffer() calls but imho that won't help that much, at least not in simple scenes.



回答3:

Create your buffers separately for each object you want on the scene otherwise they won't be able to move and use shader effects independently.

But that is in case your objects are different. From what I got here I think you just want to draw the same shape more than once on different positions right?

The way you go about that is you just set that translationLocation uniform right there with a different translation matrix after drawing the shape for the first time. That way when you draw the shape again it will be located somewhere else and not in top of the other one so you can see it. You can set all those transformation matrices differently and then just call gl.drawElements again since you're going to draw the same buffers that are already in use.



标签: webgl