Unity3d渲染系列教程一距阵(译)


目标:

创建一个立方体网格

代码支持立方体的旋转,缩放,位移,

学会使用变换矩阵

创建一个简单的摄像机投影

 

这个是渲染基础教程的第一部分。主要讲解了变换矩阵。为了了解变换矩阵,首先我们需要知道什么是网格,了解网格是如何工作的。最后本系列将探讨这些网格如何最终在显示器上以像素显示的。

Manipulating points in space.

1、       视空间

通过前面部分的学习你现在已经知道了什么是网格并且如何如何将它们放置在场景中。但是这个坐标位置实际上是如何工作的? Shader是如何知道要把网格绘制在什么地方?当然unitytransform 组件和内置shaders已经帮我们处理好了这个工作。但是懂得底层是如何工作对于你想让unity3d更好地为你服务是至关重要的。要完全理解这一过程。最好的方式是我们自己去实现这一过程。

网格的位移,旋转跟缩放是操作网格的顶点坐标来完成的。这个过程是对空间的改变,所以为了直观起见,我们需要先使这个改变可视化。我们可以通过创建一个3d网格。这个网格上的点可以是任意的prefab

using UnityEngine;
 
publicclassTransformationGrid : MonoBehaviour {
 
        publicTransform prefab;
 
        publicint gridResolution = 10;
 
        Transform[] grid;
 
        voidAwake () {
               grid = newTransform[gridResolution * gridResolution * gridResolution];
               for (int i = 0, z = 0; z < gridResolution; z++) {
                       for (int y = 0; y < gridResolution; y++) {
                               for (int x = 0; x < gridResolution; x++, i++) {
                                      grid[i] = CreateGridPoint(x, y, z);
                               }
                       }
               }
        }
}

我们通过实例化prefab来创建我们需要的点。设置它的坐标以及不同的颜色来加以区分。

        Transform CreateGridPoint (int x, int y, int z) {
               Transform point = Instantiate<Transform>(prefab);
               point.localPosition = GetCoordinates(x, y, z);
               point.GetComponent<MeshRenderer>().material.color = newColor(
                       (float)x / gridResolution,
                       (float)y / gridResolution,
                       (float)z / gridResolution
               );
               return point;
        }

立方体可以让我们的演示最直观。所以我们就把网格做成立方体的形状。我们把立方体的中心点放置在原点。这样我们的旋转缩放操作就能以立方体的中心点作为基准

        Vector3 GetCoordinates (int x, int y, int z) {
               returnnewVector3(
                       x - (gridResolution - 1) * 0.5f,
                       y - (gridResolution - 1) * 0.5f,
                       z - (gridResolution - 1) * 0.5f
               );
        }

我将使用默认的cube作为我们的prefab,并将它缩小一半使得两个cube之间能够有足够的空隙。

Small cube prefab.

 

在场景中创建一个网格对象。将我们的脚本挂上去。并给prefab赋值。然后运行游戏。你就能看到我们创建的立方体了。

 Transformation grid.

 

变换

理想情况下。我们应该可以对我们的gird应用任意数量的变换。而且我们可以想到很多不同种类的变换。不过目前就先让我们把变换限制在位移,旋转跟缩放。

如果我们针对每种变换创建单独的组件。我们就可以随心所欲地在gird上添加任意数量跟顺序的组件。而且所有这些类型的组件虽然会有不同,但都应该有一个方法让我们将变换应用到顶点上。

所以先为所有的变换组件创建一个基类。然后我们的组件就可以继承他。我们创建的应该是一个虚拟类,并且拥有一个Apply虚方法以使我们的变换类实现这个方法来完成他们各自的工作。

 
using UnityEngine;
 
publicabstractclassTransformation : MonoBehaviour {
 
        publicabstractVector3 Apply (Vector3 point);
}

一旦我们在我们的grid对象上添加了这些组件,我们还需要获得他们的引用并且在我们grid对象的点上应用这些变换。我们将使用一个泛型的list去存储这些组件的引用

using UnityEngine;
using System.Collections.Generic;
 
