webgl 介绍
- webgl 是在传统的html文件的系统上,添加了 GLSLes(主要是编写
着色器
的配置) - webgl通过
js
赋值给着色器数据,着色器
主要是编写绘制图形需要的配置项
webgl 代码的主要部分和一些需要知道的概念介绍
学习新语言,需要了解语法和一些概念,下面是我总结的一些东西
着色器
- 着色器是webgl 中的绘制图形的主要部分,每个着色器都是由
顶点着色器
片元着色器
组成 -
顶点着色器
主要负责设置元素的位置信息,尺寸大小(还可以在着色器中添加一些逻辑判断,本质上着色器语法为 opengl 语法) -
片元着色器
用于设置物体范围内的颜色插值(以设置片元的颜色为标准,设置像素的颜色值)(还可以在着色器中添加一些逻辑判断,本质上着色器语法为 opengl 语法)
// 最简单的代码片段参考
// gl 中 定义变量的方法 attribute 和 uniform
// attribute 定义的是和 顶点 着色器 相关的变量
// uniform 定义的是 对于所有顶点 都相同的数据
// attribute(存储限定符) vec4(类型) a_Position(变量)
var VSHADER_SOURCE =
'attribute vec4 a_Position;\n' + // attribute 定义一个 数据类型为 vec4 变量 名称为 a_Position
'void main() {\n' +
' gl_Position = a_Position;\n' + // 设置 位置坐标
' gl_PointSize = 10.0;\n' + // 设置尺寸大小
'}\n';
// Fragment shader program
var FSHADER_SOURCE =
'void main() {\n' +
' gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n' + // 设置片元着色器 填充颜色
'}\n';
缓冲区对象
个人认为缓冲区对象的主要作用: 一次性提取数据,提高数据的处理速度
- 创建缓冲区对象的具体步骤
1 创建缓冲区对象
2 将缓冲区分配给对应需要的缓冲区类型(有多个缓冲区类型)
3 给缓冲区对象中写入需要的数据
4 把缓冲区和着色器中的变量绑定(可以理解为赋值)
5 开启缓冲区的访问权限
- 代码逻辑
// 将多个顶点的数据 保存在缓存区对象中
// 缓冲区传给 着色器
function initVertexBuffers(gl) {
var vertices = new Float32Array([
0.0, 0.5, -0.5, -0.5, 0.5, -0.5
]); // 定义数据
var n = 3; // 点的个数
// 1 创建缓冲区对象
var vertexBuffer = gl.createBuffer();
if (!vertexBuffer) {
console.log('Failed to create the buffer object');
return -1;
}
// 2 将缓冲区对象绑定到目标
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
// 3 向缓冲区对象写入数据
// 参数3 的作用是 表示程序将如何使用存储在缓冲区对象的数据 (做优化操作)
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
// 获取变量
var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
if (a_Position < 0) {
console.log('Failed to get the storage location of a_Position');
return -1;
}
/**
* 4 把缓冲区和着色器中的变量绑定(可以理解为赋值)
* 将缓冲区对象 分配给(赋值给) a_Position 变量
* 参数含义
* 参数1 a_Position 赋值的变量
* 参数2 指定缓冲区中每个顶点的分量个数
* 参数3 指定数据类型 gl.FLOAT 是其中一种类型
* 参数4 表明是否将非浮点型数据 归一化成 [0,1] 或 [-1,1]
* 参数5 指定相邻的两个顶点间的字节数
* 参数6 指定从第几个数据位置开始(偏移数)
*/
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);
// 5 开启访问权限
//开启a_Position,为了使着色器能够访问 a_Position 变量
// 连接 a_Position 变量 与分配给他的缓冲区对象
gl.enableVertexAttribArray(a_Position);
return n;
}
纹理映射
纹理贴图的原理就是把把图片中对应的像素值(gaba) 映射到三维物体的表面
- 创建纹理对象的具体步骤
1 创建纹理对象
2 开启纹理对象
3 对纹理单元绑定 纹理对象
4 设置纹理参数
5 配置纹理图像 --> 将纹理图像分配给纹理对象
6 将纹理单元赋值给着色器中的变量
7 着色器变量 调用 texture2D 取出片元对应的颜色值
- 代码具体实现
// 1 创建一个纹理对象
var texture = gl.createTexture();
// 2 开启 0号纹理单元
gl.activeTexture(gl.TEXTURE0);
// 3 向 EXTURE_2D 绑定纹理对象
gl.bindTexture(gl.TEXTURE_2D, texture);
// 4配置纹理参数 (设置样式)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
// 5 配置纹理图像 --> 将纹理图像分配给纹理对象
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image);
// 6 将 0 号纹理传递给着色器
gl.uniform1i(u_Sampler, 0);
// 创建片元着色器
var FSHADER_SOURCE =
// '#ifdef GL_ES\n' +
'precision mediump float;\n' +
// '#endif\n' +
'uniform sampler2D u_Sampler;\n' + // 6 定义取样器 用来接收 纹理图
'varying vec2 v_TexCoord;\n' +
'void main() {\n' +
' gl_FragColor = texture2D(u_Sampler, v_TexCoord);\n' + // 6 提取纹理颜色值栅格化片元着色器
'}\n';
矩阵对象
可以看做一系列变化关系的集合的统一表达方式(用于设置图形变换旋转、缩放,设置视图顶点坐标)
- 用于表示 顶点位置、颜色数据、物体变化数据、视图投影矩阵等
- 多个矩阵可以合并
光线
用于模拟现实场景下的光照因素
- 光线分类
1 平行光源(太阳)
2 点光源(灯泡)
3 环境光(物体周围均匀分布的光线)
-
反射类型(光照下的物体表面颜色)
1 漫反射计算: 漫反射 = 入射光线颜色*物体表面颜色*cos(a)
1 环境反射: 环境反射 = 入射光线颜色*物体表面颜色
- 代码参考
// 顶点着色器
var VSHADER_SOURCE =
'attribute vec4 a_Color;\n' + // 定义顶点的 颜色 变量
'attribute vec4 a_Normal;\n' + // 定义法向量的变量、
'uniform vec3 u_LightColor;\n' + // 定义漫反射的光照颜色 变量
'uniform vec3 u_LightDirection;\n' + // 定义漫反射的光照方向 变量
'uniform vec3 u_AmbientLight;\n' + // 定义环境光的变量
'varying vec4 v_Color;\n' + // 要传给片元着色器的变量
'void main() {\n' +
// 计算 cos 值 = 漫反射的方向向量 * 法向量
' float nDotL = max(dot(u_LightDirection, a_Normal), 0.0);\n' +
// 计算漫反射后的物体颜色
' vec3 diffuse = u_LightColor * a_Color.rgb * nDotL;\n' +
// 计算环境光的物体颜色
' vec3 ambient = u_AmbientLight * a_Color.rgb;\n' +
// 计算最终的物体颜色
' v_Color = vec4(diffuse + ambient, a_Color.a);\n' +
'}\n';
// 片元着色器
var FSHADER_SOURCE =
'#ifdef GL_ES\n' +
'precision mediump float;\n' + // 设置数据精度
'#endif\n' +
'varying vec4 v_Color;\n' +
'void main() {\n' +
' gl_FragColor = v_Color;\n' +
'}\n';
视图矩阵
由 观察者位置, 目标位置, 观察者上方向位置组成的 视图矩阵
- 代码
// 包括 观察者(x y z)坐标,目标点(x y z)坐标,观察者上方向(x y z)坐标
viewMatrix.setLookAt(0.20, 0.25, 0.25, 0, 0, 0, 0, 1, 0); // 设置视图矩阵的api
可视空间矩阵
观察者可以看到区域范围,下面两个选择设置一个即可
-
盒状可视空间
// 参数依次为 近裁剪面左边界、近裁剪面右边界、近裁剪面上边界、近裁剪面下边界、近裁剪面位置、远裁剪面位置
projMatrix.setOrtho(-1.0, 1.0, -1.0, 1.0, g_near, g_far); // 设置盒装可视空间
-
四棱锥/金字塔 可视空间(又叫透视投影矩阵)
// 参数依次是 可视空间的夹角(观察者顶面和底面的夹角),近裁剪面的宽高比,近裁剪面位置,远裁剪面的位置
viewProjectionMatrix.setPerspective(30.0, canvas.width / canvas.height, 1.0, 100.0);// 设置透视投影矩阵(更真实,近处大 远处小)
隐藏面消除功能
gl.enable(gl.DEPTH_TEST);//开启隐藏面消除功能
gl.clear( gl.DEPTH_BUFFER_BIT); // 清除深度缓冲区
立体图形绘制
webgl 没有提供绘制三维图形的方法,只能通过二维绘制的api来实现
- 方法一 通过绘制多个三角形来拼成3d图形
立方体6个面,每个面需要两个三角形来组成,所以需要的定点数 =6(6个面) * 3(每个三角形需要3的顶点) * 2(每个面需要两个三角形)
通过拼接三角形需要36个顶点 - 方法二 通过 gl.drawElements() 方式索引顶点坐标来绘制立方体
调用 gl.drawElements() 方法前需要做以下准备
1创建 顶点坐标 并存放到 缓冲区中
2 创建 顶点坐标对应的 索引坐标(通过索引坐标来获取顶点坐标来绘制3d图形)
3 创建缓冲区 并绑定到gl.ELEMENT_ARRAY_BUFFER
缓冲区类型上
4 将 索引坐标 绑定到gl.ELEMENT_ARRAY_BUFFER
缓冲区 上
5 调用 gl.drawElements() 绘制3d图形
下面是简单的代码逻辑
// 1创建 顶点坐标 并存放到 缓冲区中
// 将缓冲区对象 绑定到 gl.ARRAY_BUFFER
// Create a cube
// v6----- v5
// /| /|
// v1------v0|
// | | | |
// | |v7---|-|v4
// |/ |/
// v2------v3
var verticesColors = new Float32Array([
// 8个顶点位置 和 颜色值
1.0, 1.0, 1.0, 1.0, 1.0, 1.0, // v0 White
-1.0, 1.0, 1.0, 1.0, 0.0, 1.0, // v1 Magenta
-1.0, -1.0, 1.0, 1.0, 0.0, 0.0, // v2 Red
1.0, -1.0, 1.0, 1.0, 1.0, 0.0, // v3 Yellow
1.0, -1.0, -1.0, 0.0, 1.0, 0.0, // v4 Green
1.0, 1.0, -1.0, 0.0, 1.0, 1.0, // v5 Cyan
-1.0, 1.0, -1.0, 0.0, 0.0, 1.0, // v6 Blue
-1.0, -1.0, -1.0, 0.0, 0.0, 0.0 // v7 Black
]);
var vertexColorBuffer = gl.createBuffer();
var indexBuffer = gl.createBuffer();
if (!vertexColorBuffer || !indexBuffer) {
return -1;
}
gl.bindBuffer(gl.ARRAY_BUFFER, vertexColorBuffer);
gl.bufferData(gl.ARRAY_BUFFER, verticesColors, gl.STATIC_DRAW);
var FSIZE = verticesColors.BYTES_PER_ELEMENT;
var a_Position = gl.getAttribLocation(gl.program, 'a_Position'); // 获取 变量
if(a_Position < 0) {
console.log('Failed to get the storage location of a_Position');
return -1;
}
gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, FSIZE * 6, 0);
gl.enableVertexAttribArray(a_Position);
var a_Color = gl.getAttribLocation(gl.program, 'a_Color'); // 获取 变量
if(a_Color < 0) {
console.log('Failed to get the storage location of a_Color');
return -1;
}
gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, FSIZE * 6, FSIZE * 3);
gl.enableVertexAttribArray(a_Color);
// 2 创建 顶点坐标对应的 索引坐标(通过索引坐标来获取顶点坐标来绘制3d图形)
// 这个就是索引坐标
var indices = new Uint8Array([
0, 1, 2, 0, 2, 3, // front
0, 3, 4, 0, 4, 5, // right
0, 5, 6, 0, 6, 1, // up
1, 6, 7, 1, 7, 2, // left
7, 4, 3, 7, 3, 2, // down
4, 7, 6, 4, 6, 5 // back
]);
// 3 创建缓冲区 并绑定到 `gl.ELEMENT_ARRAY_BUFFER` 缓冲区类型上
// 将缓冲区对象 绑定到 gl.ELEMENT_ARRAY_BUFFER(gl.ELEMENT_ARRAY_BUFFER 管理具有索引结构的三维数据模型)
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
// 4 将 索引坐标 绑定到 `gl.ELEMENT_ARRAY_BUFFER` 缓冲区 上
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
// 5 绘制 立方体
gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_BYTE, 0);
通过 gl.drawElements
方法绘制 需要的是 8个顶点,相比第一种减少了数据的创建
雾化
基本开发流程
- 创建着色器对象
- 创建canvas元素,并获取canvas 的 dom 元素
- 通过canvas 获取 webgl 的绘图上下文对象
- 初始化着色器,包括设置着色器的 定点位置,颜色值,视图投影矩阵,向量矩阵,纹理对象,光线对象值等的设置(是webgl中的最重要的开发部分)
- 设置canvas背景色
- 清除canvas 中的颜色缓冲区和深度缓冲区
- webgl 绘图(包含许多绘图api)
代码案例
- 案例1 通过js控制绘制元素的颜色,代码大致实现原理
主要注意的是 可以通过 js 来动态赋值着色器中的变量
Hello Point (2)
- 案例2 通过缓冲区对象来设置顶点位置 颜色信息
Draw Multiple Points
- 案例3 模型矩阵 视图矩阵 投影矩阵开启深度缓冲区
Perspective Projection
- 案例4 添加纹理贴图
Draw quad with texture
- 案例5 立方体绘制
Hello cube
- 案例6 光照
Point lighted cube (with animation)
- 雾化效果
Fog
↑↓: Increase/decrease the fog distance
webgl代码地址