可视化学习:如何用WebGL绘制3D物体

在之前的文章中,我们使用WebGL绘制了很多二维的图形和图像,在学习2D绘图的时候,我们提过很多次关于GPU的高效渲染,但是2D图形的绘制只展示了WebGL部分的能力,WebGL更强大的地方在于,它可以绘制各种3D图形,而3D图形能够极大地增强可视化的表现能力。

相信很多小伙伴都对此有所耳闻,也有不少人学习WebGL,就是冲着它的3D绘图能力。

接下来,我就用一个简单的正立方体的例子来演示在WebGL中如何绘制3D物体。

从二维到三维

首先,我们先来绘制一个熟悉的2D图形,正方形。

// vertex
attribute vec2 a_vertexPosition;
attribute vec4 color;varying vec4 vColor;void main() {gl_PointSize = 1.0;vColor = color;gl_Position = vec4(a_vertexPosition, 1, 1);
}// fragment
#ifdef GL_ES
precision highp float;
#endifvarying vec4 vColor;void main() {gl_FragColor = vColor;
}

// ...
renderer.setMeshData([{positions: [[-0.5, -0.5],[-0.5, 0.5],[0.5, 0.5],[0.5, -0.5]],attributes: {color: [[1, 0, 0, 1],[1, 0, 0, 1],[1, 0, 0, 1],[1, 0, 0, 1],]},cells: [[0, 1, 2], [2, 0, 3]]
}]);
// ...

上述这些代码比较简单,我就不过多解释了。

在画布上我们看到,绘制了一个红色的正方形,它是一个平面图形。

接下来,我们就在这个图形的基础上,将它拓展为3D的正立方体。

要想把2维图形拓展为3维几何体,第一步就是要把顶点扩展到3维。也就是把vec2扩展为vec3。

// vertex
attribute vec3 a_vertexPosition;
attribute vec4 color;varying vec4 vColor;void main() {gl_PointSize = 1.0;vColor = color;gl_Position = vec4(a_vertexPosition, 1);
}

当然仅仅修改Shader是不够的,因为数据是从JavaScript传递过来的,所以我们需要在JavaScript中计算立方体的顶点数据,然后再传递给Shader。

一个立方体有8个顶点,能组成6个面。在WebGL中需要用12个三角形来绘制它。

如果6个面的属性相同的话,我们可以复用8个顶点来绘制;

但如果属性不完全相同,比如每个面要绘制成不同的颜色,或者添加不同的纹理图片,就得把每个面的顶点分开。这样的话,就需要24个顶点来分别处理6个面。

为了方便使用,我们可以定义一个JavaScript函数,用来生成立方体6个面的24个顶点,以及12个三角形的索引,并且定义每个面的颜色。

/*** 生成立方体6个面的24个顶点,12个三角形的索引,定义每个面的颜色信息* @param size* @param colors* @returns {{cells: *[], color: *[], positions: *[]}}*/
export function cube(size = 1.0, colors = [[1, 0, 0, 1]]) {const h = 0.5 * size;const vertices = [[-h, -h, -h],[-h, h, -h],[h, h, -h],[h, -h, -h],[-h, -h, h],[-h, h, h],[h, h, h],[h, -h, h]];const positions = [];const color = [];const cells = [];let colorIdx = 0;let cellsIdx = 0;const colorLen = colors.length;function quad(a, b, c, d) {[a, b, c, d].forEach(item => {positions.push(vertices[item]);color.push(colors[colorIdx % colorLen]);});cells.push([0, 1, 2].map(i => i + cellsIdx),[0, 2, 3].map(i => i + cellsIdx));colorIdx ++;cellsIdx += 4;}quad(1, 0, 3, 2); // 内quad(4, 5, 6, 7); // 外quad(2, 3, 7, 6); // 右quad(5, 4, 0, 1); // 左quad(3, 0, 4, 7); // 下quad(6, 5, 1, 2); // 上return {positions, color, cells};
}

现在我们就可以通过调用cube这个函数,构建出立方体的顶点信息。

const geometry = cube(1.0, [[1, 0, 0, 1],   // 红[0, 0.5, 0, 1], // 绿[0, 0, 1, 1]    // 蓝
]);

通过这段代码,我们就能创建出一个棱长为1的立方体,并且六个面的颜色分别是“红、绿、蓝、红、绿、蓝”。

接下来我们就要把这个立方体的顶点信息传递给Shader。

在传递数据之前,我们需要先了解一个知识点,是关于绘制3D图形与2D图形存在的一点不同,那就是绘制3D图形时,必须要开启深度检测和启用深度缓冲区。