publicclassTransformationGrid : MonoBehaviour {
        
        
        List<Transformation> transformations;
 
        voidAwake () {
               
               transformations = newList<Transformation>();
        }
}

现在就可以添加Update方法来获得这些transformation组件。然后将他们应用在我们的grid对象所有的点上。

        voidUpdate () {
               GetComponents<Transformation>(transformations);
               for (int i = 0, z = 0; z < gridResolution; z++) {
                       for (int y = 0; y < gridResolution; y++) {
                               for (int x = 0; x < gridResolution; x++, i++) {
                                      grid[i].localPosition = TransformPoint(x, y, z);
                               }
                       }
               }
        }

我们先取到grid上每个点的初台坐标,然后用每个transformation对它进行变换。我们不能变换每个点的当前坐标,应为当前坐标已经经过变换处理,我们当然不想每帧都对变换进行叠加。

        Vector3 TransformPoint (int x, int y, int z) {
               Vector3 coordinates = GetCoordinates(x, y, z);
               for (int i = 0; i < transformations.Count; i++) {
                       coordinates = transformations[i].Apply(coordinates);
               }
               return coordinates;
        }

 

位移

我们的第一个具体的组件将实现位移功能。看起来最简单的一个功能。新建一个组件继承自Transformation, 添加一个position属性用来存放位置偏移

 
publicclassPositionTransformation : Transformation {
 
        publicVector3 position;
 
}

在这一点上编译器会正确地提示我们并没有为Apply方法提供具体的实现。现在先让我们实现它吧。我们只需要简单地把偏移加到原始的坐标上就可以了。

        publicoverrideVector3 Apply (Vector3 point) {
               return point + position;
        }

现在我们就可以通过给grid对象添加PositionTransformation组件。这使我们可以移动grid对象上面的这些点,而不是移动grid对象本身。 我们所有的转换都发生在我们对象的局部空间中。


Transforming the position.

缩放

我们下一个要实现的转换就是缩放。它的实现跟位移几乎没有区别。除了需要将把位移操作的相加操作改成相乘

using UnityEngine;
 
publicclassScaleTransformation : Transformation {
 
        publicVector3 scale;
 
        publicoverrideVector3 Apply (Vector3 point) {
               point.x *= scale.x;
               point.y *= scale.y;
               point.z *= scale.z;
               return point;
        }
}

将这个组件绑定到我们的grid对象上。这样我们就可以缩放grid对象了。需要注意的一点就是我们只是调整了gird对象上点的位置。所以这并不会改变点的大小。


现在让我们马上试一下同时使用位移跟缩放变换。你会发现当我们首先位移对象后再缩放它的时候会发现缩放操作也同时会影响对象的位置。相对的。Unity的transform组件会以另一种更合理的方式来实现这个操作。让我们也将实现改成这种方式。这只需要我们重新排序一下变换组件。我们可以点击组件右上方的的滚轮图标弹出的菜单来改变组件的位置。

Changing the order of transformations.

旋转

我们的第三个需要处理的变换就是旋转。它相对前面的2个来说要稍微复杂一点。我们创建一个新的组件继承Transformation然后在Apply方法中把直接把point值返回。

using UnityEngine;
 
publicclassRotationTransformation : Transformation {
 
        publicVector3 rotation;
 
        publicoverrideVector3 Apply (Vector3 point) {
               return point;
        }
}

那我们要如何实现旋转功能?让我们先关注如果只绕z轴旋转的情况下对象是如何变换的。空间上的一个点绕z轴旋转的情况有点类似于旋转一个轮子。由于Unity使用的是左手坐标系。(X正方向右,y正方向上,z的正方向就是我们面对的方向。即左手中指指向x轴正方向,大姆指指向y正方向,则食指指向的是z轴的正方向。)按着z值正方向看过来正值使这轮子逆时针旋转。

2D rotation around the Z axis.

当我们旋转一个点的时候它的坐标会如何变化呢?当我们把这个点看作单位圆上的一个点的时候,就能很容易地看出来了。当一个点刚好在x轴或y轴上时如果我们每次按90°去旋转这个点时。旋转后这个点的坐标就会是在0,1,-1三个值中的一个

