前端建模基础

前端建模基础

计算机图形学(Computer Graphics)主要是研究物体在计算机中的图形显示与交互,各个领域都能利用计算机图形显示获取相关的好处,作为一个前端工程师,了解相关图形学的基础有利于往更高的台阶前行。本文章借鉴了《现代计算机图形学基础》一书中的部分概念,希望整理出对前端工程师更为贴近的内容

认识计算机图形学(CG)

计算机图形在前端中最常见的应用就是数据统计了吧,echartG2等等插件提供了多样的数据图,这些插件通过将大量的数据转化成可视形式,其数据趋势与线性关系得以更容易的展示出来,使得数据在前端的显示更为直观与优雅

另外,新兴起的VR(virtual-reality)技术,也在不少的前端页面中出现,VR交互中,用户可与三维场景中的对象进行交互。此类展现方式可以让用户更好的与设计者沟通,使用户体验得到增强

CG是以建模,绘制,交互,动画,影像为核心内容并服务于各个领域的一门科学。
对于前端工程师来说,建模就是指使用前端库,通过数据来表达物体和场景并构建对应的数据化三维模型的过程。
绘制则是指将这些三维模型通过一些计算和变换,最终在屏幕上渲染出来的过程,也是从几何模型到像素的转化。
所以,工程师使用计算机语言输入的是三维模型,而通过计算机模拟人类的视觉功能,对三维模型进行加工处理,最终在屏幕上输出的是二维图像。虽然输出的是二维图像,但是通过计算机的处理,图像中的物体之间的关系符合人类对现实世界的认知,从而使大脑形成三维空间的感知。

基础概念

图形是由点,线,面等几何元素组成而成的,这些元素统称为图元。简单的图元可以组成一些立体图元,从而又组合成简单的几何模型,复杂的模型又通过这些简单的模型组合而成,通过模型变换整合坐标系最终形成复杂场景。在threejs中的场景图就是这样一层一层的构建的。

图形只得是由数据组成的几何信息,而图像指的是由像素组成的二维栅格,所以图形不会失真,图像会失真

正如上述,创建的三维模型最终要在显示器中显示,而显示器是基于像素的二维图像显示,所以需要将图形经过一系列的几何变换映射成屏幕坐标中的二维图像,确定图元对应的图像坐标位置,其中涉及各种物理坐标的转换,最后还要经过光栅化处理(确定指定分辨率下哪些像素被覆盖,以何种颜色进行色彩显示)。最后才显示在显示器中。
这一系列的过程也称做GPU的图形流水线(也称渲染流水线或渲染管线),实际的应用中,图形流水线是十分复杂的,以OpenGL为例,按照流水线操作的对象大体可分为两类操作:

处理图像元素的像素操作 处理图元元素的几何操作
区域填充 模型变换
片元组装 顶点着色
纹理映射 纹理变换
颜色合成 视点变换
深度测试 视域剔除
遮罩测试 投影变换
透明融合 背面剔除
雾化处理 窗口变换

🔺坐标的几何变换

几何变换包括了:

  • 模型变换:将物体的局部坐标系变换到世界坐标系,比如three中的场景图将一个模型自身的坐标系融合进sence中,否则模型的动画计算量将无可估量
  • 视点变换:将场景中的物体模型转化到以人眼为中心的坐标系下表示,方便人眼视网膜投影成像
  • 投影变换:将眼睛坐标系通过投影产生二维图像,这就像是three中的camera,使用正交投影或者透视投影减少一个维度,生成二维图像
  • 窗口变换:最后将二维图像进一步像素化,映射到屏幕窗口上,显示出来

几种变换的原理都是矩阵变换,几何操作中使用矩阵变换与canvas的矩阵变换类似,因为使用齐次坐标系,所以使用四阶矩阵表示线性关系

1const point = vec(x, y, z, 1)
2const matrix = [
3  a, d, g, j,
4  b, e, h, k,
5  c, f, i, l,
6  0001
7]
8// j k l 表示x y z的平移
9// a e i 表示x y z的缩放
10// ehfi, agci, adbe (cos⌀ - sin⌀ sin⌀  cos⌀)表示x y z的旋转

🔺光栅化

