WebGL Fundamentals:二维图形缩放(2D Scale)实战详解
图形学【免费下载链接】webgl-fundamentalsWebGL lessons that start with the basics项目地址https://gitcode.com/gh_mirrors/we/webgl-fundamentals点击查看免费下载本教程出自 webgl-fundamentals 开源仓库的 WebGL 基础系列日语版见 webgl/lessons/ja/webgl-2d-scale.md英文版见 webgl/lessons/webgl-2d-scale.md讲解如何在 WebGL 中对二维图形进行放大与缩小。读完本文你将掌握在顶点着色器中通过一个uniform vec2 u_scale与顶点坐标相乘实现缩放的核心思路、对应的 JavaScript 侧 uniform 状态管理完整代码以及负倍率带来的镜像翻转现象并为后续用矩阵统一三种变换打好基础。教程定位WebGL 基础系列中的「缩放」篇缩放Scale是 webgl-fundamentals 二维变换三连击平移 → 旋转 → 缩放的收官篇。该系列自 WebGL 的入门基础 开始前两篇分别是二维图形的平移与二维图形的旋转。本教程在此基础上继续演进最终引出下一篇文章二维矩阵数学——用矩阵把平移、旋转、缩放三种变换统一成更简单通用的形式。与前两篇一脉相承本篇依然采用「在顶点着色器中变换顶点、在 JavaScript 中通过 uniform 传入参数」的架构顶点数据只上传一次每次绘制只需更新 uniform 值无需在 CPU 侧重新计算顶点坐标。这正是平移教程末尾强调的核心思想——几何体哪怕有成千上万个顶点JavaScript 每帧也只需设置几个 uniform 值剩下的全部交给 GPU。缩放的核心思想把顶点坐标乘以倍率缩放与平移同样简单核心只有一句话把顶点坐标乘以缩放倍率。平移是对坐标做加法a_position u_translation而缩放是对坐标做乘法a_position * u_scale。这里u_scale是一个vec2分别代表 X 轴和 Y 轴的缩放倍率u_scale (1, 1)时图形大小不变乘以 1 等于原值u_scale (2, 2)时图形在两个方向都放大一倍u_scale (0.5, 0.5)时图形缩小为一半两个分量可以不同例如(2, 1)实现只在水平方向拉宽的「压扁」效果。注意 GLSL 中两个vec2直接相乘是逐分量乘法component-wise multiply因此a_position * u_scale等价于(a_position.x * u_scale.x, a_position.y * u_scale.y)与矩阵乘法的含义不同。缩放发生在像素空间从仓库中的完整示例 webgl/webgl-2d-geometry-scale.html 可以看到缩放是在顶点坐标尚未转换到裁剪空间clip space之前进行的。此时坐标仍处于「像素单位」的本地空间即教程中u_resolution对应的坐标系所以倍率 2 意味着图形在屏幕上占据两倍的像素尺寸。这一点与 3D 世界中通常以模型空间为单位、再叠加观察/投影变换的缩放有所不同但在 2D 教程阶段它是最直观的。顶点着色器的完整实现在上一篇旋转示例的基础上着色器的改动非常小新增一个uniform vec2 u_scale并在旋转之前先对坐标做缩放。仓库中 webgl/webgl-2d-geometry-scale.html 的完整顶点着色器如下script idvertex-shader-2d typex-shader/x-vertex attribute vec2 a_position; uniform vec2 u_resolution; uniform vec2 u_translation; uniform vec2 u_rotation; uniform vec2 u_scale; void main() { // 先缩放坐标 vec2 scaledPosition a_position * u_scale; // 再旋转坐标 vec2 rotatedPosition vec2( scaledPosition.x * u_rotation.y scaledPosition.y * u_rotation.x, scaledPosition.y * u_rotation.y - scaledPosition.x * u_rotation.x); // 最后叠加平移 vec2 position rotatedPosition u_translation; // 像素坐标 → 0~1 vec2 zeroToOne position / u_resolution; // 0~1 → 0~2 vec2 zeroToTwo zeroToOne * 2.0; // 0~2 → -1~1裁剪空间 vec2 clipSpace zeroToTwo - 1.0; gl_Position vec4(clipSpace * vec2(1, -1), 0, 1); } /script新增的只有两处其余来自前两篇uniform vec2 u_scale; // Scale the position vec2 scaledPosition a_position * u_scale;变换顺序缩放 → 旋转 → 平移着色器内依次执行缩放、旋转、平移三个步骤其中旋转的计算直接复用了scaledPosition而非原始a_position。这意味着缩放先于旋转发生图形先在自身坐标轴上被拉伸再整体旋转。如果交换顺序先旋转再缩放结果会完全不同——这正是矩阵篇中要重点讨论的「变换顺序决定结果」问题的雏形详见二维矩阵数学中对「先缩放后旋转再平移」与「先平移后旋转再缩放」两种顺序的对比说明。片段着色器保持不变片段着色器无需任何改动仍然只是输出一个uniform vec4 u_color的纯色precision mediump float; uniform vec4 u_color; void main() { gl_FragColor u_color; }JavaScript 端三个增量改动对应着色器新增的 uniformJavaScript 侧需要完成三件事查询 uniform 位置、维护缩放状态、每帧上传数值。1. 查询 uniform 位置在程序链接近成功后通过gl.getUniformLocation拿到u_scale的地址var scaleLocation gl.getUniformLocation(program, u_scale);2. 初始化缩放状态默认值为[1, 1]表示初始不缩放乘以 1 等于原值var translation [100, 150]; var rotation [0, 1]; var scale [1, 1]; var color [Math.random(), Math.random(), Math.random(), 1];3. 每帧设置 uniform在drawScene()中与平移、旋转并列调用gl.uniform2fv将两个浮点数一次性上传给vec2类型的u_scale。仓库中完整的drawScene如下见 webgl/webgl-2d-geometry-scale.htmlfunction drawScene() { webglUtils.resizeCanvasToDisplaySize(gl.canvas); // 设置 WebGL 视口从裁剪空间映射到像素空间 gl.viewport(0, 0, gl.canvas.width, gl.canvas.height); // 清空画布 gl.clear(gl.COLOR_BUFFER_BIT); // 使用当前着色器程序 gl.useProgram(program); // 打开顶点属性数组 gl.enableVertexAttribArray(positionLocation); // 绑定顶点缓冲 gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); // 告诉属性如何从缓冲区取数据 var size 2; // 每次迭代读取 2 个分量 var type gl.FLOAT; // 数据是 32 位浮点数 var normalize false; // 不归一化 var stride 0; // 0 每次按 size * sizeof(type) 前进 var offset 0; // 从缓冲区头部开始 gl.vertexAttribPointer(positionLocation, size, type, normalize, stride, offset); // 设置分辨率像素 → 裁剪空间转换参数 gl.uniform2f(resolutionLocation, gl.canvas.width, gl.canvas.height); // 设置颜色 gl.uniform4fv(colorLocation, color); // 设置平移 gl.uniform2fv(translationLocation, translation); // 设置旋转 gl.uniform2fv(rotationLocation, rotation); // 设置缩放 ← 本篇新增 gl.uniform2fv(scaleLocation, scale); // 绘制几何体 var primitiveType gl.TRIANGLES; var offset 0; var count 18; // F 由 6 个三角形组成每个三角形 3 个顶点 gl.drawArrays(primitiveType, offset, count); }需要强调的是setGeometry(gl)只在初始化时调用一次把 F 的 18 个顶点上传到 GPUdrawScene中不再触碰顶点数据。每次拖动滑杆只是修改 JavaScript 数组、调用gl.uniform2fv重新上传 uniform随后gl.drawArrays即可让 GPU 用新倍率重绘——这正是「顶点上传一次、变换全部在着色器完成」架构的优势。交互示例与滑杆配置教程配套的可运行示例是 webgl/webgl-2d-geometry-scale.html页面描述为 Drag sliders to translate, rotate, and scale.可直接在浏览器中打开体验。它借助仓库工具 webgl/resources/webgl-lessons-ui.js 中的webglLessonsUI.setupSlider生成滑杆 UIwebglLessonsUI.setupSlider(#x, {value: translation[0], slide: updatePosition(0), max: gl.canvas.width }); webglLessonsUI.setupSlider(#y, {value: translation[1], slide: updatePosition(1), max: gl.canvas.height}); webglLessonsUI.setupSlider(#angle, {slide: updateAngle, max: 360}); webglLessonsUI.setupSlider(#scaleX, {value: scale[0], slide: updateScale(0), min: -5, max: 5, step: 0.01, precision: 2}); webglLessonsUI.setupSlider(#scaleY, {value: scale[1], slide: updateScale(1), min: -5, max: 5, step: 0.01, precision: 2});注意 X/Y 轴缩放滑杆的配置参数min: -5, max: 5倍率取值范围为 -5 到 5刻意放开到负数以便演示镜像翻转step: 0.01, precision: 2步进 0.01、显示两位小数允许精细调节平移与旋转滑杆则分别限定在画布尺寸内max: gl.canvas.width/height和 0~360 度。回调函数updateScale本质上是闭包把滑杆值写入对应轴再触发重绘function updateScale(index) { return function(event, ui) { scale[index] ui.value; drawScene(); }; }负倍率镜像翻转教程特别提醒一个观察点当缩放倍率为负数时图形会发生镜像翻转。例如u_scale (-1, 1)会让图形沿 Y 轴左右镜像X 坐标全部取反u_scale (1, -1)会上下翻转两者都为负则相当于绕原点旋转 180°。从数学上看scaledPosition a_position * u_scale中负分量自然地把对应轴坐标取反因此翻转是乘法的直接结果无需任何额外代码。这也解释了为何示例滑杆范围允许取到 -5——你可以亲手拖动scaleX滑杆跨过 0 点观察 F 在某一瞬间被压成一条线然后翻转到镜像位置的过程。从手写缩放走向矩阵数学本教程把缩放写死在着色器里但正如系列所强调的缩放、旋转、平移三种变换互相依赖顺序每次调整顺序都要改写着色器非常不便。下一篇二维矩阵数学将展示如何用 3×3 矩阵把三种变换统一起来——届时缩放矩阵的构建正是本篇思路的推广。在矩阵体系中缩放被表示为一个对角矩阵仓库中的矩阵库 webgl/resources/m3.js 提供了现成实现scaling函数见该文件 L259-L273function scaling(sx, sy, dst) { dst dst || new MatType(9); dst[0] sx; dst[1] 0; dst[2] 0; dst[3] 0; dst[4] sy; dst[5] 0; dst[6] 0; dst[7] 0; dst[8] 1; return dst; }其中sx、sy与本篇的u_scale.x、u_scale.y一一对应配套的scale(m, sx, sy)函数则是「矩阵 × 缩放矩阵」的快捷方式同文件 L284-L286。在矩阵方案下顶点着色器退化为一行gl_Position vec4((u_matrix * vec3(a_position, 1)).xy, 0, 1);缩放、旋转、平移全部由 JavaScript 侧按需组合矩阵实现且调整变换顺序不再需要改写任何 GLSL 代码。附注为什么示例图形用字母 F教程的底栏补充了一个经典设计决策为什么所有示例都用字母 F原文webgl/lessons/ja/webgl-2d-scale.md 底栏给出的理由是F 的朝向在任何方向下都能一眼辨认这是调试变换效果的关键。用爱心 ❤ 或三角形 △水平翻转后无法分辨它们具有对称性用圆形 ○ 更糟旋转与否几乎看不出来用四色矩形理论上可行但必须记住哪个角是什么颜色徒增记忆负担F 的左竖、上横、中横结构使其在缩放、旋转、镜像后依然可辨识方向方便即时验证变换是否正确。配套的方向示意图为 webgl/resources/f-orientation.svg。教程作者表示任何「方向可辨识」的图形都适用只是自第一次见到有人用 F 做纹理示例后便一直沿用至今。小结二维缩放的本质极其简单——a_position * u_scale一个乘法即可完成但它承载了两个重要观念一是「顶点只上传一次、变换交给着色器 uniform」的 GPU 友好架构二是变换顺序对结果的显著影响。前者在本系列中一以贯之后者则直接引出了下一站用矩阵数学统一平移、旋转与缩放三种变换为进入三维世界做好准备。若想继续深入可以继续阅读二维矩阵数学或回顾平移与旋转两篇建立完整认知。赞分享图形学【免费下载链接】webgl-fundamentalsWebGL lessons that start with the basics项目地址https://gitcode.com/gh_mirrors/we/webgl-fundamentals点击查看免费下载相关推荐WebGL 二维缩放Scale实战在顶点着色器中使用 u_scale 实现像素级缩放变换WebGL 二维缩放Scale实战在顶点着色器中使用 u_scale 实现像素级缩放变换 本篇是 webgl fundamentals 系列「WebGL图形学webgl-fundamentals 实战WebGL 2D 矩阵Matrix完全指南——从平移旋转缩放到投影矩阵webgl fundamentals 实战WebGL 2D 矩阵Matrix完全指南——从平移旋转缩放到投影矩阵 本文是 webgl fundamenta图形学如何把QuickJS嵌入到你的C项目中C API嵌入开发8步完整教程如何把QuickJS嵌入到你的C项目中C API嵌入开发8步完整教程 QuickJS 是一个小巧、可嵌入的 JavaScript 引擎本教程带你用 C AP上一篇nodebestpractices 通用 Docker 最佳实践从 COPY/ADD 取舍到镜像完整性校验的六条守则下一篇如何通过APK安装器在Windows上原生运行安卓应用创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

