WebGPU加载Wavefront .OBJ模型文件

在开发布料模拟之前,我想使用 WebGPU 开发强大的代码基础。 这就是为什么我想从 Wavefront .OBJ 文件加载器开始渲染 3D 模型。 这样,我们可以快速渲染 3D 模型,并构建一个简单而强大的渲染引擎来完成此任务。 一旦我们有了扎实的基础,我们就可以轻松实现布料模拟部分了。

在这里插入图片描述

推荐:用 NSDT编辑器 快速搭建可编程3D场景

1、Wavefront .OBJ 文件

.OBJ 是一种文件格式,包含由 Wavefront Technologies 公司创建的 3D 几何图形的描述。 典型的 .OBJ 文件的结构包含一组:

  • 顶点
  • 法线
  • 纹理坐标

让我们看一个例子。 .obj 金字塔定义如下:

v  0  0  0
v  1  0  0
v  1  1  0
v  0  1  0
v  0.5  0.5  1.6
​
f  4// 1// 2//
f  3// 4// 2//
f  5// 2// 1//
f  4// 5// 1//
f  3// 5// 4//
f  5// 3// 2//

渲染此文件,我们可能会看到一个如下图所示的金字塔:

在这里插入图片描述

那么问题出现了🤔:

我们如何将这种文件格式加载到我们的程序中?

我们将了解如何使用现成的 .OBJ 文件来执行此操作,以渲染复杂的几何图形。 利用别人的劳动来免除我们的辛苦工作的美妙之处。 💅

如果你手头有FBX、GLTF等其他格式的模型,可以使用NSDT 3DConvert这个在线3D格式转换工具将其转换为.OBJ文件:
在这里插入图片描述

2、我们如何找到 .OBJ 文件?

我使用 Google 查找 .OBJ 文件。 也就是说,如果我找到一个我喜欢的文件,我必须将其加载到 Blender 等软件中,原因如下:

  • 格式一致性:当使用 Google 查找 .OBJs 文件时,它们都有一些小的格式特性。 例如,他们可以定义带或不带斜线的顶点和面。 我想用Blender加载和导出以保证文件内容的格式。 📁
  • 几何体的定位:有时,模型的定位方式是我们不想要的。 纠正 Blender 中的初始位置可以节省我们一些时间和代码。

让我们看一个例子。 对于这个项目,我想使用著名的斯坦福兔子。 该文件可以在这里找到:
在这里插入图片描述

3、如何在 Blender 中准备几何体

下载文件后,我们需要在Blender等3D软件中打开它进行检查。 我们立刻就可以看到这个位置有问题:
在这里插入图片描述

我想将兔子居中,使其身体位于原点。 要做到这一点非常简单。 以下是这些步骤:

将原点放在兔子上。 右键单击,然后导航到“设置原点”>“原点到几何体”。
在这里插入图片描述

将兔子移动到场景原点。 右键单击,然后导航至“捕捉”>“光标选择”。

在这里插入图片描述

兔子现在以原点为中心🎯:

在这里插入图片描述

最后,一个好主意是检查与模型相关的法线。 我们可以通过在 Blender 中进入编辑模式(按 TAB 键)并导航到“网格”>“法线”>“重新计算外部”来重做计算:

在这里插入图片描述

导航到文件 > 导出 > Wavefront (.obj)。 可以使用以下设置导出文件:

✅ 应用修饰符
✅ 写法线
✅ 包括 UV
✅ 撰写材质
✅ 三角面

在这里插入图片描述

之后,你应该准备好使用 WebGPU 在浏览器中渲染的 .OBJ 文件。 🥳

4、WebGPU 代码

💡代码现在假设该文件是由 Blender 准备的。 如果没有,请参阅上一节。

目标是将 .OBJ 文件中的所有数据存储在缓冲区中。 幸运的是,这些数据很容易读取。 我将系统设计为两部分:

  • 加载器 - 我们加载文件并将其文本存储在内存中,以便我们可以处理它。
  • 解析器 - 将文本存储在内存中,我们可以解析文本行并将它们存储在缓冲区中。
