WebGL 详解part5:传入颜色信息,绘制渐变色三角形

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.loggl.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.loggl.getError()排查问题。

骚话鬼才友情提醒:索引绘制是WebGL高效渲染的"杀手锏",玩转它,复杂模型也能轻松拿下!

相关推荐
opbr16 分钟前
🚀 告别传统枚举的痛点!TypeScript 中的 constructEnum 让你的代码更优雅 ✨
前端·javascript
鬼鬼_静若为竹16 分钟前
今天在增加了攻击小马的功能
前端
遂心_18 分钟前
React性能优化实战:掌握memo、useCallback与useMemo的精妙用法
前端·javascript·react.js
CodePlayer19 分钟前
《学会提问》读后感
前端·程序员
是晓晓吖19 分钟前
源网站数据采集方案之解析DOM(五)
前端
FogLetter20 分钟前
CSS模块化:告别样式冲突,拥抱组件化开发
前端·react.js
angelQ21 分钟前
针对"@antfu/eslint-config": "^4.17.0"最新版本使用报错Unexpected token 'with'的解决方法
前端·eslint
小飞机噗噗噗21 分钟前
记录js使用原生 XMLHttpRequest方法 封装http请求,并提供使用实例
前端·javascript·react.js
用户25191624271123 分钟前
Canvas之绘制图形后续
前端·javascript·canvas
CodePlayer29 分钟前
《有限与无限的游戏》——读后感
前端·稀土