WebGL学习
1. 环境搭建
1.1 获取 Canvas 元素和 WebGL 上下文
在 WebGL 中,我们通常使用<canvas>
元素作为绘图区域,通过getContext
方法获取 WebGL 上下文对象。
// 获取canvas
const canvas = document.querySelector('canvas');
// 获取webgl上下文对象,注意选择webgl的1.0版本,2.0版本有一些语法不一样
const gl = canvas.getContext('webgl');
解释:
document.querySelector('canvas')
:通过选择器获取 HTML 文档中的<canvas>
元素。canvas.getContext('webgl')
:从<canvas>
元素中获取 WebGL 上下文对象gl
,后续的 WebGL 操作都将基于这个上下文对象进行。
1.2 初始化着色器
着色器是 WebGL 中用于处理图形渲染的程序,分为顶点着色器和片元着色器。我们需要将着色器源码编译并链接成一个可执行的程序。
import { initShader } from '../lib';
import fragShader from '../shader/xxx/fragShader.glsl';
import vertexShader from '../shader/xxx/vertexShader.glsl';
// fragShader和vertexShader引入之后是字符串,需要被编译为可执行的程序
const program = initShader(gl, vertexShader, fragShader);
initShader
函数实现:
export function initShader(gl, vshader_source, fshader_source) {
// 创建着色器
const vertexShader = createShaderFromString(
gl,
gl.VERTEX_SHADER,
vshader_source
);
const fragmentShader = createShaderFromString(
gl,
gl.FRAGMENT_SHADER,
fshader_source
);
// 创建一个程序对象 操作手,专门负责javaScript和shader着色器的通讯
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
// 将javascrpt和程序对象关联
gl.linkProgram(program);
// 使用程序对象
gl.useProgram(program);
// 返回程序对象
return program;
}
解释:
gl.createShader(type)
:创建一个指定类型(gl.VERTEX_SHADER
或gl.FRAGMENT_SHADER
)的着色器对象。gl.shaderSource(shader, source)
:将着色器源码source
赋值给着色器对象shader
。gl.compileShader(shader)
:编译着色器对象。gl.createProgram()
:创建一个 WebGL 程序对象。gl.attachShader(program, shader)
:将着色器对象附加到程序对象上。gl.linkProgram(program)
:链接程序对象,将顶点着色器和片元着色器组合成一个可执行的程序。gl.useProgram(program)
:使用指定的程序对象进行渲染。
2. 数据传递
2.1 Attribute 变量传值
Attribute 变量用于在顶点着色器中接收顶点数据,通常是每个顶点都有不同的值。
// 获取attribute变量的内存地址
const a_Position = gl.getAttribLocation(program, 'a_Position');
// 往内存地址中写入数据
const point = [0.5, 0.5];
gl.vertexAttrib2f(a_Position, ...point);
解释:
gl.getAttribLocation(program, 'a_Position')
:获取顶点着色器中a_Position
变量的内存地址。gl.vertexAttrib2f(a_Position, ...point)
:将二维坐标point
的值传递给a_Position
变量。
2.2 Uniform 变量传值
Uniform 变量用于在顶点着色器和片元着色器中传递全局常量,所有顶点都使用相同的值。
const u_ScreenSize = gl.getUniformLocation(program, 'u_ScreenSize');
gl.uniform2f(u_ScreenSize, canvas.width, canvas.height);
解释:
gl.getUniformLocation(program, 'u_ScreenSize')
:获取程序对象中u_ScreenSize
变量的内存地址。gl.uniform2f(u_ScreenSize, canvas.width, canvas.height)
:将画布的宽度和高度作为二维向量传递给u_ScreenSize
变量。
3. 缓冲区对象
缓冲区对象用于存储大量的顶点数据,提高数据传输效率。
// 1.创建顶点数据对象
const vertices = new Float32Array([
0, 0.5,
-0.5, -0.5,
0.5, -0.5,
]);
// 2.创建缓冲区对象
const buffer = gl.createBuffer();
// 3.绑定缓冲区对象的用途
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
// 4.向缓冲区中写入数据
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
// 5.设置attribute变量对缓冲区的访问规则
const a_Position = gl.getAttribLocation(program, 'a_Position');
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);
// 6.启动数据读取
gl.enableVertexAttribArray(a_Position);
解释:
gl.createBuffer()
:创建一个缓冲区对象。gl.bindBuffer(target, buffer)
:将缓冲区对象buffer
绑定到指定的目标target
(gl.ARRAY_BUFFER
用于存储顶点数据)。gl.bufferData(target, data, usage)
:将数据data
写入到绑定的缓冲区对象中,usage
指定数据的使用方式(gl.STATIC_DRAW
表示数据不会频繁更改)。gl.vertexAttribPointer(location, size, type, normalized, stride, offset)
:设置attribute
变量对缓冲区的访问规则。location
:attribute
变量的内存地址。size
:每个顶点的分量个数。type
:数据的格式。normalized
:是否需要归一化。stride
:连续顶点属性之间的间隔。offset
:顶点属性在数组中的偏移量。
gl.enableVertexAttribArray(location)
:启动对attribute
变量的数据读取。
4. 绘制操作
4.1 gl.drawArrays
gl.drawArrays
用于从缓冲区中读取顶点数据并进行绘制。
gl.drawArrays(gl.POINTS, 0, 3);
解释:
gl.drawArrays(mode, first, count)
:mode
:指定绘制的模式,如gl.POINTS
(点)、gl.LINES
(线)、gl.TRIANGLES
(三角形)等。first
:从第几个顶点开始绘制。count
:要绘制的顶点数量。
4.2 gl.drawElements
gl.drawElements
用于通过索引缓冲区来绘制图形,适用于多个顶点共享的情况。
const indices = new Uint16Array([0, 1, 2]);
const indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0);
解释:
gl.createBuffer()
:创建一个索引缓冲区对象。gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer)
:将缓冲区对象绑定到索引缓冲区目标。gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW)
:将索引数据写入到索引缓冲区中。gl.drawElements(mode, count, type, offset)
:mode
:绘制模式。count
:要绘制的索引数量。type
:索引数据的类型。offset
:索引数据在缓冲区中的偏移量。
5. 矩阵变换
在 WebGL 中,我们通常使用矩阵来实现视图和投影变换。
import { mat4 } from 'gl-matrix';
// 创建视图矩阵
const createViewMatrix = () => {
const viewMatrix = mat4.create();
// 创建视图矩阵,三个参数,相机位置,注视的坐标,上方向
mat4.lookAt(viewMatrix, [vModelView.viewX, vModelView.viewY, vModelView.viewZ],
[vModelView.lookAtX, vModelView.lookAtY, vModelView.lookAtZ], [0, 1, 0]);
gl.uniformMatrix4fv(u_ViewMatrix, false, viewMatrix);
};
// 创建投影矩阵
const createProjMatrix = () => {
const projMatrix = mat4.create();
// 创建正交投影矩阵
mat4.orthoNO(projMatrix, ...Object.values(vModelProj));
gl.uniformMatrix4fv(u_ProjMatrix, false, projMatrix);
};
解释:
mat4.create()
:创建一个 4x4 的单位矩阵。mat4.lookAt(out, eye, center, up)
:创建一个视图矩阵,eye
表示相机位置,center
表示注视的坐标,up
表示上方向。mat4.orthoNO(out, left, right, bottom, top, near, far)
:创建一个正交投影矩阵。gl.uniformMatrix4fv(location, transpose, value)
:将 4x4 矩阵value
传递给uniform
变量location
,transpose
表示是否需要转置矩阵。
6. 深度检测
深度检测用于判断物体的正确遮挡关系,确保离相机近的物体遮挡离相机远的物体。
// 深度检测,用来判断物体的正确遮挡关系
gl.enable(gl.DEPTH_TEST);
解释:
gl.enable(capability)
:启用指定的功能,gl.DEPTH_TEST
表示启用深度检测。
7. 动画循环
使用requestAnimationFrame
实现动画循环,不断更新和重绘场景。
let angleX = 0;
let angleY = 0;
const render = () => {
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
angleX += 1;
angleY += 1;
cube.rotation = [angleX, angleY, 0];
gl.drawElements(gl.TRIANGLES, cube.count, gl.UNSIGNED_SHORT, 0);
requestAnimationFrame(render);
};
render();
解释:
requestAnimationFrame(callback)
:请求浏览器在下次重绘之前调用callback
函数,实现动画循环。