说明
【跟月影学可视化】学习笔记。
图形系统是如何绘图的?
一个通用计算机图形系统主要包括 6 个部分,分别是:
- 输入设备
- 中央处理单元:首先,数据经过 CPU 处理,成为具有特定结构的几何信息。
- 图形处理单元:然后,这些信息会被送到 GPU 中进行处理。
- 存储器
- 帧缓存:光栅信息会输出到帧缓存中
- 输出设备
光栅(Raster):几乎所有的现代图形系统都是基于光栅来绘制图形的,光栅就是指构成图像的像素阵列。
像素(Pixel):一个像素对应图像上的一个点,它通常保存图像上的某个具体位置的颜色等信息。
帧缓存(Frame Buffer):在绘图过程中,像素信息被存放于帧缓存中,帧缓存是一块内存地址。
CPU(Central Processing Unit):中央处理单元,负责逻辑计算。
GPU(Graphics Processing Unit):图形处理单元,负责图形计算。
渲染管线(RenderPipelines)
在 GPU 中要经过两个步骤生成光栅信息:
对给定的数据结合绘图的场景要素(例如相机、光源、遮挡物体等等)进行计算,最终将图形变为屏幕空间的 2D 坐标。
为屏幕空间的每个像素点进行着色,把最终完成的图形输出到显示设备上。
这整个过程是一步一步进行的,前一步的输出就是后一步的输入,我们把这个过程叫做渲染管线(RenderPipelines)。
GPU 是什么?
GPU 是由大量的小型处理单元构成的,它可能远远没有 CPU 那么强大,但胜在数量众多,可以保证每个单元处理一个简单的任务。
每处理一个像素点就相当于完成了一个简单的任务,而一个图片应用又是由成千上万个像素点组成的,要处理这么多的小任务,比起使用若干个强大的 CPU,使用更小、更多的处理单元,是一种更好的处理方式。
WebGL绘图流程
如何用 WebGL 绘制三角形?
浏览器提供的 WebGL API 是 OpenGL ES 的 JavaScript 绑定版本,它赋予了开发者操作 GPU 的能力。
绘制步骤:
- 创建 WebGL 上下文
- 创建 WebGL 程序(WebGL Program)
- 将数据存入缓冲区
- 将缓冲区数据读取到 GPU
- GPU 执行 WebGL 程序,输出结果
步骤一:创建 WebGL 上下文
const canvas = document.querySelector('canvas'); const gl = canvas.getContext('webgl');
步骤二:创建 WebGL 程序
WebGL 程序是一个 WebGLProgram 对象,它是给 GPU 最终运行着色器的程序,而不是我们正在写的三角形的 JavaScript 程序。
1、编写两个着色器(Shader)
WebGL 是以顶点和图元来描述图形几何信息的。
顶点就是几何图形的顶点
图元是 WebGL 可直接处理的图形单元,由 WebGL 的绘图模式决定,有点、线、三角形等等。
注意:图元是 WebGL 可以直接处理的图形单元,所以其他非图元的图形最终必须要转换为图元才可以被 WebGL 处理。
WebGL 绘制一个图形的过程,一般需要用到两段着色器
顶点着色器(Vertex Shader):负责处理图形的顶点信息
片元着色器(Fragment Shader):负责处理图形光栅化后的像素信息
顶点着色器的作用:
通过 gl_Position 设置顶点
向片元着色器传递数据:通过 varying 变量传给片元着色器,根据片元着色器的像素坐标与顶点像素坐标的相对位置做线性插值。
const vertex = ` attribute vec2 position; void main() { gl_PointSize = 1.0; gl_Position = vec4(position, 1.0, 1.0); } `; const fragment = ` precision mediump float; void main() { gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); } `;
2、创建成 shader 对象
const vertexShader = gl.createShader(gl.VERTEX_SHADER); gl.shaderSource(vertexShader, vertex); gl.compileShader(vertexShader); const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); gl.shaderSource(fragmentShader, fragment); gl.compileShader(fragmentShader);
3、创建 WebGLProgram 对象
将两个 shader 关联到 WebGL 程序,将 WebGLProgram 对象链接到 WebGL 上下文对象上。
const program = gl.createProgram(); gl.attachShader(program, vertexShader); gl.attachShader(program, fragmentShader); gl.linkProgram(program);
4、通过 useProgram 选择启用 WebGLProgram 对象
gl.useProgram(program);
步骤三:将数据存入缓冲区
1、WebGL 的坐标系
WebGL 的坐标系是一个三维空间坐标系,坐标原点是(0,0,0)。其中,x 轴朝右,y 轴朝上,z 轴朝外。这是一个右手坐标系。
左手右手对比图
2、定义三角形的三个顶点
比如要绘制下面这个二维的三角形,三个顶点分别如下:
WebGL 使用的数据需要用类型数组定义,默认格式是 Float32Array。
Float32Array
是 JavaScript 的一种类型化数组(TypedArray),JavaScript 通常用类型化数组来处理二进制缓冲区。
const points = new Float32Array([ -1, -1, 0, 1, 1, -1, ]);
3、将数据写入 WebGL 的缓冲区
- 创建一个缓存对象
- 将它绑定为当前操作对象
- 再把当前的数据写入缓存对象
const bufferId = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, bufferId); gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW);
步骤四:将缓冲区数据读取到 GPU
将 buffer 的数据绑定给顶点着色器的 position 变量
// 获取顶点着色器中的position变量的地址 const vPosition = gl.getAttribLocation(program, 'position'); // 给变量设置长度和类型 gl.vertexAttribPointer(vPosition, 2, gl.FLOAT, false, 0, 0); // 激活这个变量 gl.enableVertexAttribArray(vPosition);
步骤五:执行着色器程序完成绘制
调用绘图指令,就可以执行着色器程序来完成绘制
// 将当前画布的内容清除 gl.clear(gl.COLOR_BUFFER_BIT); // 传入三角形图元、顶点偏移量和顶点数量进行绘制 gl.drawArrays(gl.TRIANGLES, 0, points.length / 2);
完整代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>如何用WebGL绘制最简单的几何图形</title> <style> canvas { border: 1px dashed salmon; } </style> </head> <body> <canvas width="240" height="240"></canvas> <script> // 获取 webgl 上下文 const canvas = document.querySelector('canvas'); const gl = canvas.getContext('webgl'); // 顶点着色器代码片段:attribute 表示声明变量,vec2 是变量的类型,它表示一个二维向量,position 是变量名。 const vertex = ` attribute vec2 position; void main() { gl_PointSize = 1.0; gl_Position = vec4(position, 1.0, 1.0); } `; // 片元着色器代码片段:gl_FragColor 的值来定义和改变图形的颜色,RGBA 色值表示的四维向量数据 // rgba(250,128,114,1) vec4(250/255, 128/255, 114/255, 1) const fragment = ` precision mediump float; void main() { gl_FragColor = vec4(0.9803921568627451, 0.5019607843137255, 0.4470588235294118, 1); } `; // 将着色器代码片段创建成 shader 对象 const vertexShader = gl.createShader(gl.VERTEX_SHADER); gl.shaderSource(vertexShader, vertex); gl.compileShader(vertexShader); const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); gl.shaderSource(fragmentShader, fragment); gl.compileShader(fragmentShader); // 创建 WebGLProgram 对象 const program = gl.createProgram(); // 将两个 shader 关联到 WebGL 程序 gl.attachShader(program, vertexShader); gl.attachShader(program, fragmentShader); // 将 WebGLProgram 对象链接到 WebGL 上下文对象上 gl.linkProgram(program); // 通过 useProgram 选择启用 WebGLProgram 对象 gl.useProgram(program); // 定义三角形的三个顶点 const points = new Float32Array([ -1, -1, 0, 1, 1, -1, ]); console.log("points---->", points) // 创建一个缓存对象 const bufferId = gl.createBuffer(); // 将它绑定为当前操作对象 gl.bindBuffer(gl.ARRAY_BUFFER, bufferId); // 再把当前的数据写入缓存对象 gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW); // 获取顶点着色器中的position变量的地址 const vPosition = gl.getAttribLocation(program, 'position'); console.log("vPosition---->", vPosition) // 给变量设置长度和类型 gl.vertexAttribPointer(vPosition, 2, gl.FLOAT, false, 0, 0); // 激活这个变量 gl.enableVertexAttribArray(vPosition); // 将当前画布的内容清除 gl.clear(gl.COLOR_BUFFER_BIT); // 传入三角形图元、顶点偏移量和顶点数量进行绘制 gl.drawArrays(gl.TRIANGLES, 0, points.length / 2); </script> </body> </html>