作者:i_dovelemon

来源:CSDN

日期:2014 / 9 / 28

主题:World Transform, View Transform , Projection Transform

引言

在3D图形学中。基本几何变换是一个很重要的操作。可以说,整个3D图形可以有效的显示,就是因为几个很重要的基础3D变换贡献的。在前面的文章中,向大家承诺了,要具体的解说在3D图形学中的三个主要的坐标变换。今天,就来像大家讲述。DirectX是怎样进行变换。

变换的目的

在我们解说详细的变换工作之前,我们须要知道,为什么须要进行变换?在3D图形学中。有非常多不同的坐标系统。比方说,模型坐标系统,世界坐标系统,视空间坐标系统。裁剪空间坐标系统等等。

为什么须要如此之多的坐标系统了?那是由于,不同的工作。在不同的坐标系统中进行,会给我们的工作带来非常多方便。

比方说,我们定义模型的时候,在模型的坐标空间中定义。而不是在世界空间坐标系里面定义。在模型坐标里面,我们可以仅仅关心模型的基本构造,它的外形等,不须要考虑它将来会放在场景中的哪个地方,面朝的方向是哪里。这样做就行大大的降低我们的工作。所以,一句话。进行坐标变换的目的就是为了简化工作,让我们的工作更加easy的完毕而已(尽管初学来说。好像没有简化我们的工作???)。

怎样变换

在前面我写过一篇文章,3D图形变换

在这篇文章中,讲述了怎样使用矩阵来进行同一个维度的坐标变换。

只是,这里解说的变换,仅仅是对那些仅仅须要进行平移,旋转的坐标系统来说的。对于要进行缩放的坐标变换,并非使用这样的方法。

有时候,我们须要依据变换的各种条件,来推导出终于变换的矩阵。这样的方法在那些变换后的矩阵非常难用其它的坐标系统来描写叙述它们的坐标基的时候使用。

DirectX变换流程

在DirectX中,有三种变换,是每个3D应用程序,每一帧都会使用到的变换。

它们各自是World Transform(世界坐标变换)。 View Transform(视空间坐标变换,又称相机坐标变换)和最麻烦的Perspective Projection Transform(透视投影变换。正交投影不是常常使用的变换。这里不介绍这样的)。以下,我们来一一的介绍这些变换的作用,以及怎样构建他们。

World Transform

世界坐标变换,顾名思义,是将模型坐标变换到世界空间中的变换。我们要知道。不论什么的变换都能够拆分称为平移,旋转。缩放,这三个大的变换方式。对于世界变换来说,我们全然能够依照3D图形变换中描写叙述的那样来进行坐标的平移和旋转变换。假设须要将模型进行缩放的话。我们在构建一个缩放的矩阵(这个矩阵在不论什么3D书籍上都有介绍。这里不再赘述),然后把这个缩放矩阵。与前面的矩阵结合起来,就能够完毕一个世界坐标变换的功能了。这是三个坐标变换里面最简单的变换。

View Transform

在3D空间中,我们会定义一个虚拟的实际上在3D空间中并没实用不论什么模型表示的相机。

我们可以在程序界面上看到的内容。都是通过相机的位置,和属性等等来构建的。

相机变换的作用是将原本相对于世界坐标中心的物体。变换到以相机为坐标中心的相机坐标空间中去。由于非常多的处理。在世界坐标空间中进行比較麻烦,如物体剔除,背面消除等等。这些工作假设可以在相机坐标空间中进行的话。那么就会轻松非常多。进行相机坐标变换我们须要定义相机的属性。

在DirectX中使用的是一种称之为UVN的相机模型。我们定义了相机的三个坐标基向量Right,Up和Look向量在世界坐标系中的表示,以及相机在世界坐标空间的位置。这样,我们仅仅要简单的使用3D图形变换里面介绍的方法。将这三个向量和位置属性构成例如以下的矩阵:

只是读者须要注意:这里的矩阵,是将相机空间里面的坐标变换到世界坐标空间中去的矩阵。

我们须要的是相反效果的矩阵,那么我们仅仅要求这个矩阵的逆矩阵就能够了。求逆的过程。在不论什么的线性代数书上都有,这里直接给出逆矩阵:

读者可以发现,这里的矩阵就是DirectX中使用的矩阵了。它的推导过程就是这么的简单。

Perspective Projection Transform

投影变换是这三个中最复杂的变换。在DirectX中,它的投影变换矩阵做了非常多的事情。这也就导致了它的变换矩阵十分的复杂。

所以,为了可以具体的掌握这个变换过程。我们先要弄清楚,进行这个变换的目的何在。

在DirectX中,投影变换的目的,是让3D的模型数据等变换称为2D的图像,从而显示在屏幕上,这个过程称之为投影。

