首页 > 其他分享 >从0开始做一个软渲染器——透视投影和投影矫正

从0开始做一个软渲染器——透视投影和投影矫正

时间:2024-03-01 15:44:06浏览次数:25  
标签:矫正 mat4 渲染器 Mat Vector3f float 投影 Position Matrix4

从0开始做一个软渲染器——透视投影和投影矫正

已经做了一段时间了,一直都没记录。最近实现了一个透视投影的相机,从这一部分记录。

项目地址:https://github.com/DogWealth/PIRenderer

需要注意的是:

  • 以下代码的运算都将向量考虑成行向量,进行从左往右的乘法运算。相比列向量,对应的相乘矩阵需要进行转置
  • 右手坐标系

透视投影

correct_gloor.gif

参考文章The Perspective and Orthographic Projection Matrix (scratchapixel.com)

阅读完这一大章节(七个小节)的知识点,跟着手推一遍,就能完全掌握透视投影和正交投影。

直接给出投影矩阵的代码

Matrix4 Matrix4::Orthographic(float l, float r, float b, float t, float f, float n)
{
    Matrix4 mat4;
    mat4.m_Mat[0][0] = 2 / (r - l);
    mat4.m_Mat[0][1] = 0;
    mat4.m_Mat[0][2] = 0;
    mat4.m_Mat[0][3] = 0;

    mat4.m_Mat[1][0] = 0;
    mat4.m_Mat[1][1] = 2 / (t - b);
    mat4.m_Mat[1][2] = 0;
    mat4.m_Mat[1][3] = 0;

    mat4.m_Mat[2][0] = 0;
    mat4.m_Mat[2][1] = 0;
    mat4.m_Mat[2][2] = 2 / (n - f);
    mat4.m_Mat[2][3] = 0;

    mat4.m_Mat[3][0] = -(r + l) / (r - l);
    mat4.m_Mat[3][1] = -(t + b) / (t - b);
    mat4.m_Mat[3][2] = -(n + f) / (n - f);
    mat4.m_Mat[3][3] = 1;

    return mat4;
}

Matrix4 Matrix4::Perspective(float n, float f, float fov, float aspectRatio)
{
    float t = tan(PI * fov / (2 * 180)) * n;
    float b = -t;
    float r = t * aspectRatio;
    float l = -r;

    Matrix4 mat4;
    mat4.m_Mat[0][0] = n;
    mat4.m_Mat[0][1] = 0;
    mat4.m_Mat[0][2] = 0;
    mat4.m_Mat[0][3] = 0;

    mat4.m_Mat[1][0] = 0;
    mat4.m_Mat[1][1] = n;
    mat4.m_Mat[1][2] = 0;
    mat4.m_Mat[1][3] = 0;

    mat4.m_Mat[2][0] = 0;
    mat4.m_Mat[2][1] = 0;
    mat4.m_Mat[2][2] = f + n;
    mat4.m_Mat[2][3] = -1;

    mat4.m_Mat[3][0] = 0;
    mat4.m_Mat[3][1] = 0;
    mat4.m_Mat[3][2] = f * n;
    mat4.m_Mat[3][3] = 0;

    mat4 =  mat4 * Matrix4::Orthographic(l, r, b, t, f, n);

    return mat4;
}

透视相机

相机的作用是得到VP矩阵,建立一个Camera类,其主要作用就是计算VP矩阵

class Camera
{
public:
    Camera(Matrix4 projectionMatrix, Matrix4 viewMatrix)
        : m_ProjectionMatrix(projectionMatrix), m_ViewMatrix(viewMatrix)
    {
    }


    void LookAt(const Vector3f& eyePos, const Vector3f& lookAt, const Vector3f& upAxis)
    {

        m_ViewMatrix = Matrix4::LookAt(eyePos, lookAt, upAxis);
        m_ViewProjectionMatrix = m_ViewMatrix * m_ProjectionMatrix;
    }

    void SetPosition(const Vector3f& position)
    {
        m_Position = position;
        RecalculateViewMatrix();
    }
    const Vector3f& GetPosition() const { return m_Position; }

    void SetLookDir(const Vector3f& lookDir)
    {
        m_LookDir = lookDir;
        RecalculateViewMatrix();
    }
    const Vector3f& GetLookDir() const { return m_LookDir; }

    void SetRotation(Vector3f rotation)
    {
        m_Rotation = rotation;
        RecalculateViewMatrix();
    }
    Vector3f GetRotation() const { return m_Rotation; }

