Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WebGL 初探 #3

Open
sundway opened this issue Oct 8, 2016 · 1 comment
Open

WebGL 初探 #3

sundway opened this issue Oct 8, 2016 · 1 comment
Assignees

Comments

@sundway
Copy link
Owner

sundway commented Oct 8, 2016

目前,我们有很多方案可以快速的接触到 WebGL 并绘制复杂的图形,但最后发现我们忽视了很多细节性的东西。当然,这对初学 WebGL 是有必要的,它能迅速提起我们对 WebGL 的学习兴趣。当学习到更加深入的阶段时,我们更想了解 WebGL 的工作机制,这也将对我们编程有极大的帮助。以上也是我想写这样一个系列的原因。

简介

用更专业的描述讲,WebGL (Web Graphics Library) 是一个用以渲染交互式 3D 和 2D 图形的无需插件且兼容下一代浏览器的 JavaScript API,通过 HTML5 中 <canvas> 元素实现功能。WebGL 是由 Khronos Group 集团制定,而非 W3C 组织。目前,我们可以使用的是 WebGL 第一个版本,它继承自 OpenGL ES 2.0 。而 OpenGL ES (OpenGL for Embedded Systems) 是 OpenGL 三维图形 API 的子集,针对手机、PDA 和游戏主机等嵌入式设备而设计。以下是各版本之间的关系图:

版本关系

Hello World

首先,我们将通过实现一个简单的 WebGL 程序(清空绘图区)叩开 WebGL 的大门。下面将实现一个最简单的 WebGL 功能:

处理流程

创建 canvas 元素

WebGL 采用 HTML5 中的 <canvas> 元素。为了使用 WebGL 进行 3D 渲染,你首先需要一个 canvas 元素。这里创建了一个 canvas 元素,并使用 onload 事件创建来初始化 WebGL 上下文。

<body onload="start()">
  <canvas id="glcanvas" width="640" height="480">
    Your browser doesn't appear to support the HTML5 <code>&lt;canvas&gt;</code> element.
  </canvas>
</body>

获取 WebGL 上下文

目前,各浏览器基本都实现了对 WebGL 的支持,但 IE11 及 Edge 浏览器稍微有些不同。以下是对初始化 WebGL 的基本封装:

function initWebGL(canvas) {
  // 创建全局变量
  window.gl = null;

  try {
    // 尝试获取标准上下文,如果失败,回退到试验性上下文
    gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
  }
  catch(e) {
    throw '创建失败。';
  }

  // 如果没有GL上下文,马上放弃
  if (!gl) {
    alert("WebGL初始化失败,可能是因为您的浏览器不支持。");
    gl = null;
  }
  return gl;
}       

这里通过采用 canvas 的 getContext(contextType, contextAttributes) 方法判断浏览器是否支持 WebGL,并创建其上下文。当返回值是 canvas 的上下文时,浏览器可支持 WebGL,为 null 时,则创建失败。注意,在 IE11 及 Edge 浏览器下,需要使用 "experimental-webgl" 创建 WebGL,此处做了兼容处理。

清空绘图区

下面将背景颜色设置为黑色,并清空缓存区。

var gl; // WebGL的全局变量

function start() {
  var canvas = document.getElementById("glcanvas");

  // 初始化 WebGL 上下文
  gl = initWebGL(canvas);   

  // 只有在 WebGL 可用的时候才继续

  if (gl) {
    // 设置清除颜色为黑色,不透明
    gl.clearColor(0.0, 0.0, 0.0, 1.0);     
    // 清除颜色和深度缓存
    gl.clear(gl.COLOR_BUFFER_BIT|gl.DEPTH_BUFFER_BIT);     
  }
}

这样,我们可以在浏览器中看到一块黑色区域。你可能已经注意到,WebGL 遵循的是传统 OpenGL 颜色分量的取值范围,从 0.0 到 1.0。RGB 的值越高,颜色越亮。注意,clear() 方法在这里清除颜色和深度缓存,而不是绘制区域的 <canvas>,该方法继承自 OpenGL(基于多缓存模型)。实际还有模版缓存,但实际很少会被用到。

更进一步

上面我们完成了第一个 WebGL 程序,但是我们还未接触到 WebGL 的核心:可编程着色器。接下来,我们将使用可编程着色器在屏幕上绘制点。可编程着色器是一个较为复杂的概念,也有自己的编程语言 GLSL,后面将会又专门的文章具体讲解可编程着色器。这里我们只需要简单了解绘制的流程:

处理流程

编写着色器程序