投影意味着,我们须要对模型的X和Y坐标进行一些变换,从而可以依据它在相机空间中的上下左右顺序,正确的绘制在屏幕上。同一时候。由于是3D空间,模型的图像之间存在这遮罩的关系,远的物体会被近的物体遮挡住,我们相同还须要保存一些信息,从而可以推断哪一个在前面,哪一个在后面。这个值自然就是Z值了。Z值越小,说明了它越靠近相机。那么它后面的物体就不应该被绘制。由于都被它挡住了。

我们把DirectX的投影变换分为两个部分。一个部分是真真的投影过程。将相机坐标空间里面的模型的X和Y坐标正确的投影到我们选定的一个平面上来。由于要终于构成一个2D的图像,所以非常自然的想到将3D空间的物体坐标投影的一个平面上来。这个平面,在图形学上被称为投影面。

投影面的选择是随意的,仅仅要可以非常方便的进行处理就可以。在DirectX中选择近裁剪面为投影平面。可是,不同的程序,可能选择不同的尺寸作为程序的显示窗体。DirectX为了将这层关系忽略掉,它统一的将模型的X和Y坐标变换到[-1,1]这个范围来。这样。在最后仅仅要依据屏幕的宽和高分别乘以这个变换后的值,就行得到在屏幕上的坐标了(这个就是视口变化的原理)。同一时候。变换到[-1,1]这个范围。我们对模型进行3D裁剪时的操作也会变得十分的简单。

另外一个部分就是对遮罩关系的Z值进行保存的工作。我们须要将图像的先后关系保存起来。在上面解说过进行投影过后,他们的点都在近裁剪面上了,那么他们的Z值将都是近裁剪面的Z值,也就是说Z值的遮罩信息丢弃了。

所以,我们须要进行一些工作来将这个Z值信息保存下来。

好了,在知道了DirectX的透视投影变换做了哪些工作之后,我们来实际的进行矩阵的推导。

在推导之前,我们来看下相机所形成的视域体的结构:

这个结构体,是由近裁剪面(Front Clipping Plane)  和远裁剪面(Back Clipping Plane)截取相机的视野形成的椎体而得到的一个台体。

这个台体里面的模型就是将要变换到投影平面上的模型。

我们选取Y-Z平面来观察下投影过程。这个过程相同可以用在X-Z平面上:

从图中。我们能够看到在视域体里面的点P(px, py, pz)经过与相机的原点的连线与近平面的交点为P‘。

P’即为投影过程得到的投影点。

P‘的坐标非常easy计算出来。

仅仅要利用相似三角形的原理,我们非常easy的得出例如以下的结论:

P’.y / n = P.y / P.z  == P'.y = P.y * n / P.z              (1)

同理。我们对X-Z平面进行相同的操作。能够得到例如以下的结论:

P‘.x / n = P.x / P.z == P'.x = P.x * n / P.z                 (2)

即投影点的坐标为:

P' = (P.x * n / P.z ,  P.y * n / P.z, P.z)                       (3)

我们在上面讨论过。为了可以简单的进行3D裁剪,忽略尺寸的大小。我们须要将投影坐标变换到[-1,1]这个范围来。

也就是说,我们还须要另外的处理,如果这个过程得到的点是P’‘。

那么就有例如以下的结论:

P''.x / P'.x = 2 / W ,  P’‘.y / P'.y = 2 / H                   (4)

这个结论是由于变换后的X,Y的范围和原来的X,Y范围是线性变化的。所以我们能够利用同比的概念来进行比較,从而得到了在[-1,1]范围里面的X,Y坐标分别为:

P’‘.x = 2 * P'x / W ,   P''.y = 2 * P'.y / H                    (5)  (当中W是投影平面的宽度,H是投影平面的高度)

将公式(3)中的数据带入公式(5),得到例如以下的结论:

P''.x = 2 * P.x * n / (P.z * W) , P''.y = 2 * P.y * n / (P.z * H)   (6)

因为H,W各自是投影平面的高度和宽度(即终于屏幕的高度和宽度),所以得到例如以下的结论:

P’’.y = P.y / P.z * cot(a)                                               (7)  cot(a) = 2 * n / H , a为相机的上下视野角度的一半

在DirectX中。还传入了一个宽高比Aspect = W / H, 即得到 :

W = H * Aspect                                                          (8)

将公式(8)带入公式(6),得到P‘’.x:

P''.x = 2 * P.x * n / (P.z * H * Aspect) = (P.x / P.z ) * (cot(a) / Aspect)   (9)

好了,第一部分的推导已经完成了,我们已经知道了怎样将模型的X。Y坐标变换到[-1,1]空间中去了。

剩下的部分就是怎样保存Z值了。

由于经过上面的变换之后,模型坐标的Z值都变成了近裁剪面的值n了。而我们须要的是Z值的先后关系。

