视变换(viewing transform)和投影变换(perspective transform)是3D渲染管线中不可缺少的两个重要变换,前者将顶点坐标由世界坐标系变换到观察坐标系(又称相机坐标系),后者将顶点坐标由观察坐标系变换到屏幕坐标系,之后就是光栅化的步骤了。

关于这两种变换的原理就不多讲,随便找一本图形学的书上面都会有。具体实现也很简单,就是根据参数生成两个矩阵,然后和顶点向量相乘就行了。这里均采用列向量(即P = M2 * M1 * P0,变换顺序是先M1后M2)和右手坐标系。

视变换

视变换矩阵由三个矩阵相乘而得:

首先将相机所在位置设为原点:

然后旋转坐标系,使得世界坐标系的坐标轴与观察坐标系的坐标轴重合:

其中w是interest – eye,即观察坐标系的z轴方向,u是w和相机up向量的叉乘,即观察坐标系的x轴方向,v是u和w的叉乘,即观察坐标系的y轴方向。

最后将右手坐标系变为左手坐标系(为方便起见,不管世界坐标系如何,观察坐标系一律采用左手坐标系),即将z轴反向:

最终变换矩阵Mv = Z * R * T。

代码实现:

void GetViewTransformation(const Point3 &eye, const Point3 &interest, const Vector3 &up, Matrix4& mat)
{
    Matrix4 T = trans_mat(-(eye - Point3(0, 0, 0)));

    Vector3 w = normalize(interest - eye);
    Vector3 u = normalize(cross(w, up));
    Vector3 v = cross(u, w);
    Matrix4 R(
        u.x,  u.y,  u.z,  0,
        v.x,  v.y,  v.z,  0,
        -w.x, -w.y, -w.z, 0,
        0,    0,    0,    1);

    Matrix4 Z;
    Z.m[10] = -1;

    mat = Z * R * T;
}
投影变换

投影变换的基本原理是在观察坐标系中建立一个由近裁剪平面和远裁剪平面构成的一个平截体,将平截体内的顶点按照z值大小变换到屏幕坐标系中,以实现“近大远小”的透视投影效果。屏幕坐标系是个长方体,x和y取值均为[-1,1]、z取值为[0,1]的顶点将被允许进入最终的光栅化阶段。

在屏幕坐标系中可以很方便地实现裁剪,代价是投影变换会变换所有的顶点,增加运算量。裁剪留到下次再作讨论。

投影变换矩阵:

其中F和D分别是相机近裁剪面和远裁剪面的z值,W和H分别是近裁剪面的宽和高,不一定是屏幕的宽高,但宽高比必须和屏幕相同,否则会出现变形。关于投影变换矩阵的推导过程请自行参考相关资料。

代码实现:

void GetPerspectiveTransformation(float xfov, float aspectRatio, float znear, float zfar, Matrix4& mat)
{
    float W, H;
    W = znear * tan(DegreeToRadian(xfov/2)) * 2;
    H = W / aspectRatio;

    mat.m[0] = (2 * znear) / W;
    mat.m[5] = (2 * znear) / H;
    mat.m[10] = zfar / (zfar - znear);
    mat.m[11] = 1;
    mat.m[14] = -(zfar * znear) / (zfar - znear);
    mat.m[15] = 0;
}

矩阵乘法顺序是P = Mp * Mv * P0。要注意的是变换前P0的w值为1,变换后P的w值为P0的z值,为计算方便,这个w值将会用于裁剪过程,暂时先不处理。当裁剪过程完成之后、光栅化之前,需要把所有顶点的x,y和z值除以该顶点的w值,才能最终得到在屏幕坐标系中的坐标。

帧率计算

原理很简单,直接上代码:

void fps()
{
    _fps++;
    sceRtcGetCurrentTick(&_fpsTickNow);

    if(((_fpsTickNow - _fpsTickLast)/((float)_tickResolution)) >= 1.0)
    {
        _fpsTickLast = _fpsTickNow;
        sprintf(_fpsDisplay, "FPS: %d", _fps);
        _fps = 0;
    }

    pspDebugScreenSetXY(0, 0);
    pspDebugScreenPrintf(_fpsDisplay);
}

===========cotaku的分割线===========

下一篇应该会讨论裁剪和背面剔除。

» 转载请注明来源及链接:未来代码研究所

Related Posts:

Leave a Reply

World Line
Time Machine
Friendly Links
Online Tools