软光栅渲染器之坐标变换

从这里开始我基本上就是自己写自己的了

在之前我们使用了正交投影以及简单的zbuffer渲染了一个这样的模型

但是总感觉怪怪的,而且我们在写变换的时候也感觉有点不对劲,怎么直接就做这样一个变换呢?

worldspace_coord[j] = model.vert(face[j]);
screenspace_coord[j] = Vec3i((worldspace_coord[j].x + 1) * width / 2, (worldspace_coord[j].y + 1) * height / 2, (worldspace_coord[j].z + 1)* width / 2);

这是因为这个模型的坐标范围是[-1, 1]三维空间,我们直接导入的话,他就会在世界坐标的原点附近,这也刚好是在NDC空间(不过是右手系),之前的这个变换只是单纯用了一个正交变换并转换到屏幕空间,这样显式也正常,但是跟我们人眼看到的东西是不同的,我们现在看到的黑哥们没有透视效果(什么黑色深水王子~)

我们正常看东西应该是这样的,越远的东西越小,这个就涉及到了渲染管线中的变换。

视图变换

我们可以这样来描述视图变换的任务:将虚拟世界中以(x,y,z)为坐标的物体变换到 以一个个像素位置(x,y)来表示的屏幕坐标系之中(2维),这确实是一个较为复杂的过程,但是整个过程可以被细分为如下几个步骤:

(1) 模型变换(modelingtranformation):这一步的目的是将虚拟世界中或者更具体点,游戏场景中的物体调整至他们应该在的位置。

(2) 摄像机变换(cameratranformation):在游戏中我们真正在乎的是摄像机(或者说眼睛)所看到的东西,也就是需要得到物体与摄像机的相对位置。也就是说我知道相机的位置以及他的基在我空间的坐标,我希望知道我眼中的这些点在相机看来是什么样的。

(3) 投影变换(projectiontranformation):在摄像机变换之后,我们得到了所有可视范围内的物体相对于摄像机的相对位置坐标(x,y,z),之后根据具体情况选择平行投影或是透视投影,将三维空间投影至标准二维平面([-1,1]^2)之上(tips:这里的z并没有丢掉,为了之后的遮挡关系检测)

模型变换:

首先来看模型变换:这里建议看【熟肉】线性代数的本质 - 03 - 矩阵与线性变换_哔哩哔哩_bilibili,理解下坐标变换。线性变换包括缩放、旋转、错切,相当于坐标系空间上的旋转、错切、缩放。

可以这样理解:在世界坐标系里面我们看的点的线性变换可以看作是空间的线性变换,空间线性变换之后如何判断变换之后的点,只需要找到原空间的基现在在世界空间是多少,然后用这个三个基组成矩阵B就成了个过度矩阵。

这个过度矩阵B相当于一个翻译器,将上游空间中(世界坐标)的某个点左乘B,相当于求在我空间的某个点,与他那个空间的相同表示的点,实际上在我空间是哪一个点;乘以B的逆矩阵就代表,在我空间这个点,在他那个空间是怎么表示的。

理解好了就容易记住下面这些矩阵:

缩放相当于

表示成矩阵形式是因为有左边连乘能够表示连续的变换。

但是这里还没有引入平移,因为平移不是线性变换,所以不能用简单矩阵表示,这样在计算机中不能有连乘的性质,比如我们可以这样表示平移加线性变换。

但是当我们需要连续做两次这个操作:

这式子写起来也太复杂了,所以我们引入齐次坐标,在齐次坐标系下,能够将平移也引入矩阵形式中。

view变换:

上面已经提到了,我们希望知道本地一个点在别的坐标系下的表示,那么就用那个坐标系的基向量在我们坐标系下的坐标按列组成过度矩阵B,并用B的逆左乘所有点,就能得出所有点在那个坐标系下的表示。当然这需要满足线性变换,所以如果那个基向量原点有位移,则需要先做唯一变换拉回原点。

这样就很清晰了:

注意先平移再求基向量坐标

代码如下:

mat4 transf::viewMatrix(Vec3f position, Vec3f lookPoistion, Vec3f worldUp)
{
    mat4 trans = mat4::identity();
    for (int i = 0; i < 3; i++) {
        trans[i][3] = -position[i];
    }
    Vec3f front = (lookPoistion - position).normalize();
    
    Vec3f right = cross(front , worldUp).normalize();
    Vec3f up = cross(right , front).normalize();
    mat3 rot = mat3::identity();
    for (int i = 0; i < 3; i++) {
        rot[0][i] = right[i];
        rot[1][i] = up[i];
        rot[2][i] = -front[i];
    }
    /*std::cout << "rot:\n" << rot << std::endl;
    system("pause");*/
    mat4 rotate = mat4::identity();
    rotate.embedmat(rot);
    mat4 result = rotate * trans;
    
    return result;
}
投影变换

