Matplotlib渲染3D模型【Wavefront .OBJ】

在这里插入图片描述

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

Matplotlib 有一个非常漂亮的 3D 界面,具有许多功能(和一些限制),在用户中非常受欢迎。 然而,对于某些用户(或者可能对于大多数用户)来说,3D 仍然被认为是某种黑魔法。 因此,我想在这篇文章中解释一下,一旦你理解了一些概念,3D 渲染就会变得非常简单。 为了证明这一点,我们将使用 60 行 Python 代码和一个 Matplotlib 调用来渲染上面的兔子,而不使用 3D 轴。

如果你手头的模型不是.OBJ格式,可以用NSDT 3DConvert这个在线3D格式转换工具将其 转换为.OBJ格式:
在这里插入图片描述

1、加载兔子

首先,我们需要加载模型。 我们将使用斯坦福兔子的简化版本。 该文件使用wavefront .ob格式,这是最简单的格式之一,所以让我们制作一个非常简单(但容易出错)的加载器,它将完成这篇文章(和这个模型)的工作:

V, F = [], []
with open("bunny.obj") as f:for line in f.readlines():if line.startswith('#'):continuevalues = line.split()if not values:continueif values[0] == 'v':V.append([float(x) for x in values[1:4]])elif values[0] == 'f':F.append([int(x) for x in values[1:4]])
V, F = np.array(V), np.array(F)-1

V 现在是一组顶点(如果你愿意,也可以是 3D 点), F 是一组面(= 三角形)。 每个三角形由相对于顶点数组的 3 个索引来描述。 现在,让我们标准化顶点,使整个兔子适合单位框:

V = (V-(V.max(0)+V.min(0))/2)/max(V.max(0)-V.min(0))

现在,我们可以通过仅获取顶点的 x,y 坐标并去掉 z 坐标来初步查看模型。 为此,我们可以使用强大的 PolyCollection 对象,它可以有效地渲染非规则多边形的集合。 因为我们想要渲染一堆三角形,所以这是一个完美的匹配。 因此,我们首先提取三角形并去掉 z 坐标:

T = V[F][...,:2]

我们现在可以渲染它:

fig = plt.figure(figsize=(6,6))
ax = fig.add_axes([0,0,1,1], xlim=[-1,+1], ylim=[-1,+1],aspect=1, frameon=False)
collection = PolyCollection(T, closed=True, linewidth=0.1,facecolor="None", edgecolor="black")
ax.add_collection(collection)
plt.show()

你应该得到这样的东西(bunny-1.py):

在这里插入图片描述

2、透视投影

我们刚刚所做的渲染实际上是正交投影,而顶部的兔子使用透视投影:
在这里插入图片描述

在这两种情况下,定义投影的正确方法是首先定义观看体积,即我们想要在屏幕上渲染的 3D 空间中的体积。 为此,我们需要考虑 6 个剪裁平面(左、右、上、下、远、近),它们相对于相机封闭观察体积(视锥体)。 如果我们定义相机位置和观察方向,则每个平面都可以用单个标量来描述。 一旦我们有了这个观看体积,我们就可以使用正交投影或透视投影投影到屏幕上。

对我们来说幸运的是,这些投影是众所周知的并且可以使用 4x4 矩阵来表示:

def frustum(left, right, bottom, top, znear, zfar):M = np.zeros((4, 4), dtype=np.float32)M[0, 0] = +2.0 * znear / (right - left)M[1, 1] = +2.0 * znear / (top - bottom)M[2, 2] = -(zfar + znear) / (zfar - znear)M[0, 2] = (right + left) / (right - left)M[2, 1] = (top + bottom) / (top - bottom)M[2, 3] = -2.0 * znear * zfar / (zfar - znear)M[3, 2] = -1.0return Mdef perspective(fovy, aspect, znear, zfar):h = np.tan(0.5*radians(fovy)) * znearw = h * aspectreturn frustum(-w, w, -h, h, znear, zfar)

