【技术调研】三维(0)-webGL、三维基础知识、前置知识、数学知识以及简单示例
前言
因业务需要了解网页端三维相关技术,故对webGL相关技术学习并记录。旨在了解网页端三维的基本原理。
什么是webGL?
WebGL全称叫做Web Graphics Library,它是JavaScript API。用于在任何兼容的Web浏览器中渲染交互式的3D图形,并且无需使用插件。它基于 OpenGL ES 2.0(一个用于嵌入式系统的图形库),并与其他 Web 标准完全集成,使开发者能够利用 GPU 加速图形处理直接在网页上实现复杂的图形效果。
WebGL可以在网页上开发高性能的3D游戏,许多现代网页游戏都利用了WebGL实现了丰富的图形效果和流畅的用户体验。
WebGL地解决了现有的Web交互式三维动画的两个问题:第一,它通过HTML脚本本身实现Web交互式三维动画的制作,无需任何浏览器插件支持;第二,它利用底层的图形硬件加速功能进行的图形渲染,是通过统一的、标准的、跨平台的OpenGL接口实现的。
简单地说,webGL是一个用于在html中实现三维动画的API。
webGL坐标系
在WebGL中,坐标系使用的是右手坐标系,手掌朝向自己大拇指与食指垂直,食指与中指垂直形成一个空间。大拇指方向是X轴,食指方向是Y轴,中指方向是Z轴。
注意,其中X、Y、Z轴正方向最大值都为1,负方向最大值都为-1。
canvas画布上的坐标
Canvas画布可以支持2D,也可以支持3D。但是2D和3D坐标方向是完全不同的。
- 2D,坐标原点是canvas画布的左顶点,水平方向向右是X轴,竖直方向向下是Y轴。如图:
-
3D,坐标原点是canvas画布的中心点,水平方向向右是X轴,竖直方向向上是Y轴,屏幕朝外方向是Z轴。如图:
webGL渲染管线
- webGL是个状态机,需要先提前设置所有状态,通过DrawCall命令GPU顺着渲染管线来调用设置好的所有状态,最终获得Framebuffer
- 设置所有状态的过程都是在CPU上跑,DrawCall命令下的渲染管线是GPU上跑
- 渲染管线存在可编程阶段,可配置阶段,不可配置阶段,可选阶段等
- 不同厂商实现的渲染管线部分不同
总结:渲染管线实质上指的是一系列的绘制过程。
其他前置知识
canvas
canvas是 HTML5 提供的一个用于绘制图形的区域,它可以通过 JavaScript 动态地渲染图形内容。canvas 可用于绘制图形、动画、图表、游戏以及实时数据可视化等。具体绘制2D或者3D图形,取决于渲染上下文对象是2D还是3D。
获取渲染上下文
HTMLCanvasElement.getContext()方法用于返回绘图上下文对象,绘图上下文对象是2D上下文还是3D上下文取决于传入的参数。
//获取一个webGLRenderingContext三维的渲染上下文对象
const gl = canvas.getContext("webgl");
//获取一个CanvasRenderingContext2D二维的渲染上下文对象
const 2d = canvas.getContext("2d");
类型化数组
JavaScript的类型化数组是一种用于处理和操作二进制数据的对象,类型化数组在处理WebGL等需要高性能和大数据量操作的应用中非常有用。
类型化数组和普通的JavaScript数组不同,类型化数组的每一个元素都是同一类型的二进制数据,这种类型在创建数组时就已经确定了。
以下是常用的一些特定类型的类型化数组:
视图名称 | 元素大小(字节) | 描述 | 等价的C语言类型 |
---|---|---|---|
Int8Array | 1 | 8位二进制补码有符号整数 | signed char |
Uint8Array | 1 | 8位无符号整数 | unsigned char |
Uint8ClampedArray | 1 | 8位无符号整数(对溢出做特殊处理) | unsigned char |
Int16Array | 2 | 16位二进制补码有符号整数 | short |
Uint16Array | 2 | 16位无符号整数 | unsigned short |
Int32Array | 4 | 32位二进制补码有符号整数 | int |
Uint32Array | 4 | 32位无符号整数 | unsigned int |
Float32Array | 4 | 32位IEEE浮点数 | float |
Float64Array | 8 | 64位IEEE浮点数 | double |
关于类型化数组本文不展开介绍,要详细了解请点击
着色器
GLSL(OpenGL Shading Language)是一种用于编写图形着色器的编程语言。着色器是用于在图形处理单元(GPU)上执行特定图形处理任务的程序。通俗讲,着色器是画点的工具,一个图形是由无数个点组成的,每个点都有其自己的颜色。
着色器的类型
一个着色器就是一个绘制东西到屏幕上的函数,着色器有顶点着色器和片元着色器。
- 顶点着色器:处理每个顶点的属性,如位置、法线、纹理坐标等。它的主要任务是将顶点从对象坐标系转换到屏幕坐标系,并传递其他顶点属性给片段着色器。
- 片元着色器:处理每个像素的颜色。它接收来自顶点着色器插值后的数据,并最终决定每个像素的颜色。
着色器语法
在HTML中,如果你想把着色器代码直接写在
<!-- 顶点着色器 -->
<script id="vertex-shader" type="x-shader/x-vertex">
attribute vec2 position;
void main(){
gl_PointSize = 5.0;
gl_Position = vec4(position,0.0,1.0);
}
</script>
<!-- 片元着色器 -->
<script id="fragment-shader" type="x-shader/x-fragment">
void main(){
gl_FragColor = vec4(1.0,0.0,0.0,1.0);
}
</script>
着色器中的变量修饰符
- atrribute:用于顶点着色器,定义从顶点缓冲区传入的变量(只能在顶点缓冲区使用,可理解为用于传递顶点数据)
- uniform:定义在整个渲染过程中保持不变的变量,常用于传递变换矩阵、光照参数。
- varying:用于在点点着色器和片元着色器之间传递插值数据
着色器中的内部变量
- 顶点着色器:
- gl_Position 顶点的位置
- gl_PointSize 顶点的大小
- 片元着色器:
- gl_FragColor 片元的最终颜色
webGL API
见webGL API
线性代数、矩阵知识
略。复习可参考以下:矩阵知识复习
第一个webGL程序
绘制一个点。具体步骤见代码中注释:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>firstwebGLDemo</title>
<style type="text/css">
#myCanvas{
background-color: aqua;
}
</style>
<script>
</script>
</head>
<body>
<canvas id='myCanvas' width="700" height='600'></canvas>
<!-- 顶点着色器 -->
<script id="vertex-shader" type="x-shader/x-vertex">
void main(){
gl_PointSize = 50.0;
gl_Position = vec4(0.5,0.5,0.0,1.0);
}
</script>
<!-- 片元着色器 -->
<script id="fragment-shader" type="x-shader/x-fragment">
void main(){
gl_FragColor = vec4(1.0,0.0,0.0,1.0);
}
</script>
<script>
/**
* 第一步 创建画布、获取画布、创建三维上下文对象
**/
const canvas = document.getElementById("myCanvas");
//获取一个webGLRenderingContext三维的渲染上下文对象
const gl = canvas.getContext("webgl");
/**
* 第二步 创建着色器、与webGL渲染对象绑定、编译
**/
//通过element获取着色器源码
const vertexSource = document.getElementById("vertex-shader").innerText;
const fragmentSource = document.getElementById("fragment-shader").innerText;
//创建顶点着色器对象,并绑定源码,编译;
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader,vertexSource)
gl.compileShader(vertexShader);
//创建片元着色器对象,并绑定源码,编译
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader,fragmentSource);
gl.compileShader(fragmentShader);
/**
* 第三步 创建着色器程序对象,并将着色器附着在程序上。
**/
//创建程序对象
const program = gl.createProgram();
//将着色器附着到程序对象
gl.attachShader(program,vertexShader);
gl.attachShader(program,fragmentShader);
//webgl对象链接程序对象,将顶点着色器和片元着色器链接成一个完成的可执行程序
gl.linkProgram(program);
//webgl对象使用这个program作为渲染程序
gl.useProgram(program);
/**
* 第四步 绘制---点
*/
gl.drawArrays(gl.POINTS,0,1);
</script>
</body>
</html>
绘制线(使用attribute)
根据第一个程序可知,绘制两个点后连接即可成为线。那么如何绘制多个点以及连接呢?这时候就要用到attribute(用于顶点着色器,定义从顶点缓冲区传入的变量,只能在顶点缓冲区使用,可理解为用于传递顶点数据)。
代码如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>firstwebGLDemo</title>
<style type="text/css">
#myCanvas{
background-color: aqua;
}
</style>
<script>
</script>
</head>
<body>
<canvas id='myCanvas' width="700" height='600'></canvas>
<!-- 顶点着色器 -->
<script id="vertex-shader" type="x-shader/x-vertex">
attribute vec2 position;
void main(){
gl_PointSize = 5.0;
gl_Position = vec4(position,0.0,1.0);
}
</script>
<!-- 片元着色器 -->
<script id="fragment-shader" type="x-shader/x-fragment">
void main(){
gl_FragColor = vec4(1.0,0.0,0.0,1.0);
}
</script>
<script>
/** 第一步 创建画布、获取画布、创建三维上下文对象 **/
const canvas = document.getElementById("myCanvas");
//获取一个webGLRenderingContext三维的渲染上下文对象
const gl = canvas.getContext("webgl");
//获取一个CanvasRenderingContext2D二维的渲染上下文对象
// const 2d = canvas.getContext("2d");
/** 第一步 创建顶点并将顶点数据存到当前绑定的webGL缓冲区 **/
const vertices = new Float32Array([
0.0,0.5,
-0.5,-0.5,
0.5,-0.5,
0.0,0.7,
-0.7,-0.7,
0.7,-0.7
])
//创建了一个新的缓冲区对象,存储顶点数据、颜色数据等信息。
const buffer = gl.createBuffer();
//将之前创建的缓冲区对象绑定到当前的 WebGL 上下文。
//gl.ARRAY_BUFFER表示我们绑定的是一个顶点属性的缓冲区,例如顶点位置、纹理坐标等。绑定后,所有针对 gl.ARRAY_BUFFER 的操作都会影响到这个缓冲区对象。 //
gl.bindBuffer(gl.ARRAY_BUFFER,buffer);
//将数据传递到刚才绑定的缓冲区中
//gl.STATIC_DRAW 是一个使用提示,告诉 WebGL 该数据将不会频繁更改,通常用于静态绘图。它帮助 WebGL 对数据进行优化
gl.bufferData(gl.ARRAY_BUFFER,vertices,gl.STATIC_DRAW)
/**
* 第三步 定义顶点着色器、片元着色器源码
*
* attribute 用于顶点着色器,定义从顶点缓冲区传入的变量
* attribute vec2 position 声明一个名为position的顶点属性,它是一个二维向量
*
* void main() {}:顶点着色器的主函数,计算每个顶点的最终位置。
* vec4(positoin,0.0,1.0) 是一个四维向量,表示顶点的最终位置
* gl_FragColor = vec4(1.0,0.0,0.0,1.0); 将片元着色器设置为红色,使用vec4表示颜色,RGBA分别为(1.0,0.0,0.0,1.0)
*
**/
//模板引擎方式
// const vertexShaderSource =`
// attribute vec2 position;
// void main(){
// gl_Position = vec4(position,0.0,1.0);
// }
// `;
// const fragmentShaderSource = `
// void main(){
// gl_FragColor = vec4(1.0,0.0,0.0,1.0);
// }
// `;
//通过element获取
const vertexSource = document.getElementById("vertex-shader").innerText;
const fragmentSource = document.getElementById("fragment-shader").innerText;
/**
* 第四步 创建顶点着色器对象,并绑定源码,编译;创建片元着色器对象,并绑定源码,,编译
* createShader(gl.VERTEX_SHADER) 创建一个顶点着色器
* createShader(gl.FRAGMENT_SHADER) 创建一个片元着色器
* gl.shaderSource(vertexShader,vertexSource) 将对应的着色器源码绑定到对应的着色器对象
* gl.compileShader(vertexShader) 编译对应的着色器源码
**/
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader,vertexSource)
gl.compileShader(vertexShader);
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader,fragmentSource);
gl.compileShader(fragmentShader);
/**
* 第五步 创建着色器程序对象,并将着色器附着在程序上。
*
**/
//创建程序对象
const program = gl.createProgram();
//将着色器附着到程序对象
gl.attachShader(program,vertexShader);
gl.attachShader(program,fragmentShader);
//webgl对象链接程序对象,将顶点着色器和片元着色器链接成一个完成的可执行程序
gl.linkProgram(program);
//webgl对象使用这个program作为渲染程序
gl.useProgram(program);
/**
* 第六步 设置顶点属性
*/
//获取顶点属性position在着色器中的程序位置
const positionLocation = gl.getAttribLocation(program,"position");
//启用顶点属性数组
gl.enableVertexAttribArray(positionLocation);
/**
* gl.vertexAttribPointer(index, size, type, normalized, stride, offset);
* 指定顶点数据组的解析方式。
* index:positionLocation 顶点属性position的位置,可以理解为需要将缓冲区数据解析到程序中的哪个位置。通过前面getAttribLocation返回的结果可以看出这是一个整数,指定要修改的顶点属性的索引。它通常是通过调用
* size:2 每个顶点属性的元素数量,可以理解为从缓冲区取数据时几个数据表示一个点。通过gl_Position = vec4(position,0.0,1.0)可以看出是二维数组,即两个浮点数表示一个点,所以是2.
* type:gl.FlOAT 指定数组中每个组件的数据类型。在这个例子中,gl.FLOAT 表示每个顶点属性数据的类型为 32 位浮点数 (float)
* normalized:false 数据是否应该被标准化处理。这里设置false表示不进行标准化处理,按原值传递。
* stride:0 跨度:在缓冲区获取数据时的跨度,以字节为单位。如果数据连续则跨度为0。WebGL 会根据 size 和 type 自动计算跨度。例如,如果每个顶点都有 (x, y) 坐标,且每个坐标为 32 位浮点数,则步幅为 2 * 4 = 8 字节。
* offset:0 指定缓冲区中的起始位置偏移量,以字节为单位。可以理解为从缓冲区的哪个字节位置开始读取第一个顶点属性。
*/
gl.vertexAttribPointer(positionLocation,2,gl.FLOAT,false,0,0);
/**
* 第七步 绘制
*
* gl.clearColor(0.0,0.0,0.0,1.0) 设置 WebGL 的清屏颜色,也就是背景颜色;
* gl.clear(gl.COLOR_BUFFER_BIT) 清除颜色缓冲区,也就是将整个绘图区域用前面指定的清屏颜色(黑色)填充;
* 以上与绘制内容无关,可以注释掉查看效果
* gl.drawArrays(gl.TRIANGLES,0,3) 绘制三角形,
* gl.TRIANGLES 表示每三个顶点组成一个三角形。如果你有 6 个顶点,则 WebGL 会绘制两个三角形,依此类推。
* 0: 从顶点数组的第 0 个位置开始绘制。
* 3: 表示要绘制的顶点数量。可以理解为有效顶点数据 这里设置5表示5个点有效,即只能绘制出一个三角形
*
* gl.POINTS: 绘制一系列点。
* gl.LINE_STRIP: 绘制一个线条。即,绘制一系列线段,上一点连接下一点。
* gl.LINE_LOOP: 绘制一个线圈。即,绘制一系列线段,上一点连接下一点,并且最后一点与第一个点相连。
* gl.LINES: 绘制一系列单独线段。每两个点作为端点,线段之间不连接。
* gl.TRIANGLE_STRIP:绘制一个三角带。
* gl.TRIANGLE_FAN:绘制一个三角扇。
* gl.TRIANGLES: 绘制一系列三角形。每三个点作为顶点。
*
*/
// gl.clearColor(0.0,0.0,0.0,1.0);
// gl.clear(gl.COLOR_BUFFER_BIT);
// gl.drawArrays(gl.POINTS,0,6);
// gl.drawArrays(gl.TRIANGLES,0,6);
gl.drawArrays(gl.LINE_LOOP,0,6);
gl.drawArrays(gl.LINE_LOOP,4,3);
</script>
</body>
</html>
绘制不同颜色的图形(使用varying)
顶点着色器与片元着色器通过varying定义参数后传递参数。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>firstwebGLDemo</title>
<style type