当前位置: 首页 > article >正文

WebGL创建3D对象

目录

  • 准备WebGL上下文
  • 定义几何体
  • 创建顶点缓冲对象
  • 设置顶点属性
  • 定义几何体的面
  • 创建变换矩阵
  • 处理光照
  • 绑定纹理
  • 绘制3D对象
  • 性能优化
  • 添加动画
  • 复杂的光照模型
  • 纹理映射
  • 混合模式
  • 高级技术

准备WebGL上下文

首先,需要获取HTML5 <canvas>元素的WebGL上下文:

<canvas id="webgl-canvas"></canvas>
const canvas = document.getElementById('webgl-canvas');
const gl = canvas.getContext('webgl');

定义几何体

创建3D对象的第一步是定义几何体的顶点数据。这里以一个简单的立方体为例:

const vertices = [
  // 面1 (前)
  -1, -1,  1, // 0
   1, -1,  1, // 1
   1,  1,  1, // 2
  -1,  1,  1, // 3

  // 面2 (后)
  -1, -1, -1, // 4
  -1,  1, -1, // 5
   1,  1, -1, // 6
   1, -1, -1, // 7

  // ...其他面...
];

创建顶点缓冲对象

将顶点数据存储在顶点缓冲对象中,以便WebGL高效地处理:

const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);

设置顶点属性

在顶点着色器中声明顶点属性,并在主程序中关联顶点缓冲对象:

const vertexShaderSource = `
attribute vec3 a_position;
void main() {
  gl_Position = vec4(a_position, 1.0);
}
`;

const positionAttributeLocation = gl.getAttribLocation(program, 'a_position');
gl.enableVertexAttribArray(positionAttributeLocation);
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.vertexAttribPointer(positionAttributeLocation, 3, gl.FLOAT, false, 0, 0);

定义几何体的面

为每个面分配一组顶点,使用索引缓冲对象(Element Array Buffer):

const indices = [
  // 面1
  0, 1, 2, 2, 3, 0,

  // 面2
  4, 5, 6, 6, 7, 4,

  // ...其他面...
];

const indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);

创建变换矩阵

创建模型、视图和投影矩阵,用于将3D空间的几何体转换为2D屏幕空间:

const modelMatrix = mat4.create();
const viewMatrix = mat4.create();
const projectionMatrix = mat4.create();

// 示例:旋转立方体
mat4.rotate(modelMatrix, modelMatrix, Math.PI / 2, [1, 0, 0]); // 90度绕X轴旋转

// 示例:设置相机位置
mat4.translate(viewMatrix, viewMatrix, [-3, 0, -5]);

处理光照

在片段着色器中计算光照效果。这里使用简单的一点光源:

const fragmentShaderSource = `
precision mediump float;
uniform vec3 u_lightPosition;
uniform vec3 u_color;

varying vec3 v_normal;

void main() {
  vec3 lightDirection = normalize(u_lightPosition - v_worldPosition);
  float intensity = max(dot(v_normal, lightDirection), 0.0);
  vec3 litColor = u_color * intensity;

  gl_FragColor = vec4(litColor, 1.0);
}`

绑定纹理

加载和应用纹理到3D对象:

const texture = loadTexture(gl, 'texture.jpg');

function loadTexture(gl, url) {
  const texture = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, texture);

  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);

  const image = new Image();
  image.onload = () => {
    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);
  };
  image.src = url;

  return texture;
}

绘制3D对象

在主循环中,绘制3D对象:

function drawScene() {
  gl.viewport(0, 0, canvas.width, canvas.height);
  gl.clearColor(0.1, 0.1, 0.1, 1.0);
  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

  gl.enable(gl.DEPTH_TEST);
  gl.enable(gl.BLEND);
  gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);

  // 计算模型视图投影矩阵
  const mvpMatrix = mat4.create();
  mat4.multiply(mvpMatrix, projectionMatrix, viewMatrix);
  mat4.multiply(mvpMatrix, mvpMatrix, modelMatrix);

  // 绑定纹理
  gl.activeTexture(gl.TEXTURE0);
  gl.bindTexture(gl.TEXTURE_2D, texture);

  // 绘制立方体
  gl.useProgram(shaderProgram);
  gl.uniformMatrix4fv(shaderProgram.uniforms.u_mvpMatrix, false, mvpMatrix);
  gl.uniform3fv(shaderProgram.uniforms.u_lightPosition, [0, 10, 0]);
  gl.uniform3fv(shaderProgram.uniforms.u_color, [1, 0, 0]); // 红色
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
  gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0);
}

setInterval(drawScene, 1000 / 60);

性能优化

使用VBO和EBO减少内存拷贝和提高效率。
使用视锥体剔除和背面剔除减少不必要的渲染。
使用多线程(Web Workers)处理复杂的计算任务。
使用分批渲染(Batch Rendering)减少渲染调用次数。
使用LOD(Level of Detail)根据距离动态调整细节级别。
使用实例化(Instancing)绘制多个相似的物体。