光栅化处理包括了:

  • 简单图元生成
    复杂的图形是由基本的图元组合而成的,因此首先需要使用算法将图元变为像素集合。例如:
    图片摘自《现代计算机图形学基础》
    图片摘自《现代计算机图形学基础》

    图片摘自《现代计算机图形学基础》
  • 多边形填充
    确定屏幕上哪些像素位于多边形内部
  • 剔除
    将不会或不需要在屏幕上显示的图元排除,减少图形绘制的计算量
    包括视域剔除,小物体剔除,背面剔除,退化剔除等等
  • 可见性判断
    深度测试,判断模型是否被遮挡
    深度缓存算法(z-buffer):按照越在后面的物体深度z值越大的原则,每个像素只填充z值最小的图元数据
    但是深度问题一直是3D场景中难以处理的问题,就像和平精英这样的游戏中也避免不了类似的bug,远处观察时可以穿透建筑或是树看到后面的人,这就是因为显示器分辨不出哪个在前哪个在后的问题
  • 其他(纹理贴图,透明,反走样,阴影,雾化,模糊)

🔺数字几何

几何模型是从数学形状的角度描述物体的外观。通常建模使用多边形网格表示。
由顶点、边和多边形面组成的集合来表示的几何形状称为网格(Mesh)。其中由三角形面片组成的三角形网格更加的稳定与灵活,成为前端建模的首选。

在WebGL中,与大多数实时3D图形一样,三角形是绘制模型的基本元素。因此,在WebGL中绘制的过程涉及使用JavaScript生成信息,该信息指定了这些三角形的创建位置和方式,以及它们的外观(颜色,阴影,纹理等)。然后,此信息将馈送到 GPU 进行处理,并返回场景视图。

通过对网格的编辑,操纵和修改基础几何模型,可以实现对复杂模型的快速建模

🔺图形硬件

显卡的功能是将计算机需要显示的数据信息转换为显示器信息,并驱动其显示的计算机硬件。在GPU出现之前,图形流水线的操作大多由CPU来完成,显卡上只能处理一些简单的算法。而随着图形显示功能的需求发展,CPU的串联工作模式已经不能满足数以亿计的像素计算,(一台1920*1080的显示器,一帧就有约200万的像素需要计算,60Hz的刷新率,一秒就有上亿的计算量)。所以以并联工作方式为基础的GPU得以迅速发展,GPU是由数以千计的更小更高效的核心组成的大规模并行计算构建,专门为同时处理多重任务而设计的。
支持可编程GPU(Graphic Processing Unit)图形处理器的出现,取代了部分原本由CPU执行的工作(主要是几何变换和光栅化),GPU更适合处理大量并行而相单一的逻辑,这样就减轻了CPU的负担,并提升了绘制效率。

GPU的并行处理包括基于任务的并行处理与基于数据的并行处理
图形流水线也称渲染流水线或渲染管线,GPU的并行处理机制使得渲染流水线更加高效的进行。由固定函数进行处理流水线上功能的也称固定管线

随着开发者需求的增长,上述固定渲染流水线已经满足不了开发者的需求,所以一些用来代替固定渲染管线的可高度编程程序算法出现了,这就是着色器。通过着色器,开发者可以自己编写显卡渲染的相关算法,可以实现各种各样的图像效果而不用受显卡的固定渲染管线限制。

WebGL渲染管线

创建顶点数组。这些数组包含顶点属性,以及有关顶点的纹理、颜色或光照(垂直顶点)如何影响顶点的信息。

然后,通过将顶点数组中的数据发送到顶点缓冲区,以便GPU快速读取。我们还提供指向顶点数组元素的附加索引数组。它们控制顶点稍后如何组合成三角形。

顶点缓冲区在显存中开辟一块区域,这样绘制时直接读取显存中的数据就可以了,明显提高渲染速度。

GPU 首先从顶点缓冲区中读取每个选定的顶点,并通过顶点着色器运行它。顶点着色器将一组顶点属性作为输入并输出一组新的属性。

然后,GPU 连接投影的顶点以形成三角形。它通过按 indexs 数组指定的顺序获取顶点。

光栅器获取每个三角形,对其进行裁剪,丢弃屏幕外部的部分,并将剩余的可见部分分解为像素大小的碎片。顶点着色器其他顶点属性的输出也会在每个三角形的栅格化表面上进行插值,从而为每个片元分配一个平滑的值渐变。

