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.rhkb.cn/news/115081.html

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

相关文章

视频文件损坏无法播放如何修复?导致视频文件损坏的原因

如果我们遇到因视频文件损坏而无法正常播放&#xff0c;我们该怎么办&#xff1f;这种情况通常意味着视频文件已经损坏。我们不能访问、编辑或使用它们。那么应该用什么正确的工具和修复程序来修复视频呢&#xff1f; 视频文件损坏的原因 了解视频损坏如何修复之前&#xff0c…

【C51基础实验 LED流水灯】

51单片机项目基础篇 LED流水灯1、硬件电路设计和原理分析2、软件设计2.1、利用循环和移位操作符功能实现&#xff1a;LED流水灯2.2、利用利用封装好的库函数功能实现&#xff1a;LED流水灯 3、编译结果4、结束语 LED流水灯 前言&#xff1a; 前几篇学会了LED驱动原理&#xff…

Mysql001:Mysql概述以及安装

前言&#xff1a;本课程将从头学习Mysql&#xff0c;以我的工作经验来说&#xff0c;sql语句真的太重要的&#xff0c;现在互联网所有的一切都是建立在数据上&#xff0c;因为互联网的兴起&#xff0c;现在的数据日月增多&#xff0c;每年都以翻倍的形式增长&#xff0c;对于数…

数据库CPU飙高问题定位及解决

在业务服务提供能力的时候&#xff0c;常常会遇到CPU飙高的问题&#xff0c;遇到这类问题&#xff0c;大多不是数据库自身问题&#xff0c;都是因为使用不当导致&#xff0c;这里记录下业务服务如何定位数据库CPU飙高问题并给出常见的解决方案。 CPU 使用率飙升根因分析 在分…

概念解析 | 量子时代的灵感:探索量子感知技术

注1:本文系“概念解析”系列之一,致力于简洁清晰地解释、辨析复杂而专业的概念。本次辨析的概念是:量子感知技术。 量子时代的灵感:探索量子感知技术 量子感知技术是一个充满希望和挑战的新兴领域。在此,我们将深入探讨这个主题,概述其背景,解释其工作原理,讨论现有的…

mov怎么改成mp4?跟我一起操作吧

mov怎么改成mp4&#xff1f;mov因为并不是一种常见的视频文件格式&#xff0c;因此大家对这种视频文件可能知道的并不多&#xff0c;但如果你是用的是苹果手机&#xff0c;那么你会发现苹果手机拍摄的视频转移到电脑上后就是mov格式的&#xff0c;因为mov格式的视频并没有受到大…

JDBC使用了哪种设计模式

JDK中提供了操作数据库的接口&#xff0c;比如 java.sql.Driver java.sql.Connection java.sql.Statement java.sql.PreparedStatement 不同的数据库厂商提供操作自己数据库的驱动包&#xff0c; 比如mysql public class Driver extends NonRegisteringDriver implements jav…

一篇文章带你了解-selenium工作原理详解

前言 Selenium是一个用于Web应用程序自动化测试工具。Selenium测试直接运行在浏览器中&#xff0c;就像真正的用户在操作一样。支持的浏览器包括IE&#xff08;7, 8, 9, 10, 11&#xff09;&#xff0c;Mozilla Firefox&#xff0c;Safari&#xff0c;Google Chrome&#xff0c…

DC电源模块不同的尺寸可以适应实际应用场景

BOSHIDA DC电源模块不同的尺寸可以适应实际应用场景 DC电源模块是现代电子设备的必备部件之一&#xff0c;其可提供稳定的直流电源&#xff0c;保证电子设备正常运行。DC电源模块尺寸的选择直接影响到其适应的应用场景及其性能表现。本文将从尺寸方面分析DC电源模块的适应性&a…

【zookeeper】zookeeper介绍

分布式协调技术 在学习ZooKeeper之前需要先了解一种技术——分布式协调技术。那么什么是分布式协调技术&#xff1f;其实分布式协调技术主要用来解决分布式环境当中多个进程之间的同步控制&#xff0c;让他们有序的去访问某种临界资源&#xff0c;防止造成"脏数据"的…

