# 你好世界
createShader
shaderSource
compileShader
getShaderParameter
getShaderInfoLog
deleteShader
createProgram
attachShader
linkProgram
getProgramParameter
getShaderInfoLog
deleteProgram
getAttribLocation
createBuffer
bindBuffer
bufferData
viewport
clearColor
clear
useProgram
enableVertexAttribArray
vertexAttribPointer
drawArrays
GLSL
attribute
vec4
gl_Position
precision
highp
mediump
lowp
gl_FragColor
# 编写着色器
让我们从顶点着色器开始
Demo1VertexShaderSource.glsl
// 一个属性值,将会从缓冲中获取数据
attribute vec4 a_position;
// 所有着色器都有一个main方法
void main() {
// gl_Position 是一个顶点着色器主要设置的变量
gl_Position = a_position;
}
接下来我们需要一个片断着色器
Demo1FragmentShaderSource.glsl
// 片断着色器没有默认精度,所以我们需要设置一个精度
// mediump 是一个不错的默认值,代表“medium precision”(中等精度)
precision mediump float;
void main() {
// gl_FragColor是一个片断着色器主要设置的变量
gl_FragColor = vec4(1, 0, 0.5, 1); // 返回“红紫色”
}
上方我们设置 gl_FragColor 为 1, 0, 0.5, 1,其中 1 代表红色值,0 代表绿色值, 0.5 代表蓝色值,最后一个 1 表示阿尔法通道值。 WebGL 中的颜色值范围从 0 到 1 。
# 渲染流程
# 获取上下文
首先创建一个 WebGL 渲染上下文
<canvas ref="canvas" id="webgl" width="320" height="240"></canvas>
这里我使用了 Vue 的 ref 来获取对 canvas 标签的引用,你也可以使用 document 的元素获取方法
const canvasEl = this.$refs.canvas;
const gl = this.canvasEl.getContext("webgl");
# 编译着色器
现在我们需要编译着色器对然后提交到 GPU,这里着色器源码的引入我使用了webpack
的raw-loader
你也可以使用 DOM 对象的 text 属性来获取 script 标签中的 GLSL 源码
import vertexShaderSource from "./Demo1VertexShaderSource.glsl";
import fragmentShaderSource from "./Demo1FragShaderSource.glsl";
// 创建着色器方法,输入参数:渲染上下文,着色器类型,数据源
function createShaderFromSource(gl, type, source) {
var shader = gl.createShader(type); // 创建着色器对象
gl.shaderSource(shader, source); // 提供数据源
gl.compileShader(shader); // 编译 -> 生成着色器
var success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
if (success) {
return shader;
}
console.log(gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
}
现在我们可以创建真正的着色器方法了
const vertexShader = createShaderFromSource(gl, gl.VERTEX_SHADER, vertexShaderSource);
const fragmentShader = createShaderFromSource(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
然后我们将这两个着色器 link(链接)到一个 program(着色程序)
function createProgram(gl, vertexShader, fragmentShader) {
var program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
var success = gl.getProgramParameter(program, gl.LINK_STATUS);
if (success) {
return program;
}
console.log(gl.getProgramInfoLog(program));
gl.deleteProgram(program);
}
然后调用它
const program = createProgram(gl, vertexShader, fragmentShader);
# 获取绑定点和创建缓冲
现在我们已经在 GPU 上创建了一个 GLSL 着色程序,我们还需要给它提供数据。 WebGL 的主要任务就是设置好状态并为 GLSL 着色程序提供数据。
在这个例子中 GLSL 着色程序的唯一输入是一个属性值 a_position。 我们要做的第一件事就是从刚才创建的 GLSL 着色程序中找到这个属性值所在的位置。
找到哪个我们在 GLSL 中定义的属性a_position
const positionAttributeLocation = gl.getAttribLocation(program, "a_position");
GLSL 的属性值从 JS 缓冲中获取数据,所以我们创建一个缓冲
const positionBuffer = gl.createBuffer();
绑定点
WebGL 可以通过绑定点
操控全局范围内的许多数据,你可以把绑定点想象成一个 WebGL 内部的全局变量
。
现在让我们来绑定位置信息缓冲,就绑定到gl.ARRAY_BUFFER
吧。
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
然后们需要通过绑定点向缓冲中存放数据
// 通过绑定点向缓冲中存放数据
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array([-0.5, -0.25, 0, 0.5, 0.5, -0.25]), // 一个等腰三角形
gl.STATIC_DRAW
);
这里完成了一系列事情,第一件事是我们有了一个 JavaScript 序列 positions。 然而 WebGL 需要强类型数据,所以 new Float32Array(positions)创建了 32 位浮点型数据序列, 并从 positions 中复制数据到序列中,然后 gl.bufferData 复制这些数据到 GPU 的 positionBuffer 对象上。
它最终传递到 positionBuffer 上是因为在前一步中我们我们将它绑定到了 ARRAY_BUFFER(也就是绑定点)上。
最后一个参数gl.STATIC_DRAW
是提示 WebGL 我们将怎么使用这些数据。
WebGL 会根据提示做出一些优化。 gl.STATIC_DRAW 提示 WebGL 我们不会经常改变这些数据。
# 渲染前的准备
我们需要告诉 WebGL 怎样把提供的 gl_Position 裁剪空间坐标对应到画布像素坐标, 通常我们也把画布像素坐标叫做屏幕空间。 为了实现这个目的,我们只需要调用 gl.viewport 方法并传递画布的当前尺寸。
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
我们用 0, 0, 0, 0 清空画布
// 清空画布
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
我们需要告诉 WebGL 运行哪个着色程序
// 告诉它用我们之前写好的着色程序(一个着色器对)
gl.useProgram(program);
接下来我们需要告诉 WebGL 怎么从我们之前准备的缓冲中获取数据给着色器中的属性。 首先我们需要启用对应属性
gl.enableVertexAttribArray(positionAttributeLocation);
# 指定读取数据的方式
然后指定从缓冲中读取数据的方式
// 将绑定点绑定到缓冲数据(positionBuffer)
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// 告诉属性怎么从positionBuffer中读取数据 (ARRAY_BUFFER)
var size = 2; // 每次迭代运行提取两个单位数据
var type = gl.FLOAT; // 每个单位的数据类型是32位浮点型
var normalize = false; // 不需要归一化数据
var stride = 0; // 0 = 移动单位数量 * 每个单位占用内存(sizeof(type))
// 每次迭代运行运动多少内存到下一个数据开始点
var offset = 0; // 从缓冲起始位置开始读取
gl.vertexAttribPointer(positionAttributeLocation, size, type, normalize, stride, offset);
# 调用绘制方法
现在我们终于可以让 WebGL 运行我们的 GLSL 着色程序了。
var primitiveType = gl.TRIANGLES;
var offset = 0;
var count = 3;
gl.drawArrays(primitiveType, offset, count);
# 渲染结果
在上例中可以发现顶点着色器只是简单的传递了位置信息。 由于位置数据坐标就是裁剪空间中的坐标,所以顶点着色器没有做什么特别的事。
如果你想做三维渲染,你需要提供合适的着色器将三维坐标转换到裁剪空间坐标,因为WebGL只是一个光栅化API。