对于透视投影,我们还需要指定孔径角(或多或少)设置近平面相对于远平面的大小。 因此,对于高光圈,你会得到很多“变形”。

但是,如果查看上面的两个函数,你会发现它们返回 4x4 矩阵,而我们的坐标是 3D。 那么如何使用这些矩阵呢? 答案是齐次坐标。 长话短说,齐次坐标最适合处理 3D 中的变换和投影。 在我们的例子中,因为我们处理的是顶点(而不是向量),所以我们只需将 1 作为第四个坐标 (w) 添加到所有顶点。 然后我们可以使用点积应用透视变换。

V = np.c_[V, np.ones(len(V))] @ perspective(25,1,1,100).T

最后一步,我们需要重新标准化齐次坐标。 这意味着我们将每个变换后的顶点除以最后一个分量 (w),以便每个顶点始终具有 w=1。

V /= V[:,3].reshape(-1,1)

现在我们可以再次显示结果(bunny-2.py):
在这里插入图片描述

哦,奇怪的结果。 怎么回事? 问题是相机实际上在兔子体内。 为了获得正确的渲染效果,我们需要将兔子移离相机或将相机移离兔子。 我们再做后面的事吧。 相机当前位于 (0,0,0) 并沿 z 方向向上看(由于截锥体变换)。 因此,我们需要在透视变换之前将相机在 z 负方向上稍微移开一点:

V = V - (0,0,3.5)
V = np.c_[V, np.ones(len(V))] @ perspective(25,1,1,100).T
V /= V[:,3].reshape(-1,1)

现在你应该获得(bunny-3.py):
在这里插入图片描述

3、模型、视图、投影(MVP)

可能不太明显,但最后的渲染实际上是透视变换。 为了让它更明显,我们将旋转兔子。 为此,我们需要一些旋转矩阵(4x4),同时我们也可以定义平移矩阵:

def translate(x, y, z):return np.array([[1, 0, 0, x],[0, 1, 0, y],[0, 0, 1, z],[0, 0, 0, 1]], dtype=float)def xrotate(theta):t = np.pi * theta / 180c, s = np.cos(t), np.sin(t)return np.array([[1, 0,  0, 0],[0, c, -s, 0],[0, s,  c, 0],[0, 0,  0, 1]], dtype=float)def yrotate(theta):t = np.pi * theta / 180c, s = np.cos(t), np.sin(t)return  np.array([[ c, 0, s, 0],[ 0, 1, 0, 0],[-s, 0, c, 0],[ 0, 0, 0, 1]], dtype=float)

现在,我们将根据模型(局部变换)、视图(全局变换)和投影来分解要应用的变换,以便我们可以计算一个可以同时完成所有操作的全局 MVP 矩阵:

model = xrotate(20) @ yrotate(45)
view  = translate(0,0,-3.5)
proj  = perspective(25, 1, 1, 100)
MVP   = proj  @ view  @ model

现在我们写:

V = np.c_[V, np.ones(len(V))] @ MVP.T
V /= V[:,3].reshape(-1,1)

你应该得到(bunny-4.py):

在这里插入图片描述

现在让我们稍微调整一下光圈,以便你可以看到差异。 请注意,我们还必须调整与相机的距离,以使兔子具有相同的外观尺寸(bunny-5.py):
在这里插入图片描述

4、深度排序

现在让我们尝试填充三角形(bunny-6.py)
在这里插入图片描述

正如你所看到的,结果很“有趣”并且完全错误。 问题是 PolyCollection 会按照给定的顺序绘制三角形,而我们希望从后到前绘制三角形。 这意味着我们需要根据它们的深度对它们进行排序。 好消息是,当我们应用 MVP 转换时,我们已经计算了这些信息。 它存储在新的 z 坐标中。 然而,这些 z 值是基于顶点的,而我们需要对三角形进行排序。 因此,我们将平均 z 值作为三角形深度的代表。 如果三角形相对较小且不相交,则效果很好:

T =  V[:,:,:2]
Z = -V[:,:,2].mean(axis=1)
I = np.argsort(Z)
T = T[I,:]

现在一切都渲染正确了(bunny-7.py):
在这里插入图片描述

让我们使用深度缓冲区添加一些颜色。 我们将根据每个三角形的深度为其着色。 PolyCollection 对象的美妙之处在于你可以使用 NumPy 数组指定每个三角形的颜色,所以让我们这样做:

zmin, zmax = Z.min(), Z.max()
Z = (Z-zmin)/(zmax-zmin)
C = plt.get_cmap("magma")(Z)
I = np.argsort(Z)
T, C = T[I,:], C[I,:]

现在一切都渲染正确了(bunny-8.py):
在这里插入图片描述

最终脚本有 57 行(但很难读):

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import PolyCollectiondef frustum(left, right, bottom, top, znear, zfar):M = np.zeros((4, 4), dtype=np.float32)M[0, 0] = +2.0 * znear / (right - left)M[1, 1] = +2.0 * znear / (top - bottom)M[2, 2] = -(zfar + znear) / (zfar - znear)M[0, 2] = (right + left) / (right - left)M[2, 1] = (top + bottom) / (top - bottom)M[2, 3] = -2.0 * znear * zfar / (zfar - znear)M[3, 2] = -1.0return M
def perspective(fovy, aspect, znear, zfar):h = np.tan(0.5*np.radians(fovy)) * znearw = h * aspectreturn frustum(-w, w, -h, h, znear, zfar)
def translate(x, y, z):return np.array([[1, 0, 0, x], [0, 1, 0, y],[0, 0, 1, z], [0, 0, 0, 1]], dtype=float)
def xrotate(theta):t = np.pi * theta / 180c, s = np.cos(t), np.sin(t)return np.array([[1, 0,  0, 0], [0, c, -s, 0],[0, s,  c, 0], [0, 0,  0, 1]], dtype=float)
def yrotate(theta):t = np.pi * theta / 180c, s = np.cos(t), np.sin(t)return  np.array([[ c, 0, s, 0], [ 0, 1, 0, 0],[-s, 0, c, 0], [ 0, 0, 0, 1]], dtype=float)
V, F = [], []
with open("bunny.obj") as f:for line in f.readlines():if line.startswith('#'):  continuevalues = line.split()if not values:            continueif values[0] == 'v':      V.append([float(x) for x in values[1:4]])elif values[0] == 'f' :   F.append([int(x) for x in values[1:4]])
V, F = np.array(V), np.array(F)-1
V = (V-(V.max(0)+V.min(0))/2) / max(V.max(0)-V.min(0))
MVP = perspective(25,1,1,100) @ translate(0,0,-3.5) @ xrotate(20) @ yrotate(45)
V = np.c_[V, np.ones(len(V))]  @ MVP.T
V /= V[:,3].reshape(-1,1)
V = V[F]
T =  V[:,:,:2]
Z = -V[:,:,2].mean(axis=1)
zmin, zmax = Z.min(), Z.max()
Z = (Z-zmin)/(zmax-zmin)
C = plt.get_cmap("magma")(Z)
I = np.argsort(Z)
T, C = T[I,:], C[I,:]
fig = plt.figure(figsize=(6,6))
ax = fig.add_axes([0,0,1,1], xlim=[-1,+1], ylim=[-1,+1], aspect=1, frameon=False)
collection = PolyCollection(T, closed=True, linewidth=0.1, facecolor=C, edgecolor="black")
ax.add_collection(collection)
plt.show()

原文链接:Matplotlib渲染3D模型 — BimAnt

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

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

相关文章

基于Android 语音朗读书籍管理系统

