studyNotes
studyNotes copied to clipboard
WebGL分享
WebGL是什么? WebGL是一项用户在网页上绘制和渲染复杂的三维图形(3D图形),并允许用户与之进行交互的技术。
WebGL的结构? 包含HTML5、Javascript和GLSL ES。
Canvas 允许Javascript动态的绘制2d图形,但对于绘制3d图形,就必须使用WebGL。WebGL程序包括用 JavaScript 写的控制代码,以及在图形处理单元(GPU, Graphics Processing Unit)中执行的着色代码(GLSL,注:GLSL为OpenGL着色语言)。WebGL 元素可以和其他 HTML 元素混合使用,并且可以和网页其他部分或者网页背景结合起来。
WebGL (Web图形库) 是一种JavaScript API,用于在任何兼容的Web浏览器中呈现交互式3D和2D图形,而无需使用插件。WebGL通过引入一个与OpenGL ES 2.0紧密相符合的API,可以在HTML5
WebGL图形管线

- 初始化WebGL环境
进行3d渲染前,首先需要WebGL环境,由于WebGL是画在canvas上的,所以需要一个canvas原生,代码如下:
<body onload="main">
<canvas id="glcanvas" width="375" height="375">
Your browser doesn't appear to support the HTML5 <code><canvas></code> element.
</canvas>
</body>
canvas需要获取,然后再进行设置WebGL上下文并开始渲染内容,上面的onload中加载的main函数如下:
function main(){
const canvas=document.querySelector("#glcanvas");//获得canvas标签
const gl=canvas.getContext("webgl");//初始化gl上下文
if(!gl){
alert('初始化上下文失败,可能是你的设备或者浏览器不支持。');
return;
}
gl.clearColor(0.0,0.0,0.0,1.0);//设置颜色为黑色,不透明
gl.clear(gl.COLOR_BUFFER_BIT);//清除指定颜色的颜色缓冲区
}
获取到 canvas之后,我们会试着通过调用一个getContext 的函数并传递给它一个"webgl"字符串来获取WebGLRenderingContext。如果浏览器不支持webgl,getContext将会返回null,我们就可以显示一条消息给用户然后退出。如果WebGL上下文成功初始化,变量 ‘gl’ 会用来引用该上下文。在这个例子里,我们将清除色设为黑色,然后用该颜色清除上下文。(用背景颜色重绘canvas)。
- 渲染场景
渲染场景的情况下我们想到的是需要着色器,那什么是着色器呢?着色器是使用 OpenGL ES Shading Language(GLSL)编写的程序,它携带着绘制形状的顶点信息以及构造绘制在屏幕上像素的所需数据,换句话说,它负责记录着像素点的位置和颜色。
WebGL中有顶点着色器和片段着色器两中不同的着色器函数。通过用GLSL 编写这些着色器,并将代码文本传递给WebGL , 使之在GPU执行时编译。
顶点着色器的功能是对传进来的顶点数据通过矩阵进行换换位置、计算照明方程式以生成逐顶点的颜色以及生成或者变换纹理坐标。
片元着色器则是对即将送到屏幕上的像素内容进行更进一步的处理,包括一些特殊效果的定制等。
- 顶点着色器
每次渲染一个形状时,顶点着色器会在形状中的每个顶点运行。 它的工作是将输入顶点从原始坐标系转换到WebGL使用的缩放空间(clipspace)坐标系,其中每个轴的坐标范围从-1.0到1.0,并且不考虑纵横比,实际尺寸或任何其他因素。
顶点着色器需要对顶点坐标进行必要的转换,在每个顶点基础上进行其他调整或计算,然后通过将其保存在由GLSL提供的特殊变量(我们称为gl_Position)中来返回变换后的顶点。
下面是着色器代码:
// Vertex shader program
const vsSource = `
attribute vec4 aVertexPosition;
uniform mat4 uModelViewMatrix;
uniform mat4 uProjectionMatrix;
void main() {
gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition;
}
`;
- 片段着色器
片段着色器在顶点着色器处理完图形的顶点后,会被要绘制的每个图形的每个像素点调用一次。它的职责是确定像素的颜色,通过指定应用到像素的纹理元素(也就是图形纹理中的像素),获取纹理元素的颜色,然后将应用适当的光照。之后颜色存储在特殊变量gl_FragColor中,返回到WebGL层。该颜色将最终绘制到屏幕上图形对应像素的对应位置。下面是着色器参考代码:
const fsSource = `
void main() {
gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
}
`;
顶点数组对象(VBO)、索引数值对象(IBO)
顶点数组对象(VBO) 顶点数组对象(VBO)对应着图中最上方的红色的方块。顶点数组对象包含着WebGL要渲染的图形的数据。可以看成是流水线上的原材料。在WebGL的渲染过程中,这些图形的数据往往通过顶点进行储存和输送,顶点数组对象包含的数据一般包括顶点位置信息、顶点位置上的法线向量、纹理坐标等。
索引数组对象(IBO) 在图形的绘制过程中,我们把每3个顶点绘制成一个三角形,即图形学中“面”(surface)的含义,而后通过成千上万的面组成了我们空间中的三维模型。索引数组对象(IBO)的作用是告诉WebGL要通过什么样的顺序来将我们传入的顶点数据连接成面。为了便于理解,我们举个栗子:

当我们按照如图所示的方式绘制一个梯形时,我们弄来了5个顶点(0, 0)、(10, 10)、(20, 0)、(30, 10)、(40, 0)分别对应从0-4的五个索引。而当我们绘制图形时,定义的索引数组[0, 2, 1, 1, 2, 3, 2, 4, 3]的意思就是告诉WebGL,把索引为0、2、1的三个顶点组成一个三角形,索引为1、2、3的三个顶点组成另一个三角形,索引为2、4、3的顶点也组成一个三角形(可参照图示)。 索引数组对象的作用就是保存这些索引数组的数据,用以传输给WebGL渲染管线。
- 初始化着色器
当我们定义了两个着色器之后,我们需要把它们传给WebGL,编译并将它们链接在一起。下面代码通过调用loadShader(),为着色器传递类型和来源,创建了两个着色器。然后创建一个附加着色器的程序,将它们连接在一起。如果编译或链接失败,代码将弹出alert。
// 初始化着色器程序,让WebGL知道如何绘制我们的数据
function initShaderProgram(gl, vsSource, fsSource) {
const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource);
const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource);
// 创建着色器程序
const shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
// 创建失败, alert
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
alert('Unable to initialize the shader program: ' + gl.getProgramInfoLog(shaderProgram));
return null;
}
return shaderProgram;
}
// 创建指定类型的着色器,上传source源码并编译
function loadShader(gl, type, source) {
const shader = gl.createShader(type);//创建着器shader对象
gl.shaderSource(shader, source);//将源发送到着色器shader对象
gl.compileShader(shader);//编译shader
//判断编译是否成功。
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
alert('An error occurred compiling the shaders: ' + gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return null;
}
return shader;
}
然后初始化着色器就可以使用这个方法
const shaderProgram = initShaderProgram(gl, vsSource, fsSource);
- 创建缓冲器
以画一个正方形为例,需要创建一个缓冲器来存储它的顶点。代码如下:
var horizAspect = 480.0/640.0;
function initBuffers() {
squareVerticesBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, squareVerticesBuffer);
var vertices = [
1.0, 1.0, 0.0,
-1.0, 1.0, 0.0,
1.0, -1.0, 0.0,
-1.0, -1.0, 0.0
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
}
这段代码简单给出了绘画场景的本质。首先,它调用 gl 的成员函数 createBuffer() 得到了缓冲对象并存储在顶点缓冲器。然后调用 bindBuffer() 函数绑定上下文。
当上一步完成,我们创建一个Javascript 数组去记录每一个正方体的每一个顶点。然后将其转化为 WebGL 浮点型类型的数组,并将其传到 gl 对象的 bufferData() 方法来建立对象的顶点。
- 绘制流程
当着色器和物体都创建好后,我们可以开始渲染这个场景了。我们用drawScene() 方法来渲染。代码如下:
function drawScene() {
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);//用背景色擦除上下文
perspectiveMatrix = makePerspective(45, 640.0/480.0, 0.1, 100.0);//建立摄像机透视矩阵,设置45度的视图角度,并且宽高比设为 640/480(画布尺寸),指定在摄像机距离0.1到100单位长度的范围内,物体可见。
mvTranslate([-0.0, 0.0, -6.0]);//把正方形放在距离摄像机6个单位的的位置
gl.bindBuffer(gl.ARRAY_BUFFER, squareVerticesBuffer);//绑定正方形的顶点缓冲到上下文
gl.vertexAttribPointer(vertexPositionAttribute, 3, gl.FLOAT, false, 0, 0);//配置
setMatrixUniforms();
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);//调用 drawArrays() 方法来画出对象
}
总结
WebGL整个的绘制流程如下:
- 从HTML中获取canvas对象
- 从canvas中获取WebGL的context
- 编译着色器
- 准备模型数据
- 顶点缓存(VBO)的生成和通知
- 坐标变换矩阵的生成和通知
- 发出绘图命令
- 更新canvas并渲染
WebGL基于OpenGL ES 2.0,而早先的OpenGL用的都是固定的渲染管线,之前学OpenGL一开始都是glBegin,glEnd,glVertex2d啥的一堆东西,精简版的OpenGL ES一上来就抛弃了之前固定渲染管线的东西,使用了可编程的渲染管线,一上来就要求要编写Shader,所以绘制一个图形就变得比较繁琐一点。
bug fix:
二维平面真实坐标到webGL坐标转换
计算公式为: x′=2(w(x+a)/2a−x0−w/2))/w y′=2(h/2−recH+h(y+b)/2b+y0)/h
其中, a,b为模型在真实坐标系中的x,y方向最大值,并假设真实坐标系为对称坐标系,范围为(−a,−b),(a,b);或者理解为真是坐标系的x,y轴的最大值; w代表canvas宽度大小像素值;h代表canvas高度像大小素值; x0,y0代表canvas左上角坐标的像素值; recH 代表客户区的高度,recH=h;