我们发现这个关系就保存在原来未进行上面的变换的相机空间坐标的Z值中。也就是说,经过相机变换后的Z坐标已经可以非常好的推断哪些点是在哪些点的后面了。

我们可以将这个值保存下来。可是DirectX并没有这么做。那是由于。这个Z值可能非常大,也可能非常小。这就会导致Z缓存在设计上的困难。所以,使用对X,Y坐标相同的方法,不同的是这次变换到[0,1]范围来。

读者看到这里可能想,那么就简单的使用例如以下的公式:

P‘’.z  = (P''.z  - n )  /  (f - n)                                              (10)

的确,上面的公式看起来似乎是正确的。和上面相同的使用同比的概念来进行。可是,假设读者这么觉得就大错特错了。

同比概念可以得出这种结论的前提是Z值的变换是线性的。我们这里经过投影变换后的数据。将来都要在光栅化函数里面。通过在屏幕坐标空间线性插值来进行像素颜色的计算,填充,从而实现光栅化。对于X,Y来说。他们在屏幕空间依旧是线性变换的。

可是对于Z坐标来说。我们并不希望它在屏幕空间中是线性变换的。想象一下,我们看到的一个物体,都是靠近我们的边缘比較清晰。远的边缘比較模糊。这显示在数据上就是近的边缘,它的纹理坐标的变化比較细微,比較缓慢,而远的变化比較大,比較迅速。

这个概念,在图形学上称之为透视修正,是为了可以营造出真真意义上的透视感觉而提出的概念。

所以,DirectX也将这个概念增加进去了。尽管Z值并非线性变换的。可是实现上面的透视效果的时候,1/z的值是呈线性变化的。

也就是说。我们在进行光栅化处理的时候,可以对纹理坐标进行线性插值了。

仅仅要插值计算的时候使用的是1/z来进行的就可以。

这样。原本不是线性变换的纹理坐标,我们可以通过线性插值的方法来进行计算了。

讲到这里,大家可能有点晕晕乎乎的了。

总之中的一个句话,为了可以让显示的图像效果上更加的真实,我们须要将1/z的信息保存起来。而不是Z的信息。

好了。也就是说,我们仅仅要将原来的z值变成1/z,然后进行变换,到[0,1]空间中就可以。

实际上,在这里。我们直接保存1/z作为新的Z值也就能够了,可是这种话。我们在进行Z-Test的时候。就不是直观上的哪一个Z值小就靠前。

读者想象看,假设Z值小的话。因为Z值是1/z'计算出来的,那么原来相机空间中的Z‘值就大的值,也就是说它反而是靠后的。

DirectX在这里有做了一层工作。它希望保留这样的直观的印象,当Z值小的时候,就表示该像素是靠前的。Z值大的时候。就表示该像素靠后。

所以。DirectX在1/z的基础上,在进行了一次线性变换。因为线性变换并不改变值的线性变化属性,所以能够再次使用。尽管会改变值。可是我们并非使用值来获取像素的精确位置,而是推断他们的先后关系,仅仅要这种关系不变,它的值是多少。一点关系都没有。

线性变化关系即是正比关系,我们能够用y = ax + b这种一次函数来表示。

所以,DirectX希望满足例如以下的条件:

Z' = a * 1 / Z + b

当Z = n的时候,也就是近期的时候。Z’值最小。为0。

当Z = f的时候,也就是最远的时候,Z‘值最大,为1;

所以。得出例如以下的公式:

0 = a * 1 / n + b

1 =  a * 1/ f +  b

解这个一元一次方程组,得到例如以下的解:

a = fn / (n - f) , b = f / (f - n)                       (11)

所以,综合(7)(9)(11),我们得到:

X' = X / Z * (cot(a) / Aspect)

Y' = Y / Z * cot(a)                                         (12)

Z ' =( fn/(n - f) ) * Z + f / (f - n)

W’ = 1

上面的变换最后用一个矩阵表示为:

[X, Y , Z , W]  * M = [ X', Y', Z' , W']

得这个矩阵为:

这个矩阵。和DirectX SDK中关于函数D3DXMatrixPerspectiveLH中使用的矩阵一致。它的来源就是这种。

总结

矩阵变换是3D图形学必不可少的一个工具,对于有志于深入学习的同学来说,不能过分依赖于API。而须要自己设计来完毕这些功能。仅仅有掌握全部这些细节,才可以建成更加高级的引擎。

參考资料:http://www.cnblogs.com/graphics/archive/2012/07/25/2582119.html

DirectX SDK 文档中Transform一节

