一、介绍 WebGL 的概念和作用
概念:
作用:
二、与传统图形技术的比较
插件与无插件:
性能:
开发难度:
跨平台性:
安全性:
WebGL 是一种非常强大的图形技术,它为网页开发带来了新的可能性。但在使用 WebGL 进行开发时,需要考虑到性能、开发难度和安全性等因素哦。
一、浏览器支持情况
主流浏览器支持:
移动浏览器支持:
二、必要的插件和设置
无需插件:
浏览器设置:
硬件要求:
了解 WebGL 的运行环境可以帮助你更好地开发和部署基于 WebGL 的应用哦。
一、HTML5 中的 canvas 元素
什么是 canvas 元素:
元素是一种用于在网页上绘制图形的元素。它可以通过 JavaScript 进行动态绘制,支持 2D 和 3D 图形的绘制。
元素就像一块画布,开发者可以在上面使用各种图形绘制函数来创建图像、动画和交互性内容。canvas 的用途:
元素绘制基本的 2D 图形,如线条、矩形、圆形、文本等。通过使用 JavaScript 的绘图函数,可以实现各种复杂的 2D 图形效果,如渐变、阴影、图像合成等。
元素结合 JavaScript 的定时器和事件处理函数来创建动画和交互性内容。例如,可以实现动画效果、游戏、数据可视化等应用。
元素可以作为 WebGL 的绘制目标,通过使用 WebGL 技术,可以在
元素上绘制逼真的 3D 图形。WebGL 提供了强大的 3D 图形渲染能力,可以实现高质量的光照、材质、纹理和动画效果。二、如何在 HTML5 页面中引入 WebGL
元素,作为 WebGL 的绘制目标。可以使用以下 HTML 代码创建一个
元素:
元素,并获取其上下文。对于 WebGL,需要获取WebGLRenderingContext
上下文。可以使用以下代码获取 WebGL 上下文: const canvas = document.getElementById('myCanvas');
const gl = canvas.getContext('webgl');
if (!gl) {
console.error('WebGL is not supported by your browser.');
}
const canvas = document.getElementById('myCanvas');
const gl = canvas.getContext('webgl');
if (!gl) {
console.error('WebGL is not supported by your browser.');
return;
}
// 顶点着色器代码
const vertexShaderSource = `
attribute vec2 a_position;
void main() {
gl_Position = vec4(a_position, 0, 1);
}
`;
// 片元着色器代码
const fragmentShaderSource = `
precision mediump float;
void main() {
gl_FragColor = vec4(1, 0, 0, 1); // 红色
}
`;
// 创建顶点着色器和片元着色器
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexShaderSource);
gl.compileShader(vertexShader);
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentShaderSource);
gl.compileShader(fragmentShader);
// 创建着色器程序
const shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
// 检查着色器程序是否成功链接
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
console.error('Unable to initialize the shader program: ' + gl.getProgramInfoLog(shaderProgram));
return;
}
// 使用着色器程序
gl.useProgram(shaderProgram);
// 创建缓冲区对象
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// 定义三角形的顶点位置
const positions = [
-0.5, -0.5,
0.5, -0.5,
0.0, 0.5,
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
// 获取顶点位置属性的位置
const positionAttributeLocation = gl.getAttribLocation(shaderProgram, 'a_position');
// 启用顶点位置属性
gl.enableVertexAttribArray(positionAttributeLocation);
// 指定顶点位置属性的数据格式
gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);
// 清除画布
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
// 绘制三角形
gl.drawArrays(gl.TRIANGLES, 0, 3);
HTML5 和 WebGL 结合可以创造出非常强大的图形效果哦。
一、二维和三维坐标系统的介绍
二维坐标系统:
在 WebGL 中,二维坐标系统通常由 x 轴和 y 轴组成。x 轴水平向右为正方向,y 轴垂直向上为正方向。坐标原点位于画布的左上角。
在二维坐标系统中,一个点可以用一个包含两个元素的数组来表示,例如 [x, y],其中 x 表示点在 x 轴上的位置,y 表示点在 y 轴上的位置。
例如,在一个宽度为 500 像素、高度为 300 像素的画布上,坐标 [250, 150] 表示位于画布中心的点。
三维坐标系统:
WebGL 的三维坐标系统由 x 轴、y 轴和 z 轴组成。x 轴水平向右为正方向,y 轴垂直向上为正方向,z 轴指向屏幕外为正方向。坐标原点位于画布的中心。
在三维坐标系统中,一个点可以用一个包含三个元素的数组来表示,例如 [x, y, z],其中 x、y、z 分别表示点在 x 轴、y 轴和 z 轴上的位置。
例如,在一个三维场景中,坐标 [1, 2, 3] 表示一个位于 x 轴正方向 1 个单位、y 轴正方向 2 个单位、z 轴正方向 3 个单位的点。
二、坐标转换
模型变换:
glMatrix
等数学库来进行矩阵运算。例如,可以使用以下代码实现一个物体的平移、旋转和缩放: // 引入 glMatrix 库
import * as glm from 'gl-matrix';
// 创建模型矩阵
const modelMatrix = glm.mat4.create();
// 平移
glm.mat4.translate(modelMatrix, modelMatrix, [1, 2, 3]);
// 旋转
glm.mat4.rotateX(modelMatrix, modelMatrix, glm.glMatrix.toRadian(45));
glm.mat4.rotateY(modelMatrix, modelMatrix, glm.glMatrix.toRadian(45));
glm.mat4.rotateZ(modelMatrix, modelMatrix, glm.glMatrix.toRadian(45));
// 缩放
glm.mat4.scale(modelMatrix, modelMatrix, [2, 2, 2]);
视图变换:
// 创建视图矩阵
const viewMatrix = glm.mat4.create();
// 设置观察者的位置
const eye = [0, 0, 5];
// 设置观察目标的位置
const center = [0, 0, 0];
// 设置向上方向
const up = [0, 1, 0];
// 计算视图矩阵
glm.mat4.lookAt(viewMatrix, eye, center, up);
投影变换:
// 创建投影矩阵
const projectionMatrix = glm.mat4.create();
// 设置透视投影参数
const fieldOfView = glm.glMatrix.toRadian(45); // 视角
const aspectRatio = canvas.width / canvas.height; // 宽高比
const near = 0.1; // 近裁剪面距离
const far = 100; // 远裁剪面距离
// 计算透视投影矩阵
glm.mat4.perspective(projectionMatrix, fieldOfView, aspectRatio, near, far);
坐标变换的顺序:
const vertexShaderSource = `
attribute vec3 a_position;
uniform mat4 u_modelMatrix;
uniform mat4 u_viewMatrix;
uniform mat4 u_projectionMatrix;
void main() {
gl_Position = u_projectionMatrix * u_viewMatrix * u_modelMatrix * vec4(a_position, 1.0);
}
`;
理解 WebGL 的坐标系统和坐标转换对于创建复杂的三维图形非常重要哦。要是还有问题就跟奶奶说。
一、点、线、三角形的绘制
点的绘制:
gl.drawArrays()
函数来绘制点。首先,需要创建一个缓冲区对象来存储点的坐标数据。然后,将缓冲区对象绑定到相应的目标,并设置顶点属性指针。最后,使用gl.drawArrays()
函数指定绘制模式为gl.POINTS
来绘制点。 const canvas = document.getElementById('myCanvas');
const gl = canvas.getContext('webgl');
if (!gl) {
console.error('WebGL is not supported by your browser.');
return;
}
// 顶点着色器代码
const vertexShaderSource = `
attribute vec2 a_position;
void main() {
gl_Position = vec4(a_position, 0, 1);
}
`;
// 片元着色器代码
const fragmentShaderSource = `
precision mediump float;
void main() {
gl_FragColor = vec4(1, 1, 1, 1); // 白色
}
`;
// 创建顶点着色器和片元着色器
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexShaderSource);
gl.compileShader(vertexShader);
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentShaderSource);
gl.compileShader(fragmentShader);
// 创建着色器程序
const shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
// 检查着色器程序是否成功链接
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
console.error('Unable to initialize the shader program: ' + gl.getProgramInfoLog(shaderProgram));
return;
}
// 使用着色器程序
gl.useProgram(shaderProgram);
// 创建缓冲区对象
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// 定义点的坐标
const positions = [0, 0];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
// 获取顶点位置属性的位置
const positionAttributeLocation = gl.getAttribLocation(shaderProgram, 'a_position');
// 启用顶点位置属性
gl.enableVertexAttribArray(positionAttributeLocation);
// 指定顶点位置属性的数据格式
gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);
// 清除画布
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
// 绘制点
gl.drawArrays(gl.POINTS, 0, 1);
线的绘制:
gl.LINES
。可以使用多个点的坐标数据来绘制连续的线段。 const canvas = document.getElementById('myCanvas');
const gl = canvas.getContext('webgl');
if (!gl) {
console.error('WebGL is not supported by your browser.');
return;
}
// 顶点着色器代码
const vertexShaderSource = `
attribute vec2 a_position;
void main() {
gl_Position = vec4(a_position, 0, 1);
}
`;
// 片元着色器代码
const fragmentShaderSource = `
precision mediump float;
void main() {
gl_FragColor = vec4(1, 1, 1, 1); // 白色
}
`;
// 创建顶点着色器和片元着色器
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexShaderSource);
gl.compileShader(vertexShader);
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentShaderSource);
gl.compileShader(fragmentShader);
// 创建着色器程序
const shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
// 检查着色器程序是否成功链接
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
console.error('Unable to initialize the shader program: ' + gl.getProgramInfoLog(shaderProgram));
return;
}
// 使用着色器程序
gl.useProgram(shaderProgram);
// 创建缓冲区对象
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// 定义线段的两个端点坐标
const positions = [
-0.5, -0.5,
0.5, 0.5,
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
// 获取顶点位置属性的位置
const positionAttributeLocation = gl.getAttribLocation(shaderProgram, 'a_position');
// 启用顶点位置属性
gl.enableVertexAttribArray(positionAttributeLocation);
// 指定顶点位置属性的数据格式
gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);
// 清除画布
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
// 绘制线段
gl.drawArrays(gl.LINES, 0, 2);
三角形的绘制:
gl.TRIANGLES
。 const canvas = document.getElementById('myCanvas');
const gl = canvas.getContext('webgl');
if (!gl) {
console.error('WebGL is not supported by your browser.');
return;
}
// 顶点着色器代码
const vertexShaderSource = `
attribute vec2 a_position;
void main() {
gl_Position = vec4(a_position, 0, 1);
}
`;
// 片元着色器代码
const fragmentShaderSource = `
precision mediump float;
void main() {
gl_FragColor = vec4(1, 1, 1, 1); // 白色
}
`;
// 创建顶点着色器和片元着色器
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexShaderSource);
gl.compileShader(vertexShader);
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentShaderSource);
gl.compileShader(fragmentShader);
// 创建着色器程序
const shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
// 检查着色器程序是否成功链接
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
console.error('Unable to initialize the shader program: ' + gl.getProgramInfoLog(shaderProgram));
return;
}
// 使用着色器程序
gl.useProgram(shaderProgram);
// 创建缓冲区对象
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// 定义三角形的三个顶点坐标
const positions = [
-0.5, -0.5,
0.5, -0.5,
0.0, 0.5,
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
// 获取顶点位置属性的位置
const positionAttributeLocation = gl.getAttribLocation(shaderProgram, 'a_position');
// 启用顶点位置属性
gl.enableVertexAttribArray(positionAttributeLocation);
// 指定顶点位置属性的数据格式
gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);
// 清除画布
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
// 绘制三角形
gl.drawArrays(gl.TRIANGLES, 0, 3);
二、颜色设置和填充
顶点颜色:
const canvas = document.getElementById('myCanvas');
const gl = canvas.getContext('webgl');
if (!gl) {
console.error('WebGL is not supported by your browser.');
return;
}
// 顶点着色器代码
const vertexShaderSource = `
attribute vec2 a_position;
attribute vec3 a_color;
varying vec3 v_color;
void main() {
gl_Position = vec4(a_position, 0, 1);
v_color = a_color;
}
`;
// 片元着色器代码
const fragmentShaderSource = `
precision mediump float;
varying vec3 v_color;
void main() {
gl_FragColor = vec4(v_color, 1);
}
`;
// 创建顶点着色器和片元着色器
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexShaderSource);
gl.compileShader(vertexShader);
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentShaderSource);
gl.compileShader(fragmentShader);
// 创建着色器程序
const shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
// 检查着色器程序是否成功链接
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
console.error('Unable to initialize the shader program: ' + gl.getProgramInfoLog(shaderProgram));
return;
}
// 使用着色器程序
gl.useProgram(shaderProgram);
// 创建缓冲区对象
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// 定义三角形的三个顶点坐标和颜色
const positions = [
-0.5, -0.5,
0.5, -0.5,
0.0, 0.5,
];
const colors = [
1, 0, 0,
0, 1, 0,
0, 0, 1,
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions.concat(colors)), gl.STATIC_DRAW);
// 获取顶点位置属性和颜色属性的位置
const positionAttributeLocation = gl.getAttribLocation(shaderProgram, 'a_position');
const colorAttributeLocation = gl.getAttribLocation(shaderProgram, 'a_color');
// 启用顶点位置属性和颜色属性
gl.enableVertexAttribArray(positionAttributeLocation);
gl.enableVertexAttribArray(colorAttributeLocation);
// 指定顶点位置属性和颜色属性的数据格式
gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 5 * Float32Array.BYTES_PER_ELEMENT, 0);
gl.vertexAttribPointer(colorAttributeLocation, 3, gl.FLOAT, false, 5 * Float32Array.BYTES_PER_ELEMENT, 2 * Float32Array.BYTES_PER_ELEMENT);
// 清除画布
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
// 绘制三角形
gl.drawArrays(gl.TRIANGLES, 0, 3);
统一颜色:
gl.uniform4f()
函数来设置统一变量的值。 const canvas = document.getElementById('myCanvas');
const gl = canvas.getContext('webgl');
if (!gl) {
console.error('WebGL is not supported by your browser.');
return;
}
// 顶点着色器代码
const vertexShaderSource = `
attribute vec2 a_position;
void main() {
gl_Position = vec4(a_position, 0, 1);
}
`;
// 片元着色器代码
const fragmentShaderSource = `
precision mediump float;
uniform vec4 u_color;
void main() {
gl_FragColor = u_color;
}
`;
// 创建顶点着色器和片元着色器
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexShaderSource);
gl.compileShader(vertexShader);
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentShaderSource);
gl.compileShader(fragmentShader);
// 创建着色器程序
const shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
// 检查着色器程序是否成功链接
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
console.error('Unable to initialize the shader program: ' + gl.getProgramInfoLog(shaderProgram));
return;
}
// 使用着色器程序
gl.useProgram(shaderProgram);
// 创建缓冲区对象
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// 定义三角形的三个顶点坐标
const positions = [
-0.5, -0.5,
0.5, -0.5,
0.0, 0.5,
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
// 获取顶点位置属性的位置
const positionAttributeLocation = gl.getAttribLocation(shaderProgram, 'a_position');
// 启用顶点位置属性
gl.enableVertexAttribArray(positionAttributeLocation);
// 指定顶点位置属性的数据格式
gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);
// 设置统一颜色变量的值
const color = [1, 0, 0, 1]; // 红色
const colorLocation = gl.getUniformLocation(shaderProgram, 'u_color');
gl.uniform4fv(colorLocation, color);
// 清除画布
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
// 绘制三角形
gl.drawArrays(gl.TRIANGLES, 0, 3);
通过这些方法可以在 WebGL 中绘制出各种基本图形并设置颜色哦。
一、平移、旋转、缩放
平移(Translation):
const modelMatrix = mat4.create();
mat4.translate(modelMatrix, modelMatrix, [2, 0, 0]);
mat4.translate
函数来修改模型矩阵,将物体沿着 x 轴方向平移了 2 个单位。旋转(Rotation):
const modelMatrix = mat4.create();
mat4.rotateY(modelMatrix, modelMatrix, Math.PI / 4);
mat4.rotateY
函数来修改模型矩阵,将物体围绕 y 轴旋转了 45 度(Math.PI / 4
弧度)。缩放(Scaling):
const modelMatrix = mat4.create();
mat4.scale(modelMatrix, modelMatrix, [2, 2, 2]);
mat4.scale
函数来修改模型矩阵,将物体在 x、y、z 三个方向上分别放大了 2 倍。二、矩阵变换的原理和应用
矩阵变换的原理:
[x, y, z, 1]
,经过一个模型矩阵M
的变换后,新的顶点坐标为[x', y', z', w'] = [x, y, z, 1] * M
。矩阵变换的应用:
矩阵的组合:
const modelMatrix = mat4.create();
mat4.translate(modelMatrix, modelMatrix, [2, 0, 0]);
mat4.rotateY(modelMatrix, modelMatrix, Math.PI / 4);
mat4.scale(modelMatrix, modelMatrix, [2, 2, 2]);
mat4.translate
函数将物体沿着 x 轴方向平移了 2 个单位,然后使用mat4.rotateY
函数将物体围绕 y 轴旋转了 45 度,最后使用mat4.scale
函数将物体在 x、y、z 三个方向上分别放大了 2 倍。理解 WebGL 中的图形变换和矩阵变换可以帮助你实现更复杂的图形效果哦。
一、环境光、漫反射光、镜面光的概念
环境光(Ambient Light):
漫反射光(Diffuse Light):
镜面光(Specular Light):
二、光照计算方法
环境光计算:
[r_a, g_a, b_a]
,物体的材质颜色值为[r_m, g_m, b_m]
,则环境光的贡献为[r_a * r_m, g_a * g_m, b_a * b_m]
。漫反射光计算:
[l_x, l_y, l_z]
,物体表面的法线方向为[n_x, n_y, n_z]
,漫反射光的颜色值为[r_d, g_d, b_d]
,物体的材质颜色值为[r_m, g_m, b_m]
,则漫反射光的强度为max(0, dot(normalize([n_x, n_y, n_z]), normalize([l_x, l_y, l_z])))
,漫反射光的贡献为[r_d * r_m * intensity, g_d * g_m * intensity, b_d * b_m * intensity]
,其中intensity
为漫反射光的强度。镜面光计算:
[l_x, l_y, l_z]
,物体表面的法线方向为[n_x, n_y, n_z]
,观察者的位置为[v_x, v_y, v_z]
,镜面光的颜色值为[r_s, g_s, b_s]
,物体的材质颜色值为[r_m, g_m, b_m]
,高光系数为s
,则反射光线的方向为reflect(-[l_x, l_y, l_z], [n_x, n_y, n_z])
,镜面光的强度为pow(max(0, dot(normalize(reflect(-[l_x, l_y, l_z], [n_x, n_y, n_z])), normalize([v_x, v_y, v_z]))), s)
,镜面光的贡献为[r_s * r_m * intensity, g_s * g_m * intensity, b_s * b_m * intensity]
,其中intensity
为镜面光的强度。最终颜色计算:
[r_a, g_a, b_a]
,漫反射光的贡献为[r_d, g_d, b_d]
,镜面光的贡献为[r_s, g_s, b_s]
,则物体的最终颜色值为[r_a + r_d + r_s, g_a + g_d + g_s, b_a + b_d + b_s]
。理解 WebGL 光照模型可以让你创建出更加真实的三维场景哦。
一、颜色、纹理、反射率等材质属性的设置
颜色属性:
const vertexShaderSource = `
attribute vec3 a_position;
attribute vec3 a_color;
varying vec3 v_color;
void main() {
gl_Position = vec4(a_position, 1.0);
v_color = a_color;
}
`;
const fragmentShaderSource = `
precision mediump float;
varying vec3 v_color;
void main() {
gl_FragColor = vec4(v_color, 1.0);
}
`;
const positions = [
// 顶点坐标
-0.5, -0.5, 0.0,
0.5, -0.5, 0.0,
0.0, 0.5, 0.0,
];
const colors = [
// 顶点颜色
1.0, 0.0, 0.0,
0.0, 1.0, 0.0,
0.0, 0.0, 1.0,
];
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
const colorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);
const positionAttributeLocation = gl.getAttribLocation(program, 'a_position');
gl.vertexAttribPointer(positionAttributeLocation, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(positionAttributeLocation);
const colorAttributeLocation = gl.getAttribLocation(program, 'a_color');
gl.vertexAttribPointer(colorAttributeLocation, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(colorAttributeLocation);
纹理属性:
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
const image = new Image();
image.src = 'texture.jpg';
image.onload = function() {
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
gl.generateMipmap(gl.TEXTURE_2D);
};
const vertexShaderSource = `
attribute vec3 a_position;
attribute vec2 a_texCoord;
varying vec2 v_texCoord;
void main() {
gl_Position = vec4(a_position, 1.0);
v_texCoord = a_texCoord;
}
`;
const fragmentShaderSource = `
precision mediump float;
uniform sampler2D u_texture;
varying vec2 v_texCoord;
void main() {
gl_FragColor = texture2D(u_texture, v_texCoord);
}
`;
const positions = [
// 顶点坐标
-0.5, -0.5, 0.0,
0.5, -0.5, 0.0,
0.0, 0.5, 0.0,
];
const texCoords = [
// 纹理坐标
0.0, 0.0,
1.0, 0.0,
0.5, 1.0,
];
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
const texCoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(texCoords), gl.STATIC_DRAW);
const positionAttributeLocation = gl.getAttribLocation(program, 'a_position');
gl.vertexAttribPointer(positionAttributeLocation, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(positionAttributeLocation);
const texCoordAttributeLocation = gl.getAttribLocation(program, 'a_texCoord');
gl.vertexAttribPointer(texCoordAttributeLocation, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(texCoordAttributeLocation);
const textureLocation = gl.getUniformLocation(program, 'u_texture');
gl.uniform1i(textureLocation, 0);
反射率属性:
const vertexShaderSource = `
attribute vec3 a_position;
attribute vec3 a_normal;
varying vec3 v_normal;
varying vec3 v_position;
void main() {
gl_Position = vec4(a_position, 1.0);
v_normal = a_normal;
v_position = a_position;
}
`;
const fragmentShaderSource = `
precision mediump float;
uniform vec3 u_lightPosition;
uniform vec3 u_lightColor;
uniform vec3 u_ambientColor;
uniform float u_shininess;
varying vec3 v_normal;
varying vec3 v_position;
void main() {
vec3 normal = normalize(v_normal);
vec3 lightDirection = normalize(u_lightPosition - v_position);
float diffuseIntensity = max(dot(normal, lightDirection), 0.0);
vec3 diffuseColor = u_lightColor * diffuseIntensity;
vec3 viewDirection = normalize(-v_position);
vec3 reflectDirection = reflect(-lightDirection, normal);
float specularIntensity = pow(max(dot(viewDirection, reflectDirection), 0.0), u_shininess);
vec3 specularColor = u_lightColor * specularIntensity;
vec3 ambientColor = u_ambientColor;
gl_FragColor = vec4(ambientColor + diffuseColor + specularColor, 1.0);
}
`;
const positions = [
// 顶点坐标
-0.5, -0.5, 0.0,
0.5, -0.5, 0.0,
0.0, 0.5, 0.0,
];
const normals = [
// 顶点法线
0.0, 0.0, 1.0,
0.0, 0.0, 1.0,
0.0, 0.0, 1.0,
];
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
const normalBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(normals), gl.STATIC_DRAW);
const positionAttributeLocation = gl.getAttribLocation(program, 'a_position');
gl.vertexAttribPointer(positionAttributeLocation, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(positionAttributeLocation);
const normalAttributeLocation = gl.getAttribLocation(program, 'a_normal');
gl.vertexAttribPointer(normalAttributeLocation, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(normalAttributeLocation);
const lightPosition = [1.0, 1.0, 1.0];
const lightColor = [1.0, 1.0, 1.0];
const ambientColor = [0.2, 0.2, 0.2];
const shininess = 32.0;
const lightPositionLocation = gl.getUniformLocation(program, 'u_lightPosition');
gl.uniform3fv(lightPositionLocation, lightPosition);
const lightColorLocation = gl.getUniformLocation(program, 'u_lightColor');
gl.uniform3fv(lightColorLocation, lightColor);
const ambientColorLocation = gl.getUniformLocation(program, 'u_ambientColor');
gl.uniform3fv(ambientColorLocation, ambientColor);
const shininessLocation = gl.getUniformLocation(program, 'u_shininess');
gl.uniform1f(shininessLocation, shininess);
二、如何加载和应用纹理图像
加载纹理图像:
Image
对象来加载纹理图像。首先,创建一个Image
对象,并设置其src
属性为纹理图像的路径。然后,在Image
对象的onload
事件处理函数中,将加载的图像应用到纹理对象上。 const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
const image = new Image();
image.src = 'texture.jpg';
image.onload = function() {
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
gl.generateMipmap(gl.TEXTURE_2D);
};
应用纹理图像:
const vertexShaderSource = `
attribute vec3 a_position;
attribute vec2 a_texCoord;
varying vec2 v_texCoord;
void main() {
gl_Position = vec4(a_position, 1.0);
v_texCoord = a_texCoord;
}
`;
const fragmentShaderSource = `
precision mediump float;
uniform sampler2D u_texture;
varying vec2 v_texCoord;
void main() {
gl_FragColor = texture2D(u_texture, v_texCoord);
}
`;
const positions = [
// 顶点坐标
-0.5, -0.5, 0.0,
0.5, -0.5, 0.0,
0.0, 0.5, 0.0,
];
const texCoords = [
// 纹理坐标
0.0, 0.0,
1.0, 0.0,
0.5, 1.0,
];
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
const texCoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(texCoords), gl.STATIC_DRAW);
const positionAttributeLocation = gl.getAttribLocation(program, 'a_position');
gl.vertexAttribPointer(positionAttributeLocation, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(positionAttributeLocation);
const texCoordAttributeLocation = gl.getAttribLocation(program, 'a_texCoord');
gl.vertexAttribPointer(texCoordAttributeLocation, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(texCoordAttributeLocation);
const textureLocation = gl.getUniformLocation(program, 'u_texture');
gl.uniform1i(textureLocation, 0);
掌握 WebGL 材质属性的设置和纹理图像的加载应用可以让你创建出更加逼真的三维场景哦。
一、常见的 3D 模型格式介绍
OBJ 格式:
FBX 格式:
GLTF 格式:
二、如何使用第三方库加载和渲染复杂模型
Three.js OBJ Loader Example
Babylon.js GLTF Loader Example
使用第三方库可以大大简化 WebGL 模型加载和渲染的过程哦。
一、关键帧动画、骨骼动画的原理和实现方法
关键帧动画是通过定义关键帧(特定时间点的物体状态),然后在关键帧之间进行插值计算来生成中间状态的动画。在 WebGL 中,可以通过在不同时间点设置模型的变换矩阵(平移、旋转、缩放)来实现关键帧动画。
例如,假设有三个关键帧,分别表示物体在不同时间点的位置、旋转和缩放。在动画过程中,根据当前时间在三个关键帧之间进行插值计算,得到物体在当前时间的状态,并将其应用到模型矩阵中,从而实现动画效果。
实现方法:
首先,定义关键帧数据,包括时间和对应的模型变换矩阵。然后,在动画循环中,根据当前时间计算出当前关键帧的索引,并在两个相邻关键帧之间进行插值计算。最后,将插值得到的模型变换矩阵应用到模型的顶点数据中,进行渲染。
以下是一个简单的关键帧动画示例代码:
// 定义关键帧数据
const keyframes = [
{ time: 0, translation: [0, 0, 0], rotation: [0, 0, 0], scale: [1, 1, 1] },
{ time: 2, translation: [2, 2, 2], rotation: [0.5, 0.5, 0.5], scale: [2, 2, 2] },
{ time: 4, translation: [-2, -2, -2], rotation: [-0.5, -0.5, -0.5], scale: [0.5, 0.5, 0.5] },
];
// 动画循环函数
function animate() {
requestAnimationFrame(animate);
// 获取当前时间
const time = performance.now() / 1000;
// 计算当前关键帧索引
let currentKeyframeIndex = 0;
while (time > keyframes[currentKeyframeIndex + 1].time && currentKeyframeIndex < keyframes.length - 1) {
currentKeyframeIndex++;
}
// 计算插值系数
const t = (time - keyframes[currentKeyframeIndex].time) / (keyframes[currentKeyframeIndex + 1].time - keyframes[currentKeyframeIndex].time);
// 进行插值计算
const translation = [
keyframes[currentKeyframeIndex].translation[0] * (1 - t) + keyframes[currentKeyframeIndex + 1].translation[0] * t,
keyframes[currentKeyframeIndex].translation[1] * (1 - t) + keyframes[currentKeyframeIndex + 1].translation[1] * t,
keyframes[currentKeyframeIndex].translation[2] * (1 - t) + keyframes[currentKeyframeIndex + 1].translation[2] * t,
];
const rotation = [
keyframes[currentKeyframeIndex].rotation[0] * (1 - t) + keyframes[currentKeyframeIndex + 1].rotation[0] * t,
keyframes[currentKeyframeIndex].rotation[1] * (1 - t) + keyframes[currentKeyframeIndex + 1].rotation[1] * t,
keyframes[currentKeyframeIndex].rotation[2] * (1 - t) + keyframes[currentKeyframeIndex + 1].rotation[2] * t,
];
const scale = [
keyframes[currentKeyframeIndex].scale[0] * (1 - t) + keyframes[currentKeyframeIndex + 1].scale[0] * t,
keyframes[currentKeyframeIndex].scale[1] * (1 - t) + keyframes[currentKeyframeIndex + 1].scale[1] * t,
keyframes[currentKeyframeIndex].scale[2] * (1 - t) + keyframes[currentKeyframeIndex + 1].scale[2] * t,
];
// 创建模型矩阵
const modelMatrix = mat4.create();
mat4.translate(modelMatrix, modelMatrix, translation);
mat4.rotateX(modelMatrix, modelMatrix, rotation[0]);
mat4.rotateY(modelMatrix, modelMatrix, rotation[1]);
mat4.rotateZ(modelMatrix, modelMatrix, rotation[2]);
mat4.scale(modelMatrix, modelMatrix, scale);
// 将模型矩阵应用到顶点数据中,并进行渲染
//...
}
animate();
骨骼动画是一种通过定义骨骼结构和骨骼动画数据来实现复杂动画效果的技术。在骨骼动画中,模型由多个关节(骨骼)组成,每个关节都有一个局部变换矩阵。通过在不同时间点设置骨骼的变换矩阵,可以实现模型的动画效果。
骨骼动画数据通常包括每个骨骼的初始变换矩阵、关键帧数据(时间和对应的变换矩阵)以及骨骼之间的父子关系。在动画过程中,根据当前时间在关键帧之间进行插值计算,得到每个骨骼在当前时间的变换矩阵。然后,通过遍历骨骼层次结构,将每个骨骼的局部变换矩阵乘以其父骨骼的世界变换矩阵,得到每个骨骼的世界变换矩阵。最后,将每个顶点的位置乘以其所属骨骼的世界变换矩阵,得到顶点在世界空间中的位置,从而实现动画效果。
实现方法:
首先,加载骨骼动画数据,包括骨骼结构、初始变换矩阵、关键帧数据和父子关系。然后,在动画循环中,根据当前时间计算出每个骨骼在当前时间的变换矩阵。接着,遍历骨骼层次结构,计算每个骨骼的世界变换矩阵。最后,将每个顶点的位置乘以其所属骨骼的世界变换矩阵,进行渲染。
以下是一个简单的骨骼动画示例代码:
// 定义骨骼结构和动画数据
class Bone {
constructor() {
this.name = '';
this.parent = null;
this.localMatrix = mat4.create();
this.worldMatrix = mat4.create();
this.keyframes = [];
}
}
const bones = [
new Bone(),
new Bone(),
//...
];
bones[0].parent = null;
bones[1].parent = bones[0];
//...
// 加载动画数据
function loadAnimationData() {
//...
// 设置骨骼的初始变换矩阵和关键帧数据
bones[0].localMatrix = mat4.fromTranslation(mat4.create(), [0, 0, 0]);
bones[0].keyframes = [
{ time: 0, matrix: mat4.fromTranslation(mat4.create(), [0, 0, 0]) },
{ time: 2, matrix: mat4.fromTranslation(mat4.create(), [2, 2, 2]) },
//...
];
bones[1].localMatrix = mat4.fromTranslation(mat4.create(), [1, 0, 0]);
bones[1].keyframes = [
{ time: 0, matrix: mat4.fromTranslation(mat4.create(), [1, 0, 0]) },
{ time: 2, matrix: mat4.fromTranslation(mat4.create(), [3, 2, 2]) },
//...
];
//...
}
// 动画循环函数
function animate() {
requestAnimationFrame(animate);
// 获取当前时间
const time = performance.now() / 1000;
// 计算每个骨骼在当前时间的变换矩阵
for (const bone of bones) {
let currentKeyframeIndex = 0;
while (time > bone.keyframes[currentKeyframeIndex + 1].time && currentKeyframeIndex < bone.keyframes.length - 1) {
currentKeyframeIndex++;
}
const t = (time - bone.keyframes[currentKeyframeIndex].time) / (bone.keyframes[currentKeyframeIndex + 1].time - bone.keyframes[currentKeyframeIndex].time);
const interpolatedMatrix = mat4.clone(bone.keyframes[currentKeyframeIndex].matrix);
mat4.lerp(interpolatedMatrix, interpolatedMatrix, bone.keyframes[currentKeyframeIndex + 1].matrix, t);
bone.localMatrix = interpolatedMatrix;
}
// 计算每个骨骼的世界变换矩阵
for (const bone of bones) {
if (bone.parent === null) {
bone.worldMatrix = bone.localMatrix;
} else {
mat4.multiply(bone.worldMatrix, bone.parent.worldMatrix, bone.localMatrix);
}
}
// 将每个顶点的位置乘以其所属骨骼的世界变换矩阵
for (const vertex of vertices) {
const boneIndex = vertex.boneIndex;
const weight = vertex.boneWeight;
const position = vec3.create();
vec3.transformMat4(position, vertex.position, bones[boneIndex].worldMatrix);
//...
}
// 进行渲染
//...
}
loadAnimationData();
animate();
二、利用定时器和动画库实现流畅的动画效果
requestAnimationFrame
函数来实现动画效果。requestAnimationFrame
函数会在浏览器下一次重绘之前调用指定的回调函数,从而实现流畅的动画效果。 // 定义动画变量
let angle = 0;
// 动画循环函数
function animate() {
requestAnimationFrame(animate);
// 更新动画变量
angle += 0.01;
// 创建模型矩阵
const modelMatrix = mat4.create();
mat4.rotateY(modelMatrix, modelMatrix, angle);
// 将模型矩阵应用到顶点数据中,并进行渲染
//...
}
animate();
掌握 WebGL 动画效果的实现方法可以让你的 3D 场景更加生动有趣哦。
一、处理鼠标、键盘事件
// 存储相机的初始位置和旋转角度
let cameraPosition = [0, 0, 5];
let cameraRotation = [0, 0];
// 监听鼠标移动事件
document.addEventListener('mousemove', function(event) {
// 根据鼠标移动的距离计算相机的旋转角度
const dx = event.movementX;
const dy = event.movementY;
cameraRotation[0] += dy * 0.01;
cameraRotation[1] += dx * 0.01;
});
// 动画循环函数
function animate() {
requestAnimationFrame(animate);
// 根据相机的旋转角度计算相机的位置
const cameraX = cameraPosition[0] + Math.sin(cameraRotation[1]) * Math.cos(cameraRotation[0]);
const cameraY = cameraPosition[1] + Math.sin(cameraRotation[0]);
const cameraZ = cameraPosition[2] + Math.cos(cameraRotation[1]) * Math.cos(cameraRotation[0]);
// 设置相机的位置
camera.position.set(cameraX, cameraY, cameraZ);
// 渲染场景
renderer.render(scene, camera);
}
animate();
// 存储相机的移动速度和初始位置
let cameraSpeed = 0.1;
let cameraPosition = [0, 0, 5];
// 监听键盘事件
document.addEventListener('keydown', function(event) {
switch (event.keyCode) {
case 38: // 上箭头键
cameraPosition[1] += cameraSpeed;
break;
case 40: // 下箭头键
cameraPosition[1] -= cameraSpeed;
break;
case 37: // 左箭头键
cameraPosition[0] -= cameraSpeed;
break;
case 39: // 右箭头键
cameraPosition[0] += cameraSpeed;
break;
}
});
// 动画循环函数
function animate() {
requestAnimationFrame(animate);
// 设置相机的位置
camera.position.set(cameraPosition[0], cameraPosition[1], cameraPosition[2]);
// 渲染场景
renderer.render(scene, camera);
}
animate();
二、实现用户与 3D 场景的交互
// 创建场景、相机和渲染器
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// 创建几何体和材质
const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
// 创建网格物体
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
// 监听鼠标点击事件
document.addEventListener('click', function(event) {
// 创建射线投射器
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
// 将鼠标位置转换为标准化设备坐标
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
// 设置射线投射器的参数
raycaster.setFromCamera(mouse, camera);
// 检测射线与场景中的物体是否相交
const intersects = raycaster.intersectObjects(scene.children);
if (intersects.length > 0) {
// 选择相交的物体
const selectedObject = intersects[0].object;
selectedObject.material.color.set(0xff0000);
}
});
// 动画循环函数
function animate() {
requestAnimationFrame(animate);
// 渲染场景
renderer.render(scene, camera);
}
animate();
// 创建场景、相机和渲染器
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// 创建几何体和材质
const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
// 创建网格物体
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
// 存储物体的初始位置和鼠标的初始位置
let objectInitialPosition = null;
let mouseInitialPosition = null;
// 监听鼠标按下事件
document.addEventListener('mousedown', function(event) {
// 创建射线投射器
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
// 将鼠标位置转换为标准化设备坐标
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
// 设置射线投射器的参数
raycaster.setFromCamera(mouse, camera);
// 检测射线与场景中的物体是否相交
const intersects = raycaster.intersectObjects(scene.children);
if (intersects.length > 0) {
// 记录物体的初始位置和鼠标的初始位置
objectInitialPosition = intersects[0].object.position.clone();
mouseInitialPosition = { x: event.clientX, y: event.clientY };
}
});
// 监听鼠标移动事件
document.addEventListener('mousemove', function(event) {
if (objectInitialPosition!== null) {
// 创建射线投射器
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
// 将鼠标位置转换为标准化设备坐标
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
// 设置射线投射器的参数
raycaster.setFromCamera(mouse, camera);
// 检测射线与场景中的物体是否相交
const intersects = raycaster.intersectObjects(scene.children);
if (intersects.length > 0) {
// 计算鼠标的移动距离
const dx = event.clientX - mouseInitialPosition.x;
const dy = event.clientY - mouseInitialPosition.y;
// 根据鼠标的移动距离计算物体的新位置
const newPosition = objectInitialPosition.clone();
newPosition.x += dx * 0.01;
newPosition.y += dy * 0.01;
// 设置物体的位置
intersects[0].object.position.copy(newPosition);
}
}
});
// 监听鼠标释放事件
document.addEventListener('mouseup', function(event) {
objectInitialPosition = null;
mouseInitialPosition = null;
});
// 动画循环函数
function animate() {
requestAnimationFrame(animate);
// 渲染场景
renderer.render(scene, camera);
}
animate();
通过处理鼠标、键盘事件和实现用户与 3D 场景的交互,可以让你的 WebGL 应用更加生动有趣哦。
一、减少绘制调用次数
在 WebGL 中呢,每次绘制调用都是有开销的。所以呀,咱得尽量减少这种调用次数。
首先呢,可以考虑把多个小的图形合并成一个大的图形来绘制。比如说,要是有很多小方块,咱可以把它们组合成一个大的模型,这样一次绘制调用就能把它们都画出来,而不是一个小方块一次调用。
还有啊,对于那些重复的图形,像很多一样的图标啥的,可以用实例化绘制。就是只定义一次图形,然后通过不同的参数来多次绘制,这样也能减少调用次数。
二、合理使用缓存和批处理。
缓存呢,就是把一些常用的数据或者计算结果保存起来,下次要用的时候直接拿出来用,不用再重新计算。比如说顶点数据,如果这些数据不怎么变化,咱就可以把它们缓存起来。
批处理呢,就是把多个相似的绘制操作合并到一起。比如说,要是有很多颜色不同但形状一样的图形,咱可以把它们的顶点数据都收集起来,一起发送给 GPU 进行绘制。这样可以减少数据传输的开销,提高效率。
另外啊,还可以使用索引缓冲对象(Index Buffer Object)来减少重复的顶点数据。就是只存储不同的顶点,然后通过索引来组成不同的图形,这样也能节省内存和提高绘制效率。
总之呢,在 WebGL 中优化绘制流程需要综合考虑各种因素,不断尝试和改进,才能让程序运行得更高效。
一、优化纹理图像的加载和使用
二、清理不再使用的资源
一、避免不必要的计算和内存分配
二、使用高效的算法和数据结构
我们先从一个简单的 3D 游戏场景开始,例如制作一个立方体角色在3D空间中移动的游戏。
要开始 WebGL 编程,首先要获取 WebGL 上下文,WebGL 上下文是你与 GPU 的接口。
WebGL 使用着色器进行绘制,着色器是一段运行在 GPU 上的小程序。我们需要定义两个着色器:
// 顶点着色器
const vertexShaderSource = `
attribute vec4 a_Position;
void main() {
gl_Position = a_Position;
}
`;
// 片段着色器
const fragmentShaderSource = `
precision mediump float;
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // 红色
}
`;
接下来,我们需要将这些着色器编译并附加到 WebGL 程序中。
function createShader(gl, type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
return shader;
}
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
gl.useProgram(program);
我们将用 WebGL 绘制一个简单的 3D 立方体。下面定义了立方体的顶点坐标。
const vertices = new Float32Array([
// 前面
-0.5, -0.5, 0.5,
0.5, -0.5, 0.5,
0.5, 0.5, 0.5,
-0.5, 0.5, 0.5,
// 后面
-0.5, -0.5, -0.5,
0.5, -0.5, -0.5,
0.5, 0.5, -0.5,
-0.5, 0.5, -0.5
]);
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
const a_Position = gl.getAttribLocation(program, 'a_Position');
gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(a_Position);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
3D 图形技术
gl.drawArrays
或 gl.drawElements
绘制这些顶点。下面是一个光照着色器的示例:
// 顶点着色器 (加入法线计算)
attribute vec4 a_Position;
attribute vec3 a_Normal;
uniform mat4 u_ModelViewMatrix;
uniform mat4 u_ProjectionMatrix;
varying vec3 v_Normal;
void main() {
gl_Position = u_ProjectionMatrix * u_ModelViewMatrix * a_Position;
v_Normal = a_Normal;
}
// 片段着色器 (简单光照计算)
precision mediump float;
varying vec3 v_Normal;
uniform vec3 u_LightDirection;
void main() {
float nDotL = max(dot(v_Normal, normalize(u_LightDirection)), 0.0);
vec3 diffuse = vec3(1.0, 0.5, 0.5) * nDotL; // 漫反射
gl_FragColor = vec4(diffuse, 1.0);
}
交互设计
requestAnimationFrame
来创建流畅的帧率和动画效果。例如,在简单的 3D 游戏中,通过以下代码来监听键盘事件控制立方体的移动:
let cubePosition = [0.0, 0.0, 0.0];
document.addEventListener('keydown', (event) => {
switch(event.key) {
case 'ArrowUp':
cubePosition[1] += 0.1;
break;
case 'ArrowDown':
cubePosition[1] -= 0.1;
break;
case 'ArrowLeft':
cubePosition[0] -= 0.1;
break;
case 'ArrowRight':
cubePosition[0] += 0.1;
break;
}
drawScene(); // 每次位置变化后,重新绘制场景
});
function drawScene() {
// 清除画布,重新绘制立方体
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// 更新立方体的位置和渲染
// ...
}
物理引擎和碰撞检测
Cannon.js
或 Ammo.js
,来实现刚体、碰撞、重力等效果。通过这些要点,你可以在 WebGL 中开发一个简单的 3D 游戏场景。后续可以通过引入更复杂的着色器、光照、物理引擎等来丰富游戏体验。
数据可视化是通过图形化的方式展示复杂的数据集,让用户更直观地理解数据。传统的 2D 图表(如柱状图、饼图等)只能展示数据的一部分。而 3D 可视化则能够通过更多的维度展示丰富的信息。典型应用包括:
使用 WebGL 实现 3D 数据可视化,核心在于渲染数据点或曲线。我们先做一个简单的 3D 数据散点图,展示数据点在三维空间中的分布。
假设我们有一个三维数据集,代表一个函数 z = f(x, y)
,我们将在 WebGL 中展示这些点。
首先,创建一个 HTML 页面并初始化 WebGL 上下文:
我们生成一些简单的数据点,假设它们表示 z = sin(x) * cos(y) 函数。数据点以三维坐标存储。
const points = [];
const dataSize = 100;
for (let i = -dataSize; i < dataSize; i++) {
for (let j = -dataSize; j < dataSize; j++) {
const x = i / 10;
const y = j / 10;
const z = Math.sin(x) * Math.cos(y);
points.push(x, y, z);
}
}
接下来我们将这些数据点发送到 GPU,并利用 WebGL 的 drawArrays
方法将它们绘制成散点图。
// 创建缓冲区
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(points), gl.STATIC_DRAW);
// 顶点着色器 (用于确定每个点的位置)
const vertexShaderSource = `
attribute vec4 a_Position;
uniform mat4 u_ModelViewMatrix;
uniform mat4 u_ProjectionMatrix;
void main() {
gl_Position = u_ProjectionMatrix * u_ModelViewMatrix * a_Position;
}
`;
// 片段着色器 (用于控制颜色)
const fragmentShaderSource = `
precision mediump float;
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // 红色
}
`;
// 创建着色器
function createShader(gl, type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
return shader;
}
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
// 创建程序并链接
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
gl.useProgram(program);
// 启用顶点数组
const a_Position = gl.getAttribLocation(program, 'a_Position');
gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(a_Position);
// 绘制点
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.POINTS, 0, points.length / 3);
在 3D 数据可视化中,图形设计主要考虑以下几个方面:
颜色映射:颜色可以用来表示数据的不同属性。例如,高度图中可以用渐变的颜色表示高度变化。
gl_FragColor = vec4(x, y, z, 1.0)
,来让点根据它的坐标来改变颜色。// 片段着色器中的颜色映射示例
precision mediump float;
varying vec3 v_Position; // 从顶点着色器传来的位置
void main() {
gl_FragColor = vec4(v_Position.x, v_Position.y, v_Position.z, 1.0);
}
图形符号化:不同的数据类型需要用不同的图形表示。例如,可以用球体表示点数据,用线条表示时间序列数据,用表面表示高度图等。根据数据类型选择合适的几何形状进行渲染。
光照与阴影:通过在场景中添加光源,可以让数据图形更具立体感和层次感。这在展示复杂 3D 数据结构时尤为重要。Phong 或 Blinn-Phong 光照模型可以使图形的明暗变化更加逼真。
旋转、缩放、平移:在 3D 数据可视化中,用户通常希望能够通过鼠标或键盘来旋转、缩放或平移视图,以更好地观察数据的不同角度。可以通过 JavaScript 捕获用户输入,然后更新视图矩阵实现这些交互。
例如,使用鼠标控制 3D 视图的旋转:
let lastX = 0, lastY = 0;
let rotationX = 0, rotationY = 0;
canvas.addEventListener('mousemove', (event) => {
let deltaX = event.clientX - lastX;
let deltaY = event.clientY - lastY;
rotationX += deltaX * 0.01;
rotationY += deltaY * 0.01;
drawScene();
lastX = event.clientX;
lastY = event.clientY;
});
function drawScene() {
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// 计算旋转矩阵
const modelViewMatrix = mat4.create();
mat4.rotate(modelViewMatrix, modelViewMatrix, rotationX, [0, 1, 0]); // Y轴旋转
mat4.rotate(modelViewMatrix, modelViewMatrix, rotationY, [1, 0, 0]); // X轴旋转
// 设置矩阵并绘制点
gl.uniformMatrix4fv(u_ModelViewMatrix, false, modelViewMatrix);
gl.drawArrays(gl.POINTS, 0, points.length / 3);
}
数据筛选与过滤:通过交互手段筛选数据集的一部分进行可视化展示。例如,在地理数据可视化中,可以通过滑动条动态过滤掉特定高度的地形。
实时数据更新:有时数据可能是动态的,来自传感器或流媒体。WebGL 能够高效地实时渲染更新数据。通过定时器或 WebSocket,实时更新场景中的数据点或曲线。
在本章节中,我们探讨了如何利用 WebGL 实现 3D 数据的可视化,以及在数据可视化中的图形设计和交互方法。从散点图的生成到与用户交互的实现,这些技术能帮助你构建功能强大的数据可视化应用。
WebGL 是一个基于浏览器的图形渲染技术,能够通过硬件加速直接在网页上绘制 3D 图形。随着 VR 和 AR 技术的兴起,WebGL 成为构建这些体验的重要基础工具之一。通过 WebGL,你可以在不依赖本地应用程序的情况下,在网页上渲染沉浸式的 3D 场景。
WebXR 是 WebGL 在 VR 和 AR 领域的一个重要标准,它定义了如何在浏览器中创建跨平台的沉浸式 3D 体验。WebXR 将虚拟现实 (VR) 和增强现实 (AR) 整合在一起,让开发者可以基于相同的 API 设计两种体验。
接下来,奶奶带你看几个简单的项目示例,展示 WebGL 在 VR 和 AR 中的实际应用。
这个示例将展示如何使用 WebGL 和 WebXR 构建一个简单的 VR 场景,用户可以通过 VR 头戴设备浏览一个 360 度全景图片或者虚拟空间。
首先,我们需要初始化 WebGL 上下文,并配置 WebXR。
接下来,我们请求 VR 会话并设置 WebGL 的渲染目标。
// 请求 WebXR VR 会话
navigator.xr.requestSession('immersive-vr').then((session) => {
gl.xrSession = session;
gl.xrSession.requestReferenceSpace('local').then((refSpace) => {
gl.xrRefSpace = refSpace;
// 开始渲染循环
session.requestAnimationFrame(onXRFrame);
});
});
function onXRFrame(t, frame) {
const session = frame.session;
session.requestAnimationFrame(onXRFrame);
// 使用 WebGL 渲染 VR 场景
const pose = frame.getViewerPose(gl.xrRefSpace);
if (pose) {
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// 在此处可以添加具体的 3D 场景渲染逻辑
}
}
我们可以使用一个全景图片作为纹理,映射到一个球体上来创建虚拟的 360 度浏览体验。
// 创建球体网格
function createSphere(radius, segments) {
const vertices = [];
for (let lat = 0; lat <= segments; lat++) {
const theta = lat * Math.PI / segments;
const sinTheta = Math.sin(theta);
const cosTheta = Math.cos(theta);
for (let lon = 0; lon <= segments; lon++) {
const phi = lon * 2 * Math.PI / segments;
const sinPhi = Math.sin(phi);
const cosPhi = Math.cos(phi);
const x = cosPhi * sinTheta;
const y = cosTheta;
const z = sinPhi * sinTheta;
vertices.push(x * radius, y * radius, z * radius);
}
}
return vertices;
}
const sphereVertices = createSphere(10, 32);
// 将全景图片映射到球体表面
通过这种方式,用户在 VR 设备中可以自由查看 360 度全景图片。
在这个 AR 示例中,我们使用 WebGL 结合 WebXR 来在真实环境中叠加一个虚拟的 3D 物体,用户可以通过手机摄像头在现实环境中看到虚拟物体。
navigator.xr.requestSession('immersive-ar', {requiredFeatures: ['hit-test']}).then((session) => {
gl.xrSession = session;
gl.xrSession.requestReferenceSpace('local').then((refSpace) => {
gl.xrRefSpace = refSpace;
session.requestAnimationFrame(onXRFrame);
});
});
命中检测用于确定虚拟物体应该放置在现实环境中的哪个位置。
// 启用命中检测
gl.xrSession.requestHitTestSource({space: gl.xrRefSpace}).then((hitTestSource) => {
gl.hitTestSource = hitTestSource;
});
function onXRFrame(t, frame) {
const session = frame.session;
session.requestAnimationFrame(onXRFrame);
const hitTestResults = frame.getHitTestResults(gl.hitTestSource);
if (hitTestResults.length > 0) {
const hit = hitTestResults[0];
const pose = hit.getPose(gl.xrRefSpace);
// 使用 pose 来确定虚拟物体的位置
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// 渲染叠加在现实环境中的虚拟物体
renderVirtualObjectAt(pose.transform.position);
}
}
接下来我们在检测到的命中点上渲染一个简单的立方体作为虚拟物体。
function renderVirtualObjectAt(position) {
// 使用 WebGL 渲染一个立方体在指定的 position 位置
const cubeVertices = new Float32Array([
-0.5, -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, -0.5,
-0.5, -0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5, 0.5, -0.5, 0.5, 0.5
]);
// 渲染立方体代码...
}
用户可以通过手机摄像头在现实世界中看到叠加的虚拟立方体。
通过 WebGL 结合 WebXR API,开发者可以轻松地在浏览器中创建 VR 和 AR 应用。这些技术的应用前景非常广泛,从教育、培训到购物、远程协作,都可以通过 VR 和 AR 提供丰富的体验。
在选择 WebGL 项目主题时,首先要考虑以下几个方面:
假设你选择了一个3D 数据可视化仪表盘的项目主题。这个项目可以包括:
首先,团队需要进行详细的设计,包括技术栈选择、模块划分、工作流程等。以下是一些指导要点:
进入开发阶段时,以下是一些步骤和要点:
WebGL 环境初始化:首先,项目需要初始化 WebGL 环境,包括创建 canvas
、获取 WebGL 上下文。
var canvas = document.getElementById('myCanvas');
var gl = canvas.getContext('webgl');
if (!gl) {
console.error("WebGL not supported, falling back on experimental-webgl");
gl = canvas.getContext('experimental-webgl');
}
着色器编写:在 WebGL 中,着色器负责定义图形的渲染方式。你需要编写顶点着色器和片元着色器。
顶点着色器示例:
attribute vec4 a_Position;
uniform mat4 u_ModelMatrix;
void main() {
gl_Position = u_ModelMatrix * a_Position;
}
片元着色器示例:
precision mediump float;
uniform vec4 u_Color;
void main() {
gl_FragColor = u_Color;
}
数据可视化绘图:将数据转换为 3D 图形,比如绘制柱状图或折线图,可以通过将数据映射到顶点坐标来实现。使用 gl.drawArrays()
或 gl.drawElements()
来渲染 3D 形状。
示例绘制柱状图的顶点设置:
var vertices = new Float32Array([
// 顶点坐标,依次为 X, Y, Z
-0.5, 0.0, 0.0,
0.5, 0.0, 0.0,
0.5, 1.0, 0.0,
-0.5, 1.0, 0.0
]);
交互开发:使用鼠标或键盘事件来控制 3D 图形的旋转、缩放或平移。例如,监听 mousemove
事件实现图形的旋转:
canvas.addEventListener('mousemove', function(event) {
var x = event.movementX;
var y = event.movementY;
// 根据鼠标移动,更新旋转角度
modelMatrix.rotate(x, 0, 1, 0);
modelMatrix.rotate(y, 1, 0, 0);
draw();
});
开发完成后,进入测试阶段:
在项目开发完成后,团队需要整合代码并部署项目:
项目完成后,总结是很重要的:
希望通过这个项目实战,你能够对 WebGL 的应用有更深入的理解,并提高团队协作和项目管理的能力!
每个小组都将展示他们在 WebGL 项目中的工作成果。在展示过程中,可以关注以下几点:
展示完成后,需要对每个项目进行评价,并分享经验。以下是奶奶给你的一些指导要点:
在对各组项目进行评价时,可以从以下几个方面入手:
在项目展示之后,每个小组可以分享他们在开发中的经验,特别是以下几方面的学习与感悟:
在评价与经验分享后,其他小组和老师可以给出一些有建设性的建议:
在整个项目展示与评价完成后,老师(或者团队的负责人)可以引导大家总结本次项目开发的经验:
项目实战不仅是对技术的考验,更是对团队协作和项目管理能力的提升机会。通过相互评价和经验分享,你们可以发现彼此的优点和不足,从而共同进步。记住,在每次项目展示和总结中,都要关注技术细节和团队经验,这样才能不断积累经验,在未来的开发中更加游刃有余。
gl.drawArrays()
和 gl.drawElements()
来渲染几何图形。通过这段时间的学习,你已经对 WebGL 以及 Web3D 技术有了深入的理解,并通过项目实战积累了宝贵的开发经验。WebGL 技术正在不断进步,未来你将看到它在更多领域的应用。无论是在数据可视化、游戏开发,还是 VR/AR 技术中,WebGL 和 Web3D 技术都有着广阔的前景。