WebGL 详解part5:传入颜色信息,绘制渐变色三角形
骚话鬼才又来开讲啦!上回咱们用WebGL在canvas里画出了人生第一个三角形,今天咱们再加点"骚操作"------让三角形拥有炫酷的渐变色!
如果觉得有用,记得点赞收藏,骚话王后续还有更多WebGL骚操作等你来抄作业!
目标:绘制一个有渐变色的三角形
这次的目标是:
- 每个顶点传入不同的颜色
- 让三角形自动实现顶点到顶点的颜色渐变
这可是WebGL入门进阶的必修课,玩转attribute,3D世界的色彩才会丰富多彩!
第一步:准备HTML和Canvas
和上次一样,先来一段基础HTML:
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>WebGL渐变三角形实战</title>
<style>
body { background: #222; color: #fff; }
canvas { display: block; margin: 40px auto; background: #333; }
</style>
</head>
<body>
<canvas id="webgl-canvas" width="400" height="400"></canvas>
<script src="gradient-triangle.js"></script>
</body>
</html>
第二步:编写JavaScript,定义顶点和颜色数据
这次每个顶点不仅有坐标,还要有颜色(r,g,b):
js
// 三个顶点,每个顶点(x, y, r, g, b)
const vertices = new Float32Array([
// 位置 // 颜色
0.0, 0.5, 1.0, 0.0, 0.0, // 顶点1:红色
-0.5, -0.5, 0.0, 1.0, 0.0, // 顶点2:绿色
0.5, -0.5, 0.0, 0.0, 1.0 // 顶点3:蓝色
]);
第三步:编写着色器源码
顶点着色器要接收颜色,并传递给片元着色器:
js
const vertexShaderSource = `
attribute vec2 position;
attribute vec3 color;
varying vec3 vColor;
void main() {
gl_Position = vec4(position, 0.0, 1.0);
vColor = color; // 把颜色传递给片元着色器
}
`;
const fragmentShaderSource = `
precision mediump float;
varying vec3 vColor;
void main() {
gl_FragColor = vec4(vColor, 1.0); // 用插值后的颜色
}
`;
骚话王小贴士:
attribute
只能在顶点着色器用,varying
用于顶点到片元的"接力棒"。
第四步:创建着色器、程序对象,上传数据
js
// 获取canvas和WebGL上下文
const canvas = document.getElementById('webgl-canvas');
const gl = canvas.getContext('webgl');
if (!gl) {
alert('你的浏览器不支持WebGL,换个浏览器试试吧!');
}
// 创建着色器和程序对象(可复用上篇的封装函数)
function createShader(gl, type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
const info = gl.getShaderInfoLog(shader);
gl.deleteShader(shader);
throw new Error('着色器编译失败:' + info);
}
return shader;
}
function createProgram(gl, vertexShader, fragmentShader) {
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
const info = gl.getProgramInfoLog(program);
gl.deleteProgram(program);
throw new Error('程序对象链接失败:' + info);
}
return program;
}
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
const program = createProgram(gl, vertexShader, fragmentShader);
gl.useProgram(program);
// 创建缓冲区并上传数据
const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
第五步:配置attribute,把数据送进着色器
js
const FSIZE = vertices.BYTES_PER_ELEMENT;
// 获取位置和颜色的attribute位置
const positionLoc = gl.getAttribLocation(program, 'position');
const colorLoc = gl.getAttribLocation(program, 'color');
// 启用attribute
gl.enableVertexAttribArray(positionLoc);
gl.enableVertexAttribArray(colorLoc);
// 配置指针:
// 位置:每组2个float,步长5个float,偏移0
// 颜色:每组3个float,步长5个float,偏移2个float
gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, FSIZE * 5, 0);
gl.vertexAttribPointer(colorLoc, 3, gl.FLOAT, false, FSIZE * 5, FSIZE * 2);
骚话王小贴士:
- 步长stride和偏移offset单位都是字节,别写错!
- attribute名要和着色器里一模一样,大小写敏感。
第六步:清屏,绘制渐变三角形!
js
gl.clearColor(0.1, 0.1, 0.1, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLES, 0, 3);
完整代码一览
js
const canvas = document.getElementById('webgl-canvas');
const gl = canvas.getContext('webgl');
if (!gl) {
alert('你的浏览器不支持WebGL,换个浏览器试试吧!');
}
const vertices = new Float32Array([
0.0, 0.5, 1.0, 0.0, 0.0,
-0.5, -0.5, 0.0, 1.0, 0.0,
0.5, -0.5, 0.0, 0.0, 1.0
]);
const vertexShaderSource = `
attribute vec2 position;
attribute vec3 color;
varying vec3 vColor;
void main() {
gl_Position = vec4(position, 0.0, 1.0);
vColor = color;
}
`;
const fragmentShaderSource = `
precision mediump float;
varying vec3 vColor;
void main() {
gl_FragColor = vec4(vColor, 1.0);
}
`;
function createShader(gl, type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
const info = gl.getShaderInfoLog(shader);
gl.deleteShader(shader);
throw new Error('着色器编译失败:' + info);
}
return shader;
}
function createProgram(gl, vertexShader, fragmentShader) {
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
const info = gl.getProgramInfoLog(program);
gl.deleteProgram(program);
throw new Error('程序对象链接失败:' + info);
}
return program;
}
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
const program = createProgram(gl, vertexShader, fragmentShader);
gl.useProgram(program);
const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
const FSIZE = vertices.BYTES_PER_ELEMENT;
const positionLoc = gl.getAttribLocation(program, 'position');
const colorLoc = gl.getAttribLocation(program, 'color');
gl.enableVertexAttribArray(positionLoc);
gl.enableVertexAttribArray(colorLoc);
gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, FSIZE * 5, 0);
gl.vertexAttribPointer(colorLoc, 3, gl.FLOAT, false, FSIZE * 5, FSIZE * 2);
gl.clearColor(0.1, 0.1, 0.1, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLES, 0, 3);
常见坑和骚话王小贴士
- 三角形没颜色/没渐变? 检查attribute配置,步长和偏移别写错。
- 颜色不对? 检查顶点数据和着色器变量名。
- 只显示一个颜色? 可能attribute没启用或指针没配好。
- 调试建议 :多用
console.log
和gl.getError()
排查问题。
骚话鬼才友情提醒:玩转attribute和varying,WebGL的色彩世界才刚刚开始!
如果觉得有用,记得点赞收藏,骚话王后续还会带来更多WebGL骚操作,咱们下篇再见!
索引绘制:用最少的顶点画出渐变色正方形
骚话鬼才又来升级难度啦!三角形玩明白了,咱们来点进阶操作:用索引绘制(drawElements),只用4个顶点,画出一个带渐变色的正方形!
为什么要用索引绘制?
如果用drawArrays
画正方形,得写6个顶点(两个三角形各3个顶点),有的顶点还得重复写两遍,浪费不说还容易出错。
索引绘制(drawElements
)就像"点名上场",只需把4个顶点列出来,再用索引数组指定三角形组合方式,既省空间又高效。
你可以把索引绘制想象成"演员表+剧本",演员只报一次名,剧本里安排谁上场就行。
第一步:准备顶点和索引数据
每个顶点包含位置(x, y)和颜色(r, g, b):
js
// 4个顶点,顺时针从左上开始
const vertices = new Float32Array([
// 位置 // 颜色
-0.5, 0.5, 1.0, 0.0, 0.0, // 顶点0:左上 红
0.5, 0.5, 0.0, 1.0, 0.0, // 顶点1:右上 绿
0.5, -0.5, 0.0, 0.0, 1.0, // 顶点2:右下 蓝
-0.5, -0.5, 1.0, 1.0, 0.0 // 顶点3:左下 黄
]);
// 索引数组,两个三角形组成正方形
const indices = new Uint16Array([
0, 1, 2, // 三角形1:左上-右上-右下
2, 3, 0 // 三角形2:右下-左下-左上
]);
第二步:着色器源码
和渐变三角形一样,顶点着色器传递颜色,片元着色器插值:
js
const vertexShaderSource = `
attribute vec2 position;
attribute vec3 color;
varying vec3 vColor;
void main() {
gl_Position = vec4(position, 0.0, 1.0);
vColor = color;
}
`;
const fragmentShaderSource = `
precision mediump float;
varying vec3 vColor;
void main() {
gl_FragColor = vec4(vColor, 1.0);
}
`;
第三步:创建缓冲区,上传数据
js
// 创建顶点缓冲区
const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
// 创建索引缓冲区
const indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
第四步:配置attribute
js
const FSIZE = vertices.BYTES_PER_ELEMENT;
const positionLoc = gl.getAttribLocation(program, 'position');
const colorLoc = gl.getAttribLocation(program, 'color');
gl.enableVertexAttribArray(positionLoc);
gl.enableVertexAttribArray(colorLoc);
gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, FSIZE * 5, 0);
gl.vertexAttribPointer(colorLoc, 3, gl.FLOAT, false, FSIZE * 5, FSIZE * 2);
第五步:清屏,索引绘制正方形!
js
gl.clearColor(0.1, 0.1, 0.1, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0);
完整代码一览
js
const canvas = document.getElementById('webgl-canvas');
const gl = canvas.getContext('webgl');
if (!gl) {
alert('你的浏览器不支持WebGL,换个浏览器试试吧!');
}
const vertices = new Float32Array([
-0.5, 0.5, 1.0, 0.0, 0.0,
0.5, 0.5, 0.0, 1.0, 0.0,
0.5, -0.5, 0.0, 0.0, 1.0,
-0.5, -0.5, 1.0, 1.0, 0.0
]);
const indices = new Uint16Array([
0, 1, 2,
2, 3, 0
]);
const vertexShaderSource = `
attribute vec2 position;
attribute vec3 color;
varying vec3 vColor;
void main() {
gl_Position = vec4(position, 0.0, 1.0);
vColor = color;
}
`;
const fragmentShaderSource = `
precision mediump float;
varying vec3 vColor;
void main() {
gl_FragColor = vec4(vColor, 1.0);
}
`;
function createShader(gl, type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
const info = gl.getShaderInfoLog(shader);
gl.deleteShader(shader);
throw new Error('着色器编译失败:' + info);
}
return shader;
}
function createProgram(gl, vertexShader, fragmentShader) {
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
const info = gl.getProgramInfoLog(program);
gl.deleteProgram(program);
throw new Error('程序对象链接失败:' + info);
}
return program;
}
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
const program = createProgram(gl, vertexShader, fragmentShader);
gl.useProgram(program);
const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
const indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
const FSIZE = vertices.BYTES_PER_ELEMENT;
const positionLoc = gl.getAttribLocation(program, 'position');
const colorLoc = gl.getAttribLocation(program, 'color');
gl.enableVertexAttribArray(positionLoc);
gl.enableVertexAttribArray(colorLoc);
gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, FSIZE * 5, 0);
gl.vertexAttribPointer(colorLoc, 3, gl.FLOAT, false, FSIZE * 5, FSIZE * 2);
gl.clearColor(0.1, 0.1, 0.1, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0);
常见坑和骚话王小贴士
- 正方形没画出来? 检查索引数组和顶点顺序,别写错了三角形的拼法。
- 颜色不对/没渐变? 检查attribute配置,步长和偏移要精确。
- 只显示一半? 可能索引数量或类型写错,注意
gl.UNSIGNED_SHORT
和索引数组类型要对应。 - 调试建议 :多用
console.log
和gl.getError()
排查问题。
骚话鬼才友情提醒:索引绘制是WebGL高效渲染的"杀手锏",玩转它,复杂模型也能轻松拿下!