02-从硬件说起WebGL

1-CPU和GPU

学习Webgl,必须要知道计算机里的2种处理器:

  • CPU(中央处理器):电脑总指挥官 + 通用运算核心
  • GPU(图形处理器):并行计算加速器

CPU和GPU 都属于处理器芯片。

CPU 相当于公司的资深全能主管,一个公司只有几个,能力极强,可处理大型事务,串行工作,但由于人数较少,处理的事务不宜太多。

GPU相当于上千个流水线工人,会并行重复做同一套简单工序;任务单一、重复时效率极高;一旦工序复杂、每个人步骤不一样,就会严重卡顿。

Web前端的js 就是通过CPU运行的,shader 是通过GPU 运行的。

shader 面向的是像素,一张画布会有很多像素,通常需要快速渲染,若像CPU 那样一个个计算会很慢,所以适合GPU的海量、轻量、并行运算。

2-显卡

显卡是一整块电路板,包含:

  1. GPU 核心
  2. 供电电路、散热风扇、PCB 板、视频输出接口
  3. 若显卡是独显,还会有独立显存(VRAM)。

根据显存的来源,显卡分三类:

  • 独立显卡(独显):自带专属物理显存 VRAM。
  • CPU 核显(现在轻薄本、新机主流):无独立 VRAM,挪用系统内存。
  • 老式主板集显(十几年前老电脑):同样挪用内存,和核显原理近似。

现在NVIDIA 的显卡都是独显,比如RTX 4060、RTX 4090、GTX 1660、T 系列。

NVIDIA 的显卡具备以下特点:

  • 自带独立显存 VRAM;
  • 单独芯片、独立供电、单独 PCB 板;
  • 性能强,主打游戏、渲染、AI 运算。

3-数据的存储

CPU和GPU 所处理的数据是需要硬件存储的。

GPU 的数据存储

GPU 的数据是存在显存里的。

上面说了,只有独显才有独立的显存,若显卡是其它类型,那么显存会挪用系统内存。

CPU 的数据存储

硬盘(永久存储)→ 内存(临时存放)→ CPU 缓存 → CPU 核心计算

  1. 打开软件时,系统把程序从硬盘加载到内存;
  2. CPU 不会直接读取整根内存,先把要用的数据拷贝到CPU 一级 / 二级 / 三级缓存(离运算核心最近、速度最快);
  3. CPU 核心从缓存取数据运算,算完写回缓存,再同步回内存;
  4. 关闭软件 / 断电,内存数据清空,硬盘保留文件。

在webgl 里,我们经常会说把CPU 里的attribute 数据上传到GPU。这里的CPU 就是CPU 缓冲,GPU 指的时GPU显存里的buffer。

当然,我们也可以说把内存里的attribute 数据上传到GPU,这都没问题。

RAM和VRAM 间的数据交互

VRAM是 GPU 存放大规模数据的主力;RAM是GPU 的 "外部仓库"。

独立显卡不能直接访问主机内存,但两者可以相互间接访问数据:

  1. 数据上传:CPU 把图片、模型、文件从内存拷贝到显存,GPU 才能计算;
  2. 数据回传:GPU 算完画面 / 结果,从显存拷贝回内存,再交给显示器 / CPU 保存;
  3. 统一内存(CUDA Unified Memory)

软件层面打通内存 + 显存,自动迁移数据,但底层依然要拷贝,不会消除传输开销。

4-以硬件为锚点思考WebGL

根据硬件规则,我们可以确定2个锚点:

  • 执行

    • CPU 执行的程序,如html+css+js
    • GPU:shader program 的执行
  • 存储

    • 内存存储,如js数据
    • 显存存储,如webgl 的Buffer对象

接下来我们用WebGL 画一个三角形,看一下其中的执行和存储操作。

5-WebGL示例

我们可以依托在three.js 项目,建立一个examples/webgl_hardware.html 文件。