    const Matrix4& GetProjectionMatrix() const { return m_ProjectionMatrix; }
    const Matrix4& GetViewMatrix() const { return m_ViewMatrix; }
    const Matrix4& GetViewProjectionMatrix() const { return m_ViewProjectionMatrix; }


protected:
    void RecalculateViewMatrix()
    {
        Matrix4 transform = Matrix4::Translate(-m_Position.x, -m_Position.y, -m_Position.z);
        Matrix4 rotation = Matrix4::RotateEuler(m_Rotation.y, m_Rotation.x, m_Rotation.z);

        m_ViewMatrix = transform * Matrix4::Transpose(rotation);
        m_ViewProjectionMatrix = m_ViewMatrix * m_ProjectionMatrix;
    }

protected:
    Matrix4 m_ProjectionMatrix;
    Matrix4 m_ViewMatrix;
    Matrix4 m_ViewProjectionMatrix;

    Vector3f m_Position = { 0.0f, 0.0f, 2.0f };
    Vector3f m_LookDir = { 0.0f, 0.0f, -1.0f };
    Vector3f m_Rotation = { 0.0f, 0.0f, 0.0f };
};

通过继承这个类来实现正交相机或者投影相机

//Camera.h
class OrthographicCamera : public Camera
{
public:
    OrthographicCamera(float left, float right, float bottom, float top, float far, float near);

    void SetProjection(float left, float right, float bottom, float top, float far, float near);
};


class PerspectiveCamera : public Camera
{
public:
    PerspectiveCamera(float n, float f, float fov, float aspectRatio);

    void SetProjection(float n, float f, float fov, float aspectRatio);
};

OrthographicCamera::OrthographicCamera(float left, float right, float bottom, float top, float far, float near)
		: Camera(Matrix4::Orthographic(left, right, bottom, top, far, near), Matrix4::Identity())
	{
		RecalculateViewMatrix();
	}


//Camera.cpp
void OrthographicCamera::SetProjection(float left, float right, float bottom, float top, float far, float near)
{
    m_ProjectionMatrix = Matrix4::Orthographic(left, right, bottom, top, far, near);
    RecalculateViewMatrix();
}

PerspectiveCamera::PerspectiveCamera(float n, float f, float fov, float aspectRatio)
    : Camera(Matrix4::Perspective(n, f, fov, aspectRatio), Matrix4::Identity())
{
    RecalculateViewMatrix();
}

void PerspectiveCamera::SetProjection(float n, float f, float fov, float aspectRatio)
{
    m_ProjectionMatrix = Matrix4::Perspective(n, f, fov, aspectRatio);
    RecalculateViewMatrix();
}

在这里通过改变相机位置和旋转相机都能够改变View Matrix,或者通过LookAt函数让相机看向某个方向

LookAt矩阵参考文章:图形学:观察矩阵/LookUp矩阵的推导 - 知乎 (zhihu.com)

Matrix4 Matrix4::LookAt(const Vector3f& eyePos, const Vector3f& lookAt, const Vector3f& upAxis)
{
    Vector3f lookDir = lookAt;
    lookDir.Normalize();

    Vector3f rightDir = Vector3f::CrossProduct(upAxis, lookDir);
    rightDir.Normalize();

    Vector3f upDir = Vector3f::CrossProduct(lookDir, rightDir);
    upDir.Normalize();

    Matrix4 mat4;
    mat4.m_Mat[0][0] = rightDir.x;
    mat4.m_Mat[0][1] = upDir.x;
    mat4.m_Mat[0][2] = lookDir.x;
    mat4.m_Mat[0][3] = 0;

    mat4.m_Mat[1][0] = rightDir.y;
    mat4.m_Mat[1][1] = upDir.y;
    mat4.m_Mat[1][2] = lookDir.y;
    mat4.m_Mat[1][3] = 0;

    mat4.m_Mat[2][0] = rightDir.z;
    mat4.m_Mat[2][1] = upDir.z;
    mat4.m_Mat[2][2] = lookDir.z;
    mat4.m_Mat[2][3] = 0;

    mat4.m_Mat[3][0] = 0;
    mat4.m_Mat[3][1] = 0;
    mat4.m_Mat[3][2] = 0;
    mat4.m_Mat[3][3] = 1;

    return Matrix4::Translate(-eyePos.x, -eyePos.y, -eyePos.z) * mat4;
}

轨道相机

为了更好的观察物体,需要建立一个CameraController来控制相机的运动。轨道相机能更方便的展示模型

可以参考的文章:

建立一个Controller类来控制相机的运动

class PerspectiveCameraController
{
public:
    PerspectiveCameraController(float n, float f, float fov, float aspectRatio);

    virtual void OnUpdate() = 0;