视频演示: 基于Android 语音朗读书籍管理系统 基于 Android 的语音朗读书籍管理系统可以提供用户管理书籍、朗读书籍的功能。以下是一个简单的步骤和功能列表: 用户注册和登录功能: 用户可以注册新账号或使用现有账号登录系统。用户信息可以包…

软件自动化测试有哪些步骤?自动化测试需要找第三方检测机构吗?

您是否曾经因为软件出现问题而影响了工作进程或者个人生活的正常运转?那么,您是否了解软件自动化测试这一神奇的技术?在这篇文章中,我们将为您介绍软件自动化测试的定义和测试步骤,帮助您更好地了解自动化测试。 一、什么是软件自动化测试…

预约微信小程序源码系统制作搭建 适用于多场景 支持万能DIY功能

分享一个预约微信小程序源码系统,适用于多种预约场景,含完整代码包前端后端详细的搭建教程,支持万能DIY功能,让你轻松开发制作一个属于自己的想要的预约小程序。 一、预约微信小程序源码系统制作搭建的基本步骤和注意事项&#xf…

第5篇 vue的通信框架axios和ui框架-element-ui以及node.js

一 axios的使用 1.1 介绍以及作用 axios是独立于vue的一个项目,基于promise用于浏览器和node.js的http客户端。 在浏览器中可以帮助我们完成 ajax请求的发送在node.js中可以向远程接口发送请求 1.2 案例使用axios实现前后端数据交互 1.后端代码 2.前端代码 &…

拿走吧你,Fiddler模拟请求发送和修改响应数据

模拟伪造请求 方法一:打断点模拟HTTP请求 1、浏览器页面填好内容后(不要操作提交),打开fiddler,设置请求前断点,点击菜单fiddler,”Rules”\”Automatic Breakpoints”\”Before Requests” 2、在页面上点…

批量复制文件到指定文件夹,智能跳过相同文件名!

大家好!在进行文件管理的过程中,如果需要将大量文件快速复制到指定文件夹,并避免重复文件名带来的混乱,传统的手动操作可能会非常繁琐和耗时。为了让您能够高效地完成这一任务,我们为您提供了一种智能方法,…

SpringMVC之综合案例:参数传递,向页面传参,页面跳转

参数传递向页面传参页面跳转 1.参数传递 <?xml version"1.0" encoding"UTF-8"?><project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"htt…

长胜证券:三大拐点共振 看好智能驾驶新一轮行情

摘要 【长胜证券&#xff1a;三大拐点共振 看好智能驾驭新一轮行情】长胜证券研报指出&#xff0c;全球共振&#xff0c;国内智驾商场正迎来三大拐点&#xff1a;1&#xff09;技能上&#xff0c;“BEV Transformer数据闭环”新架构2023年开端上车&#xff0c;使得不依靠高精地…

HONEYWELL 0574-A-012 0574-A-0131 编码器模块

HONEYWELL 0574-A-012 0574-A-0131 编码器模块是一种用于测量旋转或线性位置的设备&#xff0c;通常用于自动化系统、机器控制和传感器应用。以下是HONEYWELL 0574-A-012 0574-A-0131 编码器模块可能具备的一些常见产品特点&#xff1a; 高精度测量&#xff1a;HONEYWELL 0574-…

Linux编译器-gcc/g++使用

文章目录 前言一、gcc/g编译器1、gcc/g安装2、gcc介绍3、gcc和g区别3.1 gcc不是只能编译.c源文件3.2 gcc和g编译文件3.3 gcc 不会定义 __cplusplus 宏&#xff0c;而 g 会3.5 演示 4、gcc/g编译过程 二、动态库和静态库1、动态库和静态库2、动态链接和静态链接2.1 动态链接2.2 …

STL stack 和 queue

文章目录 一、stack 类和 queue 类的模拟实现 stack 只允许在一端进行插入删除&#xff0c;是一个后进先出(LIFO)的结构&#xff0c;可以存储任意类型 queue 只允许在一端进行插入&#xff0c;另一端进行删除&#xff0c;是一个先进先出(FIFO)的结构&#xff0c;可以存储任意类…