在WebGL中,我们可以通过gl.enable(gl.DEPTH_TEST);这段代码来开启深度检测;在清空画布的时候,也要用gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);这段代码来同时清空颜色缓冲区和深度缓冲区。

启动和清空深度检测和深度缓冲区这两个步骤,非常重要。但是一般情况下,我们几乎不会用原生的方式来编写代码,所以了解一下即可。为了方便使用,在本文演示的例子中,我们还是直接使用gl-renderer这个库,它封装了深度检测,我们在使用时,在创建renderer的时候配置一个参数depth: true就可以了。

现在我们就把这个三维立方体用gl-renderer渲染出来。

// ...
renderer = new GlRenderer(glRef.value, {depth: true // 开启深度检测
});
const program = renderer.compileSync(fragment, vertex);
renderer.useProgram(program);
renderer.setMeshData([{positions: geometry.positions,attributes: {color: geometry.color},cells: geometry.cells
}]);
renderer.render();

现在我们在画布上看到的是一个红色正方形,这是因为其他面被遮挡住了。

投影矩阵:变换WebGL坐标系

但是,等等,为什么我们看到的是红色的一面呢?按照我们所编写的代码,预期看到的应该是绿色的一面,也就是说我们预期Z轴是向外的,因为规范的直角坐标系是右手坐标系。所以按照现在的绘制结果,我们发现WebGL的坐标系其实是左手系的?

但一般来说,不管什么图形库或者图形框架,在绘图的时候,都会默认将坐标系从左手系转换为右手系,因为这更符合我们的使用习惯。所以这里,我们也去把WebGL的坐标系从左手系转换为右手系,简单来说,就是将Z轴坐标方向反转。关于坐标转换,可以通过齐次矩阵来完成。对坐标转换不熟悉的小伙伴,可以参考我之前的一篇关于仿射变换的文章。

将Z轴坐标方向反转,对应的齐次矩阵是这样的:

[1, 0, 0, 0,0, 1, 0, 0,0, 0, -1, 0,0, 0, 0, 1
]

这种转换坐标的齐次矩阵,也被称为投影矩阵,ProjectionMatrix。

现在我们修改一下顶点着色器,把这个投影矩阵添加进去。

// vertex
attribute vec3 a_vertexPosition; // 1:把顶点从vec2扩展到vec3
attribute vec4 color; // 四维向量varying vec4 vColor;
uniform mat4 projectionMatrix; // 2:投影矩阵-变换坐标系void main() {gl_PointSize = 1.0;vColor = color;gl_Position = projectionMatrix * vec4(a_vertexPosition, 1.0);
}

现在我们就能看到画布上显示的是绿色的正方形了。

模型矩阵:让立方体旋转起来

现在我们只能看到立方体的一个面,因为Z轴是垂直于屏幕的,这样子从视觉上看好像和2维图形没什么区别,没法让人很直观地联想、感受到这是一个三维的几何体,为了将其他的面露出来,我们可以去旋转立方体。

要想旋转立方体,我们同样可以通过矩阵运算来实现。这个矩阵叫做模型矩阵,ModelMatrix,它定义了被绘制的物体变换。

把模型矩阵加入到顶点着色器中,将它与投影矩阵相乘,再乘上齐次坐标,就得到最终的顶点坐标了。

attribute vec3 a_vertexPosition; // 1:把顶点从vec2扩展到vec3
attribute vec4 color; // 四维向量varying vec4 vColor;
uniform mat4 projectionMatrix; // 2:投影矩阵-变换坐标系
uniform mat4 modelMatrix; // 3:模型矩阵-使几何体旋转void main() {gl_PointSize = 1.0;vColor = color;gl_Position = projectionMatrix * modelMatrix * vec4(a_vertexPosition, 1.0);
}

现在我们定义一个JavaScript函数,用立方体沿x、y、z轴的旋转来生成模型矩阵。

以x、y、z三个方向的旋转得到三个齐次矩阵,然后将它们相乘,就能得到最终的模型矩阵。