    PerspectiveCamera& GetCamera() { return m_Camera; }
    const PerspectiveCamera& GetCamera() const { return m_Camera; }

protected:
    PerspectiveCamera m_Camera;
    Vector3f m_CameraPosition = { 0.f, 0.f, 30.f };
    Vector3f m_CameraRotation = { 0.f, 0.f, 0.f };

    float m_CameraTranslationSpeed = 0.05f;
    float m_CameraRotationSpeed = 1.f;
};

class OrbitController : public PerspectiveCameraController
{
public:
    OrbitController(float n, float f, float fov, float aspectRatio)
        : PerspectiveCameraController(n, f, fov, aspectRatio)
    {

    }

    virtual void OnUpdate() override;

private:
    float Radius = 50;
    float Theta = 0;
    float Phi = 0;

};

void OrbitController::OnUpdate()
{
    if (Input::IsKeyPressed(SDL_SCANCODE_LEFT))
    {
        Theta -= m_CameraRotationSpeed;
    }

    if (Input::IsKeyPressed(SDL_SCANCODE_RIGHT))
    {
        Theta += m_CameraRotationSpeed;
    }

    if (Input::IsKeyPressed(SDL_SCANCODE_UP))
    {
        Phi += m_CameraRotationSpeed;
    }

    if (Input::IsKeyPressed(SDL_SCANCODE_DOWN))
    {
        Phi -= m_CameraRotationSpeed;
    }

    if (Input::IsKeyPressed(SDL_SCANCODE_W))
        Radius -= m_CameraTranslationSpeed;

    if (Input::IsKeyPressed(SDL_SCANCODE_S))
        Radius += m_CameraTranslationSpeed;


    m_CameraPosition.x = Radius * sin(Theta * PI / 180.0f) * cos(Phi * PI / 180.0f);
    m_CameraPosition.y = Radius * sin(Phi * PI / 180.0f);
    m_CameraPosition.z = Radius * cos(Theta * PI / 180.0f) * cos(Phi * PI / 180.0f);

    m_Camera.SetPosition(m_CameraPosition);
    m_Camera.LookAt(m_CameraPosition, m_CameraPosition, {0, 1, 0});
}

结果如下,地板被严重扭曲,这是因为透视插值没有进行矫正

floor.gif

透视矫正

参考资料:

这里要记住的插值公式如下:

\[I_{t}=\left(\frac{I_{1}}{Z_{1}}+s\left(\frac{I_{2}}{Z_{2}}-\frac{I_{1}}{Z_{1}}\right)\right) / \frac{1}{Z_{t}} \]

在顶点变换中,先对顶点的各个属性预先除以Z值,这一步在函数Vertex_rhw_Init中进行

//顶点着色器
void BasicShader::VertexShader(Vertex* v1, Vertex* v2, Vertex* v3)
{
    v1->m_Position = v1->m_Position * m_VPMatrix;
    v2->m_Position = v2->m_Position * m_VPMatrix;
    v3->m_Position = v3->m_Position * m_VPMatrix;

    v1->m_Position.x /= v1->m_Position.w;
    v2->m_Position.x /= v2->m_Position.w;
    v3->m_Position.x /= v3->m_Position.w;

    v1->m_Position.y /= v1->m_Position.w;
    v2->m_Position.y /= v2->m_Position.w;
    v3->m_Position.y /= v3->m_Position.w;

    v1->m_Position.z = -v1->m_Position.w;
    v2->m_Position.z = -v2->m_Position.w;
    v3->m_Position.z = -v3->m_Position.w;

    v1->m_Position.w = 1.0f;
    v2->m_Position.w = 1.0f;
    v3->m_Position.w = 1.0f;

    Vertex_rhw_Init(v1);
    Vertex_rhw_Init(v2);
    Vertex_rhw_Init(v3);
}

void BasicShader::Vertex_rhw_Init(Vertex* v)
{
    float rhw_z = 1.0f / v->m_Position.z;

    v->m_Position.z = rhw_z;
    v->m_Color *= rhw_z;
    v->m_Normal *= rhw_z;
    v->m_TexCoord *= rhw_z;
}

完成插值之后再除以\(\frac{1}{Z_t}\)

void Renderer::DrawScanline(Vertex* v, Vertex* v1, Vertex* v2)
{
    if (v1->m_Position.x > v2->m_Position.x)
        std::swap(v1, v2);

    int x1 = v1->m_Position.x;
    int x2 = v2->m_Position.x;

    for (int x = x1; x < x2; x++)
    {
        float t = (float)(x - x1) / (x2 - x1);
        Vertex::Interpolate(v, *v1, *v2, t);

        //透视矫正
        float rhw_z = v->m_Position.z;
        v->m_Position.z = 1.0f / rhw_z;
        v->m_Color /= rhw_z;
        v->m_Normal /= rhw_z;
        v->m_TexCoord /= rhw_z;

        //像素着色器
        m_Shader->FragmentShader(v);

        SetPixel(v->m_Position.x, v->m_Position.y, v->m_Position.z, v->m_Color);
    }
}