元服务那些事儿 | 挥剑解决隐私声明,激发开发豪情

话说元服务初上的年间&#xff0c;鸿蒙江湖高手云起&#xff0c;都是一顿键盘手猛敲&#xff0c;元服务推陈出新&#xff0c;创意层出不穷&#xff0c;无不风生水起。 江湖规矩&#xff1a;每个元服务必须提供规范的隐私声明&#xff0c;否则提交元服务发布上架后&#xff0c;将…

Flask+pyecharts结合,html统计图呈现在前端页面

是我,是你 在网上看到这样一段话: “很多时候,你必须接受这世界上突如其来的失去。洒了的牛奶,遗失的钱包,走散的爱人,断掉的友情。当你做什么都于事无补的时候,唯一能做的,就是让自己努力好过一点。” <

TCP协议报文,核心特性可靠的原因,超时重传详细介绍

目录 一、TCP协议 二、TCP核心特性的保障 三、保留的六位标志位对于应答报文的作用 四、如何处理丢包——超时重传的原理 五、超时重传的时间 一、TCP协议 每一行是四个字节&#xff0c;前面的20个字节是固定的&#xff08;TCP最短长度&#xff0c;20字节&#xff0c;选项…

PAT (Basic Level) Practice 1045~1066

PTA Basic Level Practice 解题思路和代码&#xff0c;主要用的是 C。每22题一篇博客&#xff0c;可以按目录来进行寻找。 文章目录 1045 快速排序1046 划拳1047 编程团体赛1048 数字加密1049 数列的片段和1050 螺旋矩阵1051 复数乘法1052 卖个萌1053 住房空置率1054 求平均值1…

拷贝构造函数(深拷贝+浅拷贝)

目录 拷贝构造函数浅拷贝深拷贝 拷贝构造函数 拷贝构造函数&#xff1a; Myclass(const Myclass& myclass) {amyclass.a;bmyclass.b;cmyclass.c; }浅拷贝 浅拷贝的思路就是和默认的拷贝构造函数一样: 即将原对象的值直接赋值给新对象&#xff0c;这样做一般情况下是没什…

Java“牵手”淘宝商品详情数据,淘宝商品详情接口,淘宝API接口申请指南介绍

采集场景 在淘宝首页&#xff08;taobao.com&#xff09;输入关键词搜索&#xff0c;采集搜索后得到的商品列表页数据然后再点击进去即是商品详情页面数据。示例中关键词为【新款连衣裙】&#xff0c;可根据需求进行更换&#xff0c;同时支持自动批量输入多个关键词&#xff0…

解读|美创深度参与5项电信和互联网行业数据安全标准发布实施

《数据安全法》、《个人信息保护法》等法律法规的颁布实施&#xff0c;坚持安全和发展并重的原则&#xff0c;积极应对复杂严峻的安全风险与挑战&#xff0c;加速构建数据安全保障体系&#xff0c;成为电信和互联网行业重要工作。 “安全发展、标准先行”&#xff0c;标准化工作…

力扣 -- 646. 最长数对链

参考代码&#xff1a; class Solution { public:int findLongestChain(vector<vector<int>>& pairs) {int npairs.size();sort(pairs.begin(),pairs.end());vector<int> dp(n,1);int ret1;for(int i1;i<n;i){for(int j0;j<i;j){if(pairs[j][1]<…

声音生成结果比较方法综述——FAD、JSD、NDB多种衡量参数

文章目录 引言正文Generation quality 生成质量FAD距离FAD论文总结FAD代码下载和安装FAD的使用 通过分类准确率衡量实现代码 Generation Deversity生成多样性NDBJSDNDB和JSD的实现代码代码修改和结果理解 NDB和JSD应用到声音生成 总结 引言 之前自己并没有对这方面的内容进行研…