然后,生成的像素大小的片元通过片元着色器。片元着色器输出每个像素的颜色和深度值,然后将其绘制到帧缓冲区中。

帧缓冲器是渲染作业输出的最终目标。帧缓存包括颜色、scissor、alpha、stencil、depth这些缓存,所以帧缓存不是一片缓存,而是所有这些缓存的组合,帧缓冲器还可以具有深度缓冲区和/或模具缓冲区,这两者都可以选择在片元绘制到帧缓冲器之前对其进行过滤。
深度测试会丢弃位于已绘制对象后面的对象中的片元,而模具测试使用绘制到模具缓冲区中的形状来约束帧缓冲器的可绘制部分,从而“模具化”渲染作业。在这两个滤镜中幸存下来的片元的颜色值 alpha 与它们覆盖的颜色值混合在一起。最终的颜色、深度和模具值将绘制到相应的缓冲区中。缓冲区的输出还可以用作其他渲染作业的纹理输入。

GLSL ES

专门用来为着色器编程的编程语言就是着色器语言。区别于大多数运行在CPU中的语言,着色器语言运行在GPU中,与OpenGL相配合的着色器语言是GLSL,应用在客户端,而与WebGL配合的着色器语言是GLSL ES,应用在浏览器平台。
WebGL着色器代码分为顶点着色器代码和片元着色器代码两部分,通过WebGL编译处理后,最终在GPU的顶点着色器单元与片元着色器单元上执行。

顶点着色器定义了顶点的渲染位置和点的渲染像素大小,对应几何操作。顶点着色器至少会计算顶点在屏幕空间中的投影位置。但它也可以生成其他属性,例如每个顶点的颜色或纹理坐标。您可以对自己的顶点着色器进行编码,也可以使用 WebGL 库提供的顶点着色器。
片元着色器定义了点的渲染结果像素的颜色值,对应像素操作。常见的片元着色器操作包括纹理贴图和光照。由于片元着色器为绘制的每个像素独立运行,因此它可以执行最复杂的特殊效果;但是,它也是图形管道中对性能最敏感的部分。与顶点着色器一样,您可以编写自己的片元着色器,也可以使用 WebGL 库提供的着色器。

如果使用Three.js开发项目,其已经封装好大部分功能,但若是需要自定义着色器或者使用原生WebGL的时候,便需要学习着色器语言。

首先,与OpenGL配合的GLSL使用C语言作为基础高阶着色语言,避免了使用汇编语言或硬件规格语言的复杂性。GLSL ES语言是则是在GLSL着色器语言的基础上,删除和简化一部分功能后形成的,其语法与C语言的较为类似。

GLSL ES大小写敏感,而且表达式后不能省略';'

🔺基本数据类型

着色器语言GLSL的基本数据类型和C语言一样具有常见的整型数int、浮点数float和布尔值bool类型数据。

关键字 数据类型
bool 布尔值 布尔变量值为true或false
int 整型数 值为整数,比如0,1,2,3…
float 单精度浮点数 浮点数用小数点表示,比如0.6,3.14,2.8

浮点数不能省略小数点
基本数据类型可以使用内置函数转换:int()、float()、bool()

🔺声明一个变量/常量

着色器语言ES GLSL和C语言一样属于强类型语言,声明一个变量需要定义变量的数据类型

1// 着色器语言定义一个整形常量
2int count = 10;
3// 定义一个浮点数变量num,并赋值10.0
4float num = 10.0;
5// 声明一个数据类型是布尔值的变量,并赋值为true
6bool lightBool = true;
7
8// 着色器语言定义一个整形常量
9const int count = 10;

变量名不能以数字开头
不能以gl_、webgl_或webgl开头,这些已经被OpenGL ES保留了

🔺向量

关键字 数据类型
vec2 二维向量,具有xy两个分量,分量是浮点数
vec3 三维向量 ,具有xyz三个分量,分量是浮点数
vec4 四维向量 ,具有xyzw四个分量,分量是浮点数

ivec 向量的分量是整型数
ivec 向量的分量是布尔值