Rotating (1,0) and(0,1) by 90 and 180 degrees.

如果这个点的初始位置是(1,0),第一次旋转90°后这个点的坐标就变成(0,1),然后依次变成(−1,0), (0,−1),最后回到初始位置(1,0)。

如果这个点的初始位置是(0,1),第一次旋转90°后这个点的坐标就变成(−1,0),然后依次变成(0,−1), (1,0),最后回到初始位置(0,1)。

所以我们点的坐标循环经过0,1,0,-1。他们只是起始点不同而已。

那如果我们每次旋转45°坐标又会如何改变呢?我们的点最终就会落在xy平面的对角线上。根据这个点到原点的距离不变,我们就可以算出最终的坐标在(±√½, ±√½)上。这使点的坐标在0, √½, 1, √½, 0, −√½, −1, −√½这些位置上循环。如果我们把每次旋转的角度变小,最终我们会得到一个sine曲线

Sine and cosine.

在我们的例子中,当起始坐标从(1,0)开始旋转时,正弦的值就是旋转后的y值。余弦值则是绕旋转后的x值。这意味着我们可以重新定义(1,0)

为(cosz,sinz)。同样,我们可以用(-sinz,cosz)代替(0,1)。

现在在我们计算绕z轴旋转后的坐标值时我们需要先计算旋转角度的正弦值跟余弦值。我们提供的是角度,但C#计算正统跟余弦值需要担供的是弧度值。所以我们需要先把角度转成弧度。

        publicoverrideVector3 Apply (Vector3 point) {
               float radZ = rotation.z * Mathf.Deg2Rad;
               float sinZ = Mathf.Sin(radZ);
               float cosZ = Mathf.Cos(radZ);
 
               return point;
        }

现在我们已经找到了如何计算(1,0) and(0,1)旋转后的坐标。但是我们要如何计算任意坐标的旋转的坐标?括号的两个点分别定义了XY轴的值,我们可以分解任意的坐标2D 坐标(x,y)xX+yY。在没有旋转的情况下。这等价于x(1,0)+y(0,1)。这刚好就是(x,y)的值。当加上旋转的情况,我们就可以使用x(cosZ,sinZ)+y(−sinZ,cosZ)获得旋转后的坐标。你可以把这个看作把一个点缩放成单位圆上,然后旋转,最后再把它缩放回原来的大小。变换上面的式子后最后转变成式子(xcosZ−ysinZ,xsinZ+ycosZ)。

 

               returnnewVector3(
                       point.x * cosZ - point.y * sinZ,
                       point.x * sinZ + point.y * cosZ,
                       point.z
               );

添加一个旋转组件到grid上并且让它以中心点作为旋转中心,那意味着我们先要对这个物体进行缩放,然后旋转,最后再对它进行位移。这跟unity transform组件所做完全相同。当然,目前我们只是支持了绕z轴的旋转,我们随后就开始处理X,Y轴。


All three transformations.

目前我们只是实现了绕Z轴的旋转。为了跟unity 的transform 组件一样,我们还需要支持绕XY轴的旋转。单独绕XY轴旋转时跟绕Z轴旋转时很类似,但同时绕多个轴旋转时就没那么简单了。为了能处理这种情况,我们需要使用一个更好的数学工作。

矩阵

 

从现在起,我们要把点坐标的存储方式从行优先改为列优先。相对应的(x,y)我们会写成.相应的(xcosZ−ysinZ,xsinZ+ycosZ)要写成这些表示方法在数学上称为矩阵。

需要注意的是我们把x和y轴的放在一个竖列中,相应的我们的乘法也需要改变.这是一个矩阵乘法,2X2矩阵的首列代表着X轴,而第二列代表着Y轴

 

Defining the X and Y axes with a 2D matrix.

