跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
JavaScript大前端

浅谈 WebGL 中 gl.viewport() 的原理、用法与场景

gl.viewport() 用来设置 WebGL 的视口,把 NDC 坐标映射到画布上的具体像素区域。文章说明了它的参数含义、与投影矩阵的分工关系,以及在全屏渲染、局部区域绘制、多视口分屏和窗口 resize 自适应中的常见用法,并给出一个随窗口变化动态更新画布与视口的示例。

john1007发布于 2025/1/21更新于 2026/10/2572 浏览
浅谈 WebGL 中 gl.viewport() 的原理、用法与场景

在 WebGL 里,gl.viewport() 是一个很基础、却经常被忽略的函数。它决定了绘制结果最终落在画布的哪个区域,以及这个区域有多大。只要你在做窗口自适应、多视口渲染,或者单纯想把画面准确地映射到屏幕上,都会绕不开它。

gl.viewport() 的基本原理

WebGL 的绘制并不是直接在屏幕坐标里进行的。顶点经过一系列变换后,会落到一个标准化设备坐标系(NDC)中,它的范围是 [-1, 1]。接下来,GPU 会把这个坐标映射到真正的画布像素区域,这一步就是视口变换,而 gl.viewport() 正是在做这件事。

换句话说,投影矩阵负责把世界坐标压缩到 NDC,gl.viewport() 再把 NDC 映射到画布上的具体像素位置。两者配合起来,最终图像才会准确显示在我们看到的区域里。

gl.viewport() 的参数含义

gl.viewport() 的调用形式很简单:

gl.viewport(x, y, width, height);
  • x、y:视口左下角的起点位置,单位是像素。
  • width、height:视口的宽高,单位同样是像素。

这里有个细节要记住:WebGL 里的 (x, y) 指的是左下角,这一点和很多 2D 画布或 DOM 布局习惯并不一样,调试时很容易在这里绕进去。

常见使用方式

默认情况:让视口覆盖整个画布

大多数场景下,你只需要让视口和画布大小一致。比如画布是 800x600,通常就会这样设置:

const canvas = document.getElementById('myCanvas');
const gl = canvas.getContext('webgl');

gl.viewport(0, 0, canvas.width, canvas.height);

这表示渲染内容会填满整个画布,没有额外偏移。

只渲染画布中的一部分区域

如果你希望把图像限制在画布的某个子区域里,直接调整视口的宽高就行。比如:

gl.viewport(50, 50, 400, 300);

这段代码会把渲染区域限制在从 (50, 50) 开始、宽 400、高 300 的矩形内。常见用途包括分屏界面、局部预览窗口、调试视图等。

多视口渲染

在游戏、可视化大屏或编辑器里,多视口并不少见。思路也很直接:每次绘制前先切换视口,再绘制对应内容。

// 左上角区域
gl.viewport(0, 0, canvas.width / 2, canvas.height / 2);
drawSceneLeftTop();

// 右上角区域
gl.viewport(canvas.width / 2, 0, canvas.width / 2, canvas.height / 2);
drawSceneRightTop();

// 左下角区域
gl.viewport(0, canvas.height / 2, canvas.width / 2, canvas.height / 2);
drawSceneLeftBottom();

// 右下角区域
gl.viewport(canvas.width / 2, canvas.height / 2, canvas.width / 2, canvas.height / 2);
drawSceneRightBottom();

这种写法的关键不在于'调用了几次',而在于每次切换视口后,后续绘制都会被限制在当前区域里。只要场景组织得当,就能很自然地实现多窗口、多角度或分区展示。

窗口变化时动态调整视口

WebGL 项目里最常见的坑之一,就是画布大小变了,视口却没跟着更新。结果通常就是画面拉伸、裁切,或者显示区域和实际窗口不一致。

如果你的页面需要响应窗口尺寸变化,建议在 resize 事件里同时更新画布和视口:

window.addEventListener('resize', function () {
  canvas.width = window.innerWidth;
  canvas.height = window.innerHeight;
  gl.viewport(0, 0, canvas.width, canvas.height);
});

这样处理之后,画布尺寸一变,渲染区域也会同步调整,视觉上会稳定很多。

gl.viewport() 和其他变换的关系