ini 复制代码
<!DOCTYPE html>
<html lang="en">
    <head>
        <title>three.js webgl - pure WebGL triangle </title>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
        <link type="text/css" rel="stylesheet" href="main.css">
        <style>
            body {
                margin: 0;
                overflow: hidden;
            }
        </style>
    </head>
    <body>
        <canvas id="canvas"></canvas>
        <script type="importmap">
            {
                "imports": {
                    "three": "../build/three.module.js",
                    "three/addons/": "./jsm/"
                }
            }
        </script>
        <script id="vertexShader" type="x-shader/x-vertex">
            #version 300 es
            precision highp float;
            uniform mat4 modelViewMatrix;
            uniform mat4 projectionMatrix;
            in vec3 position;
            void main() {
                gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
            }
        </script>
        <script id="fragmentShader" type="x-shader/x-fragment">
            #version 300 es
            layout(location = 0) out highp vec4 pc_fragColor;
            precision highp float;
            uniform vec3 diffuse;
            void main() {
                pc_fragColor = vec4( diffuse, 1.0 );
            }
        </script>
​
        <script type="module">
            import * as THREE from 'three';
​
            // 设置绘制缓冲区尺寸与 CSS 显示尺寸
            const canvas = document.getElementById('canvas');
            canvas.width = window.innerWidth;
            canvas.height = window.innerHeight;
            canvas.style.width = window.innerWidth + 'px';
            canvas.style.height = window.innerHeight + 'px';
            // WebGL 上下文
            const gl = canvas.getContext('webgl2');
​
            // 从 <script> 标签读取 GLSL 源码;trim() 去掉首尾空白,确保 #version 在第一行
            const vsSource = document.getElementById('vertexShader').innerText.trim();
            const fsSource = document.getElementById('fragmentShader').innerText.trim();
​
            // 声明投影矩阵和模型视图矩阵
            const camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 0.1, 100 );
            camera.position.z = 4;
            camera.updateMatrixWorld();
            camera.updateProjectionMatrix();
            const projectionMatrix = camera.projectionMatrix.clone();
            const modelMatrix = new THREE.Matrix4(); // 单位矩阵,三角形位于世界原点
            const modelViewMatrix = camera.matrixWorldInverse.clone().multiply( modelMatrix );
​
            
            // 创建Program 对象,关联 vertexShader 和 fragmentShader,并链接和使用Program对象
            const program = gl.createProgram();
            const vertexShader = compileShader( gl, gl.VERTEX_SHADER, vsSource );
            const fragmentShader = compileShader( gl, gl.FRAGMENT_SHADER, fsSource );
            gl.attachShader( program, vertexShader );
            gl.attachShader( program, fragmentShader );
            gl.linkProgram( program );
            gl.useProgram( program );
​
            function compileShader( gl, type, source ) {
                const shader = gl.createShader( type );
                gl.shaderSource( shader, source );
                gl.compileShader( shader );
                return shader;
            }
​
            // 视口映射到整个 canvas(坐标系左下角为原点)
            gl.viewport( 0, 0, canvas.width, canvas.height );
​
            // 创建 VBO(Vertex Buffer Object),在已绑定的 VAO 上配置 position 属性
            const vertices = new Float32Array( [
                -1.0, -1.0, 0.0,
                1.0, -1.0, 0.0,
                0.0, 1.0, 0.0
            ] );
            const vertexBuffer = gl.createBuffer();
            gl.bindBuffer( gl.ARRAY_BUFFER, vertexBuffer );
            gl.bufferData( gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW );
​
            // 创建 VAO(Vertex Array Object):缓存「position 怎么读」的配置,绘制时 bind 即可恢复
            const vao = gl.createVertexArray();
            gl.bindVertexArray( vao );
​
            // 查询 attribute 在 program 中的 location(与 shader 中 in vec3 position 对应)
            const a_Position = gl.getAttribLocation( program, 'position' );
            // vertexAttribPointer(index, size, type, normalized, stride, offset)
            // size=3:每顶点读 x/y/z;stride=3*4:每顶点占 12 字节;offset=0:从 buffer 开头读
            // 以下 enable / pointer 调用会写入当前 VAO,而非全局默认状态
            gl.vertexAttribPointer( a_Position, 3, gl.FLOAT, false, 3 * 4, 0 );
            gl.enableVertexAttribArray( a_Position );
​
            // 解绑 VBO(可选);避免后续 bufferData 误操作当前 buffer
            gl.bindBuffer( gl.ARRAY_BUFFER, null );
            // 解绑 VAO(可选);避免后续 bindVertexArray 误操作当前 vao
            gl.bindVertexArray( null );
