哪些网站做夜场女孩多,包装设计网站有哪些,成都网页设计的网站建设,wifi网络管理推荐#xff1a;用 NSDT编辑器 快速搭建可编程3D场景 Matplotlib 有一个非常漂亮的 3D 界面#xff0c;具有许多功能#xff08;和一些限制#xff09;#xff0c;在用户中非常受欢迎。 然而#xff0c;对于某些用户#xff08;或者可能对于大多数用户#xff09;来说用 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)-1V 现在是一组顶点如果你愿意也可以是 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],aspect1, frameonFalse)
collection PolyCollection(T, closedTrue, linewidth0.1,facecolorNone, edgecolorblack)
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), dtypenp.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)以便每个顶点始终具有 w1。
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]], dtypefloat)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]], dtypefloat)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]], dtypefloat)现在我们将根据模型局部变换、视图全局变换和投影来分解要应用的变换以便我们可以计算一个可以同时完成所有操作的全局 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(axis1)
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), dtypenp.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]], dtypefloat)
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]], dtypefloat)
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]], dtypefloat)
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(axis1)
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], aspect1, frameonFalse)
collection PolyCollection(T, closedTrue, linewidth0.1, facecolorC, edgecolorblack)
ax.add_collection(collection)
plt.show()原文链接Matplotlib渲染3D模型 — BimAnt