两矩阵相乘,左矩阵第一行乘以右矩阵第一列(分别相乘,第一个数乘第一个数),乘完之后相加,即为结果的第一行第一列的数,依次往下算。这意味它只有在第一个矩阵的列数(column)和第二个矩阵的行数(row)相同时才有意义

Multiplying two 2 by 2matrices.

3D旋转矩阵

目前为止,我们使用2X2矩阵来将我们的使我们平面上的面绕着Z轴旋转。但实际上我们需要使用的是3D上面的点。所以我们需要尝试, 但这是不可行的。因为第一个矩阵的列数跟第二个矩阵的行数相等是两个矩阵才能相乘。所以我们必须要使第一个矩阵变成一个3X3的矩阵,使它包含第三个坐标轴。如果我们只是简单的把其他的位置填上0呢?=.

If we used this trick for all three dimension at once,we'd end up with a matrix with 1s along its diagonal and 0s everywhere else.This is know as an identity matrix, as it doesn't change whatever it ismultiplied with. It's like a filter that lets everything through unchanged.

X轴和Y轴最后的结果是正确的。但是Z轴会永远是0。这并不是我们想要的结果。为了使Z值保持原值,我们需要把矩阵的右下角元素变为1.这是说得通的,因为第三列代表的是z轴,而它的值就是。相应的=.

如果我们同时使用这个技巧来处理所有的三个轴,那么我们最终会得到一个矩阵,其对角线为1,其他地方为0。这被认为是一个单位矩阵,因为它不会随着它的增加而改变。这就像一个过滤器,让一切都保持不变。

XY轴的旋转矩阵

我们可以用绕Z轴计算坐标的方式来计算绕Y轴的坐标。在开始时X轴为,在逆时针旋转90°后变为。这表示绕Y轴可以被表示为,Z轴可以表示为,而Y轴保持不变,所以完整的旋转矩阵为。

第三个旋转矩阵即保持X轴不变相应地修改YZ轴的值

 

统一旋转矩阵

我们的三个旋转矩阵分别对应X,Y,Z三条轴的旋转。我们必须让需要旋转的点分别与第三个矩阵相乘。让我们先旋转Z轴,然后Y轴,最后是X轴。

但我们也可以先让这三个矩阵相乘。这样会产生一个新的矩阵,让点直接乘以这个矩阵可以一次性完成对三个轴的旋转。让我们先计算Y x Z的值。

x=

现在让我们执行X x (Y x Z)来得到我们最终的旋转矩阵。

x=

矩阵的乘法符合结合律X×(Y×Z)=(X×Y)×Z。但是矩阵的乘法不符合交換律。即X×Y×Z≠Z×Y×X,矩阵乘法在这方面不同于单数乘法。Unity 实际的旋转顺序是Z x X xY。

现在我们得到了旋转矩阵。现在让我们在程序中实现它

        publicoverrideVector3 Apply (Vector3 point) {
               float radX = rotation.x * Mathf.Deg2Rad;
               float radY = rotation.y * Mathf.Deg2Rad;
               float radZ = rotation.z * Mathf.Deg2Rad;
               float sinX = Mathf.Sin(radX);
               float cosX = Mathf.Cos(radX);
               float sinY = Mathf.Sin(radY);
               float cosY = Mathf.Cos(radY);
               float sinZ = Mathf.Sin(radZ);
               float cosZ = Mathf.Cos(radZ);
 
               Vector3 xAxis = newVector3(
                       cosY * cosZ,
                       cosX * sinZ + sinX * sinY * cosZ,
                       sinX * sinZ - cosX * sinY * cosZ
               );
               Vector3 yAxis = newVector3(
                       -cosY * sinZ,
                       cosX * cosZ - sinX * sinY * sinZ,
                       sinX * cosZ + cosX * sinY * sinZ
               );
               Vector3 zAxis = newVector3(
                       sinY,
                       -sinX * cosY,
                       cosX * cosY
               );
 
               return xAxis * point.x + yAxis * point.y + zAxis * point.z;
        }

Rotating around three axes.unitypackage

 

如果我们将三个轴的旋转矩阵合并成一个。是不是我们可以将缩放,旋转,位移矩阵合并成一个。如果我们能将缩放,位移也以矩阵表示,那么当然可以。