​
            // 向着色器传入模型视图矩阵和投影矩阵
            const u_modelViewMatrix = gl.getUniformLocation( program, 'modelViewMatrix' );
            gl.uniformMatrix4fv( u_modelViewMatrix, false, modelViewMatrix.elements );
            const u_projectionMatrix = gl.getUniformLocation( program, 'projectionMatrix' );
            gl.uniformMatrix4fv( u_projectionMatrix, false, projectionMatrix.elements );
            
            // 向着色器传入三角形颜色
            const u_diffuse = gl.getUniformLocation( program, 'diffuse' );
            gl.uniform3fv( u_diffuse, new Float32Array( [ 0.0, 0.0, 1.0 ] ) ); // 蓝色
​
            // 绘制背景色
            gl.clearColor( 1.0, 1.0, 0.0, 1.0 ); // 黄色
            gl.clear( gl.COLOR_BUFFER_BIT ); // 清屏
​
            // 绘制三角形:先 bind VAO,一次性恢复顶点属性配置,再 draw
            gl.bindVertexArray( vao );
            gl.drawArrays( gl.TRIANGLES, 0, 3 ); // 从第 0 个顶点起,绘制 3 个顶点(1 个三角形)
            gl.bindVertexArray( null );
​
            // 画完后就释放 GPU 资源(对应 Three.js WebGLBindingStates 中的 deleteVertexArray)
            gl.deleteVertexArray( vao );
            gl.deleteBuffer( vertexBuffer );
            gl.deleteProgram( program );
            gl.deleteShader( vertexShader );
            gl.deleteShader( fragmentShader );
​
        </script>
    </body>
</html>

CPU 执行

这整个html+css+js 就是通过CPU 执行的。

RAM 存储

在绝大多数的场景下,js 声明的变量是存在RAM中的。

ini 复制代码
const vertices = new Float32Array( [
    -1.0, -1.0, 0.0,
    1.0, -1.0, 0.0,
    0.0, 1.0, 0.0
] );
  • vertices 是栈变量,只保存一个指向堆对象的引用地址。
  • new Float32Array( ......) 是堆对象,会作为实际数据存放在堆中。

上例中的栈和堆都存在于RAM中。

VRAM 存储

js 可以通过webgl 协议,在VRAM 中创建对象。

ini 复制代码
const program = gl.createProgram();

gl.createProgram() 可以在VRAM 创建一个真正的shader program。

program 是指向shader program 的引用句柄,它本身是一个普通的 JS 对象,存在RAM的栈内存中。

ini 复制代码
const program = gl.createProgram();
console.log(typeof program ); // object

在js中创建的webgl 对象基本都是这种引用句柄的形式:js引用句柄 → VRAM 中的真实对象。

ini 复制代码
const shader = gl.createShader( type ); 
const vertexBuffer = gl.createBuffer();
const vao = gl.createVertexArray();
const a_Position = gl.getAttribLocation( program, 'position' );
const u_diffuse = gl.getUniformLocation( program, 'diffuse' );

GPU 执行

在GPU 中执行的命令,是在js中通过webgl 协议调用的。

ini 复制代码
const gl = canvas.getContext('webgl2');

gl 就是根据webgl2 协议创建的上下文对象。

通过gl 可以让GPU 执行webgl 相关的各种指令,比如上面在VRAM中创建各种对象,bindBuffer,drawArrays等。

总结

这一章我们讲解了计算机中的硬件:CPU、GPU,VAM、VRAM。

并围绕执行和存储2个锚点,以webgl 绘制三角形为示例,对webgl 和硬件的关系进行了解析。

相关推荐
kyriewen2 小时前
看了微软几万人用AI编程的数据——效率涨24%的代价没人提
前端·ai编程·claude
2601_963771372 小时前
How to Scale Your WordPress Store Traffic in 2026
前端·php·plugin
亿元程序员2 小时前
老板说我的3D箭头游戏用来做试玩太普通了,没人想玩,让我变点花样...
前端
李伟_Li慢慢3 小时前
01-threejs架构原理-课程简介
前端·three.js
_瑞4 小时前
深入理解 iOS 渲染原理
前端·ios
IT_陈寒4 小时前
SpringBoot自动配置失灵?你可能忘了这个关键注解
前端·人工智能·后端
iCOD3R5 小时前
Skill - kill-ai-slop 解决“AI 味”样式
前端·css·ai编程
皮音5 小时前
Skill 在项目中的实践 —— 从 Agent 困境到 Skill 工程化
前端
大龄秃头程序员5 小时前
RN 0.86 新架构实战:TurboModule + Fabric 组件 + iOS 原生容器,完整代码开源
前端·react native