WebGL 是无法像 OpenGL 利用固定渲染管线,代替它的是可编辑渲染管线中的 GLSL 着色语言。下面是顶点及片元着色器 GLSL 程序,用字符串表示,它将直接运行在浏览器之上。

// 顶点着色器程序
var VSHADER_SOURCE = 
  'void main() {\n' +
  '  gl_Position = vec4(0.0, 0.0, 0.0, 1.0);\n' + // 设置顶点位置
  '  gl_PointSize = 10.0;\n' +                    // 设置点的大小
  '}\n';

// 片元着色器程序
var FSHADER_SOURCE =
  'void main() {\n' +
  '  gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);\n' + // 设置点的颜色,此处为白色
  '}\n';

上面程序是不是有中似曾相识的感觉?没错,GLSL 语言和 C 语言很类似。着色器程序中包含一个主函数,且返回值为空。其中 vec4() 构造函数用于生成一个四维向量(x,y,z,w)。

编译着色器

首先,需要用 createShader( type ) 方法生成相应类型的 WebGLShader。接着,使用 shaderSource( shader, sourceCode ) 作为 GLSL 源码的钩子函数。最后使用 compileShader( shader ) 完成对着色器的编译。程序中我们做了编译后的校验,当着色器编译失败时,会报出失败并删除着色器。

function createShader (gl, type, sourceCode) {
  // 编译着色器类型:顶点着色器及片元着色器。
  var shader = gl.createShader( type );
  gl.shaderSource( shader, sourceCode );
  gl.compileShader( shader );

  if ( !gl.getShaderParameter(shader, gl.COMPILE_STATUS) ) {
    var info = gl.getShaderInfoLog( shader );
    console.log( "无法编译 WebGL 程序。 \n\n" + info);
    gl.deleteShader(shader);
    return null;
  }

  return shader;
}

连接到可用程序

此时,着色器仍是不可用的,需要将其赋值到 WebGLProgram 上。这里主要进行了三步操作,首先,需要使用 createProgram() 方法创建和初始化一个 WebGLProgram 对象。接着,使用 gl.attachShader(program, shader) 将该对象结合两个已经编译的着色器。最后,使用 linkProgram(program) 将 WebGLProgram 和着色器连接。

function createProgram(gl, vshader, fshader) {
  // 创建着色器对象
  var vertexShader = createShader(gl, gl.VERTEX_SHADER, vshader);
  var fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fshader);
  if (!vertexShader || !fragmentShader) {
    return null;
  }

  // 创建编程对象
  var program = gl.createProgram();
  if (!program) {
    return null;
  }

  // 赋值已创建的着色器对象
  gl.attachShader(program, vertexShader);
  gl.attachShader(program, fragmentShader);

  // 连接编程对象
  gl.linkProgram(program);

  // 检查链接结果
  var linked = gl.getProgramParameter(program, gl.LINK_STATUS);
  if (!linked) {
    var error = gl.getProgramInfoLog(program);
    console.log('链接程序失败:' + error);
    gl.deleteProgram(program);
    gl.deleteShader(fragmentShader);
    gl.deleteShader(vertexShader);
    return null;
  }
  return program;
}

使用可用着色器程序

这一步主要使用 useProgram(program) 方法告诉 GPU 使用程序。

function initShaders(gl, vshader, fshader) {
  var program = createProgram(gl, vshader, fshader);
  if (!program) {
    console.log('创建程序失败。');
    return false;
  }

  gl.useProgram(program);
  gl.program = program;

  return true;
}

绘制一个点

最后,使用 drawArrays(mode, first, count) 绘制一个点,该函数是一个非常强大的渲染函数,后续文章会有详细介绍。此处只需要知道传入 "POINTS" 绘制了一个点。

// 绘制一个点
gl.drawArrays(gl.POINTS, 0, 1);   

至此,我们已经完成了绘制一个点的全部程序。当运行以上程序时,我们会在浏览器中看到一个白色的点。

结束语

到现在,我们虽然还没有使用 WebGL 绘制三维图形,但我们已经进入了 WebGL 世界。我们已经使用 WebGL 绘制了简单的图形。但是这只是 WebGL 的绘制的冰山一角,我们使用 WebGL 当然不是为了绘制这样一个简单的图形。为了绘制更复杂的图形,我们还有很多的细节需要去了解。但是无论如何,我们都已经开启了 WebGL 的第一步,其实问题也并没有我们想象的那么难。

@sundway sundway self-assigned this Oct 9, 2016
@caomw
Copy link

caomw commented Oct 9, 2016

Good

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants