在 webGL 中绘制参数化形状(没有 three.js)

Drawing parametric shapes in webGL (without three.js)

我编写了一个程序,仅使用 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 版本如下所示:

有 gl.LINES 好吧,差不多画出了连接线。所以 lineTo(x,y,z) 只会向用于存储线数据的 VBO 添加一个顶点。 moveTo(x,y,z) 只是在两行之间创建一个 "break" 。这可以通过新的 drawArrays 调用来完成。

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

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

基本上,要绘制您想要绘制的东西,您需要为该球体上的每个矩形生成 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>

如果您稍后想要点亮它,您还需要 法线(可以用来判断物体朝向哪个方向的值)。我们可以通过添加

来添加它们
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 实际上是在每个矩形上绘制更多的三角形。绿色和粉红色之间的区别不是直线的。