【问题标题】:Drawing parametric shapes in webGL (without three.js)在 webGL 中绘制参数形状(没有三个.js)
【发布时间】:2017-03-17 20:20:51
【问题描述】:

我编写了一个程序,仅使用 HTML5 和常规 Javascript 即可绘制一些参数形状(球体、环面和圆柱体)。我正在尝试转换此代码,以便它使用 WebGL,实现带有三角形条的形状,就像在 this tutorial 中所做的那样。我的困惑点是三角形条带是如何被用来创建球体的。我之前的做法是根据 for 循环中的纬度线计算每个水平切片或圆的绘制位置并嵌套在该循环内,即计算该圆上的每个点。在生成所有这些点之后,我将它们传递给一个函数,该函数将所有顶点添加到一个数组中,该数组返回并传递给使用moveTo()lineTo() 的曲线绘图函数,以便在每个点之间绘制线条.问题是我不知道在使用三角形时在 webGL 中 moveTo()lineTo() 的等价物是什么。如何将我的实现转换为 WebGL?

这是我最初实现的一些代码:

//Calculates point on sphere
   function spherePoint(uv) {
      var u = uv[0];
      var v = uv[1];
      var phi = -Math.PI/2 + Math.PI * v;
      var theta = 2 * Math.PI * u;
      return [ Math.cos(phi) * Math.cos(theta),
               Math.cos(phi) * Math.sin(theta),
               Math.sin(phi)];
   }

// Takes the parametric function as an argument and constructs 3D shape

   function makeShape(num_u, num_v, eq, possRad) {
      var shell = [];
      for (var j = 0 ; j <= num_v ; j++) {
          var v = j / num_v;
          shell.push([]);
         for (var i = 0 ; i <= num_u ; i++) {
             var u = i / num_u;
             var p = eq([u, v], possRad);
            shell[j].push(p);
         }
      }
      return shell;
   }
// Used to create shapes to render parametric surface

   function renderShape(shape) {
       var num_j = shape.length;
       var num_i = shape[0].length;
       for (var j = 0 ; j < num_j - 1 ; j++)
           for (var i = 0 ; i < num_i - 1 ; i++) {

              plotCurve([shape[j][i],
                          shape[j + 1][i],
                          shape[j + 1][i + 1],
                          shape[j][i + 1]]);
          }
   }
 //plot curve on canvas
   function plotCurve(C) {
      g.beginPath();
      for (var i = 0 ; i < C.length ; i++)
         if (i == 0)
            moveTo(C[i]);
         else
            lineTo(C[i]);
      g.stroke();
   }

  function moveTo(p) {
      var q = m.transform(p);  // APPLY 3D MATRIX TRANFORMATION
      var xy = viewport(q);    // APPLY VIEWPORT TRANSFORM
      g.moveTo(xy[0], xy[1]);
   }

   function lineTo(p) {
      var q = m.transform(p);  // APPLY 3D MATRIX TRANFORMATION
      var xy = viewport(q);    // APPLY VIEWPORT TRANSFORM
      g.lineTo(xy[0], xy[1]);
   }

我认为 webGL 版本应该是这样的:

纯 Javascript 版本如下所示:

