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高效渲染的"杀手锏",玩转它,复杂模型也能轻松拿下!

相关推荐
掘金者阿豪6 分钟前
把业务数据变成共享仪表盘:Metabase可视化与远程访问实践
前端·后端
kyriewen26 分钟前
折腾了半年 AI 编程工作流,最后发现效率瓶颈是桌上那块屏幕
前端·javascript·ai编程
蜗牛前端1 小时前
codex 全流程开发上线的高颜值礼簿小程序
前端·微信小程序
大龄秃头程序员2 小时前
我在图文流 App 里落地双层缓存、弱网降级与 OOM 治理
前端
老王以为2 小时前
React Renderer 分离的多平台架构
前端·react native·react.js
hunterandroid2 小时前
Kotlin Coroutines 与 Flow:让异步任务更清晰
前端
Bigger2 小时前
从零搭建 AI 代码审查服务:一份前端也能看懂的 Python 学习笔记
前端·ci/cd·ai编程
lichenyang4532 小时前
JSAPI、NAPI、Biz、Imp:ASCF Demo 如何真正调用系统能力和 C++ 能力
前端
lichenyang4533 小时前
IPC、JSVM、UIThread、libuv:ASCF 架构图里最容易混的几个词
前端
用户059540174463 小时前
Redis记忆存储故障恢复测试踩坑实录:手动测试让我漏掉了2个一致性Bug
前端·css