很多人第一次接触 WebGL 时,会把'投影矩阵'和'视口'混在一起理解。实际上它们分工很明确:

  • 投影矩阵负责把三维空间中的坐标变换到 NDC。
  • gl.viewport() 负责把 NDC 映射到画布上的像素区域。

如果投影矩阵设置得不对,画面可能本身就变形;如果视口设置得不对,图像即便内容正确,也可能显示在错误的位置,或者只占据画布的一小块。

另外,gl.clear() 清除的是当前上下文的缓冲区内容,而视口会影响你看到的绘制区域。实际开发里,通常是在设置好视口后再清屏、再绘制,这样逻辑更清楚,也更不容易出错。

一个完整的动态视口示例

下面这个例子把画布尺寸和视口绑定在一起。代码不复杂,但它很接近实际项目里的常见写法。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>WebGL Viewport Example</title>
  <style>
    body {
      margin: 0;
    }

    canvas {
      display: block;
    }
  </style>
</head>
<body>
  <canvas id="myCanvas"></canvas>
  <script>
    const canvas = document.getElementById('myCanvas');
    const gl = canvas.getContext('webgl');

    // 画布大小变化时,同步更新视口
    function resizeCanvas() {
      canvas.width = window.innerWidth;
      canvas.height = window.innerHeight;
      gl.viewport(0, 0, canvas.width, canvas.height);
    }

    // 初始设置
    resizeCanvas();

    // 监听窗口变化
    window.addEventListener('resize', resizeCanvas);

    function drawScene() {
      gl.clearColor(0.0, 0.0, 0.0, 1.0);
      gl.clear(gl.COLOR_BUFFER_BIT);

      // 这里放具体的渲染逻辑
    }

    function render() {
      drawScene();
      requestAnimationFrame(render);
    }

    render();
  </script>
</body>
</html>

这个例子的重点不在于'画了什么',而在于它展示了一条很实用的链路:画布尺寸变化 → 视口同步更新 → 渲染内容始终和窗口一致。做 WebGL 项目时,这条链路越稳定,后面遇到的布局问题就越少。

小结

gl.viewport() 的作用可以概括为一句话:它把 WebGL 的输出范围精确地落到画布的某一块区域上。对于默认全屏绘制、多视口布局、窗口自适应这几类场景,它都很关键。

如果你在项目里看到'画面位置不对''渲染结果被裁掉''窗口缩放后显示异常'这类问题,除了检查投影矩阵,也要第一时间看看视口是不是忘了更新。很多 WebGL 的显示问题,根源其实都藏在这里。

目录

  1. gl.viewport() 的基本原理
  2. gl.viewport() 的参数含义
  3. 常见使用方式
  4. 默认情况:让视口覆盖整个画布
  5. 只渲染画布中的一部分区域
  6. 多视口渲染
  7. 窗口变化时动态调整视口
  8. gl.viewport() 和其他变换的关系
  9. 一个完整的动态视口示例
  10. 小结

更多推荐文章

查看全部
  • Rembg WebUI 主题定制与功能扩展实战
  • C++ chrono 库详解:steady_clock 与 duration 原理及实战
  • Java 线程安全核心:synchronized、volatile 与 wait/notify
  • 动态规划时间复杂度和空间复杂度计算方法
  • C++ Vector 容器操作与简单实现
  • Parsera 异步编程教程:提升网页抓取效率
  • CTF Web 入门:文件上传漏洞绕过技巧
  • VR、具身智能与人形机器人:构建现实世界的智能接口
  • 滑动窗口实战:串联所有单词与最小覆盖子串解析
  • Linux 系统编程:一切皆文件原理与缓冲区机制实战
  • C++ STL 无序容器 unordered_set 与 unordered_map 模拟实现
  • 文心大模型 4.5 开源部署指南:架构解析与实战落地
  • baoyu-skills: Claude Code 高效工作流技能集
  • Coze 低代码 AI 智能体平台:100 个落地用途与发布使用指南
  • STM32H743 用定时器触发 ADC 和 DMA 做多通道采样
  • 零样本分类 WebUI 操作指南与结果可视化
  • Linux 基础 IO 系列(二):C 语言标准库 IO 接口实战
  • VMware Workstation Pro 25H2 Linux 版:新命名体系与内核深度适配
  • Java 数组全面解析:定义、内存与工具类
  • Coze(扣子)全解析:100 个落地用途与发布使用指南

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online