【青训营】WebGL基础

230 阅读4分钟

这是我参与2022首次更文挑战的第3天,活动详情查看:2022首次更文挑战

授课老师:月影老师

01. 基础知识

WebGL(全写Web Graphics Library)是一种3D绘图协议,这种绘图技术标准允许把JavaScript和OpenGL ES 2.0结合在一起,通过增加OpenGL ES 2.0的一个JavaScript绑定,WebGL可以为HTML5 Canvas提供硬件3D加速渲染。

WebGL理解

  • 很大程度上与GPU打交道,但不仅仅是GPU渲染。
  • 不仅仅是绘制3D。
  • 浏览器上的OpenGL。

现代图形系统

image.png

相关概念:

  • 光栅(Raster) : 几乎所有的现代图形系统都是基于光栅来绘制图形的,光栅就是指构成图像的像素阵列。
  • 像素(Pixel) : 一个像素对应图像上的一个点,它通常保存图像上的某个具体位置的颜色等信息。
  • 帧缓存(Frame Buffer) : 在绘图过程中,像素信息被存放于帧缓存中,帧缓存是一块内存地址 。
  • CPU (Central Processing Unit) : 中央处理单元,负责逻辑计算。
  • GPU (Graphics Processing Unit) : 图形处理单元,负责图形计算。

图形渲染流程

  1. 轮廓提取
  2. 光栅化
  3. 帧缓存
  4. 渲染

GPU:

GPU由大量的小运算单元构成,每个运算单元只负责处理很简单的计算。每个运算单元彼此独立。因此所有计算可以并行处理。

02. 图形渲染

利用WebGL绘图步骤

  1. 创建WebGL上下文
  2. 创建WebGL Program
  3. 将数据存入缓冲区
  4. 将缓冲区数据读取到GPU
  5. GPU执行WebGL程序,输出结果

image.png

WebGL呈现在浏览器页面内的流程:

image.png

1. 创建WebGL上下文(Context)

image.png

HTML代码如下:创建一个宽高400的canvas画布。

<canvas id="view-box" width="400" height="400"></canvas>

JavaScript代码如下:获取DOM之后调用getContext('webgl')获取对应的上下文。如果是绘制2d图形,那么可以传入参数2d来获取绘图的上下文。

var box = document.getElementById("view-box");
var gl = box.getContext('webgl');
if(!gl){
    console.log("没有获取到WebGL上下文");
}else{
    console.log("获取上下文成功:",gl);
}

控制台输出如下内容:

image.png

注意:获取WebGL绘图上下文时,canvas.getContext()函数接收的参数在不同浏览器中会不同。原因:早期WebGL的context,还不能通过正式的名称webgl来获取,必须使用experimental-webgl来获取context对象。当然,还有现在很多浏览器也支持webgl2等其他参数,所以可以封装成一个具有特性检测的函数:

function create3DContext(canvas,options) {
    const names = ['webgl', 'experimental-webgl','webkit-3d', ' moz-webgl'];
    if (options && options.webgl2) names.unshift('webgl2');
    let context = null;
    for (let ii = 0; ii < names.length; ++ii) {
        try {
            context = canvas.getContext(names[ii], options);
        } catch (e) {
            // no-empty
        }
        if (context) {
            break;
        }
    }
    return context;
}

2. 创建WebGL Program

  • 顶点着色器 Vertex Shader : 用于处理顶点的位置
attribute vec2 position;

void main(){
    gl_PointSize = 1.0;
    gl_Position = vec4(position,1.0,1.0);
}
  • 片元着色器 Fragment Shader

并行处理顶点包围内的所有像素,处理次数更多。颜色取值 从 0.0 - 1.0 的浮点范围。

precision mediump float;

void main(){
    gl_FragColor = vec4(1.0,0.0,0.0,1.0);
}

创建并使用着色器的相关步骤:

  • 1、创建着色器对象gl.createShader()
  • 2、向着色器对象中填充着色器程序的源代码gl.shaderSource()
  • 3、编译着色器gl.compileShader()
  • 4、创建程序对象gl.createProgram()
  • 5、为程序对象分配着色器gl.attachShader()
  • 6、连接程序对象gl.linkProgram()
  • 7、使用程序对象gl.useProgram()

继续我们的示例代码:

// 顶点着色器代码
const vertexShaderCode = `
attribute vec2 position;
void main(){
    gl_PointSize = 1.0;
    gl_Position = vec4(position,1.0,1.0);
}
`;
// 片元着色器代码
const fragmentShaderCode = `
precision mediump float;
void main(){
    gl_FragColor = vec4(1.0,0.0,0.0,1.0);
}
`;

// 顶点着色器
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexShaderCode);
gl.compileShader(vertexShader);
// 片元着色器
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentShaderCode);
gl.compileShader(fragmentShader);

// 创建程序
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
gl.useProgram(program);

3. 将数据存入缓冲区Frame Buffer

坐标系知识:canvas和浏览器的坐标轴都是左上角为原点。但WebGL的坐标体系,是一个以绘制画布的中心为原点,范围从 -1 到 1。在3D的情况下,z轴正方向是向外的。

