画布和相机
大约 15 年前,英国艺术家 David Hockney 开始发展有关原来是颇有争议的文艺复兴艺术的理论。列奥纳多 · 范 · 艾克和 Velázquez,等老主人是如何能够呈现在画布上的视觉世界的角度和底纹的惊人准确性与 Hockney 希奇。他成为了相信这个精度是仅可能的与由透镜和反射镜,暗箱和摄影机亮叶等制成的光学工具的帮助。这些设备拼合 3D 场景,并使其更接近这位艺术家的再生产。肯尼发表他的论文在华丽和有说服力的书,题为,"秘密知识:重新发现老主人的丢失的技术"(Viking,2001年)。
最近,发明人蒂姆 · 詹尼森 (NewTek,是开发视频烤箱和光波 3D 的公司的创始人),成为了痴迷于使用光学工具重新创建 Jan Vermeer 350 岁绘画"音乐的教训"。他建造一个类似于原始的房间、 装饰与家具和道具再生产、 招聘现场模型 (包括他的女儿),和他自己发明的一个简单的光学工具用于绘制现场。下巴-滴结果记载中的令人着迷的纪录片,"蒂姆的维梅尔"。
为什么是必要使用光学设备 — — 或在这些日子里,一个简单的摄像头 — — 准确地捕捉 3D 场景 2D 表面上的吗?我们认为我们在现实世界中看到的大部分被建造在大脑中从相对较稀疏的视觉信息。我们认为我们看到整个现实世界场景中一个大全景,但在任何时间点上,我们真的很专注于只有它的一个小细节。它几乎是不可能,这些单独的视觉片段拼凑成复合画类似于真实世界。当画布上辅以一种机制很像一个摄像头是容易得多 — — 本身模仿光学的人的眼睛,但没有眼的不足之处。
一个类似的过程发生在计算机图形学:当呈现 2D 图形,隐喻的画布上工作只是正常。画布是一个绘图表面上,并以其最明显的形式,它直接对应的行和列组成视频显示的像素。
但在移动时从 2D 到 3D,隐喻画布需要辅以隐喻的相机。像真实世界摄像机,这隐喻相机捕捉 3D 空间中的对象和将它们展平到一个曲面,然后可以转移到画布上。
摄像机特性
可以有几个特点,轻松地转换为所需的 3D 图形的数学描述描述真实世界摄像机。相机有空间,可以表示为一个 3D 点中的特定位置。
相机也被为了在特定的方向:3D 矢量。您可以通过获取对象位置的摄像机直接对准,并减去摄像机的位置来计算此向量。
如果你把相机固定在特定的位置,并指出在一个特定的方向,你仍然有自由倾斜相机一边到另一边,并实际上将它旋转 360 度。这意味着另一个向量是需要以指示"上"与相机。此向量是相机方向垂直的。
建立后如何在空间中定位相机,你能摆弄照相机上的旋钮。
今天的照相机往往能够缩放:您可以调整相机镜头从广角涵盖到长焦的视图,为结束了缩小了大场面。区别基于捕捉的图像,在飞机和协调中心,如中所示是光线穿过,单点之间的距离图 1。焦平面与协调中心之间的距离叫做焦距长度。
图 1 的焦平面、 联络中心和焦距长度
焦平面和焦距的大小意味着角的视野所产生的协调中心。长焦视图 (更加正确地称为"长焦点") 是通常与视野小于 35 度,虽然广角是大于 65 度,与更多普通字段的视图之间。
计算机图形添加摄像机不可能在现实生活中的一个特征:在 3D 图形编程你常常有一个实现的角度或正交投影的摄像机之间的选择。视角就像真实生活:对象从相机似乎较小,因为字段的视图包含进一步协调中心从更大的范围。
与正交投影,那不是这种情况。一切都是在该对象的实际规模大小,其与相机的距离大小中呈现。数学上,这是较简单的两种预测,,是最合适的技术和建筑图纸。
照相机变换
在 3D 图形编程中,摄像机是数学构造。相机组成的类似操作对象在 3D 空间中的那两个矩阵转换。两个照相机变换称为视图和投影。
视图矩阵有效职位和安置照相机在 3D 空间 ; 投影矩阵描述什么摄像头"看见"和它如何看待它。毕竟应用转换这些相机其他矩阵转换是用来在 3D 空间中,对象的位置通常称为"世界空间"。在所有其他转换之后首先应用视图变换,和投影变换的最后。
DirectX 编程中 — — 是否 Direct3D 或 3D 概念在 Direct2D 中的探索 — — 这是最简单的方法构建这些矩阵转换使用 DirectX 数学库,DirectX 命名空间中的函数,以 XM 字母开头,并使用 XMVECTOR 和 XMMATRIX 的数据类型的集合。这两种数据类型是 CPU 寄存器的代理,因此这些函数往往是相当迅速。
四个功能可以用来计算视图矩阵:
- XMMatrixLookAtRH (EyePosition,FocusPosition,UpDirection)
- XMMatrixLookAtLH (EyePosition,FocusPosition,UpDirection)
- XMMatrixLookToRH (EyePosition,EyeDirection,UpDirection)
- XMMatrixLookToLH (EyePosition,EyeDirection,UpDirection)
函数的参数包括"眼"一词,但文档使用 word"相机"。
缩写,LH 和 RH 立场为左手和右手。我会假设这些例子左手坐标系统。如果你的 X 轴和你中指的正面 Y 方向的积极方向点的你的左手食指,拇指将指向正 Z。如果正 X 轴转右,并积极 Y 向上 (在 3D 编程中的共同方向) 然后 — — Z 轴来的屏幕。
所有四个函数需要三个类型的对象的 XMVECTOR 并返回一个 XMMATRIX 类型的对象。在所有四个函数中,这些参数的两个显示摄像机的位置 (标记为 EyePosition 的函数模板中) 和 UpDirection。看功能包括一个 FocusPosition 参数 — — 一个摄像机正对着的位置 — — 虽然 LookTo 函数有 EyeDirection,是一个向量。它是只是一个简单的计算,要从一种形式转换到另。
例如,假设您想要放置相机,在点 (0,0,– 100),用相机顶部的向上指指点向原点方向 (和,因此,在积极的 Z 轴方向)。你可以调用任一
XMMATRIX view =
XMMatrixLookAtLH(XMVectorSet(0, 0, -100, 0),
XMVectorSet(0, 0, 0, 0),
XMVectorSet(0, 1, 0, 0));
or
XMMATRIX view =
XMMatrixLookToLH(XMVectorSet(0, 0, -100, 0),
XMVectorSet(0, 0, 1, 0),
XMVectorSet(0, 1, 0, 0));
在任一情况下,该函数创建此视图矩阵:
此特定的视图矩阵只是转移中的正 Z 轴方向的整个 3D 场景 100 单位。 许多视图矩阵还将涉及各种类型,但无缩放的旋转。 视图变换应用到 3D 场景后,可以假定 (与相机顶部的指出的正面 Y 方向) 在原点定位相机,并指出,在积极的 Z 方向 (为左手的系统) 或 (右侧) — — Z 方向。 这种定位允许投影变换要简单得多会以其它方式比。
投影公约
在此列以前进军 3D 内 Direct2D 编程,我过的对象从 3D 空间转换到 2D 视频显示简单地通过忽略的 Z 坐标。
它是从 3D 转换为 2D 以更专业的方式在标准摄像机投影矩阵中使用封装的约定的时间。 2D 到 3D 的标准转换实际上发生在两个阶段:第一次从三维坐标到归一化的三维坐标,然后到二维坐标。 由程序指定的投影变换控制第一个转换。 在 Direct3D 编程中,第二个转换是通常会自动执行呈现系统。 使用 Direct2D 来显示 3D 图形的程序,必须执行此第二个转换本身。
投影变换的目的是部分实现正常化场景中的所有三维坐标。 这种正常化定义哪些对象中是可见的最终呈现和哪些被排除。 之后这种规范化,最终渲染的场景涵盖范围从 – 1 的左侧和右侧 1 的坐标,Y 坐标从 – 1 底部到顶部,和 Z 坐标范围从 0 (靠近相机) 至 (距离最远相机) 1 1 X。 Z 坐标也用于确定哪些对象掩盖与查看器中的其他对象。
一切都不在这一空间被丢弃,然后的归一化坐标 X 和 Y 坐标映射到的宽度和高度的显示图面,而忽略的 Z 坐标。
正常化的 Z 坐标,总是计算投影矩阵的函数需要参数类型的浮命名 NearZ 和 FarZ 表示沿 Z 轴的摄像机的距离。 这些两个距离被分别转换为规范化 Z 坐标的 0 和 1。
这是似乎违反常理的因为它意味着有一个区域是太接近的 3D 空间的照相机是可见的,太远的另一个领域。 但由于实际原因有必要限制以这种方式的深度。 相机背后的一切必须予以消除,例如,与物体过于接近相机会掩盖其他一切。 如果 Z 坐标出到无穷大被允许,将征税浮点数的决议,确定哪些对象重叠他人时。
由于相机视图矩阵占可能平移和旋转的相机,投影矩阵总是基于摄像机位于原点和沿 Z 轴指向。 我会用左手坐标为这些示例中,这意味着相机指出积极的 Z 轴的方向。 左手坐标时处理投影变换因为 NearZ 和 FarZ 都是等于沿正 Z 轴,而不是 — — Z 轴坐标变得简单一点。
DirectX 数学库定义计算的投影矩阵 10 职能 — — 四项正投影及六个角度预测,左手坐标的一半,一半的右侧。
在 XMMatrixOrthographicRH 和 LH 功能,可以指定 ViewWidth 和 ViewHeight 以及 NearZ 和 FarZ。 图 2 是从积极的 Y 轴上的位置往下看 3D 坐标系上的视图。 此图显示了这些参数如何定义在左手坐标系中可查看的眼球在原点的长方体。
图 2 顶视图的正交变换
通常,ViewWidth 到 ViewHeight 的比例是显示的相同,用于呈现长宽比。 投影变换缩放一切从 — — ViewWidth / 2 到 ViewWidth / 2 系列 – 1 到 1,和以后那些归一化的坐标进行缩放显示表面呈现的半个像素宽度。 计算是类似的 ViewHeight。
这里对 XMMatrixOrthographicLH 与 ViewWidth 和 ViewHeight 的调用设置为 40 和 20 和 NearZ 和 FarZ 设置为 50 和 100,相匹配假设每 10 个单位的刻度线的关系图:
XMMATRIX orthographic =
XMMatrixOrthographicLH(40, 20, 50, 100);
这将导致下面的矩阵:
变换公式是:
您可以看到-20 ℃ 和 20 的值将转换为 – 1 和 1,分别,那 x 和-10 和 10,y 值将转换为 – 1 和 1,分别。 50 一个 Z 值转换为 0,和 100 一个 Z 值转换为 1。
两个额外的正射投影函数包含单词偏心,让您指定左、 右、 顶部,和底部的坐标,而不是宽度和高度。
XMMatrixPerspectiveRH 和 LH 函数具有相同的参数作为 XMMatrixOrthograhicRH 和 LH,但定义四个边的可视范围 (像一个金字塔顶尖切断) 中所示图 3。
图 3 顶视图的透视变换
变换函数中的 ViewWidth 和 ViewHeight 的参数控制的宽度和高度在 NearZ,锥但的宽度和高度在 FarZ 是按比例更大基于 FarZ 到 NearZ 的比率。 此图还演示如何更多的 x 和 y 坐标从相机将映射到相同的空间 (和,因此,使更小) 作为 x 和 y 坐标接近相机。
这里是以相同的参数,使用 XMMatrixOrthographicLH 作为对 XMMatrixPerspectiveLH 的调用:
XMMATRIX perspective =
XMMatrixPerspectiveLH(40, 20, 50, 100);
此调用创建的矩阵是:
第四列指示非仿射变换的通知 ! 在仿射变换的 m14、 m24 和 m34 值均为 0,和 m44 是 1。 在这里,m34 是 1 和 m44 是 0。
这是在 3D 编程环境中如何实现的角度,那么让我们看看详细的变换乘法:
下面的变换公式中的矩阵乘法运算结果:
请注意 w´ 的值。 在此列的 4 月分期付款的如上所述,3D 转换中的 w 坐标的使用表面上可容纳的翻译,但也带来了在数学中的同质 (或投影) 坐标。 仿射变换总是发生在 3D 4 D 空间哪里 w 等于 1,但此非仿射变换已移到坐标之外,3D 空间的子集。 坐标必须搬回入那 3D 空间除以它们所有 w´。 纳入这一必要调整的变换公式改为如下:
Z 等于 NearZ 或 50,变换公式时正交投影相同:
从-20 到 20 x 的值被转换为 x´ 值从-1 到 1,例如。
对于其他的 z 值,变换公式是不同的和当 z 等于 FarZ 或 100,他们看起来像这样:
在这个距离相机,从 x 的值从-40 至 40 变成 x´ 值从-1 到 1。 较大范围的 x 和 y 值在联邦共和国武装部队总指挥在 NearZ 占据同一视觉领域作为一个规模较小的范围。
因为与正射投影功能,两个附加功能中的函数名称的单词偏心和让你集左、 右、 上、 和底部坐标而不是宽度和高度。
XMMatrixPerspectiveFovRH 和 LH 功能让您指定角的视野 (FOV) 而不是宽度和高度。 此字段的视图有可能沿 X 轴和 Y 轴不同。 您需要指定它沿 Y 轴,并且还提供宽度与高度的比例。
若要创建一个透视矩阵一致与前面的示例中,可以用 atan2 函数,等于半高度在 NearZ,一个 y 参数与 x 参数等于 NearZ,然后翻了一番,结果计算字段的视图:
float angleY = 2 * atan2(10.0f, 50.0f);
第二个参数是宽度的比率和高度或在此示例 2:
XMMATRIX perspective =
XMMatrixPerspectiveFovLH(angleY, 2, 50, 100);
此调用的结果只是使用 XMMatrixPerspectiveLH 创建的相同的角度看矩阵中。
圆的文本
在 2 月份的一部分的此列中,我演示了如何创建一个 3D 的文本,圈子和其旋转进行动画处理。 然而,我 Direct2D 几何用于这项工作,遇到性能很差。 3 月列中,我演示了如何 tessellate 成三角形的集合,其中似乎有性能大大优于几何形状的文本。 在 5 月的专栏中,我演示了如何使用三角形创建和渲染 3D 对象。
它是时间把这些不同的技术。 为此列的可下载的源代码包含一个叫做 TessellatedText3D 的 Windows 8 Direct2D 项目。 此程序我定义了一个 3D 的三角形,使用 XMFLOAT3 对象:
struct Triangle3D
{
DirectX::XMFLOAT3 point1;
DirectX::XMFLOAT3 point2;
DirectX::XMFLOAT3 point3;
};
TessellatedText3DRenderer 类的构造函数在字体文件中加载、 创建字体并生成 ID2D1Path从使用任意字体大小为 100 的 GetGlyphRunOutline 方法的几何对象。 这个几何然后转换成三角形与自定义镶嵌接收器使用 Tessellate 方法的集合。 用特定的字体,字体大小和标志符号索引指定了,Tessellate 生成 1,741 的三角形。
2D 三角形,然后转换成 3D 三角形文字环绕一圈。 基于这种任意字体大小的 100,这个圈子碰巧有一半径约 200 (存储在 m_source半径),和圈子的 3D 起源上居中显示。
TessellatedText3DRenderer 类的更新方法,在 XMMatrixRotationY 和 XMMatrixRotationX 函数提供两个变换进行动画处理的文本围绕 X 轴和 Y 轴的旋转。 这些存储在 XMMATRIX 对象分别命名为 rotateMatrix 和 tiltMatrix。
Update 方法比继续在所示的代码图 4。 此代码计算视图矩阵和投影矩阵。 视图矩阵基于圆半径,因此相机是圆形文本外部的半径长度,但指出朝中心 — — Z 轴上设置摄像机的位置。
图 4 视图和投影矩阵在 TessellatedText3D
void TessellatedText3DRenderer::Update(DX::StepTimer const& timer)
{
...
// Calculate camera view matrix
XMVECTOR eyePosition = XMVectorSet(0, 0, -2 * m_sourceRadius, 0);
XMVECTOR focusPosition = XMVectorSet(0, 0, 0, 0);
XMVECTOR upDirection = XMVectorSet(0, 1, 0, 0);
XMMATRIX viewMatrix = XMMatrixLookAtLH(eyePosition,
focusPosition,
upDirection);
// Calculate camera projection matrix
float width = 1.5f * m_sourceRadius;
float nearZ = 1 * m_sourceRadius;
float farZ = nearZ + 2 * m_sourceRadius;
XMMATRIX projMatrix = XMMatrixPerspectiveLH(width,
width,
nearZ,
farZ);
// Calculate composite matrix
XMMATRIX matrix = rotateMatrix * tiltMatrix *
viewMatrix * projMatrix;
// Apply composite transform to all 3D triangles
XMVector3TransformCoordStream(
(XMFLOAT3 *) m_dstTriangles3D.data(),
sizeof(XMFLOAT3),
(XMFLOAT3 *) m_srcTriangles3D.data(),
sizeof(XMFLOAT3),
3 * m_srcTriangles3D.size(),
matrix);
...
}
带参数也基于圆半径,代码继续通过计算一个投影矩阵,然后将所有的矩阵相乘在一起。 XMVector3TransformCoordStream 使用并行处理,以将此转换应用于一个 XMFLOAT3 对象 (实际上的 Triangle3D 对象的数组) 的数组,自动执行该司的 w*'*。
更新方法继续超越所示图 4 通过转换那些转换为 2D 所用的基础的视频显示,一半宽度的缩放因子的三维坐标和忽略 z 坐标。 更新方法也使用 3D 的每个三角形的顶点计算一个跨产品,是正常的表面 — — 一个向量垂直于表面。 然后,2D 三角形分为两个组的基础的 z 坐标的曲面。 如果 z 坐标为负值,三角形朝向查看器,和如果是正面,三角正面临消失。 Update 方法得出结论通过创建两个基于 2D 三角形的两个组的 ID2D1Mesh 对象。
Render 方法然后显示用灰色画笔的后方三角形网格和前台网格用蓝色画笔。 结果显示在图 5。
图 5 TessellatedText3D 显示
正如您所看到的正面三角形更接近到查看器是远远大于背面三角形更远。 这个程序有没有一个具有呈现几何形状时遇到的性能问题。
与光的底纹?
在此列 5 月分期付款,我光考虑到了显示 3D 对象时。 程序假定光来自一个特定的方向和它计算不同底纹边的基于角度的光线照射到每个表面的固体数字。
与此程序是相似的事可能吗?
理论上能。 但我的实验揭示了一些严重的性能问题。 在 Update 方法中创建两个 ID2D1Mesh 对象,而是实施底纹的文本的程序需要呈现每个三角形用不同的颜色,而这需要 1,741 不同的 ID2D1Mesh 对象,在更新的每个调用中重新创建和 1,741 对应的 ID2D1SolidColorBrush 对象。 这减慢到每秒大约一帧动画。
更糟的是,视觉效果并不令人满意。 每个三角形获取不同的离散纯色基于其角度的光源,但这些离散颜色之间的界限变得可见 ! 三角形用于呈现 3D 对象更加适当地必须以与三个顶点之间的渐变着色,但这种渐变不支持由从 ID2D1Brush 派生的接口。
这意味着我必须甚至深挖 Direct2D 并获得相同的底纹工具,Direct3D 程序员使用。
Charles Petzold 是 MSDN 杂志和作者的"编程窗口,第 6 版"长期贡献 (微软出版社,2013年),一本关于编写应用程序的 Windows 8 书。 他的网站是 charlespetzold.com。
衷心感谢以下 Microsoft 技术专家对本文的审阅:道格 · 埃里克森
道格 · 埃里克森是铅编程作家为微软的 OSG 开发人员文档团队。 当不编写和开发 DirectX 图形代码和内容,他正在读像查尔斯的文章,因为那是他有多么喜欢花他的空闲时间。 嗯,和骑摩托车。