基本3D变换之World Transform, View Transform and Projection Transform的更多相关文章

  1. (一一九)通过CALayer实现阴影、圆角、边框和3D变换

    在每个View上都有一个CALayer作为父图层,View的内容作为子层显示,通过layer的contents属性决定了要显示的内容,通过修改过layer的一些属性可以实现一些华丽的效果. [阴影和圆 ...

  2. 论文阅读 Characterization of Multiple 3D LiDARs for Localization and Mapping using Normal Distributions Transform

    Abstract 在这个文章里, 我们细致的比较了10种不同的3D LiDAR传感器, 用了一般的 Normal Distributions Transform (NDT) 算法. 我们按以下几个任务 ...

  3. CSS3之3d变换与关键帧

    3d变换是在transform基础上实现的 transform-style:preserve-3d; 建立3d空间 perspective:; 景深(设置用户看的距离) perspective-ori ...

  4. CSS3之3D变换实例详解

    CSS3的3D效果很赞,本文实现简单的两种3D翻转效果.首先看效果和源代码,文末是文绉绉的总结部分^_^ 以下CSS代码为了简洁没有添加前缀,请视情况自行添加(自动化时代推荐使用其他的一些方法,如gu ...

  5. CSS3 3D变换

    可以这么说,3D变换是基于三维坐标系的.以下是“盗用”的图 CSS3中的3D变换主要包括以下几个功能函数: 3D位移:包括translateZ()和translate3d(): 3D旋转:包括rota ...

  6. WPF 3D 小小小小引擎 - ·WPF 3D变换应用

    原文:WPF 3D 小小小小引擎 - ·WPF 3D变换应用 WPF可以提供的3D模型使我们可以轻松地创建3D实体,虽然目前来看还很有一些性能上的问题,不过对于一些简单的3D应用应该是可取的,毕竟其开 ...

  7. css3 3D变换和动画

    3D变换和动画 建立3D空间,transform-style: preserve-3d perspective: 100px; 景深 perspective-origin:center center ...

  8. Unity 3D Framework Designing(3)——构建View和ViewModel的生命周期

    > 对于一个View而言,本质上是一个MonoBehaviour.它本身就具备生命周期这个概念,比如,Awake,Start,Update,OnDestory等.这些是非常好的方法,可以让开发者 ...

  9. css之3D变换

    3D变换的x,y,z轴是分别效果是: x轴旋转的话,就是头和脚进行转动 y轴旋转的话,就是左右手进行转动 z轴旋转的话,就是整个身体平铺在旋转. 上面是针对旋转的意思去,但是对于其他的类似一样,就是这 ...

随机推荐

  1. python描述符和属性查找

    python描述符 定义 一般说来,描述符是一种访问对象属性时候的绑定行为,如果这个对象属性定义了__get__(),__set__(), and __delete__()一种或者几种,那么就称之为描 ...

  2. Hadoop RPC框架

    1.RPC框架概述 1.1 RPC(Remote Procedure Call Protocol)--远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不须要了解底层网络技术的协议. R ...

  3. 识别CentOS和Ubuntu的系统版本

    识别CentOS和Ubuntu的系统版本1.用 lsb-release#!/bin/bashInstall_LSB(){        if [ "$PM" = "yum ...

  4. Android Design Support Library初探,NavigationView实践

    前言 在前几天的IO大会上,Google带来了Android M,同时还有Android支持库的新一轮更新,其中更是增加一个全新的支持库Android Design Support Library,包 ...

  5. 8. java操作mongodb——查询数据

    转自:https://www.cnblogs.com/adjk/p/6430074.html 通过find方法查询集合中的文档信息 ---------------------------------- ...

  6. JAVA线程队列BlockingQueue

    JAVA线程队列BlockingQueue 介绍 BlockingQueue阻塞队列,顾名思义,首先它是一个队列,通过一个共享的队列,可以使得数据由队列的一端输入,从另外一端输出. 常用的队列主要有以 ...

  7. SQL Server 2008 R2 超详细安装图文教程及问题解决(锐姿公司安装)

    问题点: 1.为了sqlserver与mysql 的安全,建议数据库低权限运行.禁止远程访问 1433与 3306端口等. 2.安装提示.net 3.5没有安装 ,在server2012的添加 3. ...

  8. React开发实时聊天招聘工具 -第二章

    2-1 介绍React开发环境 npm install -g create-react-app xxx npm run eject   来配置webpack 2-2 ES6常用语法 其他 还有一些特性 ...

  9. ES6学习笔记(二)变量的解构与赋值

    1.数组的解构赋值 1.1基本用法 ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring). 以前,为变量赋值,只能直接指定值. let a = 1 ...

  10. codeforces1114D. Flood Fill(区间Dp)

    传送门: 解题思路: 区间Dp,发现某一个区间修改后区间颜色一定为左边或右边的颜色. 那么只需要设方程$f_(l,r,0/1)$表示区间$[l,r]$染成左/右颜色的最小代价 转移就是枚举左右颜色就好 ...