这样就完成了透视矫正,最终结果如下

correct_gloor.gif

标签:矫正,mat4,渲染器,Mat,Vector3f,float,投影,Position,Matrix4
From: https://www.cnblogs.com/dogwealth/p/18047243

相关文章

  • Leaflet实现极地坐标投影
    <!DOCTYPEhtml><htmllang="en"><head> <metacharset="utf-8"> <metaname="viewport"content="width=device-width,initial-scale=1"> <title>LeafletPolarGraticule-Arctic<......
  • GIS基础知识 - 坐标系、投影、EPSG:4326、EPSG:3857(转)
    原文:https://www.cnblogs.com/haolb123/p/16553036.html作者:我命由我不由天—hao最近接手一个GIS项目,需要用到PostGIS,GeoServer,OpenLayers等工具组件,遇到一堆地理信息相关的术语名词,在这里做一个总结。1.大地测量学(Geodesy)大地测量学是一门量测和描绘地球表面的学科,也包......
  • oc渲染器初始参数怎么设置?oc渲染器初始参数怎么弄
    ​OC渲染器以其用户友好的界面、卓越的渲染品质而受到众多初学者的欢迎,而且它使得创建逼真的视觉效果变得轻而易举。对于产品展示、建筑设计以及室内布局渲染来说,OC渲染器都能表现出优异的性能。下面,我们将介绍新手如何进行OC渲染器的基本初始设置,以便顺利开始他们的渲染之旅!oc渲......
  • 一个简单的Vulkan渲染器管线组织
    目录预计算管线逐帧管线逐帧预计算部分ShadowDepthPass逐帧计算部分G-BufferPassAOPassSSAOShadowPassCSMLightingPassPBR-IBLPassCompositePass总结问题参考资料延迟渲染管线核心在于先记录一遍每个像素上的信息,如Material、Normal、Albedo等,即G-Buffer,世界空间坐标可通......
  • WebGL之三维正射投影(高级)
    一,前言1,绘制一个正方体的数据,我们以前,上,右逆时针绘制,对面的用顺时针绘制。  2,数据准备cubeModel.js/***获得正方体所有顶点位置*@paramsideLength边长*/window.getCubeVertexesPosition=(sideLength)=>{//前constFRONT=[0.0,0.0,0.0......
  • GIS坐标系与投影
    GIS坐标系与投影笛卡尔坐标系空间直角坐标系空间直角坐标系是指坐标原点位于参考椭球的中心,Z轴指向参考椭球的北极,X轴指向起始子午面与赤道的交点,Y轴位于赤道面与X轴成90度夹角,并指向东,形成右手系。空间直角坐标系有右手系和左手系的区分:右手系右手系有两种表示方法,......
  • NLog 配置文件中布局渲染器(layout renderers)
    ​ NLog配置文件中,布局渲染器(layoutrenderers)是一种机制,用于在日志消息中插入动态内容或格式化信息。它们允许您将变量、属性、日期时间信息等添加到日志消息中,以便更详细地记录和分析日志。布局渲染器是在${}中包含的占位符,会在运行时替换为实际值。1、所有的布局参数......
  • OpenLayers6使用天地图&ldquo;经纬度投影(CGCS2000)&rdquo;和&ldquo;球面墨卡托投影(E
    转自:https://blog.csdn.net/nudtcadet/article/details/1029084581.封装生成图层类/***@fileOverview天地图WMTS服务API*@author<ahref=”https://blog.csdn.net/nudtcadet”>老胡</a>*@version1.0*/import{getWidth,getTopLeft}from'ol/extent';impo......
  • 线段上离p最近的点 - 投影方式
    点到线段的最近距离判断依据1)投影结果<0,则线段端点a离p最近2)投影结果>线段ab的长度,则线段端点b离p最近3)否则p在线段上的垂点为最近点 p与ab不共线时1)p在线段两侧2-a)p在线段内侧2-b)p在线段内侧2 p与ab共线时1) p在线段两侧 2-a)p在线段内侧2-b......
  • 如何在 C# 中使用 投影(Projection)
    如何在C#中使用投影(Projection)一线码农 ​关注他 你浏览过TA的个人主页投影(Projection)是一种可以将查询结果进行 塑性 的一种操作,你可以使用 投影 将一个object转成仅包含你需要属性的新对象,这篇文章中,我们就一起看看如何使用投影功......