WebGL
webgl
是一种3d
绘图协议,衍生于OpenGL ES2.0
,可以结合html
、js
在网页上渲染二维三维图像
用途
- 数据可视化
- 图形、游戏引擎
- 地图
VR
- 物品展示
- 室内设计
- 城市规划
优势
- 可在浏览器中运行
- 适合前端开发者
Canvas
画布
使用<canvas></canvas>
元素绘制二维和三维的图像
二维图像主要通过 CanvasRenderingContext2D
接口完成:canvas.getContext('2d')
三维图像主要通过 CanvasRenderingContext
接口完成 canvas.getContext('webgl')
,可以使用canvas.getContext('webgl2')
获取2.0
标准的api
第一个webgl
应用,为三维画布重置画布颜色:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>webgl</title>
</head>
<body>
<canvas></canvas>
<script>
const canvas = document.querySelector('canvas')
const gl = canvas.getContext('webgl')
// 设置清空颜色缓存时的颜色值
gl.clearColor(1, 0, 0, 0.5)
// 使用预设值清空缓冲区
gl.clear(gl.COLOR_BUFFER_BIT)
</script>
</body>
</html>
着色器(渲染管线)
开发者自行编写的程序,用来代替固定渲染管线,处理图像的渲染。
它以字符串的形式存在在js
中,被js
读取后传递给webgl
- 顶点着色器:位置
- 片元着色器:像素
创建一个着色器项目初始化代码:
export default function initProgram(
gl,
VERTEX_SHADER_SOURCE,
FRAGMENT_SHADER_SOURCE
) {
// 创建着色器
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
// 指定源码
gl.shaderSource(vertexShader, VERTEX_SHADER_SOURCE);
gl.shaderSource(fragmentShader, FRAGMENT_SHADER_SOURCE);
// 编译
gl.compileShader(vertexShader);
gl.compileShader(fragmentShader);
// 创建程序对象
const program = gl.createProgram();
// 程序对象连接着色器
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
// js跟程序对象关联
gl.linkProgram(program);
// 使用程序对象
gl.useProgram(program);
return program;
}
绘制图形:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>绘制一个点</title>
</head>
<body>
<canvas></canvas>
<script type="module" >
import initProgram from './lib/program-init.js'
const canvas = document.querySelector('canvas')
const gl = canvas.getContext('webgl')
// 顶点着色器源码
const VERTEX_SHADER_SOURCE = `
void main() {
// 点的坐标
gl_Position = vec4(0.0,0.0,0.0,1.0);
// 点的大小
gl_PointSize = 10.0;
}
`
// 片元着色器源码
const FRAGMENT_SHADER_SOURCE = `
void main() {
// 颜色
gl_FragColor = vec4(1.0,0.0,0.0,1.0);
}
`
// 在顶点着色器中 vec4 代表: x,y,z,w 其次坐标(x/w,y/w,z/w)
// 在片元着色器中 vec4 代表:r,g,b,a
const program = initProgram(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE)
// 执行绘制
// 绘制点
gl.drawArrays(gl.POINTS, 0, 1)
// 绘制线段
// gl.drawArrays(gl.LINES, 0, 1)
// 绘制三角形
// gl.drawArrays(gl.TRIANGLES, 0, 1)
</script>
</body>
</html>
坐标系
二维坐标系:
三维坐标系:
着色器glsl
代码语法
变量声明:attribute
只在顶点着色器源码中使用,定义:
attribute vec4 aPosition;
attribute
:存储限定符vec4
:类型aPosition
:变量名
分号不可忽略,
aPosition
会被赋值默认值,默认值为中心点。
通过变量修改着色器实现:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>顶点着色器-attribute</title>
<style>
canvas{
background-color: #eee;
}
</style>
</head>
<body>
<canvas></canvas>
<script type="module">
import initProgram from './lib/program-init.js'
const canvas = document.querySelector('canvas')
const gl = canvas.getContext('webgl')
const VERTEX_SHADER_SOURCE = `
attribute vec4 aPosition;
void main() {
gl_Position = aPosition;
gl_PointSize = 10.0;
}
`
const FRAGMENT_SHADER_SOURCE = `
void main() {
gl_FragColor = vec4(1.0,0.0,0.0,1.0);
}
`
const program = initProgram(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE)
// 返回变量的存储地址
const aPosition = gl.getAttribLocation(program,'aPosition')
// 赋值
gl.vertexAttrib4f(aPosition,0.5,0.5,0.0,1.0);
// vertexAttrib1f vertexAttrib2f vertexAttrib3f 传入参数数量不同
// 赋值后需要重新绘制
gl.drawArrays(gl.POINTS, 0, 1)
</script>
</body>
</html>
变量声明:uniform
可以在顶点和片元着色器中使用,但是不能传递给顶点着色器的变量上
uniform vec4 uColor;
使用方式和
attribute
一样,但是片元着色器和顶点着色器不一样,顶点着色器设置变量时有默认精度,片元着色器没有,需要进行设置。gl_FragColor
固定接收 4 个参数值的vec4
类型,在使用uniform vec2
等方式定义变量时需要选择性的将参数值填充到gl_FragColor
:gl_FragColor = vec4(uColor.r,uColor.g,0.0,1.0)
通过变量修改着色器实现:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>顶点着色器-attribute</title>
<style>
canvas{
background-color: #eee;
}
</style>
</head>
<body>
<canvas></canvas>
<script type="module">
import initProgram from './lib/program-init.js'
const canvas = document.querySelector('canvas')
const gl = canvas.getContext('webgl')
const VERTEX_SHADER_SOURCE = `
attribute vec4 aPosition;
void main() {
gl_Position = aPosition;
gl_PointSize = 10.0;
}
`
// 低精度:lowp 中精度:mediump 高精度:highp
const FRAGMENT_SHADER_SOURCE = `
precision mediump float;
uniform vec4 uColor;
void main() {
gl_FragColor = uColor;
}
`
const program = initProgram(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE)
// 返回变量的存储地址
const uColor = gl.getUniformLocation(program,'uColor')
// 赋值
gl.uniform4f(uColor,-0.5,-0.5,0.0,1.0);
// uniform1f uniform2f uniform3f 传入参数数量不同
// 当使用 uniform1f 时,应该使用 float 而不是vec1来定义变量,使用float后变量被定义为浮点数,可以作为值直接在 gl_FragColor 中使用
// 赋值后需要重新绘制
gl.drawArrays(gl.POINTS, 0, 1)
</script>
</body>
</html>
鼠标事件
获取鼠标所在位置的二维坐标:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>得到鼠标所在位置</title>
<style>
canvas {
background-color: #eee;
}
</style>
</head>
<body>
<canvas></canvas>
<script type="module">
import initProgram from './lib/program-init.js'
const canvas = document.querySelector('canvas')
const gl = canvas.getContext('webgl')
const VERTEX_SHADER_SOURCE = `
attribute vec4 aPosition;
void main() {
gl_Position = aPosition;
gl_PointSize = 10.0;
}
`
const FRAGMENT_SHADER_SOURCE = `
void main() {
gl_FragColor = vec4(1.0,0.0,0.0,1.0);
}
`
const program = initProgram(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE)
// 返回变量的存储地址
const aPosition = gl.getAttribLocation(program, 'aPosition')
const rect = canvas.getBoundingClientRect();
const width = canvas.offsetWidth
const height = canvas.offsetHeight
const halfWidth = width / 2
const halfHeight = height / 2
canvas.addEventListener('click', (event) => {
const clientX = event.clientX
const clientY = event.clientY
var offsetX = clientX - rect.left;
var offsetY = clientY - rect.top;
const x = (offsetX - halfWidth) / halfWidth
const y = (halfHeight - offsetY) / halfHeight
// 赋值
gl.vertexAttrib2f(aPosition, x, y);
// 赋值后需要重新绘制
gl.drawArrays(gl.POINTS, 0, 1)
})
</script>
</body>
</html>
缓冲区对象
处理多个顶点绘制带来的渲染时机问题,它是一块内存区域,可以一次性将大量顶点数据填充进去缓存起来,供着色器使用
创建和绑定缓冲区对象:
- 创建缓冲区对象:
const buffer = gl.createBuffer();
- 绑定缓存区对象
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.ARRAY_BUFFER
表示缓存顶点数据gl.ELEMENT_ARRAY_BUFFER
表示缓存顶点的索引值
- 使用类型化数组数据结构创建顶点数据
const points = new Float32Array([-0.5, -0.5, 0.5, -0.5, 0.0, 0.5]);
- 存入数据
gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW);
最后一个参数表示数据的使用方式:
gl.STATIC_DRAW
:写入一次,多次绘制gl.STREAM_DRAW
:写入一次,若干次绘制gl.DYNAMIC_DRAW
:写入多次,多次绘制
- 修改数据
const aPosition = gl.getAttribLocation(program,'aPosition)
gl.vertexAttribPointer(aPosition,2,gl.FLOAT,false,0,0)
- 激活顶点着色器变量
gl.enableVertexAttribArray(aPosition);
- 渲染
gl.drawArrays(gl.POINTS, 0, 3);
类型化数组:
在webgl
中,需要处理大量的同类型数据,使用类型化数组可以让程序预知数组元素的类型,提高执行性能
Int8Array
:8 位整型UInt8Array
:8 位无符号整型Int16Array
:16 位整型Unit16Array
:16 位无符号整型Int32Array
:32 位整型UInt32Array
:32 位无符号整型Float32Array
:单精度 32 位浮点型Float64Array
:双精度 64 位浮点型
多缓冲区