import { multiply } from 'ogl/src/math/functions/Mat4Func.js';
// ...
export function fromRotation(rotationX, rotationY, rotationZ) {let c = Math.cos(rotationX);let s = Math.sin(rotationX);const rx = [1,  0, 0, 0, // 绕X轴旋转0,  c, s, 0,0, -s, c, 0,0,  0, 0, 1];c = Math.cos(rotationY);s = Math.sin(rotationY);const ry = [c,  0, s, 0,0,  1, 0, 0, // 绕Y轴旋转-s, 0, c, 0,0,  0, 0, 1];c = Math.cos(rotationZ);s = Math.sin(rotationZ);const rz = [c,  s, 0, 0,-s, c, 0, 0,0,  0, 1, 0, // 绕Z轴旋转0,  0, 0, 1];const ret = [];multiply(ret, rx, ry);multiply(ret, ret, rz);return ret;
}

我们把模型矩阵传给顶点着色器,不断更新三个旋转角度,就能实现立方体旋转的效果。

// ...
let rotationX = 0;
let rotationY = 0;
let rotationZ = 0;
function update() {rotationX += 0.003;rotationY += 0.005;rotationZ += 0.007;renderer.uniforms.modelMatrix = fromRotation(rotationX, rotationY, rotationZ);requestAnimationFrame(update);
}
update();
// ...

现在我们就能在旋转中看到立方体的其他几个面了,能更直观地感受到这是一个三维物体。

总结

至此,我们就实现了正立方体的绘制。在3D物体的绘制中,正立方体属于是比较简单的一类,屏幕前的小伙伴们都可以来动手尝试下,感兴趣的小伙伴,还可以尝试去实现圆柱体、正四面体等等这些几何体的绘制。

文章转载自:beckyye

原文链接:https://www.cnblogs.com/beckyyyy/p/18293794

体验地址:引迈 - JNPF快速开发平台_低代码开发平台_零代码开发平台_流程设计器_表单引擎_工作流引擎_软件架构

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/45036.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C语言之数据在内存中的存储(2),浮点数在内存中的存储

目录 前言 一、引例 二、浮点型在内存中的存储 三、浮点数在内存中的存和取过程 1.浮点数的存储过程 2.浮点数的取过程 四、引例解析 总结 前言 想知道浮点数在内存中是如何存储的吗,本文就告诉你答案,虽然一般情况题目还是面试涉及到浮点数在内…

新华三H3CNE网络工程师认证—ACL使用场景

ACL主要用于实现流量的过滤,业务中网络的需求不止局限于能够连同。 一、过略工具 你的公司当中有研发部门,包括有财务部门,财务部门的访问是要做到控制的,防止被攻击。 这种的过滤方法为,在设备侧可以基于访问需求来…

AI算力发展现状与趋势分析

综合算力发展现状与趋势分析 在数字经济的疾速推动下,综合算力作为驱动各类应用和服务的新型生产力,其价值日益凸显。我们深入探讨了综合算力的定义、重要性以及当前发展状况;并从算力形态、运力性能和存储技术等角度,预见了其发展…

基于Java技术的校友社交系统

你好呀,我是计算机学姐码农小野!如果你对校友社交系统感兴趣或者有相关需求,可以私信联系我。 开发语言 Java 数据库 MySQL 技术 Java技术SpringBoot框架 工具 IDEA/Eclipse、Navicat、Maven 系统展示 首页 校友会信息界面 校友活动…

Sqli-labs 3

1.按照路径http://localhost/sqli-labs/sqli-labs-master/Less-3/进入 2.判断注入类型----字符型 Payload:?id1’) and 11-- 注:根据报错提示的语法错误,在第一行中使用接近’union select 1,2,3--’)的正确语法 3.判断注入点:…

【Linux】vim详解

1.什么是vi/vim? 简单来说,vi是老式的文本编辑器,不过功能已经很齐全了,但是还是有可以进步的地方。vim则可以说是程序开发者的一项很好用的工具,就连 vim的官方网站( http://www.vim.org)自己也说vim是一…

区块链项目全球成功指南:全面覆盖的媒体宣发策略与实践

随着区块链技术的迅速普及和发展,全球范围内对区块链项目的关注度不断提升。为了在国际市场上取得成功,区块链项目需要通过有效的媒体宣传策略来提高知名度,吸引投资,并建立强大的社区支持。本文将详细介绍区块链项目在海外媒体宣…

为企业提升销售工作效率的工作手机管理系统

在竞争日益激烈的市场环境中,企业的销售团队如同前线战士,其作战效率直接关乎企业的生存与发展。然而,传统销售管理模式下的信息孤岛、沟通不畅、数据混乱等问题,正悄然成为制约销售效率提升的瓶颈。今天,我们为您揭秘…

在 Windows 平台搭建 MQTT 服务

引言 MQTT 是一种轻量级、基于发布/订阅模式的消息传输协议,旨在用极小的代码空间和网络带宽为物联网设备提供简单、可靠的消息传递服务。MQTT 经过多年的发展,如今已被广泛应用于资源开采、工业制造、移动通信、智能汽车等各行各业,使得 MQ…

汇聚荣做拼多多电商怎么样?

汇聚荣做拼多多电商怎么样?在当前电商平台竞争激烈的背景下,拼多多凭借其独特的商业模式和市场定位迅速崛起。对于想要加入拼多多的商家而言,了解平台的特点、优势及挑战是至关重要的。本文将深入分析加入拼多多电商的多个方面,帮助读者全面…

ubuntu计划任务反弹

目录 实验环境 实验步骤 目标主机构造任务计划 构造语句 语句解释 kali开启监听 监听成功 问题 原因 实验环境 攻击者 操作系统:kali IP:192.168.244.141 目标主机 操作系统:ubuntu IP:192.168.244.151 实验步骤 目…

论文学习_An Empirical Study of Deep Learning Models for Vulnerability Detection

1. 引言 研究背景:近年来,深度学习漏洞检测工具取得了可喜的成果。最先进的模型报告了 0.9 的 F1 分数,并且优于静态分析器。结果令人兴奋,因为深度学习可能会给软件保障带来革命性的变化。因此,IBM、谷歌和亚马逊等行业公司非常感兴趣,并投入巨资开发此类工具和数据集。…

(8)揭示Python编程精髓:深潜继承与多态的奇幻之旅

目录 1. 命名空间与作用域1.1 命名空间概述1.2 作用域1.2.1 局部作用域1.2.2 全局作用域1.2.3 修改全局变量1.2.4 嵌套作用域 2. 继承3. 多态(Polymorphism) 1. 命名空间与作用域 1.1 命名空间概述 命名空间是一个从名字到对象的映射,它在P…

Qt:19.浮动窗口/子窗口(子窗口介绍、代码方式创建子窗口、设置子窗口标题、为子窗口添加控件、设置子窗口停靠位置)

目录 1.子窗口介绍: 2.代码方式创建子窗口: 3.设置子窗口标题: 4.为子窗口添加控件: 5.设置子窗口停靠位置。 1.子窗口介绍: 在 Qt 中,可以创建和管理子窗口(子窗口体)以实现多窗…

图片怎么制作成长期可用的活码?扫码提供图片预览的制作技巧

现在图片的尺寸和清晰度的质量越来越高,相对应的会占用更多的存储空间,现在很多人会将图片存入云端后,通过生成二维码的方式,扫码来查看图片内容。图片转换二维码有利于将图片分享给其他人查看,还能够节省更多的空间&a…

SpringBoot自己开发一个starter

提示:本文主要讲述如何自行开发一个SpringBoot的starter 文章目录 目录 文章目录 前言 一、Starter是什么 二、创建一个SpringBoot项目 1.创建一个基本的SpringBoot项目 2.选择要下载的库 三、设置项目 1.提示 2.配置pom.xml 3.重点代码 1.创建一个User类…

16.x86游戏实战-汇编指令push pop pushad popad

免责声明:内容仅供学习参考,请合法利用知识,禁止进行违法犯罪活动! 本次游戏没法给 内容参考于:微尘网络安全 工具下载: 链接:https://pan.baidu.com/s/1rEEJnt85npn7N38Ai0_F2Q?pwd6tw3 提…

【第29章】MyBatis-Plus之分页插件

文章目录 前言一、支持的数据库二、配置方法三、属性介绍四、自定义 Mapper 方法中使用分页五、其他注意事项六、Page 类七、实战1. 配置类2. 分页类3. 测试 总结 前言 MyBatis-Plus 的分页插件 PaginationInnerInterceptor 提供了强大的分页功能,支持多种数据库&a…

Windows电脑安装Python结合内网穿透轻松搭建可公网访问私有网盘

文章目录 前言1.本地文件服务器搭建1.1.Python的安装和设置1.2.cpolar的安装和注册 2.本地文件服务器的发布2.1.Cpolar云端设置2.2.Cpolar本地设置 3.公网访问测试4.结语 前言 本文主要介绍如何在Windows系统电脑上使用python这样的简单程序语言,在自己的电脑上搭建…

电力需求预测挑战赛笔记 Taks1 跑通baseline

#AI夏令营 #Datawhale #夏令营 赛题 一句话介绍赛题任务可以这样理解赛题: 【训练时序预测模型助力电力需求预测】 电力需求的准确预测对于电网的稳定运行、能源的有效管理以及可再生能源的整合至关重要。 赛题任务 给定多个房屋对应电力消耗历史 N 天的相关序列数…