1vec3 dirction = vec3(1.00.00.0)
2dirction.x = 1.0
3dirction.y = 0.0 + 2.0
4// dirction === vec3(1.0, 2.0, 0.0)
5
6vec2 v2 = v3.xy
7// v2 === vec2(1.0, 2.0)

向量与数字的运算和向量之间的运算都可以化简为向量分量之间的运算

🔺矩阵

关键字 数据类型
mat2 2×2矩阵,4个元素
mat3 3×3矩阵,9个元素
mat4 4×4矩阵,16个元素
1// 对角矩阵可以简写
2// 2.0 0.0 0.0 0.0
3// 0.0 2.0 0.0 0.0
4// 0.0 0.0 2.0 0.0
5// 0.0 0.0 0.0 2.0
6mat4 matrix = mat4(2.0)
7// 需要表示的矩阵
8// 1.1 1.2 1.3 1.4
9// 2.1 2.2 2.3 2.4
10// 3.1 3.2 3.3 3.4
11// 4.1 4.2 4.3 4.4
12// 注意行列对应关系,按照列的先后顺序,从上到下依次传入mat构造函数参数中
13mat4 matrix4 = mat4(
141.1,2.1,3.1,4.1,
151.2,2.2,3.2,4.2,
161.3,2.3,3.3,4.3,
171.4,2.4,3.4,4.4
18);
19
20// 访问矩阵matrix4的第二列
21vec4 v4 = matrix4[1];//返回值vec4(1.2,2.2,3.2,4.2)
22// 访问矩阵matrix4的第三列第四行对应的元素
23float f = matrix4[2][3];//返回4.3

矩阵与数字运算可以化简为矩阵中的元素与数字的运算
矩阵与向量之间的运算和矩阵之间的运算则遵循线性代数的算法

🔺数组

和javascript语言、C语言一样 可以声明数组类型变量,不过WebGL着色器的数据仅仅支持一维数组,不支持多维数组。

浮点数数组使用Float32Array声明

🔺条件语句/fors语句

关于if语句、for语句的使用,和javascript语言逻辑规则一致

🔺函数

函数有返回值时,函数计算后需要返回的值通过关键字return返回,注意声明函数时候,函数名称前需要声明return返回值的数据类型。

1int add(int x,int y){
2  return ...
3}

自定义函数无返回值的时候,和主函数main一样,使用关键字void声明函数。

自定义声明一个函数,传入一个参数,默认情况下,参数被修改了,但是并不影响传入的参数对应变量的值,如果使用out关键字声明参数变量,函数内部改变参数,函数外参数对应的变量会改变

🔺结构体

结构体主要功能就是利用WebGL着色器已经提供的常见数据类型,自定义一个新的数据类型。

1struct newVar {
2  int a;
3  float b;
4};
5
6uniform newVar myVar;
7
8myVar.a = 8
9myVar.b = 8.0

🔺内置变量

内置变量就是不用声明可以直接赋值,主要是为了实现特定的功能。

内置变量 含义 值数据类型
gl_PointSize 点渲染模式,方形点区域渲染像素大小 float
gl_Position 顶点位置坐标 vec4(x,y,z,1.0)
gl_FragColor 片元颜色值 vec4(r,g,b,a)
gl_FragCoord 片元坐标,单位像素 vec2
gl_PointCoord 点渲染模式对应点像素坐标 vec2

🔺attribute、uniform 和 varying

关键字(变量类型) 数据传递 声明变量
attribute javascript——>顶点着色器 声明顶点数据变量
uniform javascript——>顶点、片元着色器 声明非顶点数据变量
varying 顶点着色器——>片元着色器 声明需要插值计算的顶点变量

顶点着色器和片元着色器代码都有一个唯一的主函数main(),attribute、varying和uniform类型的变量需要在main函数之外声明,在main函数中通常编写,逐片元或逐顶点处理的代码。
着色器中通过attribute和uniform关键字声明的变量,需要通过javascript代码传递相关的数据。varying类型变量主要是为了完成顶点着色器和片元着色器之间的数据传递和插值计算。

🔺discard

舍弃片元,可用于光栅化中的剔除操作

WebGL