interface Mesh {positions: Float32Array;uvs: Float32Array;normals: Float32Array;indices: Uint16Array;
}type ObjFile = string
type FilePath = stringtype CachePosition = number
type CacheFace = string
type CacheNormal = number
type CacheUv = number
type CacheArray<T> = T[][]type toBeFloat32 = number
type toBeUInt16 = number/*** ObjLoader to load in .obj files. This has only been tested on Blender .obj exports that have been UV unwrapped* and you may need to throw out certain returned fields if the .OBJ is missing them (ie. uvs or normals)*/
export default class ObjLoader {constructor() {}/*** Fetch the contents of a file, located at a filePath.*/async load(filePath: FilePath): Promise<ObjFile> {const resp = await fetch(filePath)if (!resp.ok) {throw new Error(`ObjLoader could not fine file at ${filePath}. Please check your path.`)}const file = await resp.text()if (file.length === 0) {throw new Error(`${filePath} File is empty.`)}return file}/*** Parse a given obj file into a Mesh*/parse(file: ObjFile): Mesh {const lines = file?.split("\n")// Store what's in the object file hereconst cachedPositions: CacheArray<CachePosition> = []const cachedFaces: CacheArray<CacheFace> = []const cachedNormals: CacheArray<CacheNormal> = []const cachedUvs: CacheArray<CacheUv> = []// Read out data from file and store into appropriate source buckets{for (const untrimmedLine of lines) {const line = untrimmedLine.trim() // remove whitespaceconst [startingChar, ...data] = line.split(" ")switch (startingChar) {case "v":cachedPositions.push(data.map(parseFloat))breakcase "vt":cachedUvs.push(data.map(Number))breakcase "vn":cachedNormals.push(data.map(parseFloat))breakcase "f":cachedFaces.push(data)break}}}// Use these intermediate arrays to leverage Array API (.push)const finalPositions: toBeFloat32[] = []const finalNormals: toBeFloat32[] = []const finalUvs: toBeFloat32[] = []const finalIndices: toBeUInt16[] = []// Loop through faces, and return the buffers that will be sent to GPU for rendering{const cache: Record<string, number> = {}let i = 0for (const faces of cachedFaces) {for (const faceString of faces) {// If we already saw this, add to indices list.if (cache[faceString] !== undefined) {finalIndices.push(cache[faceString])continue}cache[faceString] = ifinalIndices.push(i)// Need to convert strings to integers, and subtract by 1 to get to zero index.const [vI, uvI, nI] = faceString.split("/").map((s: string) => Number(s) - 1)vI > -1 && finalPositions.push(...cachedPositions[vI])uvI > -1 && finalUvs.push(...cachedUvs[uvI])nI > -1 && finalNormals.push(...cachedNormals[nI])i += 1}}}return {positions: new Float32Array(finalPositions),uvs: new Float32Array(finalUvs),normals: new Float32Array(finalNormals),indices: new Uint16Array(finalIndices),}}
}

5、加载器

让我们看一下 load() 函数:

async function load(filePath: FilePath): Promise<ObjFile> {const resp = await fetch(filePath);if (!resp.ok) {throw new Error(`ObjLoader could not fine file at ${filePath}. Please check your path.`);}const file = await resp.text();
​if (file.length === 0) {throw new Error(`${filePath} File is empty.`);}
​return file;
}

这段代码的想法只是获取位于 filePath 的文件的内容。 我将文件存储在硬盘上,但可以通过 HTTP 请求数据。

6、解析器

这是代码的第一部分:

parse(file: ObjFile): Mesh {const lines = file?.split("\n");
​// Store what's in the object file hereconst cachedVertices: CacheArray<CacheVertice> = [];const cachedFaces: CacheArray<CacheFace> = [];const cachedNormals: CacheArray<CacheNormal> = [];const cachedUvs: CacheArray<CacheUv> = [];
​// Read out data from file and store into appropriate source buckets{for (const untrimmedLine of lines) {const line = untrimmedLine.trim(); // remove whitespaceconst [startingChar, ...data] = line.split(" ");switch (startingChar) {case "v":cachedVertices.push(data.map(parseFloat));break;case "vt":cachedUvs.push(data.map(Number));break;case "vn":cachedNormals.push(data.map(parseFloat));break;case "f":cachedFaces.push(data);break;}}}
​
... Rest of code
}

这部分包括简单地从内存中读取数据并将它们存储在相应的数组中。 幸运的是,每行文本都标有其关联的类型:

  • v - 顶点的位置
  • vt - 纹理坐标(uv)
  • vn - 法线向量(法线)
  • f - 面(形成三角形的三个顶点)

这是其余的代码:

... the code before// Use these intermediate arrays to leverage Array API (.push)const finalVertices: toBeFloat32[] = [];const finalNormals: toBeFloat32[] = [];const finalUvs: toBeFloat32[] = [];const finalIndices: toBeUInt16[] = [];
​// Loop through faces, and return the buffers that will be sent to GPU for rendering{const cache: Record<string, number> = {};let i = 0;for (const faces of cachedFaces) {for (const faceString of faces) {// If we already saw this, add to indices list.if (cache[faceString] !== undefined) {finalIndices.push(cache[faceString]);continue;}
​cache[faceString] = i;finalIndices.push(i);
​// Need to convert strings to integers, and subtract by 1 to get to zero index.const [vI, uvI, nI] = faceString.split("/").map((s: string) => Number(s) - 1);
​vI > -1 && finalVertices.push(...cachedVertices[vI]);uvI > -1 && finalUvs.push(...cachedUvs[uvI]);nI > -1 && finalNormals.push(...cachedNormals[nI]);
​i += 1;}}}
​return {vertices: new Float32Array(finalVertices),uvs: new Float32Array(finalUvs),normals: new Float32Array(finalNormals),indices: new Uint16Array(finalIndices),};}

