图形学【免费下载链接】webgl-fundamentalsWebGL lessons that start with the basics项目地址https://gitcode.com/gh_mirrors/we/webgl-fundamentals点击查看免费下载本篇教程承接 webgl-fundamentals 项目中的 WebGL 基础教程深入剖析 WebGL 与 GPU 真正的工作方式显卡如何把顶点数据一步步变成屏幕上的彩色像素。读完本篇你将彻底理解gl.drawArrays的调用语义、顶点着色器与片元着色器各自扮演的角色、varying变量逐像素插值的本质以及vertexAttribPointer每个参数含normalize的真实含义并掌握用UNSIGNED_BYTE压缩颜色数据节省 75% 显存带宽的实战技巧。一、WebGL 到底在做什么GPU 的两阶段工作模型很多初学者以为 WebGL 是3D 渲染 API但实际上WebGL 本质上是一个像素化rasterizationAPI。显卡的工作可以粗略地分成两大部分顶点处理阶段处理顶点至少处理它们的属性数据并把它们转换成裁剪空间clipspace中的顶点坐标像素着色阶段根据第一阶段产出的几何为覆盖到的每个像素计算颜色。当你调用gl.drawArrays(gl.TRIANGLES, 0, 9);这里的9表示有 9 个顶点要处理因此总共会渲染 9 个顶点按TRIANGLES模式可组成 3 个三角形。左侧是开发者为程序提供的数据。顶点着色器vertex shader是你用 GLSL 编写的一个函数它会被逐顶点调用一次。你在其中执行若干运算并把当前顶点在裁剪空间中的坐标写入内建输出变量gl_Position显卡取走该值并存入内部存储器。在TRIANGLES模式下每当第一阶段产出 3 个顶点显卡就用它们构成一个三角形找出这三个点覆盖了哪些像素然后逐像素调用片元着色器fragment shader询问这个像素想要什么颜色。片元着色器必须把每个像素的最终颜色写入内建输出变量gl_FragColor。源码佐证项目根目录下的 webgl-fundamentals.html 展示了最简形式——顶点着色器只把a_position原样写入gl_Position片元着色器直接返回固定颜色最终渲染出占满画布的矩形。二、让片元着色器获得更多信息引入 Varying 变量到目前为止的示例里片元着色器能拿到的信息非常少只有一个固定颜色。幸运的是我们可以给它传递更多数据为每个想从顶点着色器传到片元着色器的值定义一个varying变量。2.1 最简单的例子把裁剪空间坐标直接当作颜色我们沿用 2D 矩阵教程 的代码框架把原来的 F 字形换成简单三角形。首先修改几何数据填充一个三角形的顶点// 用定义三角形的数据填充缓冲区 function creerGeometrie(gl) { gl.bufferData( gl.ARRAY_BUFFER, new Float32Array([ 0, -100, 150, 125, -175, 100]), gl.STATIC_DRAW); }接着只渲染 3 个点// 渲染场景 function rendreLaScene() { ... // 渲染几何体。 gl.drawArrays(gl.TRIANGLES, 0, 3); }然后在顶点着色器中声明一个varying变量把数据传给片元着色器varying vec4 v_couleur; ... void main() { // 用矩阵乘上位置 gl_Position vec4((u_matrix * vec3(a_position, 1)).xy, 0, 1); // 从裁剪空间转换到颜色空间 // 裁剪空间范围是 -1.0 到 1.0 // 颜色空间范围是 0.0 到 1.0 v_couleur gl_Position * 0.5 0.5; }在片元着色器中声明同名、同类型的varyingprecision mediump float; varying vec4 v_couleur; void main() { gl_FragColor v_couleur; }WebGL 会把顶点着色器中声明的varying与片元着色器中名字和类型都一致的varying自动关联起来实现数据传递。这个示例的可运行版本就在仓库中webgl-2d-triangle-with-position-for-color.html。实际源码中着色器采用英文命名a_position、u_matrix、v_color逻辑与上文完全一致v_color gl_Position * 0.5 0.5;。拖动页面上的滑块对三角形做平移、旋转和缩放你会发现颜色不会随变换而改变——因为颜色是从裁剪空间坐标计算出来的它们相对于背景是固定的。三、从数据到像素完整追踪一遍数字现在我们停下来思考一个关键问题整个程序只计算了 3 个顶点。顶点着色器只被调用 3 次因此只算出 3 个颜色值。但三角形内部显然有远多于 3 个的像素——这就是为什么我们把这种在着色器之间传递的变量称为varying变化的。WebGL 拿到 3 个顶点各自算出的颜色值后在光栅化三角形的过程中在它们之间做插值interpolate每个像素被调用片元着色器时都会拿到一个针对该像素位置插值后的varying值。让我们以上一节的三顶点三角形为例手动演算一遍完整流程。第一步输入的顶点数据Vertices顶点XY顶点 00-100顶点 1150125顶点 2-175100第二步顶点着色器输出到gl_Position的值顶点着色器应用矩阵完成平移、旋转、缩放并把坐标转换到裁剪空间。默认参数为平移 (200, 150)、旋转 0、缩放 (1, 1)所以实际只发生了一次平移缓冲区尺寸为 400×300即画布大小。矩阵运算对应仓库 webgl/resources/m3.js 中的projection、translate、rotate、scale之后得到裁剪空间中的 3 个顶点gl_PositionXY顶点 00.0000.660顶点 10.750-0.830顶点 2-0.875-0.660第三步写入 varyingv_couleur的值顶点着色器同时把这些值从裁剪空间转换到颜色空间写入v_couleurv_couleurRGBA顶点 00.50000.8300.51顶点 10.87500.0860.51顶点 20.06250.1700.51验证一下换算v_couleur gl_Position * 0.5 0.5例如顶点 0 的 Y 分量0.660 × 0.5 0.5 0.830顶点 1 的 X 分量0.750 × 0.5 0.5 0.875与表中数值完全吻合。第四步插值并交给片元着色器这 3 个写入v_couleur的值会被插值三角形内的每个像素都会拿到插值后的v_couleur再进入片元着色器。于是三角形内部便呈现出从三个顶点颜色平滑过渡的渐变效果——这就是varying一词的由来它的值在整个三角形内是连续变化的。四、进阶用第二个 attribute 把颜色数据送进着色器我们还可以给顶点着色器发送更多数据再经由varying传给片元着色器。例如画一个由 2 个三角形组成的矩形并让它显示两种颜色在顶点着色器中新增一个attribute来接收颜色信息并把它直接转发给片元着色器。顶点着色器改造如下在 webgl-2d-rectangle-with-2-colors.html 中为英文命名版本attribute vec2 a_position; attribute vec4 a_couleur; // 新增颜色属性 ... varying vec4 v_couleur; void main() { ... // 把颜色从 attribute 复制到 varying。 v_couleur a_couleur; }接下来需要在 JavaScript 中为颜色数据建立完整的缓冲区 属性链路// 查询顶点数据的属性位置 var emplacementPosition gl.getAttribLocation(program, a_position); var emplacementCouleur gl.getAttribLocation(program, a_couleur); ... // 为颜色创建缓冲区 var tampon gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, tampon); gl.enableVertexAttribArray(emplacementCouleur); gl.vertexAttribPointer(emplacementCouleur, 4, gl.FLOAT, false, 0, 0); // 填充颜色值 creerCouleurs(gl); ... // 用组成矩形的两个三角形的颜色填充缓冲区 function creerCouleurs(gl) { // 随机挑两种颜色 var r1 Math.random(); var b1 Math.random(); var g1 Math.random(); var r2 Math.random(); var b2 Math.random(); var g2 Math.random(); gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( [ r1, b1, g1, 1, r1, b1, g1, 1, r1, b1, g1, 1, r2, b2, g2, 1, r2, b2, g2, 1, r2, b2, g2, 1]), gl.STATIC_DRAW); }注意这里把同一个三角形的三个顶点都设为同一种颜色所以虽然varying仍然逐像素插值最终每个三角形内部却呈现出均匀的纯色因为三个端点的值相同插值结果处处相同。仓库中的可运行示例为 webgl-2d-rectangle-with-2-colors.html。如果把颜色改成每个顶点各不相同插值效果立刻就显形了// 用组成矩形的两个三角形的颜色填充缓冲区 function creerCouleurs(gl) { // 每个顶点一个随机颜色 gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( [ Math.random(), Math.random(), Math.random(), 1, Math.random(), Math.random(), Math.random(), 1, Math.random(), Math.random(), Math.random(), 1, Math.random(), Math.random(), Math.random(), 1, Math.random(), Math.random(), Math.random(), 1, Math.random(), Math.random(), Math.random(), 1]), gl.STATIC_DRAW); }运行 webgl-2d-rectangle-with-random-colors.html可以看到矩形内部呈现出平滑的颜色渐变——这就是varying插值的直观体现。这个例子虽然视觉上平平无奇但它证明了多属性attribute的使用以及两个着色器之间的数据传递。如果你去看 图像处理示例会发现它们同样使用一个特殊的 attribute 来传递纹理坐标。五、缓冲区Buffer与属性函数到底做了什么缓冲区是显卡内存中存放几何数据的地方三条核心命令各司其职gl.createBuffer()创建一个缓冲区gl.bindBuffer(gl.ARRAY_BUFFER, buffer)把缓冲区绑定到ARRAY_BUFFER绑定点后续操作都作用于该缓冲区gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)把数据放入缓冲区。数据放入缓冲区后还必须告诉 WebGL如何读取这些数据以及各属性要送往顶点着色器的什么位置。首先查询 WebGL 为属性分配的槽位相当于指针// 查询顶点数据的属性位置 var emplacementPosition gl.getAttribLocation(programme, a_position); var emplacementCouleur gl.getAttribLocation(programme, a_couleur);拿到属性位置后还需要提交两条命令gl.enableVertexAttribArray(emplacement);这条命令告诉 WebGL我们希望用缓冲区中的数据来喂给这个属性。gl.vertexAttribPointer( emplacement, // 属性位置 nombreDeComposantes, // 每个顶点该属性的分量个数 typeDeDonnée, // 数据类型 donnéesNormaliséesOuNon, // 是否归一化 tailleDeLaFenêtreDeLecture, // 读取窗口的字节步长stride décalage); // 起始偏移offset这条命令告诉 WebGL使用最近一次gl.bindBuffer激活的缓冲区数据并说明每个顶点包含该属性的几个分量1 到 4 个、数据的类型BYTE、FLOAT、INT、UNSIGNED_SHORT等、读取窗口的字节步长以及第一个分量在缓冲区中的起始偏移。其中分量个数始终介于 1 到 4 之间。如果每种数据各用一个缓冲区那么步长和偏移都可以填 0步长stride为 0 表示采用与数据类型和分量个数相匹配的紧凑布局即每次迭代前进size × sizeof(type)字节偏移offset为 0 表示从缓冲区第一份数据开始读取。传入其他值意味着要在同一个缓冲区中交错存放多种属性这虽然能带来性能收益但复杂度也随之上升除非你在压榨显卡的性能极限否则并不总是值得这么做。六、实战优化normalize 参数与 UNSIGNED_BYTE 颜色压缩vertexAttribPointer的第 4 个参数donnéesNormaliséesOuNonnormalize值得单独展开——它只对非浮点类型即整型数据起作用设为false整型值按原样解释。BYTE范围为 -128 到 127UNSIGNED_BYTE为 0 到 255SHORT为 -32768 到 32767依此类推设为true整型值会被映射到 [-1.0, 1.0] 或 [0.0, 1.0]。例如BYTE-128~127被映射到 -1.0 ~ 1.0UNSIGNED_BYTE0~255被映射到 0.0 ~ 1.0SHORT同样映射到 -1.0 ~ 1.0但精度高于BYTE。归一化最常见的应用场景就是颜色。颜色值通常在 0.0 到 1.0 之间如果每个顶点的 R、G、B、A 四个分量都用完整FLOAT表示则每个顶点每种颜色要占用 16 字节对大型几何体来说内存开销相当可观。改用UNSIGNED_BYTE后0 对应 0.0、255 对应 1.0每种颜色只需 4 字节省下 75% 的内存。改造后的代码分两步。第一步告诉 WebGL 按归一化的无符号字节读取颜色gl.vertexAttribPointer(emplacementCouleur, 4, gl.UNSIGNED_BYTE, true, 0, 0);第二步用Uint8Array填充颜色缓冲区注意Math.random() * 256产生的值在存入Uint8Array时会被截断为整数// 用组成矩形的两个三角形的颜色填充缓冲区 function creerCouleurs(gl) { // 随机挑两种颜色 var r1 Math.random() * 256; // 0 到 255.99999 var b1 Math.random() * 256; // 这些值在放入 var g1 Math.random() * 256; // Uint8Array 时 var r2 Math.random() * 256; // 会被截断 var b2 Math.random() * 256; var g2 Math.random() * 256; gl.bufferData( gl.ARRAY_BUFFER, new Uint8Array( // 使用 Uint8Array [ r1, b1, g1, 255, r1, b1, g1, 255, r1, b1, g1, 255, r2, b2, g2, 255, r2, b2, g2, 255, r2, b2, g2, 255]), gl.STATIC_DRAW); }完整可运行示例见 webgl-2d-rectangle-with-2-byte-colors.html其源码中gl.vertexAttribPointer(colorLocation, 4, gl.UNSIGNED_BYTE, true, 0, 0)与setColors函数内的Uint8Array填充逻辑相互印证。七、总结与下一步至此WebGL 渲染一条完整的数据链路已经清晰缓冲区createBufferbindBufferbufferData存放原始几何数据属性getAttribLocationenableVertexAttribArrayvertexAttribPointer描述 WebGL 如何从缓冲区读取数据并交给顶点着色器顶点着色器逐顶点执行把位置写入gl_Position裁剪空间把需要传给片元着色器的数据写入varying光栅化与插值显卡用每 3 个顶点构成三角形把varying值逐像素插值片元着色器逐像素执行把插值后的varying加工成最终颜色写入gl_FragColor。理解这套流水线是继续深入学习的基础。接下来推荐阅读 着色器与 GLSL进一步掌握 GLSL 语言本身的语法与能力边界相关的可运行示例都集中在 webgl 目录 下配套的 2D 矩阵数学库实现可参见 webgl/resources/m3.js着色器编译链接与画布尺寸管理等公共代码见 webgl/resources/webgl-utils.js。赞分享图形学【免费下载链接】webgl-fundamentalsWebGL lessons that start with the basics项目地址https://gitcode.com/gh_mirrors/we/webgl-fundamentals点击查看免费下载相关推荐Sigma.js的WebGL渲染原理顶点着色器与片元着色器深度剖析Sigma.js的WebGL渲染原理顶点着色器与片元着色器深度剖析 Sigma.js 是一个专注 图可视化 的 JavaScript 库能够借助 WebGL数据可视化前端图形学WebGL 的 Shader 与 GLSL 全面解析顶点着色器、片段着色器与着色器语言实战指南WebGL 的 Shader 与 GLSL 全面解析顶点着色器、片段着色器与着色器语言实战指南 导读 本文是 webgl fundamentals 开源课程系图形学WebGL 着色器与 GLSL 完全指南顶点着色器、片段着色器与着色器语言详解WebGL 着色器与 GLSL 完全指南顶点着色器、片段着色器与着色器语言详解 本文是 WebGL 基础 https://link.gitcode.com/i图形学创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考