COMSOL多物理场耦合实现三维材料损伤模拟

COMSOL多物理场耦合实现三维材料损伤模拟

1. 项目背景与核心价值在工程仿真领域,材料损伤模拟一直是极具挑战性的课题。传统有限元软件往往只能实现简单的线性损伤模型,而COMSOL Multiphysics凭借其强大的多物理场耦合能力和用户自定义功能,为复杂损伤行为的精确模拟提供了可能。这个…

2026/10/13 8:17:34 阅读更多 →
城市通勤轮胎选择指南:性能平衡与安全考量

城市通勤轮胎选择指南:性能平衡与安全考量

1. 城市通勤轮胎选择的核心考量城市道路环境对轮胎的考验远比我们想象中复杂。柏油路面、水泥接缝、减速带、井盖、施工路段碎石......这些日常通勤中频繁遇到的路况,都在无声地考验着轮胎的性能边界。作为每天与地面直接接触的部件,轮胎的选择直接关系到…

2026/10/13 8:17:34 阅读更多 →
Schema标记实战:用结构化数据占据整个SERP

Schema标记实战:用结构化数据占据整个SERP

1. "占据整个SERP"到底是怎么回事:从一次搜索结果截图说起先别急着上代码,我们得先搞清楚一个问题:所谓"占据整个SERP",到底长什么样?去年我接手了一个垂直电商的小项目,卖的是某种小众…

