渲染管线——透视投影变换

来源:互联网 发布:javascript 获取时间 编辑:程序博客网 时间:2024/06/05 04:41

综述

透视投影变换是渲染管线几何阶段最难理解的地方,实际上,透视投影变换这个过程是要分解成好几个子过程的,它的目的是把物体的三维视图坐标变换为可以在屏幕上描绘出来的二维屏幕坐标。
现在理清一下渲染管线几何阶段的过程:

这里写图片描述

可以看出,透视投影变换被分解为了三个子过程
1.齐次裁剪变换 : 在该过程中主要是通过Camrea、远近平面、宽高比、视角等信息求出投影变换矩阵,并对结点进行变换,变换后结点在齐次裁剪坐标系下。
2.裁剪 : 对位于齐次裁剪坐标系下的Vertex进行必要的裁剪
3.透视除法 : 对结点进行透视除法,完成从三维坐标到二维屏幕坐标的映射。

齐次裁剪变换

所谓的齐次裁剪变换可以用一张图来直观的说明:
这里写图片描述

原来的不规则视椎体被变换为了一个规则的小盒子(这个小盒子又称为规则观察体(Canonical View Volume) 简称CVV),在该坐标系下,所有物体的坐标都满足以下条件:

x[1,1]y[1,1]z[0,1]

(这里采用的是Directx对齐次裁剪空间的定义)

那么,齐次裁剪变换矩阵是如何求得的呢?在推导有详细的推导过程,这里不再赘述,给出代码:

public static Matrix4x4 GetProjection(float fov, float aspect, float zn, float zf){    //fov filed of view  视域    //aspect 宽高比    //zn 近平面    //zf 远平面    Matrix4x4 p = new Matrix4x4();    p.SetZero();    p[0, 0] = (float)(1 / (System.Math.Tan(fov * 0.5f) * aspect));    p[1, 1] = (float)(1 / System.Math.Tan(fov * 0.5f));    p[2, 2] = zf / (zf - zn);    p[2, 3] = 1f;    p[3, 2] = -(zn * zf) / (zf - zn);    //近平面作为视平面    //x[-1,1] y[-1,1] z[0,1]    //投影结束后,w保存着原来的z信息,可以用来进行深度检测   return p;}Vertext v.position *= GetProjection(3.1415926 / 4, width/height, 1, 500);

裁剪

经过上面的变换后,结点位于齐次裁剪空间之下,w的值得到了正确的计算,且x,y,z,w被规格化(这两个是做齐次裁剪变换的重要目的),此时应当做裁剪操作。
这里有一个问题值得思考,为什么不在透视除法之后再进行裁剪操作呢?
一方面考虑到效率问题,而最重要的是,在齐次裁剪空间下,结点的坐标是具备线性关系的,这使得它可以进行线性插值运算。

例如,假设Vertex在视空间下的坐标为(x,y,z,w),经过齐次裁剪变换之后,它的坐标为

[x1,y1,z1,w1]=[xcot(θ)/Aspect,ycot(θ),zffn+fnnf,z]

显然x1与x、y1与y、z1与z有线性关系,若做透视除法,那么
[x2,y2,z2,w2]=[x1/w,y1/w,z1/w,1]

显然不具备线性关系,那么此时进行线性插值,原来结点存储的很多信息都会扭曲变形(例如纹理、颜色等)

因此我们才CVV空间进行裁剪:

这里写图片描述

private bool Clip(Vertex v){    //w存储的是z信息,并且Vertex.point已经经过规格化    //CVV空间下   x[-w,w] y[-w,w] z[0,w]    //在本程序下 w=1    if( v.point.x >= -v.point.w && v.point.x <= v.point.w &&        v.point.y >= -v.point.w && v.point.y <= v.point.w &&         v.point.z >= 0f && v.point.z <= v.point.w){                return true;        }        return false;    }

透视除法

一旦完成了裁剪,即可像屏幕空间投影,从而在输出窗口上显示。
显然屏幕是二维的,只需要做一次透视除法即可将三维的坐标转换过去,并适当调整x,y的坐标,使其与输出窗口统一。

 private void TransformToScreen(ref Vertex v) {    if(v.point.w != 0)    {        //先进行透视除法,完成3D到2D的坐标映射        v.point.x *= 1 / v.point.w;        v.point.y *= 1 / v.point.w;        v.point.z *= 1 / v.point.w;        v.point.w = 1;        //调整x,y到输出窗口        v.point.x = (v.point.x + 1) * 0.5f * this.MaximumSize.Width;        v.point.y = (1 - v.point.y) * 0.5f * this.MaximumSize.Height;     } }    

到这里,渲染管线的几何阶段就结束了,下面就将进入到光栅化阶段。

原创粉丝点击