webgl 绘制一个线的最简单流程 WebGL 绘制线段最简完整流程原生无框架整体分 7 大核心步骤附带可直接运行完整代码只画一条直线。一、整体流程总览获取 Canvas 创建 WebGL 上下文编写顶点着色器、片元着色器源码创建、编译、链接着色器程序定义线段顶点数据创建缓冲区并写入数据绑定缓冲区、设置顶点属性读取规则设置视口、清除画布调用绘制函数gl.drawArrays(gl.LINES, 0, 2)二、最简完整可运行代码!DOCTYPE html html body canvas width400 height300 idc/canvas script // 1. 获取画布与WebGL上下文 const canvas document.getElementById(c); const gl canvas.getContext(webgl); if (!gl) throw 不支持WebGL; // 2. 着色器源码 // 顶点着色器接收顶点坐标输出位置 const vsSource attribute vec2 a_Pos; void main() { gl_Position vec4(a_Pos, 0.0, 1.0); } ; // 片元着色器统一线段颜色 const fsSource precision mediump float; void main() { gl_FragColor vec4(1,0,0,1); // 红色 } ; // 工具函数创建编译着色器 function createShader(gl, type, source) { const shader gl.createShader(type); gl.shaderSource(shader, source); gl.compileShader(shader); return shader; } // 3. 编译链接着色器程序 const vShader createShader(gl, gl.VERTEX_SHADER, vsSource); const fShader createShader(gl, gl.FRAGMENT_SHADER, fsSource); const program gl.createProgram(); gl.attachShader(program, vShader); gl.attachShader(program, fShader); gl.linkProgram(program); gl.useProgram(program); // 4. 线段顶点数据2个点构成一条线 // WebGL坐标系[-1,1] 中心为画布中点 const points new Float32Array([ -0.8, -0.5, // 起点 0.8, 0.5 // 终点 ]); // 创建缓冲区并绑定写入数据 const buf gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, buf); gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW); // 5. 绑定顶点属性 const a_Pos gl.getAttribLocation(program, a_Pos); // 每个顶点2个浮点数不偏移步长2*4字节 gl.vertexAttribPointer(a_Pos, 2, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(a_Pos); // 6. 清空画布底色 gl.viewport(0, 0, canvas.width, canvas.height); gl.clearColor(1,1,1,1); // 白色背景 gl.clear(gl.COLOR_BUFFER_BIT); // 7. 绘制线段核心 // LINES每2个点一条线段从第0个点开始共2个点 gl.drawArrays(gl.LINES, 0, 2); /script /body /html三、分步详细解释1. 初始化上下文const canvas document.getElementById(c); const gl canvas.getContext(webgl);WebGL 所有绘图操作都通过gl对象完成。2. 着色器作用顶点着色器处理每个顶点的坐标输出gl_Position裁剪空间坐标范围[-1,1]片元着色器光栅化后每个像素的颜色输出gl_FragColorRGBA 0~13. 程序链接流程创建着色器 → 填入源码 → 编译 → 挂载到程序 → 链接程序 → 使用程序所有绘制前必须gl.useProgram(program)。4. 顶点缓冲区核心存点线段由一组顶点组成数据必须存入 GPU 缓冲区createBuffer创建缓冲对象bindBuffer绑定当前操作缓冲bufferData将 JS 数组上传到 GPU数据类型必须用Float32ArrayWebGL 只识别浮点数组。5. 顶点属性映射vertexAttribPointer(a_Pos, 2, gl.FLOAT, false, 0, 0)参数含义告诉 GPU显存里那块缓冲区的数据该怎么拆成一个个顶点、怎么喂给着色器的 attribute 变量。属性地址单个顶点分量数量xy2数据类型浮点是否归一化坐标不需要 false顶点步长字节数据起始偏移你的缓冲区数据是[-0.8, -0.5, 0.8, 0.5]4 个 Float32每个浮点数占 4 字节。 内存字节排布简化字节0~3-0.8 (x1) 字节4~7-0.5 (y1) 字节8~110.8 (x2) 字节12~150.5 (y2)函数调用gl.vertexAttribPointer(a_Pos, 2, gl.FLOAT, false, 0, 0);参数 1a_Pos属性索引就是你要给谁传数据。 GLSL 里attribute vec2 a_Pos;getAttribLocation拿到它的编号。 告诉 WebGL下面这套解析规则是给a_Pos这个变量用的。参数 22 —— 分量数 size每个顶点要取几个数字给这个 attribute。vec2坐标x、y → 2 个分量填 2如果是 vec3 (x,y,z) 就填 3vec4 (x,y,z,w) 填 4本例一次读 2 个浮点数组成一个顶点坐标。参数 3gl.FLOAT —— 数据类型 type缓冲区里存的是什么类型数字gl.FLOAT32 位浮点数我们用的 Float32Arraygl.UNSIGNED_BYTE/gl.SHORT 等整数类型这里缓冲区是 Float32所以固定写 gl.FLOAT。参数 4false —— 归一化 normalize最难懂只对整数缓冲区生效浮点数直接忽略这个参数。 规则如果缓冲区是整数字节、短整型等normalizetrue GPU 会把整数范围映射到[-1, 1]或[0, 1]例0~255 的颜色字节 → 自动转 0.0~1.0我们这里是 Float32 浮点本身范围就是 -1~1不需要转换直接填 false。你存的坐标-0.8、0.5本身就是浮点不用归一所以 false。参数 50 —— 步长 stride跨多少字节读下一个顶点步长 两个相邻顶点的起始位置相差多少字节。先算我们一个顶点占多少字节1 个顶点 2 个 float 1 float 4 字节 单个顶点字节2 × 4 8字节情况 1数据紧密排列顶点之间没有空隙 数组[x1,y1, x2,y2, x3,y3...] 两个顶点起点相差正好 8 字节 → stride8 WebGL 规定填 0 自动计算紧密排列的步长等价于直接写 8 所以stride0等同于stride8。举个对比例子带空隙不能写 0如果缓冲区里夹杂别的数据比如 [x1,y1,r1,g1,b1, x2,y2,r2,g2,b2...] 一个顶点包含坐标 2float 颜色 3float共 5float20 字节 这时 stride 必须手动填 20不能写 0。参数 60 —— 偏移 offset从缓冲区第几字节开始读第一个顶点GPU 从缓冲区内存的哪个字节开始提取第一个顶点数据。本例数据开头就是 x1,y1没有多余前置数据所以 offset0。举个偏移不为 0 的例子缓冲区数据[无用数字 (4 字节), x1,y1, x2,y2...] 真正顶点数据从第 4 字节才开始offset 就要填整套逻辑串起来代入你的数据gl.vertexAttribPointer(a_Pos, 2, gl.FLOAT, false, 0, 0);翻译给 GPU这套解析规则给a_Pos每个顶点读取 2 个浮点数缓冲区里是 32 位浮点不用归一转换顶点紧密排列自动算步长 8 字节从缓冲区第 0 字节开始读第一个顶点。GPU 自动拆分内存0~7 字节-0.8、-0.5 → 顶点 1 (x,y)8~15 字节0.8、0.5 → 顶点 2 (x,y)改个直观测试手动填 stride 和 offset等价写法效果完全一样gl.vertexAttribPointer(a_Pos, 2, gl.FLOAT, false, 8, 0);这里 stride 直接写计算好的 8和 0 无区别。常见误区总结stride0 不是 “没有步长”是自动计算紧凑排列的步长normalize 只影响整数数组浮点永远 falseoffset 是字节偏移不是数组下标size 是单个 attribute 的分量不是整个顶点所有数据。enableVertexAttribArray启用该属性通道否则读不到数据。6. 视口与清屏gl.viewport映射裁剪空间到画布像素区域gl.clearColor设置清除色gl.clear(gl.COLOR_BUFFER_BIT)清空画布7. 绘制线段关键 APIgl.drawArrays(gl.LINES, 起始索引, 顶点总数)绘制模式三种常用线型gl.LINES每 2 个独立点一条线段两点一组断开gl.LINE_STRIP连续折线点 0-1-2-3 首尾相连gl.LINE_LOOP闭合折线最后一点连回第一点四、扩展画折线示例只需增加顶点数量绘制模式改为LINE_STRIPconst points new Float32Array([ -0.8, -0.5, 0.0, 0.8, 0.8, -0.5 ]); gl.drawArrays(gl.LINE_STRIP, 0, 3);五、常见极简流程记忆口诀取画布 → 写着色器 → 编译链接程序 → 顶点数据存缓冲 → 绑定顶点属性 → 清屏 → drawArrays 画线完整逐行拆解这份 WebGL 绘制直线代码这份代码是原生 WebGL 最简示例在白色画布中间画一条红色直线全程无第三方库只使用底层 WebGL1 标准 API。 整体流程遵循 WebGL 固定流水线获取画布→创建着色器程序→顶点缓冲传坐标→绑定顶点属性→清空画布→绘制。一、HTML 基础结构!DOCTYPE html html body canvas width400 height300 idc/canvas script // JS WebGL逻辑 /script /body /htmlcanvas width400 height300 idc画布元素逻辑分辨率 400×300 像素不是 CSS 样式是画布内部像素尺寸idc用于 JS 获取 DOM 元素script内部是所有 WebGL 图形渲染逻辑。二、JS 代码分段详细讲解1. 获取画布、创建 WebGL 上下文const canvas document.getElementById(c); const gl canvas.getContext(webgl); if (!gl) throw 不支持WebGL;document.getElementById(c)拿到 canvas DOM 对象canvas.getContext(webgl)获取 WebGL1 绘图上下文浏览器会创建 WebGL 状态机所有绘图操作都通过gl对象完成若浏览器不支持 WebGLgl为null抛出报错终止程序2. 两段着色器源码GLSL ES 语言GPU 执行WebGL 渲染必须有两个着色器运行在 GPU 而非 CPU顶点着色器 (vs)处理每个顶点坐标决定顶点位置片元着色器 (fs)处理每个像素决定像素颜色顶点着色器 vsSourceattribute vec2 a_Pos; void main() { gl_Position vec4(a_Pos, 0.0, 1.0); }逐行解释attribute vec2 a_Pos;attribute顶点属性变量从 CPU JS 传入每个顶点的数据vec2二维浮点向量x、y 坐标存储一个点的坐标a_Pos变量名JS 中通过名字找到这个变量void main()着色器入口函数GPU 自动执行gl_Position vec4(a_Pos, 0.0, 1.0);gl_Position内置输出变量必须赋值代表顶点在裁剪空间的坐标WebGL 要求坐标是 4 维向量vec4(x,y,z,w)a_Pos是 vec2 (x,y)补 z0.0、w1.0转成四维坐标WebGL 坐标系规则画布中心(0,0)左右范围[-1, 1]上下范围[-1, 1]左上角不是 (0,0)和 Canvas2D 完全相反。片元着色器 fsSourceprecision mediump float; void main() { gl_FragColor vec4(1,0,0,1); // 红色 }precision mediump float;片元着色器必须声明浮点数精度mediump中等精度平衡性能与画质void main()每个像素都会执行一次gl_FragColor内置输出当前像素最终颜色vec4(R,G,B,A)取值范围 0~11,0,0,1 红、绿 0、蓝 0、不透明纯红色线段3. 工具函数编译单个着色器function createShader(gl, type, source) { const shader gl.createShader(type); gl.shaderSource(shader, source); gl.compileShader(shader); return shader; }WebGL 创建着色器三步标准流程gl.createShader(type)创建着色器容器gl.VERTEX_SHADER顶点着色器类型gl.FRAGMENT_SHADER片元着色器类型gl.shaderSource(shader, source)把 GLSL 字符串源码写入着色器gl.compileShader(shader)GPU 编译 GLSL 代码4. 链接着色器生成可执行渲染程序 programconst vShader createShader(gl, gl.VERTEX_SHADER, vsSource); const fShader createShader(gl, gl.FRAGMENT_SHADER, fsSource); const program gl.createProgram(); gl.attachShader(program, vShader); gl.attachShader(program, fShader); gl.linkProgram(program); gl.useProgram(program);分别编译顶点、片元着色器得到两个 shader 对象gl.createProgram()创建渲染程序容器用来组合两个着色器gl.attachProgram把两个着色器挂载到程序上gl.linkProgram(program)链接阶段处理变量匹配、GPU 指令合并gl.useProgram(program)激活这个渲染程序后续所有绘制都用这套着色器5. 顶点数据 缓冲区 BufferCPU→GPU 传坐标const points new Float32Array([ -0.8, -0.5, // 起点(x-0.8,y-0.5) 0.8, 0.5 // 终点(x0.8,y0.5) ]); const buf gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, buf); gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW);1顶点数组 Float32ArrayFloat32Array32 位浮点数组WebGL 专用数组类型能直接上传 GPU数据结构[点1x,点1y,点2x,点2y]两个点刚好画一条线段坐标遵循 WebGL 标准坐标系2缓冲区操作三步gl.createBuffer()在 GPU 显存创建一块缓冲区gl.bindBuffer(gl.ARRAY_BUFFER, buf)绑定缓冲区到数组缓冲目标ARRAY_BUFFER 专门存放顶点坐标、顶点属性数据gl.bufferData(target, 数据, 使用模式)把 CPU 数组 points 拷贝到 GPU 绑定的 buffer 显存里gl.STATIC_DRAW数据上传后几乎不会修改GPU 优化显存存储6. 绑定顶点属性告诉 GPU 如何解析缓冲区数据const a_Pos gl.getAttribLocation(program, a_Pos); gl.vertexAttribPointer(a_Pos, 2, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(a_Pos);gl.getAttribLocation(program, a_Pos)在链接好的 program 中找到 GLSL 里attribute a_Pos的内存位置返回索引gl.vertexAttribPointer(attrib索引, 分量数, 数据类型, 归一化, 步长, 偏移)参数逐个解释a_Pos属性索引2每个顶点包含 2 个浮点数 (x,y)gl.FLOAT缓冲区里的数据是 32 位浮点数false不需要把数值归一到 0~1 区间仅整型数据才需要 true0步长一个顶点占用字节数0 代表自动计算 (2×4 字节 8 字节)0从缓冲区第 0 字节开始读取数据gl.enableVertexAttribArray(a_Pos)关键启用顶点属性读取不调用这句 GPU 不会读取缓冲区数据画面空白7. 画布视口配置 清空背景gl.viewport(0, 0, canvas.width, canvas.height); gl.clearColor(1,1,1,1); // 白色背景 gl.clear(gl.COLOR_BUFFER_BIT);gl.viewport(x,y,w,h)定义 WebGL 渲染输出区域映射到 canvas 像素0,0,400,300 整个画布全部用于渲染gl.clearColor(R,G,B,A)设置清空时的填充色1,1,1,1 纯白色gl.clear(gl.COLOR_BUFFER_BIT)清空颜色缓冲区把画布全部填充白色8. 核心绘制绘制线段gl.drawArrays(gl.LINES, 0, 2);gl.drawArrays(绘制模式, 起始顶点索引, 顶点总数)gl.LINES绘制模式每两个顶点组成一条独立线段顶点 0、顶点 1 → 一条线顶点 2、顶点 3 → 第二条线以此类推0从缓冲区第 0 个顶点开始渲染2一共读取 2 个顶点执行后 GPU 流水线完整走一遍 读取 2 个顶点 → 顶点着色器计算坐标 → 光栅化生成线段中间所有像素 → 片元着色器给每个像素填充红色 → 输出到画布三、整体执行流程总结CPU→GPU 完整链路获取 canvas初始化 WebGL 上下文编写 GLSL 顶点 / 片元着色器源码编译、链接着色器生成渲染程序并激活在 CPU 定义线段两个顶点坐标数组创建 GPU 缓冲区把顶点数据上传显存绑定顶点属性告诉 GPU 缓冲区数据格式、关联着色器 a_Pos 变量并启用读取设置视口、清空画布为白色gl.drawArrays触发 GPU 渲染画出红色直线四、拓展知识点方便你理解 WebGL 底层坐标系区别 Canvas2D原点左上角y 向下增大 WebGL原点画布中心y 向上增大范围 [-1,1]drawArrays 几种常用模式gl.LINES两点一线gl.LINE_STRIP连续折线点 0-1-2-3 连成一条连续线gl.TRIANGLES每三点绘制三角形Buffer 作用顶点数据放在 GPU 显存避免 CPU 反复传数据大幅提升性能attribute 变量逐顶点数据uniform 全局统一数据varying 顶点→片元插值数据五、修改测试小例子快速验证理解修改顶点数组换成斜线 / 竖线只改points数组即可// 竖直线 const points new Float32Array([ 0, -0.8, 0, 0.8 ]);修改片元着色器颜色改成蓝色gl_FragColor vec4(0,0,1,1);