绘制点
attribute (顶点着色器变量)
可以使用 attribute 存储限定符,在顶点着色器代码里声明变量,然后在 JS 代码里修改变量的值
import { useEffect } from "react";
import { initShader } from "./libs/webgl.js"
function App() {
useEffect(() => {
const canvas = document.querySelector("#glcanvas");
const gl = canvas.getContext("webgl");
const vertexShaderSource = `
// 1. 声明变量
attribute vec4 aPosition;
void main() {
gl_Position = aPosition;
gl_PointSize = 30.0;
}
`;
const fragmentShaderSource = `
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
`;
const program = initShader(gl, vertexShaderSource, fragmentShaderSource);
// 2. 获取顶点着色器变量
const aPosition = gl.getAttribLocation(program, 'aPosition');
// 3. 修改变量值
// gl.vertexAttrib4f(aPosition, 0.0, 0.0, 0.0, 1.0); // x y z w
// gl.vertexAttrib3f(aPosition, 0.0, 0.0, 0.0); // x y z
// gl.vertexAttrib2f(aPosition, 0.0, 0.0); // x y
gl.vertexAttrib1f(aPosition, 0.5); // x
gl.drawArrays(gl.POINTS, 0, 1);
}, []);
return <canvas id="glcanvas" width="200" height="200" style={{ backgroundColor: "#666" }}></canvas>
}
export default App;预览
流程

鼠标点击绘制单点、多点
绘制单点
- 给 canvas 设置点击事件
- 计算点击的 webgl 坐标
- 设置顶点坐标,绘制到画布上
示例代码
import { useEffect } from "react";
import { initShader } from "./libs/webgl.js"
function App() {
useEffect(() => {
const canvas = document.querySelector("#glcanvas");
const gl = canvas.getContext("webgl");
const vertexShaderSource = `
attribute vec4 aPosition;
void main() {
gl_Position = aPosition;
gl_PointSize = 10.0;
}
`;
const fragmentShaderSource = `
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
`;
const program = initShader(gl, vertexShaderSource, fragmentShaderSource);
const aPosition = gl.getAttribLocation(program, 'aPosition');
canvas.addEventListener("click", (e) => {
const { clientX, clientY } = e;
// 获取 canvas 宽高
const clientWidth = e.target.clientWidth;
const clientHeight = e.target.clientHeight;
// 获取点击坐标距离 canvas 左和上的距离
const offsetLeft = clientX - canvas.offsetLeft;
const offsetTop = clientY - canvas.offsetTop;
// webgl 坐标系原点在 canvas 中心,取宽高一半的值
const halfWidth = clientWidth / 2;
const halfHeight = clientHeight / 2;
// x = (左边距 - 宽度/2) / 宽度/2
const x = (offsetLeft - halfWidth) / halfWidth;
// x = (高度/2 - 上边距) / 高度/2
const y = (halfHeight - offsetTop) / halfHeight;
console.log("wfer", x, y);
gl.vertexAttrib2f(aPosition, x, y);
gl.drawArrays(gl.POINTS, 0, 1);
})
}, []);
return <canvas
id="glcanvas"
width="200"
height="200"
style={{ backgroundColor: "#666", margin: "20px auto", display: "block" }}
></canvas>
}
export default App;预览
绘制多点
存储下每次点击的坐标,然后在点击时全部绘制出来
import { useEffect } from "react";
import { initShader } from "./libs/webgl.js"
function App() {
useEffect(() => {
const canvas = document.querySelector("#glcanvas");
const gl = canvas.getContext("webgl");
const vertexShaderSource = `
attribute vec4 aPosition;
void main() {
gl_Position = aPosition;
gl_PointSize = 10.0;
}
`;
const fragmentShaderSource = `
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
`;
const program = initShader(gl, vertexShaderSource, fragmentShaderSource);
const aPosition = gl.getAttribLocation(program, 'aPosition');
const points = [];
canvas.addEventListener("click", (e) => {
const { clientX, clientY } = e;
// 获取 canvas 宽高
const clientWidth = e.target.clientWidth;
const clientHeight = e.target.clientHeight;
// 获取点击坐标距离 canvas 左和上的距离
const offsetLeft = clientX - canvas.offsetLeft;
const offsetTop = clientY - canvas.offsetTop;
// webgl 坐标系原点在 canvas 中心,取宽高一半的值
const halfWidth = clientWidth / 2;
const halfHeight = clientHeight / 2;
// x = (左边距 - 宽度/2) / 宽度/2
const x = (offsetLeft - halfWidth) / halfWidth;
// x = (高度/2 - 上边距) / 高度/2
const y = (halfHeight - offsetTop) / halfHeight;
points.push({ x, y })
points.forEach(({ x, y }) => {
gl.vertexAttrib2f(aPosition, x, y);
gl.drawArrays(gl.POINTS, 0, 1);
})
})
}, []);
return <canvas
id="glcanvas"
width="200"
height="200"
style={{ backgroundColor: "#666", margin: "20px auto", display: "block" }}
></canvas>
}
export default App;预览
uniform
uniform 可以给顶点着色器和片元着色器使用,一般用于偏于着色器。
危险
uniform 变量需要指定精度
// mediump 中精度 | highp 高精度 | lowp 低精度
precision mediump float;
uniform vec4 uFragColor;