WebGL是一个JavaScript API,它允许我们直接在浏览器中实现交互式3D图形。WebGL API多数与GPU硬件相关,GPU硬件(渲染管线)=>显卡驱动=>操作系统=>浏览器=>WebGL API为上一层提供接口,开发者调用WebGL API控制图形处理单元

Canvas对象方法.getContext('2d')可以在二维画布上绘制图像。执行canvas.getContext('webgl');返回对象具有一系列绘制渲染三维场景的方法,也就是WebGL API。

几乎整个WebGL API都是关于如何设置着色器的状态值以及运行它们。
对于想要绘制的每一个对象,都需要先设置一系列状态值,然后通过调用 gl.drawArrays 或 gl.drawElements 运行一个着色方法对(一个着色程序),使得你的着色器对能够在GPU上运行。

这边不详述WebGL的各个API,只通过下述例子简单实践管线渲染的流程,从而更好的理解前文内容,更多的请自行查阅官方文档
实际上学习WebGL是为了学习Web3D知识,而通过原生WebGL直接编写程序,会比较麻烦,实际开发中一般会使用Three.js这样或那样的库

1<!-- html -->
2<!DOCTYPE html>
3<html lang="en">
4  <head>
5    <meta charset="UTF-8" />
6    <title>WebGL</title>
7  </head>
8  <body>
9    <!--canvas标签创建一个宽高均为500像素,背景为蓝色的矩形画布-->
10    <canvas id="c"></canvas>
11    <script>
12      // js (内置vue3(Vue),jQuery($))
13      var canvas = document.querySelector("#c");
14      var gl = canvas.getContext("webgl");
15
16      // 着色器代码
17      var vertexShaderSource = `
18        attribute vec4 a_position;
19        void main() {
20          gl_Position = a_position;
21        }
22      `
;
23      var fragmentShaderSource = `
24        precision mediump float;
25        void main() {
26          gl_FragColor = vec4(1, 0, 0.5, 1); // return redish-purple
27        }
28      `
;
29
30      //创建顶点着色器对象
31      var vertexShader = gl.createShader(gl.VERTEX_SHADER);
32      //创建片元着色器对象
33      var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
34      //引入顶点、片元着色器源代码
35      gl.shaderSource(vertexShader,vertexShaderSource);
36      gl.shaderSource(fragmentShader,fragmentShaderSource);
37      //编译顶点、片元着色器
38      gl.compileShader(vertexShader);
39      gl.compileShader(fragmentShader);
40      //创建程序对象program
41      var program = gl.createProgram();
42      //附着顶点着色器和片元着色器到program
43      gl.attachShader(program,vertexShader);
44      gl.attachShader(program,fragmentShader);
45      //链接program
46      gl.linkProgram(program);
47      //使用program
48      gl.useProgram(program);
49
50      // 从创建的着色程序中找到属性【a_position】值所在的位置
51      var positionAttributeLocation = gl.getAttribLocation(program, "a_position");
52      // 属性值从缓冲中获取数据,所以我们创建一个缓冲
53      var positionBuffer = gl.createBuffer();
54      // 绑定一个数据源到绑定点,然后可以引用绑定点指向该数据源
55      gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
56      // 创建顶点数据
57      var positions = [
58        00,
59        00.5,
60        0.70,
61      ];
62      // 通过绑定点向缓冲中存放数据
63      gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
64
65      // 设置背景色
66      gl.clearColor(0001);
67      gl.clear(gl.COLOR_BUFFER_BIT);
68
69      // 允许顶点着色器读取GPU(服务器端)数据
70      gl.enableVertexAttribArray(positionAttributeLocation);
71      // 需要告诉WebGL怎么从我们之前准备的缓冲中获取数据给着色器中的属性
72      var size = 2;
73      var type = gl.FLOAT;
74      var normalize = false;
75      var stride = 0;
76      var offset = 0;
77      gl.vertexAttribPointer(positionAttributeLocation, size, type, normalize, stride, offset);
78
79      // 设置primitiveType(图元类型)为 gl.TRIANGLES(三角形)
80      var primitiveType = gl.TRIANGLES;
81      // 着色器将运行三次
82      var count = 3;
83      gl.drawArrays(primitiveType, 0, count);
84    
</script>
85  </body>
86</html>

posted @ 2022-04-11 21:46  前端订阅  阅读(472)  评论(0编辑  收藏  举报