前言
对于 webgl 的学习,我打算从头开始。从简到繁,希望可以扎实的过一遍。现在我们就从最简单的绘制单个点入手,文中涉及到的 API,我会给大家列出重点,以及在线文档地址。
步骤
引入 glMatrix.js 文件
glMatrix.js 是一个 JavaScript 库,主要用于高性能矩阵和向量
操作,这些操作在 WebGL 编程中非常常见。这个库中封装了矩阵和向量
相关的方法,我们可以直接调用。
实现过程
初始化 4*4 矩阵
ini
var projMat4 = mat4.create(); // 初始化一个4*4的矩阵
申明 webgl 变量
csharp
var webgl; // 声明全局变量
编写顶点着色器代码
顶点着色器主要负责处理 3D 模型的顶点数据。
顶点着色器
的主要任务包括: `
-
变换(Transformations):顶点着色器通常负责将
3D 模型的顶点坐标
从模型空间(Model Space)
变换到裁剪空间(Clip Space)
。这个过程通常需要应用几个变换矩阵
,包括模型矩阵(Model Matrix)、视图矩阵(View Matrix)和投影矩阵(Projection Matrix)
。模型矩阵
负责将顶点坐标从模型空间变换到世界空间
,视图矩阵
负责将顶点坐标从世界空间变换到视图空间
,投影矩阵
负责将顶点坐标从视图空间变换到裁剪空间
。 -
光照计算(Lighting):虽然更复杂的光照模型通常在片元着色器中实现,但是一些简单的光照计算,如 Gouraud 光照,可以在顶点着色器中进行。这种方法的优点是性能开销较小,因为顶点的数量通常比片元的数量要少得多。缺点是光照效果可能不够精细。
-
传递数据(Passing Data):
顶点着色器还负责将数据传递到片元着色器
。这些数据可以包括顶点的颜色、纹理坐标、法线等
。这些数据在顶点着色器中计算或获取,然后通过 varying 变量传递到片元着色器
。 -
其他计算:顶点着色器还可以用于实现一些特效,如顶点位移(Vertex Displacement)、实例化(Instancing)等。
总的来说,顶点着色器是 3D 渲染管线中的第一个阶段,它处理和操作顶点数据,为后续的光栅化、片元着色等阶段做准备
。
关于几个坐标系空间:
- 模型空间:也被称为
局部空间(Local Space)
,是指模型本身的坐标空间
。例如,如果你有一个立方体模型,那么在模型空间中,这个立方体的中心可能就是原点(0,0,0),而立方体的8个顶点则可能被定义为(±1, ±1, ±1)
。 - 世界空间:世界空间(World Space)是指
世界坐标系空间
。在3D图形中,世界空间是一个全局的坐标系统,所有的物体都在这个空间中定义其位置
。 - 视图空间:也被称为
相机空间(Camera Space)
,在视图空间中,相机位于原点,朝向通常是Z轴的负方向(这个因具体的坐标系定义可能会有所不同,但在右手坐标系中是这样的)。换句话说,视图空间是以相机的位置和朝向为基准的坐标系。
- 裁剪空间:可以进行
裁剪操作
,也就是将那些不在相机视野内的几何体(或者部分)裁剪掉
。具体来说,任何在裁剪空间的立方体(通常是从-1 到 1 的立方体)之外的顶点都会被裁剪掉。这个立方体被称为裁剪体(Clipping Volume)或者可视体(Viewing Volume)
. 在裁剪之后,顶点的坐标会被转换到归一化设备坐标(Normalized Device Coordinates, NDC)空间
,这个过程通常被称为透视除法(Perspective Division)
。在 NDC 空间中,所有的坐标都在立方体(通常是从-1 到 1 的立方体)内。
最后,NDC坐标会被转换到屏幕空间(Screen Space)坐标
,这个过程被称为视口变换(Viewport Transform)
。屏幕空间的坐标是以像素为单位的,用于最终的光栅化(Rasterization)操作,将3D图形渲染到2D屏幕上
。
所以:几个空间变化的顺序为:模型空间->世界空间->视图空间->裁剪空间
几个变化矩阵的顺序为:模型矩阵->视图矩阵->投影矩阵

ini
var vertexString = `
attribute vec4 a_position;
uniform mat4 proj;
void main(){
gl_Position =proj * a_position;
gl_PointSize = 60.0;
}
`;
- 首先我们需要了解下,shader 中的变量类型有那些?
- attribute:这是
只能在顶点着色器
中使用的变量
。它们通常用于存储每个顶点的数据,如位置、颜色、纹理坐标
等。 - uniform: 这是在
顶点着色器和片元着色器
中都可以使用的变量
。它们通常用于存储在一个渲染调用过程中不会改变的值
,如变换矩阵、光源位置、材质属性
等。所有的顶点和片元都共享相同的 uniform 值。 - varying: 这是在
顶点着色器和片元着色器
之间传递数据的变量
。顶点着色器计算出varying变量的值
,然后这些值会被插值并传递给片元着色器
。这使得每个片元都可以获取到自己的 varying 值,常用于实现如颜色混合、纹理映射等效果
。 - sampler: 这是一种
特殊类型的uniform
,用于访问纹理
。例如,sampler2D和samplerCube分别用于访问2D纹理和立方体贴图
。 - const: 这是
常量变量
,其值在编译时就已经确定
,不能在运行时改变
。这些变量可以在顶点着色器和片元着色器
中使用。 - in/out:这是
GLSL ES 3.0(WebGL 2.0)中新增的关键字
,用于替代GLSL ES 1.0(WebGL 1.0)中的attribute和varying
。在顶点着色器中,in用于接收输入数据,out用于输出数据到片元着色器;在片元着色器中,in用于接收来自顶点着色器的数据,out用于输出最终的颜色。
- 了解了这些变量类型之后,我们再来看下我们写的代码
- 声明一个四维向量(x,y,z,w),去储存顶点的位置
ini
attribute vec4 a_position;
-
声明一个 4*4 的
变换矩阵
uniform mat4 proj;
-
主函数
ini
void main(){
gl_Position =proj * a_position; //点在屏幕上的位置为webgl坐标系中的位置*变换矩阵
gl_PointSize = 60.0; //点的大小设置为60.0
}
注意,着色器中说有的数字都是浮点数,所以我们的数字都要加上小数点,否则会报错。
编写片元着色器代码
片元着色器的主要作用是计算渲染到屏幕上每个像素(或者更准确地说,每个片元)的颜色和其他属性。
csharp
var fragmentString = `
void main(){
gl_FragColor = vec4(0,0,1.0,1.0); //定义输出的颜色
}
`; // 片元着色器
初始化 webgl
javascript
// webgl初始化函数
function initWebgl() {
// 获取canvas容器
let webglDiv = document.querySelector("#webglCanvas");
// 设置webgl上下文
webgl = webglDiv.getContext("webgl");
// 设置可视范围 https://developer.mozilla.org/zh-CN/docs/Web/API/WebGLRenderingContext/viewport
webgl.viewport(0, 0, webglDiv.width, webglDiv.height);
// 定义阴影范围
mat4.ortho(
0,
webglDiv.clientWidth,
webglDiv.clientHeight,
0,
-1,
1,
projMat4
);
}
mat4.ortho 语法:
css
mat4.ortho( left, right, bottom, top, near, far,out);
-
out: 这是一个已经存在的矩阵,新创建的正交投影矩阵会存储在这里。
-
left, right: 这是视图空间的左边界和右边界。
-
bottom, top: 这是视图空间的底边界和顶边界。
-
near, far: 这是视图空间的近裁剪面和远裁剪面。
该函数会创建一个新的正交投影矩阵,并将其存储在 out 参数中。
初始化 Shader
scss
// shder初始化函数
function initShader() {
// createShader() 用于创建一个 WebGLShader 着色器对象,该对象可以使用 shaderSource()和 compileShader() 方法配置着色器代码。
// 参数为gl.VERTEX_SHADER 或 gl.FRAGMENT_SHADER两者中的一个。
let vsshader = webgl.createShader(webgl.VERTEX_SHADER);
let fssagder = webgl.createShader(webgl.FRAGMENT_SHADER);
// 用于将我们创建的 WebGLShader着色器对象和GLSL程序中定义的着色器相关联。
// 第一个参数为webglShader对象,第二个参数为GLSL中定义的着色器
webgl.shaderSource(vsshader, vertexString);
webgl.shaderSource(fssagder, fragmentString);
// 编译WebGLShader着色器,使其成为为二进制数据,然后就可以被WebGLProgram对象所使用。
// 参数为一个片元着色器或顶点着色器
webgl.compileShader(vsshader);
webgl.compileShader(fssagder);
// 创建一个webglProgram对象,该对象由两个编译过后的 WebGLShader 组成 - 顶点着色器和片段着色器(均由 GLSL 语言所写)
let program = webgl.createProgram();
// attachShader() 方法负责往 WebGLProgram 添加一个片段或者顶点着色器。
// 第一个参数为webglProgram对象,第二个参数为片段或者顶点的 WebGLShader
webgl.attachShader(program, vsshader);
webgl.attachShader(program, fssagder);
// linkProgram()方法链接给定的WebGLProgram,从而完成为程序的片元和顶点着色器准备 GPU 代码的过程。参数为一个用于链接的WebGLProgram对象
webgl.linkProgram(program);
// useProgram() 方法将定义好的WebGLProgram 对象添加到当前的渲染状态中。
webgl.useProgram(program);
webgl.program = program;
}
初始化数据缓冲区
javascript
// 数据缓冲区初始化函数
function initBuffer() {
//Float32Array 类型数组代表的是平JS内置的标准对象,为 32 位的浮点数型数组,其内容初始化为 0。一旦建立起来,你可以使用这个对象的方法对其元素进行操作,或者使用标准数组索引语法 (使用方括号)。https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Float32Array
let pointPosition = new Float32Array([100.0, 100.0, 0.0, 1.0]);
// getAttribLocation() 方法返回给定WebGLProgram对象中某属性的下标指向位置
// 第一个参数为WebGLProgram,第二个参数为需要获取下标指向位置的GLSL变量名
let aPosition = webgl.getAttribLocation(webgl.program, "a_position");
// vertexAttrib4fv()方法可以为顶点 attibute 变量赋值。
// 第一个参数为指定了待修改顶点 attribute 变量的存储位置。第二个参数为用于设置顶点 attibute 变量的向量值。
webgl.vertexAttrib4fv(aPosition, pointPosition);
// getUniformLocation()方法用于获取指定WebGLProgram对象中uniform变量的位置。
// 第一个参数为要获取uniform变量的WebGLProgram对象,第二个参数为要获取位置的uniform变量的名称。
let uniforproj = webgl.getUniformLocation(webgl.program, "proj");
// uniformMatrix4fv()用于设置一个4*4的矩阵类型的uniform变量值。接收4个参数
// 第一个参数为 要设置值的uniform变量的地址
// 第二个参数为 是否将矩阵转置,默认false
// 第三个参数为 要设置的值,应该是一个4*4的矩阵
// 第四个参数为 矩阵在数组中的偏移量,默认为0
webgl.uniformMatrix4fv(uniforproj, false, projMat4);
}
绘制函数
scss
// webgl的绘制函数
function draw() {
// clearColor ()方法指定在清除颜色缓冲区时使用的颜色值。接收的4个参数分别表示 r,g,b,a。取值均在0和1之间。
webgl.clearColor(0.0, 0.0, 0.0, 1.0);
// clear() 方法使用预设值来清空缓冲。预设值可以使用 clearColor() 、 clearDepth() 或 clearStencil() 设置。裁剪、抖动处理和缓冲写入遮罩会影响 clear() 方法。参数为一个用于指定需要清除的缓冲区的 GLbitfield (en-US) 。可能的值有:gl.COLOR_BUFFER_BIT(颜色缓冲区);gl.DEPTH_BUFFER_BIT (深度缓冲区) gl.STENCIL_BUFFER_BIT(模板缓冲区) 没有返回值
webgl.clear(webgl.COLOR_BUFFER_BIT);
// drawArrays() 方法用于从向量数组中绘制图元。接收3个参数
// 第一个参数为指定绘制图元的方式,可能值如下:
// gl.POINTS: 绘制一系列点。
// gl.LINE_STRIP: 绘制一个线条。即,绘制一系列线段,上一点连接下一点。
// gl.LINE_LOOP: 绘制一个线圈。即,绘制一系列线段,上一点连接下一点,并且最后一点与第一个点相连。
// gl.LINES: 绘制一系列单独线段。每两个点作为端点,线段之间不连接。
// gl.TRIANGLE_STRIP:绘制一个三角带。
// gl.TRIANGLE_FAN:绘制一个三角扇。
// gl.TRIANGLES: 绘制一系列三角形。每三个点作为顶点。
// 第二个参数为指定从哪个点开始绘制。
// 第三个参数为指定绘制需要使用到多少个点。
webgl.drawArrays(webgl.POINTS, 0, 1);
}
初始化函数
scss
// 入口函数
function init() {
initWebgl(); // 固定流程
initShader(); //固定流程
initBuffer();
draw(); // 自定义绘制
}
完整代码
xml
<!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>绘制点</title>
<script src="./glMatrix-0.9.6.min.js"></script>
<script>
var projMat4 = mat4.create(); // 初始化一个4*4的矩阵
var webgl; // 声明全局变量
/**
* GLSL代码
* 声明一个顶点着色器
* vec4:四维向量 ,具有xyzw四个分量,分量是浮点数
* mat4: 4*4矩阵
* **/
var vertexString = `
attribute vec4 a_position;
uniform mat4 proj;
void main(){
gl_Position =proj * a_position;
gl_PointSize = 60.0;
}
`;
var fragmentString = `
void main(){
gl_FragColor = vec4(0,0,1.0,1.0);
}
`; // 片元着色器
// 入口函数
function init() {
initWebgl();
initShader();
initBuffer();
draw();
}
// webgl初始化函数
function initWebgl() {
// 获取canvas容器
let webglDiv = document.querySelector("#webglCanvas");
// 设置webgl上下文
webgl = webglDiv.getContext("webgl");
// 设置可视范围 https://developer.mozilla.org/zh-CN/docs/Web/API/WebGLRenderingContext/viewport
webgl.viewport(0, 0, webglDiv.width, webglDiv.height);
// 定义阴影范围
mat4.ortho(
0,
webglDiv.clientWidth,
webglDiv.clientHeight,
0,
-1,
1,
projMat4
);
}
// shder初始化函数
function initShader() {
// createShader() 用于创建一个 WebGLShader 着色器对象,该对象可以使用 shaderSource()和 compileShader() 方法配置着色器代码。
// 参数为gl.VERTEX_SHADER 或 gl.FRAGMENT_SHADER两者中的一个。
let vsshader = webgl.createShader(webgl.VERTEX_SHADER);
let fssagder = webgl.createShader(webgl.FRAGMENT_SHADER);
// 用于将我们创建的 WebGLShader着色器对象和GLSL程序中定义的着色器相关联。
// 第一个参数为webglShader对象,第二个参数为GLSL中定义的着色器
webgl.shaderSource(vsshader, vertexString);
webgl.shaderSource(fssagder, fragmentString);
// 编译WebGLShader着色器,使其成为为二进制数据,然后就可以被WebGLProgram对象所使用。
// 参数为一个片元着色器或顶点着色器
webgl.compileShader(vsshader);
webgl.compileShader(fssagder);
// 创建一个webglProgram对象,该对象由两个编译过后的 WebGLShader 组成 - 顶点着色器和片段着色器(均由 GLSL 语言所写)
let program = webgl.createProgram();
// attachShader() 方法负责往 WebGLProgram 添加一个片段或者顶点着色器。
// 第一个参数为webglProgram对象,第二个参数为片段或者顶点的 WebGLShader
webgl.attachShader(program, vsshader);
webgl.attachShader(program, fssagder);
// linkProgram()方法链接给定的WebGLProgram,从而完成为程序的片元和顶点着色器准备 GPU 代码的过程。参数为一个用于链接的WebGLProgram对象
webgl.linkProgram(program);
// useProgram() 方法将定义好的WebGLProgram 对象添加到当前的渲染状态中。
webgl.useProgram(program);
webgl.program = program;
}
// 数据缓冲区初始化函数
function initBuffer() {
//Float32Array 类型数组代表的是平JS内置的标准对象,为 32 位的浮点数型数组,其内容初始化为 0。一旦建立起来,你可以使用这个对象的方法对其元素进行操作,或者使用标准数组索引语法 (使用方括号)。https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Float32Array
let pointPosition = new Float32Array([100.0, 100.0, 0.0, 1.0]);
// getAttribLocation() 方法返回给定WebGLProgram对象中某属性的下标指向位置
// 第一个参数为WebGLProgram,第二个参数为需要获取下标指向位置的GLSL变量名
let aPosition = webgl.getAttribLocation(webgl.program, "a_position");
// vertexAttrib4fv()方法可以为顶点 attibute 变量赋值。
// 第一个参数为指定了待修改顶点 attribute 变量的存储位置。第二个参数为用于设置顶点 attibute 变量的向量值。
webgl.vertexAttrib4fv(aPosition, pointPosition);
// getUniformLocation()方法用于获取指定WebGLProgram对象中uniform变量的位置。
// 第一个参数为要获取uniform变量的WebGLProgram对象,第二个参数为要获取位置的uniform变量的名称。
let uniforproj = webgl.getUniformLocation(webgl.program, "proj");
// uniformMatrix4fv()用于设置一个4*4的矩阵类型的uniform变量值。接收4个参数
// 第一个参数为 要设置值的uniform变量的地址
// 第二个参数为 是否将矩阵转置,默认false
// 第三个参数为 要设置的值,应该是一个4*4的矩阵
// 第四个参数为 矩阵在数组中的偏移量,默认为0
webgl.uniformMatrix4fv(uniforproj, false, projMat4);
}
// webgl的绘制函数
function draw() {
// clearColor ()方法指定在清除颜色缓冲区时使用的颜色值。接收的4个参数分别表示 r,g,b,a。取值均在0和1之间。
webgl.clearColor(0.0, 0.0, 0.0, 1.0);
// clear() 方法使用预设值来清空缓冲。预设值可以使用 clearColor() 、 clearDepth() 或 clearStencil() 设置。裁剪、抖动处理和缓冲写入遮罩会影响 clear() 方法。参数为一个用于指定需要清除的缓冲区的 GLbitfield (en-US) 。可能的值有:gl.COLOR_BUFFER_BIT(颜色缓冲区);gl.DEPTH_BUFFER_BIT (深度缓冲区) gl.STENCIL_BUFFER_BIT(模板缓冲区) 没有返回值
webgl.clear(webgl.COLOR_BUFFER_BIT);
// drawArrays() 方法用于从向量数组中绘制图元。接收3个参数
// 第一个参数为指定绘制图元的方式,可能值如下:
// gl.POINTS: 绘制一系列点。
// gl.LINE_STRIP: 绘制一个线条。即,绘制一系列线段,上一点连接下一点。
// gl.LINE_LOOP: 绘制一个线圈。即,绘制一系列线段,上一点连接下一点,并且最后一点与第一个点相连。
// gl.LINES: 绘制一系列单独线段。每两个点作为端点,线段之间不连接。
// gl.TRIANGLE_STRIP:绘制一个三角带。
// gl.TRIANGLE_FAN:绘制一个三角扇。
// gl.TRIANGLES: 绘制一系列三角形。每三个点作为顶点。
// 第二个参数为指定从哪个点开始绘制。
// 第三个参数为指定绘制需要使用到多少个点。
webgl.drawArrays(webgl.POINTS, 0, 1);
}
</script>
</head>
<body onload="init()">
<canvas id="webglCanvas" width="500" height="500"></canvas>
</body>
</html>
总结
这个例子是个很简单的小例子,但我们可以通过这个小例子去看下 webgl 从定义数据到绘制在屏幕上的整个流程。以上便是,使用 webgl 绘制单个点的全部内容,如有错误之处,请在评论区留言指出,感谢大家的指点。谢谢大家了。