这里讲的很好,请跳转去看看,games101的推导方式:

作者:happyfire

链接:https://juejin.cn/post/7085176703085412383

来源:稀土掘金

透视矩阵与正交投影矩阵计算代码如下

mat4 transf::perspective(float fov, float aspect, float near, float far)
{

    //注意near,far代表近、远平面与视点距离,near_view, far_view代表在view空间近远平面z轴坐标
    float near_view = -near;
    float far_view = -far;
    float theta = radians(fov / 2);
    float top = near * tan(theta);
    float bottom = -top;
    float right = top * aspect;
    float left = -right;


    mat4 orth = orthoProjection(left, right, top, bottom, near, far);
    //std::cout << "ortho:\n" << orth << std::endl;
    mat4 per2orth ;
    per2orth[0][0] = near_view;
    per2orth[1][1] = near_view;
    per2orth[2][2] = near_view + far_view;
    per2orth[2][3] = -near_view * far_view;
    per2orth[3][2] = 1.0f;
    
    //std::cout << "per2ortho:\n" << per2orth << std::endl;

    return orth * per2orth;
}

mat4 transf::orthoProjection(float left, float right, float top, float bottom, float near, float far)
{

    near = -near;
    far = -far;
    mat4 translate = mat4::identity();
    translate[0][3] = -((left + right) / 2.0f);
    translate[1][3] = -((top + bottom) / 2.0f);
    translate[2][3] = -((near + far) / 2.0f);

    mat4 scale = mat4::identity();
    scale[0][0] = 2 / (right - left);
    scale[1][1] = 2 / (top - bottom);
    scale[2][2] = 2 / (near - far);

    return scale * translate;

}

这样我们就完成了MVP变换。

接下来我们要使用他:

void renderModel(Model& model, TGAImage& img) {
    std::vector<int> zBuffer(width * height, INT32_MIN);
    mat4 modelM = mat4::identity();
    mat4 viewM = transf::viewMatrix(camraPosition, Vec3f(0.0f, 0.0f, 0.0f), Vec3f(0.0f, 1.0f, 0.0f));
    mat4 projectionM = transf::perspective(45.0, 1.0f, 1.0, 100);
    auto transform_matrix = projectionM * viewM * modelM;

    for (int i = 0; i < model.nfaces(); i++) {
        auto face = model.face(i);
        Vec3f worldspace_coord[3];
        Vec3i screenspace_coord[3];

        for (int j = 0; j < face.size(); j++) {
            //这里将存储在本地空间的模型(这里存的ndc)坐标变换到屏幕坐标,做了个简单的视口变换
            Vec3f point = model.vert(face[j]);
            Vec4f homopoint = Vec4f(point.x, point.y, point.z, 1.0f);
            homopoint = projectionM * viewM * modelM * homopoint;
            float w = homopoint.w;
            if (homopoint.x / w <= -1.0f || homopoint.x / w >= 1.0f || homopoint.y / w <= -1.0f || homopoint.y / w >= 1.0f || homopoint.z / w <= -1.0f || homopoint.z / w >= 1.0f) {
                continue;
            }
            homopoint = homopoint / homopoint.w;
            worldspace_coord[j] = point;
            Vec3f sc = Vec3f((homopoint.x + 1.0f) / 2.0 * width, (homopoint.y + 1.0f) / 2.0f * height, (homopoint.z + 1.0f) / 2.0f);
            screenspace_coord[j] = Vec3i(sc);
        }
值得注意的地方:
  1. 要注意一点,我在结束的时候,发现当我是视角垂直向下或者向上的时候没有任何图像,这时为什么呢?这是因为我的视角旋转使用的是欧拉角,也就是给定视角方向,以及世界向上方向,通过视角方向叉乘向上方向得到right方向,再有right X forward得到相机up方向,当视角垂直向上或者向下之后,前向方向与世界向上方向共线,叉乘为0,view矩阵失效,所以用这种方式尽量不要让视角垂直。

  1. 在透视除法之后,重心插值会出问题,需要进行重心插值的透视矫正。具体方法看这里:

图形学 - 关于透视矫正插值那些事 - 知乎 (zhihu.com)

  1. 在推导透视矩阵的时候注意nf到底代表具体坐标还是距离,注意统一。

  1. opengl推导的透视矩阵旋向会改变,但是games101的不会,也就是nf在两者看来映射的【-1, 1】刚好相反。这反映在箭头所指位置是1还是-1.

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值