构造缩放矩阵是很简单的。我们用单位矩阵的乘上各个轴的缩放就可以了。

=.

 

但是我们要怎么实现位移呢?3x3的矩阵是无法实现它的。我们需要额外的一列来存储这些偏移值。

=.

 

但是。这个乘法是不成立的。因为我们的第一个矩阵有四列,所以我们需要为我们的点坐标添加第四个元素。而且这个元素需要乘上偏移值。所以这个值应该是1。这样最后我们就是一个4x4的矩阵跟一个四个元素的点相乘。

使用 4x4矩阵意味我们的缩放跟旋转矩阵也需要额外添加一行一列。添加的元素除了右下角的元素是1外其余的元素都是0。而且我们的点也需要添加第四个坐标轴,而且它的值永远是1.

齐次坐标。

那我们添加的第四个坐标是否有意义呢?我们现在添加第四个坐标值为1,用来使位移起作用。但如果我们把这个值改为0,那么位移将会失效,但是缩放跟旋转依然会起作用。

什么东西可以被缩放跟旋转,但是却不能被移动?对了,那就是向量。

所以代表的是一个点,而代表的是一个向量。这是很有用的。因为这样我们就可以使用同一个矩阵转换点,法线跟切线

那如果第四个坐标我们把它设置成0跟1以外的值又会发生什么呢?我们不应该这么去做,或者说,这并不会有什么区别。齐次坐标的表示的是空间中的每个点都能用无限多的坐标集来表示。最直接的形式就是用1来作为第四个坐标。这个集合中所有其他元素都可以用这个坐标乘以任意的值来获得。

====w

所以为了获得欧几里德坐标点,xyz三维坐标。只需要所每一个坐标值除以第四个坐标值,然后把第四个坐标值丢弃即可。

==

当然如果第四个坐标值是0的话这个方法是无法使用的。因为这些点被定义为无限远。因为它们代表的是方向

现在我们可以使用unity3d里面的Matrix4x4结构去进行矩阵的乘法运算。现在开始,我们将会使用它来进行转换。

将一个抽象只读属性来添加到Transformation类中来获得变換矩阵

        Public abstract Matrix4x4 Matrix { get; }

Apply也不需要再是虚方法了。它只是获得变換矩阵然后与点相乘。

        publicVector3 Apply (Vector3 point) {
               return Matrix.MultiplyPoint(point);
        }

需要注意的是Matrix4x4.MultiplyPoint方法的参数是一个3维的 vector.它假定缺少的第四个坐标值是1。如果你想乘的是一个方向而不是一个顶点的话,你需要使用Matrix4x4.MultiplyVector方法

现在所有具体的转换类需要将Apply方法改成Matrix属性

我们首先处理PositionTransformationMatrix4x4.SetRow方法可以让我们方便地去设置矩阵的值

        publicoverrideMatrix4x4 Matrix {
               get {
                       Matrix4x4 matrix = newMatrix4x4();
                       matrix.SetRow(0, newVector4(1f, 0f, 0f, position.x));
                       matrix.SetRow(1, newVector4(0f, 1f, 0f, position.y));
                       matrix.SetRow(2, newVector4(0f, 0f, 1f, position.z));
                       matrix.SetRow(3, newVector4(0f, 0f, 0f, 1f));
                       return matrix;
               }
        }

接下来处理ScaleTransformation

        publicoverrideMatrix4x4 Matrix {
                       get {
                       Matrix4x4 matrix = newMatrix4x4();
                       matrix.SetRow(0, newVector4(scale.x, 0f, 0f, 0f));
                       matrix.SetRow(1, newVector4(0f, scale.y, 0f, 0f));
                       matrix.SetRow(2, newVector4(0f, 0f, scale.z, 0f));
                       matrix.SetRow(3, newVector4(0f, 0f, 0f, 1f));
                       return matrix;
               }
        }

 