2026/10/13 8:16:34 阅读更多 →

最新新闻

跨境电商独立站运营实战:从选品到流量获取

跨境电商独立站运营实战:从选品到流量获取

1. 项目背景与市场机会跨境电商独立站近年来成为许多创业者的首选赛道,相比平台电商模式,独立站具有更高的利润空间和品牌溢价能力。根据行业数据显示,成功运营的独立站平均毛利率可达50-70%,远高于亚马逊等第三方平台的15-25%。这…

2026/10/13 8:59:49 阅读更多 →
分布式探针Agent-Reach:端到端网络可达性监控的架构与实践

分布式探针Agent-Reach:端到端网络可达性监控的架构与实践

1. 项目概述1.1 核心需求解析做运维或者后端开发的朋友一定不陌生,线上系统出问题十次有八次是从"某个服务连不上"开始的。尤其这套架构一旦铺到微服务、多云多机房、边缘节点这种分布式环境里,节点之间互相依赖、调用链路盘根错节&#xff0c…

2026/10/13 8:59:49 阅读更多 →
高可靠性继电器选型指南:从厂家资质到寿命验证的完整要点

高可靠性继电器选型指南:从厂家资质到寿命验证的完整要点

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 8:59:49 阅读更多 →
计算机网络概述:不是入门课,而是数据交付的元逻辑

计算机网络概述:不是入门课,而是数据交付的元逻辑

1. 为什么“计算机网络01——概述”不是入门课,而是整座知识大厦的地基?你有没有试过:学完“OSI七层模型”,能默写出每一层名字和典型协议,但一看到Wireshark里密密麻麻的TCP重传包就发懵;背熟了“子网掩码…

2026/10/13 8:59:49 阅读更多 →
IP地址库离线解析实战:从2019快照到批量归属查询

IP地址库离线解析实战:从2019快照到批量归属查询

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 8:59:49 阅读更多 →
Python Tkinter GUI开发全指南:从窗口搭建到打包发布

Python Tkinter GUI开发全指南:从窗口搭建到打包发布

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 8:58:45 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 21:05:09 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 14:48:49 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →