目录

WebGLFundamentals.org

Fix, Fork, Contribute

WebGL 光栅化 vs 三维库

此文是WebGL系列文章的一个侧面话题,第一篇是基础概念

我之所以写这个是因为之前说的WebGL只是一个光栅化API不是三维API,触动了一些人的神经, 我不知道我说WebGL是光栅化API时为什么会使他们感到受到威胁或者沮丧。

理论上只是视角不同,我可能会说刀是一个进食器具,有的人可能会说刀是一个工具, 但也可能有人会说刀是武器。

对于WebGL来说,我认为说它是光栅化API是很有必要的,因为你想绘制一些三维物体时, 需要大量三维数学知识。

我认为称作时三维库的东西应该帮你完成三维部分,你应该可以给它提供一些三维数据, 一些材质参数,一些光源它就会帮你画出三维场景。WebGL (和 OpenGL ES 2.0+) 都被用来绘制三维但都不符合这个描述。

举一个例子,C++ 本身不会提供“处理文字”的功能,即使文字处理器可以用C++写出来但是并不会将 C++ 称为一个“文字处理器”。同样的WebGL本身并不能绘制出三维图形,你可以写一个库帮你用WebGL 绘制三维图形,但WebGL本身并不绘制三维图形。

提供一个例子以便进一步说明,假设我们想要绘制一个三维的立方体并且要有灯光。

这是用 three.js 实现的代码

  // 设置 WebGL.
  var c = document.querySelector("#c");
  renderer = new THREE.WebGLRenderer();
  renderer.setSize(c.clientWidth, c.clientHeight);
  c.appendChild(renderer.domElement);

  // 创建并设置一个相机
  camera = new THREE.PerspectiveCamera(
      70, c.clientWidth / c.clientHeight, 1, 1000);
  camera.position.z = 400;
  camera.updateProjectionMatrix();

  // 创建一个场景
  scene = new THREE.Scene();

  // 创建一个立方体
  var geometry = new THREE.BoxGeometry(200, 200, 200);

  // 创建一个材质
  var material = new THREE.MeshPhongMaterial({
    ambient: 0x555555,
    color: 0x555555,
    specular: 0xffffff,
    shininess: 50,
    shading: THREE.SmoothShading
  });

  // 创建一个基于材质和几何体的格网
  mesh = new THREE.Mesh(geometry, material);
  scene.add(mesh);

  // 添加两个光源
  light1 = new THREE.PointLight(0xff0040, 2, 0);
  light1.position.set(200, 100, 300);
  scene.add(light1);

  light2 = new THREE.PointLight(0x0040ff, 2, 0);
  light2.position.set(-200, 100, 300);
  scene.add(light2);

这是显示的结果。

这是使用 OpenGL (不是 ES) 实现的代码。

  // 设置
  glViewport(0, 0, width, height);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(70.0, width / height, 1, 1000);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();

  glClearColor(0.0, 0.0, 0.0, 0.0);
  glEnable(GL_DEPTH_TEST);
  glShadeModel(GL_SMOOTH);
  glEnable(GL_LIGHTING);

  // 设置两个光源
  glEnable(GL_LIGHT0);
  glEnable(GL_LIGHT1);
  float light0_position[] = {  200, 100, 300, };
  float light1_position[] = { -200, 100, 300, };
  float light0_color[] = { 1, 0, 0.25, 1, };
  float light1_color[] = { 0, 0.25, 1, 1, };
  glLightfv(GL_LIGHT0, GL_DIFFUSE, light0_color);
  glLightfv(GL_LIGHT1, GL_DIFFUSE, light1_color);
  glLightfv(GL_LIGHT0, GL_POSITION, light0_position);
  glLightfv(GL_LIGHT1, GL_POSITION, light1_position);
...

  // 绘制一个立方体
  static int count = 0;
  ++count;

  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glLoadIdentity();
  double angle = count * 0.1;
  glTranslatef(0, 0, -400);
  glRotatef(angle, 0, 1, 0);

  glBegin(GL_TRIANGLES);
  glNormal3f(0, 0, 1);
  glVertex3f(-100, -100, 100);
  glVertex3f( 100, -100, 100);
  glVertex3f(-100,  100, 100);
  glVertex3f(-100,  100, 100);
  glVertex3f( 100, -100, 100);
  glVertex3f( 100,  100, 100);

  /*
  ...
  ... 重复立方体的剩下 5 个面
  ...
  */

  glEnd();

会发现这两个例子几乎不需要三维数学知识。相比于WebGL,我不会去写WebGL实现的代码, 代码量也不是那么多,重点也不是需要更多的代码行数,是需要大量的知识。 在两个三维库中它们只关心三维,你给它们相机位置和视场角,一对光源,和一个立方体, 它们就会帮你完成其他的部分,换句话说:它们是三维库。

而WebGL就需要矩阵运算,单位化坐标,视锥,叉乘,点乘,可变量插值, 高光计算和其他需要几个月甚至几年去完全理解的东西。

一个三维库关键就是内置这些知识,你不要自己去理解,你只需要依靠库帮你处理, OpenGL就是这样的。但对于 OpenGL ES 2.0+ 或 WebGL 就不是这样的。

误将WebGL称作三维库,一个新用户用户会想“嗯,它是三维库。它可以帮我实现三维”, 然后仔细查找后发现根本不是这样。

我们甚至可以更进一步,在画布中绘制三维立方体线框。

这是使用WebGL绘制的线框。

如果你查看代码就会发现它们都需要大量的知识甚至代码去实现, 最终画布版本循环顶点,做我们提供的数学运算绘制一些二维线段。 WebGL版本除了提供的是GLSL,运行在GPU上,其它是一样的。

最后一个例子有效的说明了WebGL只是一个光栅化引擎,和 Canvas 2D 相似。 当然WebGL也有一些特性帮助你实现三维,WebGL有深度缓冲,可以让深度排序更容易。 WebGL也有多种数学方法用于三维数学运算,尽管这些本质上没有创建三维, 但它是一个数学库,可以用在一维,二维或三维上。无论如何,最终WebGL只进行光栅化。 你需要提供想要绘制内容的裁剪空间坐标,当然提供的 x,y,z,w 在渲染前会除以 w, 这也很难说明WebGL就是一个三维库。在三维库中你提供三维数据,库会计算出裁剪空间坐标。

希望这些能够让你理解,当初说WebGL时说它不是一个三维库的原因。我也希望你能知道三维库会帮你处理三维问题, OpenGL 会,Three.js 会,OpenGL ES 2.0 和 WebGL 不会。因此理论上它们并不属于“三维库”的类别。

这些的重点是让WebGL新手理解WebGL的核心是什么。知道WebGL不是一个三维库, 并且需要自己提供所有的知识,让他们知道接下来是学三维数学知识还是选择一个三维库帮助实现, 并且还揭秘了它的工作原理。

有疑问? 在stackoverflow上提问.
Issue/Bug? 在GitHub上提issue.
使用 <pre><code> 代码 </code></pre> 的格式编写代码块
comments powered by Disqus