对于RotationTransformation类来说。设置矩阵的列更方便。

        publicoverrideMatrix4x4 Matrix {
                       get {
                       float radX = rotation.x * Mathf.Deg2Rad;
                       float radY = rotation.y * Mathf.Deg2Rad;
                       float radZ = rotation.z * Mathf.Deg2Rad;
                       float sinX = Mathf.Sin(radX);
                       float cosX = Mathf.Cos(radX);
                       float sinY = Mathf.Sin(radY);
                       float cosY = Mathf.Cos(radY);
                       float sinZ = Mathf.Sin(radZ);
                       float cosZ = Mathf.Cos(radZ);
                       
                       Matrix4x4 matrix = newMatrix4x4();
                       matrix.SetColumn(0, newVector4(
                               cosY * cosZ,
                               cosX * sinZ + sinX * sinY * cosZ,
                               sinX * sinZ - cosX * sinY * cosZ,
                               0f
                       ));
                       matrix.SetColumn(1, newVector4(
                               -cosY * sinZ,
                               cosX * cosZ - sinX * sinY * sinZ,
                               sinX * cosZ + cosX * sinY * sinZ,
                               0f
                       ));
                       matrix.SetColumn(2, newVector4(
                               sinY,
                               -sinX * cosY,
                               cosX * cosY,
                               0f
                       ));
                       matrix.SetColumn(3, newVector4(0f, 0f, 0f, 1f));
                       return matrix;
               }
        }

 

现在让我们把所有的矩阵组合成一个矩阵。在TransformationGrid在类中添加一个名为transformation的Matrix4x4属性

        Matrix4x4 transformation;

 

我们会在每次Update循环中更新transformation值。这需要先获得第一个矩阵。然后依次乘上后面的所有矩阵。并且确保他们是以正解的顺序相乘。

        voidUpdate () {
               UpdateTransformation();
               for (int i = 0, z = 0; z < gridResolution; z++) {
                       …
               }
        }
 
        void UpdateTransformation () {
               GetComponents<Transformation>(transformations);
               if (transformations.Count >0) {
                       transformation = transformations[0].Matrix;
                       for (int i = 1; i < transformations.Count; i++) {
                               transformation = transformations[i].Matrix * transformation;
                       }
               }
        }

现在我们的grid类不再需要调用Apply方法了。

        Vector3 TransformPoint (int x, int y, int z) {
               Vector3 coordinates = GetCoordinates(x, y, z);
               return transformation.MultiplyPoint(coordinates);
        }

这种新的方法效率更高。因为我们原来我们为每个转换创建矩阵并且分别乘上所有的点。现在我们只需创建一次统一矩阵,然后就可以把这个矩阵复用到每一个点上。Unity也是使用这个方法将每个对象层次结构减少为单个变换矩阵。

在我们的案例中,我们甚至可以令它效率变得更高。所以矩阵最底下那行的值都是相同的[0001]。我们可以跳过这一行的计算和最后的转换步骤。Matrix4x4.MultiplyPoint4x3方法就是用来处理这种情况的。但我们不会去使用去。毕竟在转换时改变最后一行的值有时候是用的。

 

投影矩阵

目前,我们已经可以将3d空间中的一个点从一个地方转换到另一个地方。但是这些点是如何最终显示在我们的显示屏上的?这需要一个需要将3d空间转到2d空间。我们可以创建一个转换矩阵来做这个事情。

创建一个新的类作为投影矩阵。我们先从单位矩阵开始。

using UnityEngine;
 
publicclassCameraTransformation : Transformation {
 
        publicoverrideMatrix4x4 Matrix {
               get {
                       Matrix4x4 matrix = newMatrix4x4();
                       matrix.SetRow(0, newVector4(1f, 0f, 0f, 0f));
                       matrix.SetRow(1, newVector4(0f, 1f, 0f, 0f));
                       matrix.SetRow(2, newVector4(0f, 0f, 1f, 0f));
                       matrix.SetRow(3, newVector4(0f, 0f, 0f, 1f));
                       return matrix;
               }
        }
}

将它添加到最后。

The camera projection comes at the end.

 