import { useEffect } from "react";
import { initShader } from "./libs/webgl.js"
function App() {
useEffect(() => {
const canvas = document.querySelector("#glcanvas");
const gl = canvas.getContext("webgl");
const vertexShaderSource = `
attribute vec4 aPosition;
void main() {
gl_Position = aPosition;
gl_PointSize = 10.0;
}
`;
const fragmentShaderSource = `
precision mediump float;
uniform vec4 uFragColor;
void main() {
gl_FragColor = uFragColor;
}
`;
const program = initShader(gl, vertexShaderSource, fragmentShaderSource);
const aPosition = gl.getAttribLocation(program, 'aPosition');
const uFragColor = gl.getUniformLocation(program, 'uFragColor');
const points = [];
canvas.addEventListener("click", (e) => {
const { clientX, clientY } = e;
// 获取 canvas 宽高
const clientWidth = e.target.clientWidth;
const clientHeight = e.target.clientHeight;
// 获取点击坐标距离 canvas 左和上的距离
const offsetLeft = clientX - canvas.offsetLeft;
const offsetTop = clientY - canvas.offsetTop;
// webgl 坐标系原点在 canvas 中心,取宽高一半的值
const halfWidth = clientWidth / 2;
const halfHeight = clientHeight / 2;
// x = (左边距 - 宽度/2) / 宽度/2
const x = (offsetLeft - halfWidth) / halfWidth;
// x = (高度/2 - 上边距) / 高度/2
const y = (halfHeight - offsetTop) / halfHeight;
points.push({ x, y })
points.forEach(({ x, y }) => {
gl.vertexAttrib2f(aPosition, x, y);
gl.uniform4f(uFragColor, x, y, 0.0, 1.0);
gl.drawArrays(gl.POINTS, 0, 1);
})
})
}, []);
return <canvas
id="glcanvas"
width="200"
height="200"
style={{ backgroundColor: "#666", margin: "20px auto", display: "block" }}
></canvas>
}
export default App;预览
缓冲区绘制多点
缓冲区对象是 WebGL 中一块内存区域,可以一次性的存储大量的顶点数据,供顶点着色器使用
步骤
- 定义顶点
- 创建 buffer 缓冲区对象
- 绑定缓冲区对象
- 将数据写入缓冲区对象
- 将缓冲区对象分配给一个 attribute 变量
- 开启 attribute 变量

