1. 绘制一个点
(1) 流程介绍
(2) 初始化着色器
/*
* lib/index.js
*/
function initShader(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)
gl.linkProgram(program)
gl.useProgram(program)
return program
}
// 平移矩阵
function getTranslateMatrix(x = 0, y = 0, z = 0) {
return new Float32Array([
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
x, y, z, 1,
])
}
// 缩放矩阵
function getScaleMatrix(x = 1, y = 1, z = 1) {
return new Float32Array([
x, 0.0, 0.0, 0.0,
0.0, y, 0.0, 0.0,
0.0, 0.0, z, 0.0,
0.0, 0.0, 0.0, 1,
])
}
// 绕z轴旋转的矩阵
function getRotateMatrix(deg) {
return new Float32Array([
Math.cos(deg), Math.sin(deg), 0.0, 0.0,
-Math.sin(deg), Math.cos(deg), 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1,
])
}
// 矩阵复合函数
function mixMatrix(A, B) {
const result = new Float32Array(16)
for (let i = 0; i < 4; i++) {
result[i] = A[i] * B[0] + A[i + 4] * B[1] + A[i + 8] * B[2] + A[i + 12] * B[3]
result[i + 4] = A[i] * B[4] + A[i + 4] * B[5] + A[i + 8] * B[6] + A[i + 12] * B[7]
result[i + 8] = A[i] * B[8] + A[i + 4] * B[9] + A[i + 8] * B[10] + A[i + 12] * B[11]
result[i + 12] = A[i] * B[12] + A[i + 4] * B[13] + A[i + 8] * B[14] + A[i + 12] * B[15]
}
return result
}
(3) 绘制顶点
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#canvas {
background-color: #ccc;
}
</style>
<script src="./lib/index.js"></script>
</head>
<body>
<canvas id="canvas" width="400" height="400">浏览器不支持canvas</canvas>
<script>
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);
}
`; // 片元着色器
initShader(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE)
// 执行绘制
// 要绘制的图形是什么?从哪个开始?使用几个顶点
gl.drawArrays(gl.POINTS, 0, 1)
</script>
</body>
</html>
2. attribute变量
(1) 流程介绍
(2) 代码实现
...
// 创建着色器源码
const VERTEX_SHADER_SOURCE = `
atrribute vec4 aPosition;
void main() {
gl_Position = aPosition; // 默认值vec4(0.0, 0.0, 0.0, 1.0)
gl_PointSize = 10.0;
}
`; // 顶点着色器
...
const program = initShader(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE)
const aPosition = gl.getAttribLocation(program, 'aPosition');
gl.vertexAttrib4f(aPosition, 0.5, 0.5, 0.0, 1.0);
...
3. 通过鼠标控制绘制
(1) 流程介绍
(2) 书写签名效果
...
const aPosition = gl.getAttribLocation(program, 'aPosition')
const points = []
let isDraw = false
canvas.onmousedown = function () {
isDraw = true
}
canvas.onmouseup = function () {
isDraw = false
}
canvas.onmousemove = function (ev) {
if (!isDraw) {
return
}
const x = ev.clientX
const y = ev.clientY
const domPosition = ev.target.getBoundingClientRect()
const domx = x - domPosition.left
const domy = y - domPosition.top
const halfWidth = canvas.offsetWidth / 2;
const halfHeight = canvas.offsetHeight / 2;
const clickX = (domx - halfWidth) / halfWidth
const clickY = (halfHeight - y) / halfHeight
points.push({
clickX, clickY
})
for (let i = 0; i < points.length; i++) {
const point = points[i];
gl.vertexAttrib2f(aPosition, point.clickX, point.clickY)
gl.drawArrays(gl.POINTS, 0, 1)
}
}
4. 绘制不同颜色的点
(1) 设置精度
precision mediump float
- hightp 高精度
- mediump 中精度
- lowp 低精度
(2) 流程介绍
(3) 使用uniform变量-绘制不同颜色的点
const canvas = document.querySelector('#canvas')
const gl = canvas.getContext("webgl")
// 创建着色器源码
const VERTEX_SHADER_SOURCE = `
attribute vec4 aPosition;
void main() {
gl_Position = aPosition; // 默认值vec4(0.0, 0.0, 0.0, 1.0)
gl_PointSize = 10.0;
}
`; // 顶点着色器
const FRAGMENT_SHADER_SOURCE = `
precision mediump float;
uniform vec2 uColor;
void main() {
gl_FragColor = vec4(uColor.r, uColor.g, 0.0, 1.0);
}
`; // 片元着色器
const program = initShader(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE)
const aPosition = gl.getAttribLocation(program, 'aPosition')
const uColor = gl.getUniformLocation(program, 'uColor')
const points = []
canvas.onclick = function (ev) {
const x = ev.clientX
const y = ev.clientY
const domPosition = ev.target.getBoundingClientRect()
const domx = x - domPosition.left
const domy = y - domPosition.top
const halfWidth = canvas.offsetWidth / 2;
const halfHeight = canvas.offsetHeight / 2;
const clickX = (domx - halfWidth) / halfWidth
const clickY = (halfHeight - y) / halfHeight
points.push({
clickX, clickY
})
for (let i = 0; i < points.length; i++) {
const point = points[i];
gl.vertexAttrib2f(aPosition, point.clickX, point.clickY)
gl.uniform2f(uColor, point.clickX, point.click)
gl.drawArrays(gl.POINTS, 0, 1)
}
}
5. 使用缓冲对象绘制多个点
(1) 流程介绍
(2) 绘制多个点
const canvas = document.querySelector('#canvas')
const gl = canvas.getContext('webgl')
// 创建着色器源码
const VERTEX_SHADER_SOURCE = `
attribute vec4 aPosition;
void main() {
gl_Position = aPosition; // 默认值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);
}
` // 片元着色器
const program = initShader(
gl,
VERTEX_SHADER_SOURCE,
FRAGMENT_SHADER_SOURCE
)
const aPosition = gl.getAttribLocation(program, 'aPosition')
// 创建三个点的数组
const points = new Float32Array([-0.5, -0.5, 0.5, -0.5, 0.0, 0.5])
// 创建缓冲区对象
const buffer = gl.createBuffer()
/*
* 绑定缓冲区对象 gl.bindBuffer(taget, buffer)
* @params: taget gl.ARRAY_BUFFER 表示缓冲区存储的是顶点的数据,gl.ELEMENT_ARRAY_BUFFER 表示缓冲区存储的是顶点的索引值
* @params: buffer 缓冲区对象
*/
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
/*
* 将数据写入缓冲区对象 gl.bufferData(taget, data, type)
* @params: taget gl.ARRAY_BUFFER 表示缓冲区存储的是顶点的数据,gl.ELEMENT_ARRAY_BUFFER 表示缓冲区存储的是顶点的索引值
* @params: data 写入缓冲区的顶点数据
* @params: type 如何使用缓冲区对象中的数据:gl.STATIC_DRAW 写入一次 多次绘制,gl.STREAM_DRAW 写入一次 绘制若干次,gl.DYNAMIC_DRAW 写入多次 绘制多次,
*/
gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW)
/*
* 将数据写入缓冲区对象 gl.vertexAttribPointer(location, size, type, normalized, stride, offset)
* @params: location attribute变量的存储地址
* @params: size 每个顶点使用数据的个数
* @params: type 指定数据格式
* @params: normalized 表示是否将数据归一化到[0, 1][-1, 1]这个区间
* @params: stride 两个相邻顶点之间的字节数
* @params: offset 数据偏移量
*/
gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, 0, 0)
/*
* 启用 attribute 变量
* @params: attribute 变量的存储地址
*/
gl.enableVertexAttribArray(aPosition)
gl.drawArrays(gl.POINTS, 0, 3)
6. 多缓冲区和数据偏移
(1) 流程介绍
(2) 控制多个点的多个属性
const canvas = document.querySelector('#canvas')
const gl = canvas.getContext('webgl')
// 创建着色器源码
const VERTEX_SHADER_SOURCE = `
attribute vec4 aPosition;
attribute float aPointSize;
void main() {
gl_Position = aPosition; // 默认值vec4(0.0, 0.0, 0.0, 1.0)
gl_PointSize = aPointSize;
}
` // 顶点着色器
const FRAGMENT_SHADER_SOURCE = `
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
` // 片元着色器
const program = initShader(
gl,
VERTEX_SHADER_SOURCE,
FRAGMENT_SHADER_SOURCE
)
const aPosition = gl.getAttribLocation(program, 'aPosition')
const aPointSize = gl.getAttribLocation(program, 'aPointSize')
// 创建三个点的数组
const points = new Float32Array([
-0.5, -0.5, 10.0,
0.5, -0.5, 20.0,
0.0, 0.5, 30.0,
])
// 创建缓冲区对象
const buffer = gl.createBuffer()
// 绑定缓冲区对象
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
// 将数据写入缓冲区对象
gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW)
// 获取数据字节数
const BYTES = points.BYTES_PER_ELEMENT
// 将数据写入缓冲区对象
gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, BYTES * 3, 0)
gl.vertexAttribPointer(aPointSize, 1, gl.FLOAT, false, BYTES * 3, BYTES * 2)
// 启用 attribute 变量
gl.enableVertexAttribArray(aPosition)
gl.enableVertexAttribArray(aPointSize)
gl.drawArrays(gl.POINTS, 0, 3)
7. 多种图形绘制
8. 着色器图形平移、缩放、旋转
// 平移
...
// 创建着色器源码
const VERTEX_SHADER_SOURCE = `
attribute vec4 aPosition;
attribute float aTranslate;
void main() {
gl_Position = vec4(aPosition.x + aTranslate, aPosition.y, aPosition.z, 1.0);
gl_PointSize = 10.0;
}
`; // 顶点着色器
...
const aPosition = gl.getAttribLocation(program, 'aPosition');
const aTranslate = gl.getAttribLocation(program, 'aTranslate');
// 创建三个点的数组
const points = new Float32Array([
-0.5, -0.5,
0.5, -0.5,
0, 0.5,
])
// 创建缓冲区对象
const buffer = gl.createBuffer()
// 绑定缓冲区对象
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
// 将数据写入缓冲区对象
gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW)
// 获取数据字节数
const BYTES = points.BYTES_PER_ELEMENT
// 将数据写入缓冲区对象
gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, BYTES * 2, 0)
// 启用 attribute 变量
gl.enableVertexAttribArray(aPosition)
let x = -1
setInterval(() => {
x += 0.01
if (x > 1) {
x = -1
}
gl.vertexAttrib1f(aTranslate, x)
gl.drawArrays(gl.TRIANGLES, 0, 3)
}, 60);
// 缩放
...
// 创建着色器源码
const VERTEX_SHADER_SOURCE = `
attribute vec4 aPosition;
attribute float aScale;
void main() {
gl_Position = vec4(aPosition.x * aScale, aPosition.y, aPosition.z, 1.0);
gl_PointSize = 10.0;
}
`; // 顶点着色器
...
const aPosition = gl.getAttribLocation(program, 'aPosition')
const aScale = gl.getAttribLocation(program, 'aScale')
// 创建三个点的数组
const points = new Float32Array([
-0.5, -0.5,
0.5, -0.5,
0, 0.5,
])
// 创建缓冲区对象
const buffer = gl.createBuffer()
// 绑定缓冲区对象
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
// 将数据写入缓冲区对象
gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW)
// 获取数据字节数
const BYTES = points.BYTES_PER_ELEMENT
// 将数据写入缓冲区对象
gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, BYTES * 2, 0)
// 启用 attribute 变量
gl.enableVertexAttribArray(aPosition)
let x = 1
setInterval(() => {
x += 0.1
if (x > 2) {
x = 1
}
gl.vertexAttrib1f(aScale, x)
gl.drawArrays(gl.TRIANGLES, 0, 3)
}, 60);
// 旋转
...
// 创建着色器源码
const VERTEX_SHADER_SOURCE = `
attribute vec4 aPosition;
attribute float deg;
void main() {
gl_Position.x = aPosition.x * cos(deg) - aPosition.y * sin(deg);
gl_Position.y = aPosition.x * sin(deg) + aPosition.y * cos(deg);
gl_Position.z = aPosition.z;
gl_Position.w = aPosition.w;
}
`; // 顶点着色器
...
const aPosition = gl.getAttribLocation(program, 'aPosition')
const deg = gl.getAttribLocation(program, 'deg')
// 创建三个点的数组
const points = new Float32Array([
-0.5, -0.5,
0.5, -0.5,
0, 0.5,
0.5, 0.5,
])
// 创建缓冲区对象
const buffer = gl.createBuffer()
// 绑定缓冲区对象
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
// 将数据写入缓冲区对象
gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW)
// 获取数据字节数
const BYTES = points.BYTES_PER_ELEMENT
// 将数据写入缓冲区对象
gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, BYTES * 2, 0)
// 启用 attribute 变量
gl.enableVertexAttribArray(aPosition)
let x = 1
function animate() {
x += -0.03
gl.vertexAttrib1f(deg, x)
gl.drawArrays(gl.TRIANGLES, 0, 3)
requestAnimationFrame(animate)
}
animate()
9. 矩阵图形平移、缩放、旋转
(1) 平移矩阵
const canvas = document.querySelector('#canvas')
const gl = canvas.getContext('webgl')
// 创建着色器源码
const VERTEX_SHADER_SOURCE = `
attribute vec4 aPosition;
uniform mat4 mat;
void main() {
gl_Position = mat * aPosition;
}
` // 顶点着色器
const FRAGMENT_SHADER_SOURCE = `
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
` // 片元着色器
const program = initShader(
gl,
VERTEX_SHADER_SOURCE,
FRAGMENT_SHADER_SOURCE
)
const aPosition = gl.getAttribLocation(program, 'aPosition')
const mat = gl.getUniformLocation(program, 'mat')
// 获取矩阵
function getTranslateMatrix(x = 0, y = 0, z = 0) {
return new Float32Array([
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
x, y, z, 1,
])
}
// 创建三个点的数组
const points = new Float32Array([
-0.5, -0.5,
0.5, -0.5,
0, 0.5,
])
// 创建缓冲区对象
const buffer = gl.createBuffer()
// 绑定缓冲区对象
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
// 将数据写入缓冲区对象
gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW)
// 获取数据字节数
const BYTES = points.BYTES_PER_ELEMENT
// 将数据写入缓冲区对象
gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, BYTES * 2, 0)
// 启用 attribute 变量
gl.enableVertexAttribArray(aPosition)
let x = -1
function animation() {
x += 0.01
if (x > 1) {
x = -1
}
const matrix = getTranslateMatrix(x, x)
/*
* 修改矩阵变量 gl.uniformMatrix4fv(location, transpose, array)
* @params: transpose 在webgl中恒为false
* @params: array 矩阵数组
*/
gl.uniformMatrix4fv(mat, false, matrix)
gl.drawArrays(gl.TRIANGLES, 0, 3)
requestAnimationFrame(animation)
}
animation()
(2) 缩放矩阵
...
// 获取矩阵
function getScaleMatrix(x = 1, y = 1, z = 1) {
return new Float32Array([
x, 0.0, 0.0, 0.0,
0.0, y, 0.0, 0.0,
0.0, 0.0, z, 0.0,
0.0, 0.0, 0.0, 1,
])
}
...
let x = 0.1
function animation() {
x += 0.01
if (x > 1.5) {
x = 0.1
}
const matrix = getScaleMatrix(x, x)
/*
* 修改矩阵变量 gl.uniformMatrix4fv(location, transpose, array)
* @params: transpose 在webgl中恒为false
* @params: array 矩阵数组
*/
gl.uniformMatrix4fv(mat, false, matrix)
gl.drawArrays(gl.TRIANGLES, 0, 3)
requestAnimationFrame(animation)
}
animation()
(3) 旋转矩阵
...
// 获取矩阵
function getRotateMatrix(deg) {
return new Float32Array([
Math.cos(deg), Math.sin(deg), 0.0, 0.0,
-Math.sin(deg), Math.cos(deg), 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1,
])
}
...
let x = 0
function animation() {
x += 0.01
const matrix = getRotateMatrix(x)
gl.uniformMatrix4fv(mat, false, matrix)
gl.drawArrays(gl.TRIANGLES, 0, 3)
requestAnimationFrame(animation)
}
animation()
10. 图形复合变换-组合矩阵
const canvas = document.querySelector('#canvas')
const gl = canvas.getContext('webgl')
// 创建着色器源码
const VERTEX_SHADER_SOURCE = `
attribute vec4 aPosition;
uniform mat4 mat;
void main() {
gl_Position = mat * aPosition;
}
` // 顶点着色器
const FRAGMENT_SHADER_SOURCE = `
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
` // 片元着色器
const program = initShader(
gl,
VERTEX_SHADER_SOURCE,
FRAGMENT_SHADER_SOURCE
)
const aPosition = gl.getAttribLocation(program, 'aPosition')
const mat = gl.getUniformLocation(program, 'mat')
// 创建三个点的数组
const points = new Float32Array([
-0.5, -0.5,
0.5, -0.5,
0, 0.5,
])
// 创建缓冲区对象
const buffer = gl.createBuffer()
// 绑定缓冲区对象
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
// 将数据写入缓冲区对象
gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW)
// 获取数据字节数
const BYTES = points.BYTES_PER_ELEMENT
// 将数据写入缓冲区对象
gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, BYTES * 2, 0)
// 启用 attribute 变量
gl.enableVertexAttribArray(aPosition)
let deg = 0
let translateX = -1
let scaleX = 0.1
function animation() {
deg += 0.01
translateX += 0.01
scaleX += 0.01
if (translateX > 1) {
translateX = -1
}
if (scaleX > 1.5) {
scaleX = 0.1
}
const translate = getTranslateMatrix(translateX)
const scale = getScaleMatrix(scaleX)
const rotate = getRotateMatrix(deg)
const matrix = mixMatrix(mixMatrix(translate, scale), rotate)
gl.uniformMatrix4fv(mat, false, matrix)
gl.drawArrays(gl.TRIANGLES, 0, 3)
requestAnimationFrame(animation)
}
animation()