正交摄像机

 

最简单的从3d到2d的方法就是丢弃第三维的数据。这将把数据从3d空间坍塌到平面上。这个平面类似于画板。用来显示场景。让我们丢弃Z轴看看会发生什么。

                       matrix.SetRow(0, newVector4(1f, 0f, 0f, 0f));
                       matrix.SetRow(1, newVector4(0f, 1f, 0f, 0f));
                       matrix.SetRow(2, newVector4(0f, 0f, 0f, 0f));
                       matrix.SetRow(3, newVector4(0f, 0f, 0f, 1f));

 

Orthographic Projection.

确实。我们的grid对象变成2d的了。我们仍然可以缩放,旋转移动它。但是它最终会投影到XY平面上。这就是一个最基本的正交投影。

我们的原始相机坐落在原点处并看向正Z方向。我们可以移动它并旋转它吗?是的,实际上我们已经可以做到了。移动摄像机与向相反方向移动世界具有相同的视觉效果。旋转和缩放也是如此。所以我们可以使用我们现有的转换来移动相机,虽然这有点尴尬。 Unity使用矩阵求逆来做同样的事情。

投影矩阵。

正交摄像机工作地很好。但是我们看到的世界不是这个样子的。想要实现我们看到的世界。我们需要一个透视摄像机。物体离我们越远我们看到的就越小。我们可以根据点离我们的远近对这个点进行缩放来实现这个效果。

 

让我们直接将所有点阶除以它的z轴坐标。我们可以用矩阵乘法来实现这个目的吗?当然可以。我们只需要改变单位的最下面一行。

 

                       matrix.SetRow(0, newVector4(1f, 0f, 0f, 0f));
                       matrix.SetRow(1, newVector4(0f, 1f, 0f, 0f));
                       matrix.SetRow(2, newVector4(0f, 0f, 0f, 0f));
                       matrix.SetRow(3, newVector4(0f, 0f, 1f, 0f));

跟正交矩阵最大的不同是点不是直接向下投影到投影平面。相反的。他们朝摄像机的原点位置移动。直到撞上投影平面。当然。这只对在摄像机前面的点有效。如果我们不丢弃摄像机后面的点,这些点会造成不正确的投影。所以要确保所有的东西都是在摄像机的前面。当网格未缩放或旋转时,这些点距离为摄像机超过5就足够了,否则您可能需要更大的距离

 Perspective Projection.

 

原点和投影平面之间的距离也会影响投影。它的作用就像相机的焦距。这个距离越大,视野越小。现在我们使用1的焦距,产生90°的视场。我们可以将它设为配置。

        publicfloat focalLength = 1f;


Focal length.

由于较大的焦距意味着我们正在放大,这有效地增加了我们最终点的缩放,所以我们可以通过这种方式来支持它。当我们折叠Z维时,这个不需要缩放。

                       matrix.SetRow(0, newVector4(focalLength, 0f, 0f, 0f));
                       matrix.SetRow(1, newVector4(0f, focalLength, 0f, 0f));
                       matrix.SetRow(2, newVector4(0f, 0f, 0f, 0f));
                       matrix.SetRow(3, newVector4(0f, 0f, 1f, 0f));

Adjusting the focal length.

我们现在拥有了一个非常简单的透视相机。如果我们要完全模仿Unity的相机投影,我们还必须处理近距离和远距平面。这需要投影到立方体而不是平面,因此深度信息会保留。然后还需要关注视图宽高比。此外,Unity的相机在负Z方向看,这需要将某些数字变为负数。你可以将所有这些都纳入投影矩阵。如果你愿意,我会告诉你如何去做。

那么所有这一切是什么?我们很少需要自己构造矩阵,而且绝对不会构造投影矩阵。重点是你现在明白发生了什么事。矩阵并不可怕,它们只是将点和矢量从一个空间转换到另一个空间。你明白如何这是怎么处理的会对我们后面的学习很有帮助,因为一旦我们开始编写自己的着色器,就会再次遇到矩阵。我们将在第2部分着色器基础知识中这样做。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值