C++ list模拟实现

list模拟实现代码&#xff1a; namespace djx {template<class T>struct list_node{T _data;list_node<T>* _prev;list_node<T>* _next;list_node(const T& x T()):_data(x),_prev(nullptr),_next(nullptr){}};template<class T,class Ref,class Pt…

Mac操作系统Safari 17全新升级:秋季推出全部特性

苹果的内置浏览器可能是Mac上最常用的应用程序&#xff08;是的&#xff0c;甚至比Finder、超级Mac Geeks还要多&#xff09;。因此&#xff0c;苹果总是为其浏览器Safari添加有用的新功能。在今年秋天与macOS Sonoma一起推出的第17版中&#xff0c;Safari可以帮助你提高工作效…

活用 命令行通配符

本文是对 阮一峰老师命令行通配符教程[1]的学习与记录 通配符早于正则表达式出现,可以看作是原始的正则表达式. 其功能没有正则那么强大灵活,而胜在简单和方便. - 字符 切回上一个路径/分支 如图: !! 代表上一个命令, 如图: [Linux中“!"的神奇用法](https://www.cnblogs.…

不会还有人排长队吃饭吧?用这招,快速搞定!

随着现代企业对员工福利和工作环境的关注不断增加&#xff0c;企业智慧食堂已经成为了企业管理的重要组成部分。 智慧收银系统的出现不仅使员工用餐变得更加便捷和高效&#xff0c;还提供了一种强大的管理工具&#xff0c;有助于企业更好地理解员工消费行为、优化食堂运营&…

比较器的工作原理及性能指标介绍

一、什么是比较器 比较器的功能是比较两个或更多数据项&#xff0c;以确定它们是否相等&#xff0c;或者确定它们之间的大小关系和排列顺序&#xff0c;这称为比较。可以实现此比较功能的电路或设备称为比较器。比较器是将模拟电压信号与参考电压进行比较的电路。比较器的两个…

说说Flink中的State

分析&回答 基本类型划分 在Flink中&#xff0c;按照基本类型&#xff0c;对State做了以下两类的划分&#xff1a; Keyed State&#xff0c;和Key有关的状态类型&#xff0c;它只能被基于KeyedStream之上的操作&#xff0c;方法所使用。我们可以从逻辑上理解这种状态是一…

掌握逻辑漏洞复现技术,保护您的数字环境

环境准备 这篇文章旨在用于网络安全学习&#xff0c;请勿进行任何非法行为&#xff0c;否则后果自负。 1、支付逻辑漏洞 攻击相关介绍 介绍&#xff1a; 支付逻辑漏洞是指攻击者利用支付系统的漏洞&#xff0c;突破系统的限制&#xff0c;完成非法的支付操作。攻击者可以采…

ZKP硬件加速

1. 引言 本文重点关注&#xff1a; 1&#xff09;何为硬件加速&#xff1f;为何需要硬件加速&#xff1f;2&#xff09;ZKP的关键计算原语&#xff1a; Multiscalar MultiplicationNumber Theoretic TransformationArithmetic Hashes 3&#xff09;所需的硬件资源4&#xff0…

2D-2D对极几何中的基本矩阵、本质矩阵和单应矩阵

本文主要参考高翔博士的视觉SLAM十四讲第二版中的7.3章节内容。文章目录 1 对极约束2 本质矩阵E3 单应矩阵 1 对极约束 现在&#xff0c;假设我们从两张图像中得到了一对配对好的特征点&#xff0c;如图7.9所示&#xff08;假如后面我们有若干对这样的匹配点&#xff0c;根据这…

烟草企业物流管理信息系统的分析与设计(论文+源码)_kaic

摘要 在经济高速发展的今天&#xff0c;物流业已经成为支撑国民经济的基础性产业。作为一种新型服务业&#xff0c;物流业集仓储、运输、信息等为一体&#xff0c;发展成为复合型战略性产业。S烟草企业设计的物流管理信息系统利用B/S模式的三层结构&#xff0c;基于JSP技术和J…