WebGL 手把手入门指南(一)

avatar

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第1天,点击查看活动详情

一、什么是WebGL

WebGL 是一个 JavaScript API,可以基于 OpenGL ES 2.0 的 API 在 canvas 中进行 2D 和 3D 渲染。THREE.jsBABYLON.js等很多框架封装了 WebGL,提供了各个平台之间的兼容性。使用这些框架而非原生的 WebGL 可以更容易地开发 3D 应用和游戏。了解了WebGL后能更深刻的理解这些框架的实现。虽然说我们开发中用不上这么底层的API,但是了解了WebGL后能更深刻的理解这些框架的实现。

WebGL 程序包括用JavaScript 写的控制代码,和在图形处理单元(GPU)中执行的着色代码(GLSL)

二、最简的WebGL程序

WebGL在浏览器上是通过Canvas画布渲染所以我们需要一个Canvas

通过getWebGLContext获得WebGL的绘图上下文。其实是我们自定义的方法,因为WebGL获取上下文的方式有些许繁琐所以我们直接引入一个工具库来帮我获取WebGL的上下文。

通过clearColor设置清空颜色,其中颜色的设置是rgba但是值是从0.0到1.0。

通过clear执行清空颜色缓冲区。除了颜色缓冲区还能清空:

  • gl.DEPTH_BUFFER_BIT 深度缓冲区
  • gl.STENCIL_BUFFER_BIT 模板缓冲区

code 后续都可以在这个基础上改js里的代码

<canvas id="mycanvas" height="500" width="500"></canvas>
const canvas = document.getElementById('mycanvas')
// 获取上下文
const gl = getWebGLContext(canvas)
// 设置清空的颜色
gl.clearColor(0, 0, 0, .1)
// 清空
gl.clear(gl.COLOR_BUFFER_BIT)

这就是最简的的WebGL程序,可能你会说为什么没有着色器呀,别急我们下面来带你在WebGL中绘制一个点。

三、绘制一个点

3.1 使用着色器绘制一个点

想要使用WebGL作图就必须要使用到着色器(shader)。着色器包括:

  • 顶点着色器(Vertex shader)用来描述顶点的特征(位置、颜色、大小等),顶点指的就是空间中的某一个点。
  • 片元着色器(Fragment shader)处理每一个片元,片元可以理解成像素点。

比如说我们想画一个三角形首先要知道三角形的三个点在哪,这就需要我们通过顶点着色器来描述点的位置,然后如果要把这个三角形画出来就需要填充颜色,三角形上每个像素点需要什么颜色就可以使用片元着色器来决定。

我们先从画一个点开始code

// 顶点着色器
const VSHADER_SOURCE = `
  void main() {
  	// 点的位置
  	gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
  	// 点的大小
	  gl_PointSize = 10.0;
	}
 `;
// 片元着色器
const FSHADER_SOURCE = `
  void main() {
  	// 设置颜色
  	gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
	}
`;
// 初始化着色器
initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)
// 绘制一个点
gl.drawArrays(gl.POINTS, 0, 1)

着色器用的是glsl来编写,glsl是以c语言为基础的强类型语言,在js中储存为字符串然后通过辅助函数initShaders就可以进行读取。

其中顶点着色器gl_Positiongl_PointSize内置变量分别表示顶点的位置和尺寸。因为是强类型语言赋值类型必须相同,gl_Position类型是vec4,gl_PointSize类型是float,vec4是glsl特有的类型,由四个浮点数组成的矢量,相同意思的类型还有vec2,vec3。位置使用了四个分量是因为顶点位置是齐次坐标,只要知道第四个分量是1.0就可以了,其他分别表示x,y,z三个分量。

片元着色器最后的颜色输出需要把颜色值赋值给gl_FragColor,同样也是vec4类型,分别对应RGBA的四个分量。