【问题讨论】:

    标签: javascript html graphics opengl-es webgl


    【解决方案1】:

    这是一个非常基本的 WebGL 问题。 Some more tutorials on webgl might be helpful

    WebGL 只绘制数据块。它实际上没有lineTomoveTo。相反,你给它数据缓冲区,告诉它如何从这些缓冲区中提取数据,然后编写一个函数(顶点着色器)来使用该数据告诉 WebGL 如何将其转换为剪辑空间坐标以及是否绘制点、线, 或带有结果的三角形。您还提供了一个函数(片段着色器)来告诉它为点、线或三角形使用什么颜色。

    基本上,要绘制您想要绘制的东西,您需要为该球体上的每个矩形生成 2 个三角形。换句话说,您需要为每个矩形生成 6 个顶点。原因是为了以不同的颜色绘制每个三角形,您不能共享任何顶点,因为颜色与顶点相关联。

    所以对于一个矩形,你需要生成这些点

    0--1 4
    | / /|
    |/ / |
    2 3--5
    

    其中 0、1 和 2 是粉色点,3、4、5 是绿色点。 1 和 4 具有相同的位置,但由于它们的颜色不同,它们必须是不同的点。第 2 点和第 3 点相同。

    var pink   = [1, 0.5, 0.5, 1];
    var green  = [0.5, 1, 0.5, 1];
    var positions = [];
    var colors = [];
    var across = 20;
    var down = 10;
    
    function addPoint(x, y, color) {
      var u = x / across;
      var v = y / down;
      var radius = Math.sin(v * Math.PI);
      var angle = u * Math.PI * 2;
      var nx = Math.cos(angle);
      var ny = Math.cos(v * Math.PI);
      var nz = Math.sin(angle);
      positions.push(
         nx * radius,   // x
         ny,            // y
         nz * radius);  // z
      colors.push(color[0], color[1], color[2], color[3]);
    }
    
    for (var y = 0; y < down; ++y) {
      for (var x = 0; x < across; ++x) {
        // for each rect we need 6 points
        addPoint(x    , y    , pink);
        addPoint(x + 1, y    , pink);
        addPoint(x    , y + 1, pink);
    
        addPoint(x    , y + 1, green);
        addPoint(x + 1, y    , green);
        addPoint(x + 1, y + 1, green);
      }
    }
    

    这是上面渲染的球体,但没有任何照明、透视或任何东西。

    var pink   = [1, 0.5, 0.5, 1];
    var green  = [0.5, 1, 0.5, 1];
    var positions = [];
    var colors = [];
    var across = 20;
    var down = 10;
    
    function addPoint(x, y, color) {
      var u = x / across;
      var v = y / down;
      var radius = Math.sin(v * Math.PI);
      var angle = u * Math.PI * 2;
      var nx = Math.cos(angle);
      var ny = Math.cos(v * Math.PI);
      var nz = Math.sin(angle);
      positions.push(
        nx * radius,   // x
        ny,            // y
        nz * radius);  // z
      colors.push(color[0], color[1], color[2], color[3]);
    }
    
    for (var y = 0; y < down; ++y) {
      for (var x = 0; x < across; ++x) {
        // for each rect we need 6 points
        addPoint(x    , y    , pink);
        addPoint(x + 1, y    , pink);
        addPoint(x    , y + 1, pink);
    
        addPoint(x    , y + 1, green);
        addPoint(x + 1, y    , green);
        addPoint(x + 1, y + 1, green);
      }
    }
    
    var gl = twgl.getWebGLContext(document.getElementById("c"));
    var programInfo = twgl.createProgramInfo(gl, ["vs", "fs"]);
     
    var arrays = {
      position: positions,
      color: colors,
    };
    var bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);
    
    var uniforms = {
      resolution: [gl.canvas.width, gl.canvas.height],
    };
    
    gl.useProgram(programInfo.program);
    twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
    twgl.setUniforms(programInfo, uniforms);
    twgl.drawBufferInfo(gl, bufferInfo);
    canvas { border: 1px solid black; }
    <canvas id="c"></canvas>  
    
    <script id="vs" type="not-js">
    attribute vec4 position;
    attribute vec4 color;
    
    uniform vec2 resolution;
    
    varying vec4 v_color;
    
    void main() {
      gl_Position = position * vec4(resolution.y / resolution.x, 1, 1, 1);
      v_color = color;
    }
    </script>
    <script id="fs" type="not-js">
    precision mediump float;
    
    varying vec4 v_color;
    
    void main() {
      gl_FragColor = v_color;
    }
    </script>
    <script src="https://twgljs.org/dist/3.x/twgl-full.min.js"></script>

    如果您稍后想要点亮它,您还需要 normals(您可以使用这些值来判断某物朝向哪个方向)。我们可以通过添加来添加它们

    var normals = [];
    

    addPoint内部

    function addPoint(x, y, color) {
      var u = x / across;
      var v = y / down;
      var radius = Math.sin(v * Math.PI);
      var angle = u * Math.PI * 2;
      var nx = Math.cos(angle);
      var ny = Math.cos(v * Math.PI);
      var nz = Math.sin(angle);
      positions.push(
         nx * radius,   // x
         ny,            // y
         nz * radius);  // z
      colors.push(color[0], color[1], color[2], color[3]);
      normals.push(nx, ny, nz);
    }
    

    这是一个带有被黑照明的示例

    var pink   = [1, 0.5, 0.5, 1];
    var green  = [0.5, 1, 0.5, 1];
    var positions = [];
    var colors = [];
    var normals = [];
    var across = 20;
    var down = 10;
    
    function addPoint(x, y, color) {
      var u = x / across;
      var v = y / down;
      var radius = Math.sin(v * Math.PI);
      var angle = u * Math.PI * 2;
      var nx = Math.cos(angle);
      var ny = Math.cos(v * Math.PI);
      var nz = Math.sin(angle);
      positions.push(
        nx * radius,   // x
        ny,            // y
        nz * radius);  // z
      normals.push(nx, ny, nz);
      colors.push(color[0], color[1], color[2], color[3]);
    }
    
    for (var y = 0; y < down; ++y) {
      for (var x = 0; x < across; ++x) {
        // for each rect we need 6 points
        addPoint(x    , y    , pink);
        addPoint(x + 1, y    , pink);
        addPoint(x    , y + 1, pink);
    
        addPoint(x    , y + 1, green);
        addPoint(x + 1, y    , green);
        addPoint(x + 1, y + 1, green);
      }
    }
    
    var gl = document.getElementById("c").getContext("webgl");
    var programInfo = twgl.createProgramInfo(gl, ["vs", "fs"]);
     
    var arrays = {
      position: positions,
      normal: normals,
      color: colors,
    };
    var bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);
    
    var uniforms = {
      resolution: [gl.canvas.width, gl.canvas.height],
      lightDirection: [0.5, 0.5, -1],
    };
    
    gl.useProgram(programInfo.program);
    twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
    twgl.setUniforms(programInfo, uniforms);
    twgl.drawBufferInfo(gl, bufferInfo);
    canvas { border: 1px solid black; }
    <canvas id="c"></canvas>  
    
    <script id="vs" type="not-js">
    attribute vec4 position;
    attribute vec4 color;
    attribute vec3 normal;
    
    uniform vec2 resolution;
    
    varying vec4 v_color;
    varying vec3 v_normal;
    
    void main() {
      gl_Position = position * vec4(resolution.y / resolution.x, 1, 1, 1);
      v_color = color;
      v_normal = normal;
    }
    </script>
    <script id="fs" type="not-js">
    precision mediump float;
    
    varying vec4 v_color;
    varying vec3 v_normal;
    
    uniform vec3 lightDirection;
    
    void main() {
      float light = pow(abs(dot(v_normal, normalize(lightDirection))), 2.0);
      gl_FragColor = vec4(v_color.xyz * light, v_color.a);
    }
    </script>
    <script src="https://twgljs.org/dist/3.x/twgl-full.min.js"></script>

    PS:您发布的picture 实际上是在每个矩形中绘制更多三角形。绿色和粉红色之间的区别并不直截了当。

    【讨论】:

    • 谢谢!这是对这个问题的一个非常全面和全面的答案!只是为了跟进,如果我使用gl.TRIANGLE_STRIPS 而不是gl.TRIANGLES 来制作正方形,提供的代码是否仍然正确?顺便说一句,感谢所有代码,照明的东西很快就会派上用场。
    • 您无法使用TRIANGLE_STRIP 获得相同的结果,因为条带共享顶点,因此您无法获得粉色和绿色三角形,因为某些三角形必须同时共享粉色和绿色顶点。仅供参考:没有游戏(即没有专业的图形程序员)使用条带
    • 好吧,这是有道理的。我实际上正在用圆环和圆柱体(没有不同的颜色)做类似的事情,但我无法正确渲染它们。看来您对这个主题有很多经验,所以也许您可以help here,再次感谢。
    【解决方案2】:

    gl.LINES 很好,几乎可以绘制连接线。所以 lineTo(x,y,z) 只是向用于存储线数据的 VBO 添加一个顶点。 moveTo(x,y,z) 只是在行之间创建一个“中断”。这可以通过一个新的 drawArrays 调用来完成。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-12-19
      • 2013-11-28
      • 1970-01-01
      • 2023-03-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多