API
- bindBuffer(target,buffer):绑定缓冲区对象
- target
- gl.ARRAY_BUFFER:标识缓冲区存储的是
顶点数据 - gl.ELEMENT_ARRAY_BUFFER:标识缓冲区存储的是
顶点的索引
- gl.ARRAY_BUFFER:标识缓冲区存储的是
- buffer:缓冲区对象
- target
- bufferData(target, data, type):将数据写入缓冲区对象
- target: 同 bindBuffer target
- data:顶点数据
- type 如何绘制缓冲区中的数据
- gl.STATIC_DRAW:写入一次,多次绘制
- gl.STREAM_DRAW:写入一次,绘制若干次
- gl.DYNAMIC_DRAW:多次写入,多次绘制
- vertexAttribPointer(location, size, type, normalized, stride, offset):将缓冲区对象分配给 attribute 变量,告诉 GPU 如何解释缓冲区对象中的数据
- location:attribute 变量存储地址
- size:每个顶点需要数据个数
- type:顶点数据类型
- gl.FLOAT:浮点型
- gl.UNSIGNED_BYTE:无符号字节
- gl.SHORT:短整型
- gl.UNSIGNED_SHORT:无符号短整型
- gl.INT:整型
- gl.UNSIGNED_INT:无符号整型
- normalized:是否将数据归一化到
[0,1][-1,1]这个区间 - stride:两个相邻顶点之间的字节数
- offset:数据偏移量,表示读取数据的开始位置
- enableVertexAttribArray(location):启用顶点属性数组,开启后 GPU 将根据缓冲区的对象使用顶点数据
- location:attribute 变量存储地址
- gl.disableVertexAttribArray(aPosition); 使⽤此⽅法禁⽤
单缓冲区多点
import { useEffect } from "react";
import { initShader } from "./libs/webgl.js"
function App() {
useEffect(() => {
const canvas = document.querySelector("#glcanvas");
const gl = canvas.getContext("webgl");
const vertexShaderSource = `
attribute vec4 aPosition;
void main() {
gl_Position = aPosition;
gl_PointSize = 10.0;
}
`;
const fragmentShaderSource = `
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
`;
const program = initShader(gl, vertexShaderSource, fragmentShaderSource);
const aPosition = gl.getAttribLocation(program, 'aPosition');
// 1. 定义顶点
const points = new Float32Array([
-0.5, -0.5,
0.5, -0.5,
0.0, 0.5
]);
// 2. 创建 buffer 缓冲区对象
const glBuffer = gl.createBuffer();
// 3. 绑定缓冲区对象
gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer);
// 4. 将数据写入缓冲区对象
gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW);
// 5. 将缓冲区对象分配给一个 attribute 变量
gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, 0, 0);
// 6. 开启 attribute 变量
gl.enableVertexAttribArray(aPosition);
// 7. 绘制
gl.drawArrays(gl.POINTS, 0, 3);
}, []);
return <canvas
id="glcanvas"
width="200"
height="200"
style={{ backgroundColor: "#666", margin: "20px auto", display: "block" }}
></canvas>
}
export default App;预览
多缓冲区多点
import { useEffect } from "react";
import { initShader } from "./libs/webgl.js"
function App() {
useEffect(() => {
const canvas = document.querySelector("#glcanvas");
const gl = canvas.getContext("webgl");
const vertexShaderSource = `
attribute vec4 aPosition;
attribute float aSize;
void main() {
gl_Position = aPosition;
gl_PointSize = aSize;
}
`;
const fragmentShaderSource = `
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
`;
const program = initShader(gl, vertexShaderSource, fragmentShaderSource);
const aPosition = gl.getAttribLocation(program, 'aPosition');
const aSize = gl.getAttribLocation(program, 'aSize');
// 1. 定义顶点
const points = new Float32Array([
-0.5, -0.5,
0.5, -0.5,
0.0, 0.5
]);
const glBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer);
gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW);
gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(aPosition);
const sizes = new Float32Array([
10.0,
20.0,
30.0
]);
const sizeBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, sizeBuffer);
gl.bufferData(gl.ARRAY_BUFFER, sizes, gl.STATIC_DRAW);
gl.vertexAttribPointer(aSize, 1, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(aSize);
gl.drawArrays(gl.POINTS, 0, 3);
}, []);
return <canvas
id="glcanvas"
width="200"
height="200"
style={{ backgroundColor: "#666", margin: "20px auto", display: "block" }}
></canvas>
}
export default App;预览
单缓冲区偏移量绘制多点
import { useEffect } from "react";
import { initShader } from "./libs/webgl.js"
function App() {
useEffect(() => {
const canvas = document.querySelector("#glcanvas");
const gl = canvas.getContext("webgl2");
const vertexShaderSource = `
attribute vec4 aPosition;
attribute float aSize;
void main() {
gl_Position = aPosition;
gl_PointSize = aSize;
}
`;
const fragmentShaderSource = `
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
`;
const program = initShader(gl, vertexShaderSource, fragmentShaderSource);
const aPosition = gl.getAttribLocation(program, 'aPosition');
const aSize = gl.getAttribLocation(program, 'aSize');
const points = new Float32Array([
-0.5, -0.5, 10.0,
0.5, -0.5, 20.0,
0.0, 0.5, 30.0,
]);
const glBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer);
gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW);
const BYTES = points.BYTES_PER_ELEMENT;
gl.vertexAttribPointer(aSize, 1, gl.FLOAT, false, BYTES * 3, BYTES * 2);
gl.enableVertexAttribArray(aSize);
gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, BYTES * 3, 0);
gl.enableVertexAttribArray(aPosition);
gl.drawArrays(gl.POINTS, 0, 3);
}, []);
return <canvas id="glcanvas" width="200" height="200"></canvas>
}
export default App;预览