创建着色器后需要使用gl.drawArrays方法进行绘制,第一个参数是绘制方式,第二个参数是从哪个顶点开始绘制,第三个参数是绘制多少个顶点,具体意思在后续会说明。

WebGL使用的是右手坐标系,最后可以在画布中心看到我们绘制的一个点。

你可以会认为片元着色器是不是像油漆桶工具一样直接把我们画的点绘制成红色,为了证明片元着色器是逐像素执行每一个点的颜色,我们可以看看下一节。

3.2 一个点里的shader世界

const VSHADER_SOURCE = `
  void main() {
  	gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
  	gl_PointSize = 90.0;
	}
 `;
const FSHADER_SOURCE = `
  // 精度限定词
  precision mediump float;
  void main() {
    // 当前片元坐标
    vec2 uv = gl_FragCoord.xy;
    float color = 0.0;
    if (uv.x > 50.0)
      color = 1.0;
  	gl_FragColor = vec4(color, color, color, 1.0);
	}
`;

我们先看看上面这段shader,顶点着色器只是把点的大小变大了,方便我们观察。

片元着色器首先我们定义了float类型的精度,因为webgl中只有float类型没有默认的精度所以使用float类型必须需要手动指定。

gl_FragCoord是片元着色器的内置变量,gl_FragCoord的x和y元素是当前片段的窗口空间坐标。它们的起始处是窗口的左下角。通过[变量].xy就可以获得变量的前两个分量。然后下面的代码意思是当前片元x大于50就会设置color为1.0也就是白色,否则就是0.0黑色。

const FSHADER_SOURCE = `
  precision mediump float;
  void main() {
    vec2 uv = gl_FragCoord.xy / vec2(100.0);
    // step:第二个参数大于第一个参数返回1 否自返回0
    // length:返回参数到左下角的距离
    vec3 color = vec3(step(0.3, length(uv - 0.5)));
    gl_FragColor = vec4(color, 1.0);
	}
`;

通过一些内置的计算方法和聪明的头脑就可以用shader玩出各种花样具体可以看看shader高级应用,我们这里主要介绍webgl的使用所以shader内容只是点到为止。

3.3 从js传入点的信息

回到正题,一般我们的数据都是通过js拿到的,所以js和shader是怎么实现交互的呢。

在顶点着色器中有两种变量可以从js中获得数据:

attribute变量:传输与顶点相关的数据,每个顶点获得的数据不一样。只能在顶点着色器使用。

uniform变量:传输与顶点无关的数据,每个顶点获得相同的数据。

const VSHADER_SOURCE = `
  // 声明变量
  attribute vec4 a_Position;
  attribute float a_PointSize;
  void main() {
  	gl_Position = a_Position;
  	gl_PointSize = a_PointSize;
	}
 
 `;
const FSHADER_SOURCE = `
  precision mediump float;
  // 声明变量
  uniform vec4 u_FragColor;
  void main() {
    gl_FragColor = u_FragColor;
	}
`;

// 获得变量的储存位置
const a_Position = gl.getAttribLocation(gl.program, 'a_Position');
const a_PointSize = gl.getAttribLocation(gl.program, 'a_PointSize');
const u_FragColor = gl.getUniformLocation(gl.program, 'u_FragColor');

// 传值
gl.vertexAttrib2f(a_Position, 0.5, 0.5);
gl.vertexAttrib1f(a_PointSize, 10.0);
gl.uniform4f(u_FragColor, 1.0, 0.0, 0.0, 1.0);

通过getAttribLocationgetUniformLocation就可以获得对应变量的储存地址,获得地址后通过vertexAttrib2fvertexAttrib1funiform4f向变量赋值。

你可能注意到了方法后面的1f、2f、4f,vertexAttrib[1234]f分别为你想要传入的浮点数的数量,然后我们为什么只给a_Position传入了两个浮点数呢,如果定义的是vec4然后只传了两个浮点数的话webgl会自动补充没有传的位数,并不会报错。

下一章会给大家带来如何绘制多个点,敬请期待!