image.jpeg

比如绘制一个三角形,需要一个浮点类型数组表示顶点数据,创建buffer后将返回的bufferid绑定到上下文,最后绑定数据到Buffer里。

//绘制的顶点数据
const points = new Float32Array([
    -1, -1,
    0, 1,
    1, -1,
]);
// 创建缓冲区 ,会得到对应的id  
const bufferId = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, bufferId);
gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW);

4. 将缓冲区数据读取到GPU Buffer to GPU

做一个指针的绑定:由于我们之前在vertex_shader定义了一个二维的向量position,所以需要传入相关的长度和类型。(类似于图形学的uniform类型,这里是attribute类型。)

// 从缓冲区到 GPU
const vPosition = gl.getAttribLocation(program,'position');//获取顶点着色器中的position变量的地址
gl.vertexAttribPointer(vPosition, 2, gl.FLOAT, false,0, 0); //给变量设置长度和类型
gl.enableVertexAttribArray(vPosition); //激活这个变量

5. GPU执行WebGL程序,输出结果 Output

清除缓冲区数据,再以三角形图元方式绘制:(因为每个点是一个二维的向量,length为6,所以需要除2)

gl.clear(gl.COLOR_BUFFER_BIT);//清除缓冲区
gl.drawArrays(gl.TRIANGLES, 0 ,points.length / 2);//绘制

最终实现效果如下:

image.png

ps:如果给三角形的三个顶点分别赋予一个颜色,那么片元着色器会根据三个顶点对内部的顶点做一个线性的插值,从而实现颜色渐变的效果。

03. 其他方式

费了好大的劲终于绘制出了一个三角形,难怪没人用原生的啦。 下面尝试使用其他的方式绘制图形。

1. canvas 2D 绘制三角形

获取2d的canva上下文后,通过beginPathmoveTolineTo等等非常语义化的API直接调用。8行JS代码就能绘制出三角形。

image.png

HTML代码如下:创建一个500*500的myCanvas画布。

<canvas id="myCanvas" width="500" height="500"></canvas>

JavaScript代码如下:

const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');

ctx.beginPath();
ctx.moveTo(250, 0);
ctx.lineTo(500, 500);
ctx.lineTo(0, 500);
ctx.fillStyle = 'red';
ctx.fill();

效果也能画出一个三角形。不过和第一种方式不同的是,这里的顶点坐标是浏览器的坐标系(px),而第一种WebGL上下文的坐标范围是(-1,1)。

2. 使用mesh.js的开源库

const {Renderer, Figure2D, Mesh2D} = meshjs;
const canvas = document.querySelector ('canvas');
const renderer = new Renderer (canvas) ;

const figure = new Figure2D();
figurie.beginPath();
figure.moveTo(250, 0);
figure.lineTo(500500) ;
figure.lineTo(0, 500) ;
const mesh = new Mesh2D(figure, canvas) ;
mesh.setFill({
    color: [1, 0, 0, 1],
});
renderer.drawMeshes( [mesh]) ;

3. 其他图形(Polygons)的绘制

方法:三角剖分,图形学的基操(基本操作)。

image.png

4. 3D Meshing

一般步骤:先用设计软件设计模型,接着提取所有的顶点数据,最后渲染出来。

04. 图形变换

  • 平移 image.png

  • 旋转 image.png

  • 缩放 image.png

旋转+缩放是线性变换,会改变顶点位置。通过那些变换矩阵相乘,得到相同的复合想过。

image.png

image.png

包括了平移效果的变化矩阵P,再做运算,从线性变换到齐次矩阵。

image.png

使用uniform声明模型矩阵(公共变量)

uniform mat3 modelMatrix;

3D标准模型的四个齐次矩阵(mat4)

  1. 投影矩阵Projection Matrix (相对于相机的视角投影到的面:正交投影,透视投影)
  2. 模型矩阵Model Matrix : (对顶点做平移,旋转,缩放)
  3. 视图矩阵View Matrix: (相机的位置,在视口下)
  4. 法向量矩阵Normal Matrix: (根据3D物体垂直表面的法向量,计算光照)

04. Read More

  1. The book of shaders
  2. Mesh.js
  3. glsl-doodle
  4. SpriteJS
  5. ThreeJS:学得不多,游戏却没少玩。
  6. shadertoy:超炫酷着色器实现效果

里面还有很多...

  • 利用分型思想,随机性,噪声生成更加炫酷的内容。
  • 纹理贴图
  • cube全景投影(天空盒?)
  • gpgpu(体现GPU的优势性)
  • 实例渲染(复用同一个物体)
  • pbr:超真实体现物体
  • post实现带有液体效果。
  • skinning:实现动画的贴图。
  • video在渲染里面播放视频

答疑: canvas和WebGL对比

  • canvas20帧,WebGL性能更好。如果需要实现复杂效果,肯定需要使用WebGL。
  • WebGL可以压缩相同材质的物体顶点到一起,统一传到着色器,一次渲染成千上万个图形。