接下来,我们迭代面部以创建数据并将其存储在最终缓冲区中。 我们使用称为索引缓冲区的东西,这是避免存储重复数据的一种方法。 我们稍后会看到如何进行。

7、缓冲器类型

正如我们在讨论中看到的,我们在 WebGPU 中渲染了一个三角形,我们使用缓冲区来存储每个顶点的属性。

更具体地,使用一个或多个顶点缓冲对象(VBO)和索引缓冲对象(IBO)。 我们使用 IBO 中的索引来索引 VBO 以避免存储重复数据。

让我们看一个例子:
在这里插入图片描述

标签为 2 的顶点位于两个三角形中(一个由顶点 1、2、3 形成,另一个由顶点 3、2、4 形成)。 我们将数据定义如下:

position_vbo = [-1, 0, 0, #v11, 0, 0, #v20, 1, 0, #v32, 1, 0, #v4]
​color_vbo = [1, 0, 0, #v10, 0, 1, #v21, 1, 0, #v32, 1, 0, #v4]
​indices_ibo = [0, 1, 2, # triangle 12, 1, 3  # triangle 2
]

原文链接:WebGPU加载.OBJ模型 — BimAnt

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

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

相关文章

我们把“高血压”小游戏真正做到了不用下载,点击即玩!!!

相信大家经常在短视频网站上刷到各种“高血压“小游戏吧&#xff0c;当你按捺不住点击&#xff0c;却发现手机上多了一大堆“流氓软件”的时候&#xff0c;血压就更高了。 但是&#xff01; 今天&#xff01; 我们把“虚假广告”做成了真实的游戏&#xff0c;并且可以轻松部署到…

【openEuler创新项目探索】一个Java端的向量化BLAS库VectorBLAS

VectorBLAS简介 VectorBLAS是一个使用Java语言实现的向量化BLAS高性能库&#xff0c;目前已在openEuler社区开源。 VectorBLAS通过循环展开、矩阵分块和内存布局优化等算法优化&#xff0c;对BLAS函数进行了深度优化&#xff0c;并利用VectorAPI JDK提供的多种向量化API实现。…

利用Jmeter做接口测试(功能测试)全流程分析

利用Jmeter做接口测试怎么做呢&#xff1f;过程真的是超级简单。 明白了原理以后&#xff0c;把零碎的知识点填充进去就可以了。所以在学习的过程中&#xff0c;不管学什么&#xff0c;我一直都强调的是要循序渐进&#xff0c;和明白原理和逻辑。这篇文章就来介绍一下如何利用…

开源vue动态表单组件

一、项目简介 vueelement的动态表单组件&#xff0c;拖拽组件到面板即可实现一个表单 二、实现功能 支持拖拽 支持输入框 支持文本框 支持数字输入框 支持下拉选择器 支持多选框 支持日期控件 支持开关 支持动态表格 支持上传图片 支持上传文件 支持标签 支持ht…

vue中实现echarts三维散点图

需要安装 echarts 同时引入 echarts-gl 我安装的版本&#xff1a; "echarts": "^5.3.2", "echarts-gl": "^2.0.9", import Vue from "vue"; import * as echarts from "echarts"; Vue.prototype.$echarts echa…

常用Web漏洞扫描工具汇总(持续更新中)

常用Web漏洞扫描工具汇总 常用Web漏洞扫描工具汇总1、AWVS&#xff0c;2、OWASP Zed&#xff08;ZAP&#xff09;&#xff0c;3、Nikto&#xff0c;4、BurpSuite&#xff0c;5、Nessus&#xff0c;6、nmap7、X-ray还有很多不是非常知名&#xff0c;但可能也很大牌、也较常见的。…

生成对抗网络(GAN):在图像生成和修复中的应用

文章目录 什么是生成对抗网络&#xff08;GAN&#xff09;&#xff1f;GAN在图像生成中的应用图像生成风格迁移 GAN在图像修复中的应用图像修复 拓展应用领域总结 &#x1f389;欢迎来到AIGC人工智能专栏~生成对抗网络&#xff08;GAN&#xff09;&#xff1a;在图像生成和修复…

Visual Studio Code 终端配置使用 MySQL

Visual Studio Code 终端配置使用 MySQL 找到 MySQL 的 bin 目录 在导航栏中搜索–》服务 找到MySQL–>双击 在终端切换上面找到的bin目录下输入指令 终端为Git Bash 输入命令 ./mysql -u root -p 接着输入密码&#xff0c;成功在终端使用 MySQL 数据库。

Annual Inspection

机动车年检流程【交警12123】APP 到【检查地方】门口墙上贴着 然后上缴钥匙&#xff0c;等待&#xff0c;本次等待不到半小时搞定&#xff0c;速度很满意&#xff0c; 发现检测人员把你的里程数纠正了。 给你的行驶证&#xff0c;打印这些字样&#xff1a;检验有效期至XXXX 再给…

ChatGPT帮助高职院校学生实现个性化自适应学习与对话式学习

一、学习层面&#xff1a;ChatGPT帮助高职院校学生实现个性化自适应学习与对话式学习 1.帮助高职院校学生实现个性化自适应学习 数字技术的飞速发展引起了教育界和学术界对高职院校学生个性化自适应学习的更多关注和支持&#xff0c;其运作机制依赖于人工智能等技术&#xff0…

SLAM从入门到精通(开始篇)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 很多同学喜欢学习linux&#xff0c;但是他们只是把linux当成是一个嵌入式技术在学习&#xff0c;而不是当成工具在使用。平时&#xff0c;要么是自…

Samba服务器

目录 一、什么是Samba&#xff1f; 二、Samba进程 三、Samba主要功能 四、Samba工作流程 五、Samba安全级别 六、Sam主配置文件/etc/samba/smb.conf 七、Samba服务配置案例 一、什么是Samba&#xff1f; Samba可以让linux计算机和windows计算机之间实现文件和打印机资源共享的一…

解决D盘的类型不是基本,而是动态的问题

一、正确的图片 1.1图片 1.2本人遇到的问题 二、将动态磁盘 转为基本盘 2.1 基本概念&#xff0c;动态无法转化为基本&#xff0c;不是双向的&#xff0c;借助软件 网址&#xff1a;转换动态磁盘到普通磁盘_检测到计算机本地磁盘为动态分区_卫水金波的博客-CSDN博客 2.2分区…

软考:中级软件设计师:数据库恢复与备份,故障与恢复,反规范化

软考&#xff1a;中级软件设计师:数据库恢复与备份 提示&#xff1a;系列被面试官问的问题&#xff0c;我自己当时不会&#xff0c;所以下来自己复盘一下&#xff0c;认真学习和总结&#xff0c;以应对未来更多的可能性 关于互联网大厂的笔试面试&#xff0c;都是需要细心准备…

Linux 下 Java Socket 编程报 java.net.Exception:Permission denied (权限不足)

本人用Linux部署springboot项目时遇见这个错误&#xff0c;原因很简单&#xff0c;就是端口号没有选对。 在linux系统中&#xff0c;端口号再1024以下的需要root权限&#xff0c;只要把端口改成大于1024的就可以了&#xff0c;但避开一些软件的默认端口&#xff0c;如Tomcat的8…

Midjourney学习(三)6个高级应用

使用Remix Mode在原图片的基础上进行二次创作 通过prompt得到大图之后&#xff0c;点击Make Variations按钮&#xff0c;输入Remix Prompt&#xff0c;即可得到意想不到的效果&#xff01; 局部内容重绘 通过局部重绘可以实现对画面内容更加精细化的控制&#xff0c;同样也是需…

设计模式—策略模式

目录 一、定义 二、特点 三、优点 四、缺点 五、实例 六.涉及到的知识点 1、一个类里面有哪些东西&#xff1f; 2、类和实例 什么是类&#xff1f; 什么是实例&#xff1f; 什么是实例化&#xff1f; 3、字段和属性 什么是字段&#xff1f; 属性是什么&#xff1…

新的雅思口语6分标准

目录 新的雅思口语6分标准 要有细节&#xff0c;要有充分的话题词汇资源 要拥有具象思维能力&#xff0c;要有画面感 下义词是什么意思&#xff1f; 方法&#xff1a;现在时未来时 &#xff08;形成时态多样&#xff09;观点解释 原因要有排他性 "Kick off" 是…

windows系统服务器在不解锁屏幕不输入密码的前提下,电脑通电开机启动程序。

在控制面板中找到“管理工具”中的 “任务计划程序”&#xff0c;打开“任务计划程序”窗口。如图&#xff1a; 双击打开任务计划程序&#xff0c;空白出右键创建基本任务&#xff0c;或者点击最右侧的创建基本任务。 输入名称&#xff0c;点击下一步。 先选择计算机启动时&a…

开启智能时代:深度解析智能文档分析技术的前沿与应用

开启智能时代&#xff1a;深度解析智能文档分析技术的前沿与应用 本章主要介绍文档分析技术的理论知识&#xff0c;包括背景介绍、算法分类和对应思路。通过本文学习&#xff0c;你可以掌握&#xff1a;1. 版面分析的分类和典型思想 2. 表格识别的分类和典型思想 3. 信息提取的…