WebGL 的运行流程包括多个阶段,从初始化 WebGL 上下文、设置着色器程序,到加载数据并绘制到屏幕。这里分成两个部分来说明:WebGL 的整体运行流程 和 Shader 是如何从内存取值的。
一、WebGL 整个运行流程
-
获取 WebGL 上下文
首先,通过
<canvas>
元素获取 WebGL 上下文(gl
),这提供了 WebGL API 操作的入口。javascriptconst canvas = document.getElementById('canvas'); const gl = canvas.getContext('webgl');
-
编写并编译着色器 (Shaders)
WebGL 使用两种着色器:顶点着色器 (Vertex Shader) 和片段着色器 (Fragment Shader)。顶点着色器用于处理顶点数据(如位置和纹理坐标),而片段着色器用于处理每个像素的颜色。
javascriptconst vertexShaderSource = `...`; // 顶点着色器源码 const fragmentShaderSource = `...`; // 片段着色器源码 function createShader(gl, type, source) { const shader = gl.createShader(type); gl.shaderSource(shader, source); gl.compileShader(shader); return shader; } const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource); const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
-
创建和链接着色器程序 (Shader Program)
将顶点着色器和片段着色器链接到一个程序对象,这个程序对象将用于后续的绘制。
javascriptconst program = gl.createProgram(); gl.attachShader(program, vertexShader); gl.attachShader(program, fragmentShader); gl.linkProgram(program); gl.useProgram(program);
-
设置缓冲区和属性指针
将顶点数据(如位置、颜色或纹理坐标)加载到缓冲区中,并将其绑定到着色器的属性上。使用
gl.vertexAttribPointer()
来指定如何读取这些数据。javascriptconst positionBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW); const positionLocation = gl.getAttribLocation(program, 'a_position'); gl.enableVertexAttribArray(positionLocation); gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
-
设置 Uniform 变量
Uniform 变量是全局变量,供顶点和片段着色器共享。常用于传递诸如颜色、纹理、变换矩阵等数据。
javascriptconst texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, texture); gl.uniform1i(gl.getUniformLocation(program, 'u_image'), 0); // 绑定纹理到片段着色器
-
绘制图形
设置好所有的缓冲区和着色器后,使用
gl.drawArrays()
或gl.drawElements()
开始绘制。javascriptgl.clear(gl.COLOR_BUFFER_BIT); gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
-
呈现到屏幕
绘制命令执行后,WebGL 会自动将渲染结果显示在
<canvas>
上。
二、Shader 如何从内存中取值
Shader 程序是运行在 GPU 上的小程序,在 WebGL 中,数据通过以下两种方式从内存中传递到 Shader 中:
-
Attribute 变量
Attribute 变量用于每个顶点的数据(如位置、法线、纹理坐标等)。这些数据通常从 CPU 端的缓冲区传递到 GPU。
- 顶点数据加载到缓冲区 :通过
gl.bufferData()
将顶点数据上传到 GPU 的缓冲区。 - 指定数据格式 :通过
gl.vertexAttribPointer()
定义每个顶点数据的布局,例如每个顶点由多少个分量组成、数据类型等。 - 传递到顶点着色器:顶点着色器通过 Attribute 变量访问这些数据。
javascript// 在 JavaScript 中 const positionLocation = gl.getAttribLocation(program, 'a_position'); gl.enableVertexAttribArray(positionLocation); gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0); // 在顶点着色器中 attribute vec4 a_position; void main() { gl_Position = a_position; }
- 顶点数据加载到缓冲区 :通过
-
Uniform 变量
Uniform 变量是全局的,适用于每个顶点或片段都相同的数据(如颜色、变换矩阵、纹理采样器等)。
- 设置 Uniform 值 :在 JavaScript 中使用
gl.uniform*
函数设置 Uniform 的值。 - 传递到着色器:Uniform 变量在着色器中声明后,可以直接使用。
javascript// 在 JavaScript 中 const colorLocation = gl.getUniformLocation(program, 'u_color'); gl.uniform4f(colorLocation, 1.0, 0.0, 0.0, 1.0); // 设置颜色为红色 // 在片段着色器中 uniform vec4 u_color; void main() { gl_FragColor = u_color; }
- 设置 Uniform 值 :在 JavaScript 中使用
三、数据流的总结
- 顶点数据 (Attribute):从 JavaScript 上传到 GPU 缓冲区,并绑定到顶点着色器的 Attribute 变量。
- 全局数据 (Uniform):通过 JavaScript 直接传递到着色器,适用于全局常量或纹理等数据。
- Shader 内部执行:顶点着色器运行每个顶点,片段着色器运行每个像素,使用传递的数据进行计算。
- 最终渲染:着色器的输出最终会写入 GPU 帧缓冲区,并显示到屏幕上。
这些数据传递和处理使得 WebGL 能够高效地在 GPU 上执行复杂的图形操作。