在WebGL中绘制多个形状

5
我正在阅读这里的教程。
<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>

我的 WebGL 程序用于绘制一个形状,大致如下:

  1. 从画布元素中获取上下文 (gl)。
  2. 使用对象的形状初始化缓冲区
  3. drawScene():调用 gl.drawArrays()
  4. 如果有动画,则更新函数会更新我的形状的角度、位置,然后在 tick() 中同时调用 drawScene(),以便重复调用。

现在当我需要多个形状时,我应该一次性填充单个缓冲区,并使用它来稍后调用 drawScene() 以一次性绘制所有对象[OR],还是应该从 requestAnimFrame() 重复调用 initBufferdrawScene()

3个回答

12

伪代码:

在初始化时

  • 从画布元素获取上下文 (gl)。
  • 对于每个着色器
    • 创建着色器
    • 查找属性和统一位置
  • 对于每个形状
    • 使用该形状初始化缓冲区
  • 对于每个纹理
    • 创建纹理并/或用数据填充它们。

在绘制时

  • 对于每个形状
    • 如果上一个使用的着色器与当前形状所需的着色器不同,调用 gl.useProgram
    • 对于每个着色器所需的属性
      • 对于每个形状所需的属性,使用当前着色器的属性位置调用 gl.enableVertexAttribArray, gl.bindBuffergl.vertexAttribPointer
    • 对于每个着色器所需的统一变量
      • 使用当前着色器的位置调用 gl.uniformXXX 并使用期望的值
    • 调用 gl.drawArrays 或者如果数据是索引的,则调用 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, bufferOfIndicesForCurrentShape),然后调用 gl.drawElements

常见优化

1) 通常不需要设置每个统一变量。例如,如果你使用相同的着色器绘制10个形状,并且该着色器需要一个 viewMatrix 或 cameraMatrix ,则这些统一变量在每个形状中可能都是相同的,因此只需设置一次即可。

2) 通常可以将对 gl.enableVertexAttribArray 的调用移动到初始化时。


3
在一个缓冲区中拥有多个网格(并像您建议的那样使用单个gl.drawArrays()进行渲染)在复杂场景中可以获得更好的性能,但很明显在这一点上您无法更改着色器统一变量(例如变换)。
如果您想要网格独立运行,您将需要分别渲染每一个网格。您仍然可以将所有网格保留在一个缓冲区中以避免一些gl.bindBuffer()调用的开销,但在简单场景中,我认为这并不能帮助太多。

对于你对Delta的回答的问题:你不必一遍又一遍地填充缓冲区。你只需要为每个形状初始化一个缓冲区(gl.createBuffer -> gl.bindBuffer -> gl.bufferData),然后在循环中,你只需要在它们之间切换即可。 - Petr Broz
1
“switch” 是指每次绘制时调用 bindBufferenableVertexAttribArrayvertexAttribPointer,对吧?这样该形状对象的缓冲区就会被置于顶部。对吧? - batman
1
是的,你可以这样做。或者,如果你想要为所有形状使用相同的着色器,只需使用 bindBuffervertexAttribPointer 方法即可。enableVertexAttribArray 仅用于“配置”当前着色器程序。 - Petr Broz
1
FYI,enableVertexAttribArray 配置属性并且独立于着色器程序。换句话说,它配置全局状态而不是当前的着色器程序。 - gman

1

为每个想要在场景中使用的对象单独创建缓冲区,否则它们将无法独立移动和使用着色器效果。

但是,如果您的对象不同,则需要这样做。从我这里得到的信息是,您只想在不同的位置上绘制相同的形状,对吗?

您可以通过在第一次绘制形状后使用不同的平移矩阵设置该translationLocation统一变量来实现这一点。这样,当您再次绘制形状时,它将位于其他位置而不是顶部,因此您可以看到它。您可以将所有这些转换矩阵设置为不同,并再次调用gl.drawElements,因为您将绘制已经在使用中的相同缓冲区。


不,我想在不同的位置绘制许多不同的形状。 - batman
1
那么,如果是这种情况,我应该为每个对象[填充缓冲区,gl.drawArray,填充缓冲区,gl.drawArray,填充缓冲区,gl.drawArray...]吗? - batman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接