添加动画

为了使3D对象动态变化,我们可以修改模型矩阵(modelMatrix)中的旋转、平移或缩放值。例如,我们可以实现一个旋转动画:

let rotationAngle = 0;

function animate() {
  requestAnimationFrame(animate);
  
  // 旋转立方体
  rotationAngle += 0.01;
  mat4.fromRotation(modelMatrix, rotationAngle, [0, 1, 0]);

  drawScene();
}
animate();

复杂的光照模型

简单的点光源不足以模拟真实世界的光照。可以实现更复杂的光照模型,如环境光、漫射光、镜面光和菲涅尔效应:

const fragmentShaderSource = `
precision mediump float;
uniform vec3 u_ambientLight;
uniform vec3 u_diffuseLight;
uniform vec3 u_specularLight;
uniform vec3 u_eyePosition;
uniform vec3 u_shininess;

varying vec3 v_normal;
varying vec3 v_worldPosition;

void main() {
  vec3 lightDirection;
  vec3 viewDirection;
  vec3 ambient, diffuse, specular;

  // 环境光
  ambient = u_ambientLight;

  // 漫射光
  lightDirection = normalize(u_lightPosition - v_worldPosition);
  diffuse = u_diffuseLight * max(dot(v_normal, lightDirection), 0.0);

  // 镜面光
  viewDirection = normalize(u_eyePosition - v_worldPosition);
  vec3 halfVector = normalize(lightDirection + viewDirection);
  specular = u_specularLight * pow(max(dot(v_normal, halfVector), 0.0), u_shininess);

  vec3 litColor = ambient + diffuse + specular;

  gl_FragColor = vec4(litColor, 1.0);
}`

纹理映射

除了颜色,我们还可以使用纹理来增加3D对象的细节。在片段着色器中,可以使用纹理坐标采样纹理:

uniform sampler2D u_texture;

varying vec2 v_texCoord;

void main() {
  vec4 texColor = texture2D(u_texture, v_texCoord);
  vec3 litColor = ... // 计算光照
  gl_FragColor = vec4(litColor * texColor.rgb, texColor.a);
}

在主程序中,设置纹理坐标属性,并在绘制时传入纹理:

const texCoordAttributeLocation = gl.getAttribLocation(shaderProgram, 'a_texCoord');
gl.enableVertexAttribArray(texCoordAttributeLocation);
gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
gl.vertexAttribPointer(texCoordAttributeLocation, 2, gl.FLOAT, false, 0, 0);

gl.uniform1i(shaderProgram.uniforms.u_texture, 0);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, texture);

混合模式

WebGL支持多种混合模式,可以通过gl.blendFuncSeparate()gl.blendEquationSeparate()设置。例如,实现半透明混合:

gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
gl.blendEquationSeparate(gl.FUNC_ADD, gl.FUNC_ADD);

高级技术

  • 阴影映射:使用额外的渲染通道和深度贴图模拟物体在其他物体上的阴影。
  • 法线映射:使用法线贴图模拟高光和凹凸感,无需额外的几何信息。
  • 环境映射:使用环境贴图模拟物体反射周围环境的外观。
  • GPU粒子系统:利用GPU的并行处理能力创建粒子效果,如火花、烟雾、水波等。
  • 屏幕空间后处理:在所有3D渲染完成后,对整个屏幕应用额外的效果,如模糊、色彩校正、抗锯齿等。

http://www.kler.cn/a/322734.html

相关文章:

  • 【Git】Git Clone 指定自定义文件夹名称:详尽指南
  • 华为USG5500防火墙配置NAT
  • 《Java核心技术 卷I》用户界面中首选项API
  • latex中,两个相邻的表格,怎样留一定的空白
  • 【ubuntu18.04】vm虚拟机复制粘贴键不能用-最后无奈换版本
  • MySQL中Flashback(闪回)技术
  • springboot 引入mqtt
  • Redis 缓存雪崩、缓存穿透、缓存击穿详解
  • 基于 LangChain 的自动化测试用例的生成与执行
  • Java单体服务和集群分布式SpringCloud微服务的理解
  • 17、网络安全合规审查五大内容
  • vue按钮接收键盘回车事件
  • python:基于django的html订单提交页面
  • 小程序振动
  • 从零开始Ubuntu24.04上Docker构建自动化部署(三)Docker安装Nginx
  • centos8 升级openssh-9.8p1
  • 《C++开源贡献:提升职业竞争力的新途径》
  • 搜索引擎onesearch3实现解释和升级到Elasticsearch v8系列(四)-搜索
  • Spark Job 对象 详解
  • ‌[AI问答] Auto-sklearn‌ 与 scikit-learn 区别
  • 【SpringCloud】环境和工程搭建
  • 数据分析学习之学习路线
  • AI 将会促生哪些新的职业?
  • AT89C51 利用SBIT寻址,并且在内存中实现伪动态密码的混淆
  • gRPC协议简介
  • C++的动态数组