【GAMES101】作业1(提高)与框架理解

这篇具有很好参考价值的文章主要介绍了【GAMES101】作业1(提高)与框架理解。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、作业描述

本次作业的任务是填写一个旋转矩阵和一个透视投影矩阵。给定三维下三个点 v0(2.0, 0.0, −2.0), v1(0.0, 2.0, −2.0), v2(−2.0, 0.0, −2.0), 你需要将这三个点的坐标变换为屏幕坐标并在屏幕上绘制出对应的线框三角形 (在代码框架中,我们已经提供了 draw_triangle 函数,所以你只需要去构建变换矩阵即可)。简而言之,我们需要进行模型、视图、投影、视口等变换来将三角形显示在屏幕上。在提供的代码框架中,我们留下了模型变换和投影变换的部分给你去完成。
以下是你需要在 main.cpp 中修改的函数(请不要修改任何的函数名和其他已经填写好的函数,并保证提交的代码是已经完成且能运行的):

• get_model_matrix(float rotation_angle):
逐个元素地构建模型变换矩阵并返回该矩阵。在此函数中,你只需要实现三维中绕 z 轴旋转的变换矩阵,而不用处理平移与缩放。

• get_projection_matrix(float eye_fov, float aspect_ratio, floatzNear, float zFar): 使用给定的参数逐个元素地构建透视投影矩阵并返回该矩阵。

• [Optional] main(): 自行补充你所需的其他操作。

当你在上述函数中正确地构建了模型与投影矩阵,光栅化器会创建一个窗口显示出线框三角形。由于光栅化器是逐帧渲染与绘制的,所以你可以使用 A 和 D 键去将该三角形绕 z 轴旋转(此处有一项提高作业,将三角形绕任意过原点的轴旋转)。当你按下 Esc 键时,窗口会关闭且程序终止。

另外,你也可以从命令行中运行该程序。你可以使用以下命令来运行和传递旋转角给程序,在这样的运行方式下,是不会生成任何的窗口,输出的结果图像会被存储在给定的文件中 (若未指定文件名,则默认存储在 output.png 中)。图像的存储位置在可执行文件旁,所以如果你的可执行文件是在 build 文件夹中,那么图像也会在该文件夹内。
命令行的使用命令如下:

./Rasterizer //循环运行程序,创建一个窗口显示,且你可以使 用A键 和D键 旋 转 三 角 形。

./Rasterizer −r 20 //运行程序并将三角形旋转20度,然后将结果存在output.png中

./Rasterizer −r 20 image.png //运行程序并将三角形旋转20度,然后将结果存在image.png中。

二、解

Eigen::Matrix4f get_model_matrix(float rotation_angle)//模型变换矩阵

Eigen::Matrix4f get_model_matrix(float rotation_angle)//模型变换矩阵
{
    Eigen::Matrix4f model = Eigen::Matrix4f::Identity();

    // TODO: Implement this function
    // Create the model matrix for rotating the triangle around the Z axis.
    // Then return it.
    Eigen::Matrix4f rotation;
    double fangle = rotation_angle / 180 * MY_PI;//角度转弧度,便于计算

    rotation << cos(fangle), -sin(fangle), 0, 0,
                sin(fangle), cos(fangle), 0, 0,
                0, 0, 1, 0,
                0, 0, 0, 1;//模型旋转矩阵(绕z轴)

    model = rotation * model;

    return model;
}

Eigen::Matrix4f get_projection_matrix(float eye_fov, float aspect_ratio, float zNear, float zFar)//投影变换矩阵

Eigen::Matrix4f get_projection_matrix(float eye_fov, float aspect_ratio,
                                      float zNear, float zFar)//投影变换矩阵
{
    // Students will implement this function

    Eigen::Matrix4f projection = Eigen::Matrix4f::Identity();

    // TODO: Implement this function
    // Create the projection matrix for the given parameters.
    // Then return it.
    Eigen::Matrix4f proj, ortho;

    proj << zNear, 0, 0, 0,
            0, zNear, 0, 0,
            0, 0, zNear + zFar, -zNear * zFar,
            0, 0, 1, 0;//透视投影矩阵

    double w, h, z;
    h = zNear * tan(eye_fov / 2) * 2;
    w = h * aspect_ratio;
    z = zFar - zNear;

    ortho << 2 / w, 0, 0, 0,
             0, 2 / h, 0, 0,
             0, 0, 2 / z, -(zFar+zNear) / 2,
             0, 0, 0, 1;//正交投影矩阵,因为在观测投影时x0y平面视角默认是中心,所以这里的正交投影就不用平移x和y了
             				
    projection = ortho * proj * projection;

    return projection;
}

这次作业本身难度并不大,更多是用来温习MVP的流程和公式,具体要点的都在代码注释里写出了,这里稍微多讲一些我对提高部分的实现,首先看到提高部分的要求:在 main.cpp 中构造一个函数,该函数的作用是得到绕任意
过原点的轴的旋转变换矩阵。Eigen::Matrix4f get_rotation(Vector3f axis, float angle)

这就很明显是lecture 04里提到过的罗德里格斯旋转公式了:
【GAMES101】作业1(提高)与框架理解根据给定的参数写出公式即可,这里要注意齐次坐标在这个公式应用时第四维的计算结果可能会影响最终成像,要记得改过来(推导过程见附录)。

Eigen::Matrix4f get_rotation(Vector3f axis, float angle) //任意轴旋转矩阵

Eigen::Matrix4f get_rotation(Vector3f axis, float angle) {//任意轴旋转矩阵(罗德里格斯旋转公式,默认轴过原点)
    double fangle = angle / 180 * MY_PI;
    Eigen::Matrix4f I, N, Rod;
    Eigen::Vector4f axi;
    Eigen::RowVector4f taxi;

    axi << axis.x(), axis.y(), axis.z(), 0;

    I.Identity();

    N << 0, -axis.z(), axis.y(), 0,
         axis.z(), 0, -axis.x(), 0,
         -axis.y(), axis.x(), 0, 0,
         0, 0, 0, 1;
    
    Rod = cos(fangle) * I + (1 - cos(fangle)) * axi * axi.transpose() + sin(fangle) * N;
    Rod(3, 3) = 1;//这里要注意,非齐次坐标的公式应用在齐次坐标上时记得运算完成后把矩阵的右下角改为1,否则会导致图形比例错误
    return Rod;
}

但是现在我有了函数输出的矩阵,应该怎么操作才能让它真正的应用到图像上呢?
当然就是加入一个类似mvp矩阵的传递接口,在main.cpp文件中我们可以看到类似set_projection这种函数,他的作用是将内部的投影矩阵设为给定矩阵 p,并传递给光栅化器,同样的我们也可以在rasterizer.cpp加入接口函数

void rst::rasterizer::set_rodrigues(const Eigen::Matrix4f& r)
{
    rodrigues = r;
}

接下来在draw函数里加入mvp矩阵的计算:

Eigen::Matrix4f mvp = projection * view * model * rodrigues;//计算mvp矩阵,因为绕任意轴旋转也是模型变换的一种,所以放在model矩阵的相邻位置

注意这步之前要在rasterizer.hpp里声明接口函数和矩阵:

class rasterizer
{
	...
  public:
    void set_rodrigues(const Eigen::Matrix4f& r);
  private:
    Eigen::Matrix4f rodrigues;
  ...
};

最后在main.cpp的main函数里面加入旋转轴和角度的参数输入:

std::cout << "Please enter the axis and angle:" << std::endl;
std::cin >> raxis.x() >> raxis.y() >> raxis.z() >> ra;//定义罗德里格斯旋转轴和角

加入按下键盘’r’键就绕指定轴旋转的判定:

if (rflag) //如果按下r了,就开始绕给定任意轴旋转
	r.set_rodrigues(get_rotation(raxis, rangle));
else
	r.set_rodrigues(get_rotation({ 0,0,1 }, 0));
	
	...
	
else if (key == 'r') {//按下r,再次绕给定任意轴旋转
   rflag = true;
   rangle += ra;
}

三、效果

我们输入时设定每按一次’R’就绕z轴(0,0,1)旋转90度:
【GAMES101】作业1(提高)与框架理解这是初始的图像:
【GAMES101】作业1(提高)与框架理解

这是按下三次‘A’(即绕z轴逆时针旋转30°)时的图像:
【GAMES101】作业1(提高)与框架理解

这是再按下四次‘D’(即顺时针旋转10°)时的图像:
【GAMES101】作业1(提高)与框架理解

这是再按下一次’R’(即逆时针旋转80°)时的图像:
【GAMES101】作业1(提高)与框架理解

可以自己尝试,比如绕(1,2,3)旋转10°的图像:
【GAMES101】作业1(提高)与框架理解

四、框架理解

这份作业框架包含了许多功能,提前了解的话有助于我们后续的学习,为方便c++基础一般的朋友理解,给大部分代码做了注释:

main.cpp

int main(int argc, const char** argv)
{
    float angle = 0;//定义角度
    bool command_line = false;//定义命令行开关标志,默认为关
    std::string filename = "output.png";//定义文件名,默认为output.png"

    Eigen::Vector3f raxis(0, 0, 1);
    double rangle = 0, ra;

    if (argc >= 3) {//接收到的参数大于三个,即检测到通过命令行传入参数时
        command_line = true;//设命令行开关标志为开
        angle = std::stof(argv[2]); //从命令行获取角度参数
        if (argc == 4) {//接收到的参数为四个,那么说明命令行输入了文件名参数
            filename = std::string(argv[3]);//从命令行获取文件名
        }
    }

    rst::rasterizer r(700, 700);//设定700*700像素的光栅器视口
    Eigen::Vector3f eye_pos = { 0, 0, 5 };//设定相机位置
    std::vector<Eigen::Vector3f> pos{ {2, 0, -2}, {0, 2, -2}, {-2, 0, -2} };//设定三顶点位置
    std::vector<Eigen::Vector3i> ind{ {0, 1, 2} };//设定三顶点序号,用于画图时确定需要处理几个顶点,这里表示的是三个顶点

    auto pos_id = r.load_positions(pos);
    auto ind_id = r.load_indices(ind);//保存多个图形的顶点和序号,本次作业只涉及一个图形,可以不管

    int key = 0;//键盘输入
    int frame_count = 0;//帧序号

    if (command_line) {//如果命令行开关标志为开(这一段if代码是为了应用命令行传入的参数,比如初始角度和文件名)

        r.clear(rst::Buffers::Color | rst::Buffers::Depth);//初始化帧缓存和深度缓存(本次作业本次作业只涉及一个图形,所以不涉及深度,可以不管)

        r.set_model(get_model_matrix(angle));
        r.set_view(get_view_matrix(eye_pos));
        r.set_projection(get_projection_matrix(45, 1, 0.1, 50));//向光栅器传入MVP矩阵
        r.set_rodrigues(get_rotation(raxis, rangle));

        r.draw(pos_id, ind_id, rst::Primitive::Triangle);//开始画图
        cv::Mat image(700, 700, CV_32FC3, r.frame_buffer().data());
        image.convertTo(image, CV_8UC3, 1.0f);
        cv::imwrite(filename, image);//写入文件名

        return 0;
    }

    bool rflag = false;

    std::cout << "Please enter the axis and angle:" << std::endl;
    std::cin >> raxis.x() >> raxis.y() >> raxis.z() >> ra;//定义罗德里格斯旋转轴和角

    while (key != 27) {//只要没有检测到按下ESC就循环(ESC的ASCII码是27)

        r.clear(rst::Buffers::Color | rst::Buffers::Depth);
        r.set_model(get_model_matrix(angle));
        r.set_view(get_view_matrix(eye_pos));
        r.set_projection(get_projection_matrix(45, 1, 0.1, 50));

        if (rflag) //如果按下r了,就开始绕给定任意轴旋转
            r.set_rodrigues(get_rotation(raxis, rangle));
        else
            r.set_rodrigues(get_rotation({ 0,0,1 }, 0));

        r.draw(pos_id, ind_id, rst::Primitive::Triangle);

        cv::Mat image(700, 700, CV_32FC3, r.frame_buffer().data());
        image.convertTo(image, CV_8UC3, 1.0f);
        cv::imshow("image", image);//显示图像
        key = cv::waitKey(10);//等待10号码接收键盘输入,没有输入就为空,图像不做调整,保持原状

        std::cout << "frame count: " << frame_count++ << '\n';//显示当前是第几帧画面

        if (key == 'a') {//按下a,逆时针旋转10°
            angle += 10;
        }
        else if (key == 'd') {//按下d,顺时针旋转10°
            angle -= 10;
        }
        else if (key == 'r') {//按下r,绕给定任意轴旋转
            rflag = true;
            rangle += ra;
        }
    }

    return 0;

}

rasterization.cpp

#include <algorithm>
#include "rasterizer.hpp"
#include <opencv2/opencv.hpp>
#include <math.h>
#include <stdexcept>


rst::pos_buf_id rst::rasterizer::load_positions(const std::vector<Eigen::Vector3f> &positions)
{
    auto id = get_next_id();
    pos_buf.emplace(id, positions);

    return {id};
}

rst::ind_buf_id rst::rasterizer::load_indices(const std::vector<Eigen::Vector3i> &indices)
{
    auto id = get_next_id();
    ind_buf.emplace(id, indices);

    return {id};
}

// Bresenham's line drawing algorithm
// Code taken from a stack overflow answer: https://stackoverflow.com/a/16405254
void rst::rasterizer::draw_line(Eigen::Vector3f begin, Eigen::Vector3f end)//直线扫描画线,本次作业没有用到,这段代码的原理是用的中点画线算法
{
    auto x1 = begin.x();
    auto y1 = begin.y();
    auto x2 = end.x();
    auto y2 = end.y();//获得传入的线段起始点坐标

    Eigen::Vector3f line_color = {255, 255, 255};//设置默认线段颜色

    int x,y,dx,dy,dx1,dy1,px,py,xe,ye,i;

    dx=x2-x1;
    dy=y2-y1;
    dx1=fabs(dx);
    dy1=fabs(dy);
    px=2*dy1-dx1;
    py=2*dx1-dy1;

    if(dy1<=dx1)//如果线段斜率的绝对值小于等于1就执行下列代码,这样区分是因为像素点坐标是整数,而如果斜率的绝对值小于1,在对线段进行采样时,每次x坐标加1,y坐标要么是加1,要么是减1,要么不变,简化了计算,而当斜率的绝对值大于1时,斜率的倒数的绝对值就小于1,就可以每次对y坐标加1,也是一样的效果
    {
        if(dx>=0)
        {
            x=x1;
            y=y1;
            xe=x2;
        }
        else
        {
            x=x2;
            y=y2;
            xe=x1;
        }//这一段if else是为了保证x,y是起始点的坐标(从左到右,最左为起始点)
        Eigen::Vector3f point = Eigen::Vector3f(x, y, 1.0f);//转换齐次坐标
        set_pixel(point,line_color);//设置起始点颜色
        for(i=0;x<xe;i++)//从起始点开始遍历处理线段上每个点
        {
            x=x+1;
            if(px<0)//若px小于0,说明中点在线段之上,y坐标还不用加1
            {
                px=px+2*dy1;
            }
            else
            {
                if((dx<0 && dy<0) || (dx>0 && dy>0))//根据斜率判断x坐标加1时y坐标是加还是减
                {
                    y=y+1;
                }
                else
                {
                    y=y-1;
                }
                px=px+2*(dy1-dx1);
            }
//            delay(0);
            Eigen::Vector3f point = Eigen::Vector3f(x, y, 1.0f);
            set_pixel(point,line_color);//将得到的新点画上颜色
        }
    }
    else//如果线段斜率的绝对值大于1,基本处理与上面相似,只是是以y坐标为基准处理,不再赘述
    {
        if(dy>=0)
        {
            x=x1;
            y=y1;
            ye=y2;
        }
        else
        {
            x=x2;
            y=y2;
            ye=y1;
        }
        Eigen::Vector3f point = Eigen::Vector3f(x, y, 1.0f);
        set_pixel(point,line_color);
        for(i=0;y<ye;i++)
        {
            y=y+1;
            if(py<=0)
            {
                py=py+2*dx1;
            }
            else
            {
                if((dx<0 && dy<0) || (dx>0 && dy>0))
                {
                    x=x+1;
                }
                else
                {
                    x=x-1;
                }
                py=py+2*(dx1-dy1);
            }
//            delay(0);
            Eigen::Vector3f point = Eigen::Vector3f(x, y, 1.0f);
            set_pixel(point,line_color);
        }
    }
}

auto to_vec4(const Eigen::Vector3f& v3, float w = 1.0f)//转换齐次坐标
{
    return Vector4f(v3.x(), v3.y(), v3.z(), w); 
}

void rst::rasterizer::draw(rst::pos_buf_id pos_buffer, rst::ind_buf_id ind_buffer, rst::Primitive type)
{
    if (type != rst::Primitive::Triangle)
    {
        throw std::runtime_error("Drawing primitives other than triangle is not implemented yet!");
    }
    auto& buf = pos_buf[pos_buffer.pos_id]; //根据传入的id参数获取图形顶点
    auto& ind = ind_buf[ind_buffer.ind_id];//根据传入的id参数获取图形顶点序号

    float f1 = (100 - 0.1) / 2.0;
    float f2 = (100 + 0.1) / 2.0;

    Eigen::Matrix4f mvp = projection * view * model * rodrigues;//计算mvp矩阵
    for (auto& i : ind)//顺序处理所有图形顶点
    {
        Triangle t;

        Eigen::Vector4f v[] = {
                mvp * to_vec4(buf[i[0]], 1.0f),
                mvp * to_vec4(buf[i[1]], 1.0f),
                mvp * to_vec4(buf[i[2]], 1.0f)
        };//计算获得三个顶点经过mvp转换后的坐标

        for (auto& vec : v) {//齐次坐标除以第四维转常规坐标
            vec /= vec.w();
        }

        for (auto & vert : v)
        {
            vert.x() = 0.5*width*(vert.x()+1.0);
            vert.y() = 0.5*height*(vert.y()+1.0);
            vert.z() = vert.z() * f1 + f2;
        }//视口变换

        for (int i = 0; i < 3; ++i)
        {
            t.setVertex(i, v[i].head<3>());
            t.setVertex(i, v[i].head<3>());
            t.setVertex(i, v[i].head<3>());
        }

        t.setColor(0, 255.0,  0.0,  0.0);
        t.setColor(1, 0.0  ,255.0,  0.0);
        t.setColor(2, 0.0  ,  0.0,255.0);

        rasterize_wireframe(t);
    }
}

void rst::rasterizer::rasterize_wireframe(const Triangle& t)//画出三角形的框架(三条边)
{
    draw_line(t.c(), t.a());
    draw_line(t.c(), t.b());
    draw_line(t.b(), t.a());
}

void rst::rasterizer::set_model(const Eigen::Matrix4f& m)
{
    model = m;
}

void rst::rasterizer::set_view(const Eigen::Matrix4f& v)
{
    view = v;
}

void rst::rasterizer::set_projection(const Eigen::Matrix4f& p)
{
    projection = p;
}

void rst::rasterizer::set_rodrigues(const Eigen::Matrix4f& r)
{
    rodrigues = r;
}

void rst::rasterizer::clear(rst::Buffers buff)//初始化,设置帧缓冲内所有像素颜色为(0,0,0),深度缓冲的所有像素深度为无限大
{
    if ((buff & rst::Buffers::Color) == rst::Buffers::Color)
    {
        std::fill(frame_buf.begin(), frame_buf.end(), Eigen::Vector3f{0, 0, 0});
    }
    if ((buff & rst::Buffers::Depth) == rst::Buffers::Depth)
    {
        std::fill(depth_buf.begin(), depth_buf.end(), std::numeric_limits<float>::infinity());
    }
}

rst::rasterizer::rasterizer(int w, int h) : width(w), height(h)//根据宽高比设置帧缓冲大小和深度缓冲大小(大小就是像素个数)
{
    frame_buf.resize(w * h);
    depth_buf.resize(w * h);
}

int rst::rasterizer::get_index(int x, int y)//根据坐标求像素在缓冲区的序号
{
    return (height-y)*width + x;
}

void rst::rasterizer::set_pixel(const Eigen::Vector3f& point, const Eigen::Vector3f& color)//将屏幕像素点 (x, y) 设 为 (r, g, b) 的颜色,并写入相应的帧缓冲区位置。
{
    //old index: auto ind = point.y() + point.x() * width;
    if (point.x() < 0 || point.x() >= width ||
        point.y() < 0 || point.y() >= height) return;//如果像素点坐标超出屏幕范围,不处理
    auto ind = (height-1-point.y())*width + point.x();//这一步是根据坐标求像素在帧缓冲区的序号
    frame_buf[ind] = color;//将屏幕像素点 (x, y) 设 为 (r, g, b) 的颜色,并写入相应的帧缓冲区位置。
}

五、附件

罗德里格斯旋转公式推导

【GAMES101】作业1(提高)与框架理解

首先第一步,可以将原向量S分解为与旋转轴a平行和垂直的两个向量S∥和S⊥,其中,S∥为S在a轴上的投影,自然可以表示为
S ∥ = a ∗ ( S ⋅ a ) , S_∥ = a * (S·a), S=a(Sa)
又因为列向量间的点乘可以表示为一个向量的转置(行向量)和另一个向量的乘积,所以
S ∥ = a ∗ a T ∗ S , S_∥ = a * a^T * S , S=aaTS
自然也就可以得到
S ⊥ = S − S ∥ = S − a ∗ a T ∗ S \begin{equation*} \begin{split} S_⊥ & = S - S_∥\\ & = S - a * a^T * S \end{split} \end{equation*} S=SS=SaaTS
【GAMES101】作业1(提高)与框架理解

接下来再关注旋转后的向量 S R O T S_{ROT} SROT,我们也可以将它分解为与旋转轴 a a a平行和垂直的两个向量 S R O T ∥ S_{ROT∥} SROT S R O T ⊥ S_{ROT⊥} SROT,其中 S R O T ∥ S_{ROT∥} SROT自然是与 S ∥ S_{∥} S相等的(因为是绕轴旋转,所以平行与旋转轴的分量不会变化),所以接下来的关键就是在于求出 S R O T ⊥ S_{ROT⊥} SROT了,同样的,我们可以用垂直于旋转轴 a a a的平面上两个互相垂直的向量 b b b c c c去表示它,其中一个向量是 S ⊥ S_{⊥} S,而另一个向量我们则可以利用 a × b a × b a×b得到,就此,我们得到了一个局部坐标系,他们的轴的单位向量分别为 a a a
b = S ⊥ / ∣ S ⊥ ∣ , b = S⊥ / |S⊥|, b=S⊥/∣S⊥∣
c = a × b = a × S ⊥ ∣ S ⊥ ∣ = a × S ∣ S ⊥ ∣ \begin{equation*} \begin{split} c & = a × b \\ & = a × \frac{S_⊥} {|S_⊥|}\\ & = a × \frac{S} {|S_⊥|} \end{split} \end{equation*} c=a×b=a×SS=a×SS
其中 c c c的最后一步转换的依据为:
a × S ⊥ = ∣ a ∣ ∗ ∣ S ⊥ ∣ ∗ s i n θ ∗ n (因为 a 和 S ⊥ 的夹角 θ 为 90 ° ) = ∣ a ∣ ∗ ∣ S ⊥ ∣ ∗ n \begin{equation*} \begin{split} a × S⊥ & = |a| * |S⊥| * sinθ * n (因为a和S_⊥的夹角θ为90°)\\ & = |a|*|S⊥| * n \end{split} \end{equation*} a×S=aS⊥∣sinθn(因为aS的夹角θ90°=aS⊥∣n
a × S = ∣ a ∣ ∗ ∣ S ∣ ∗ s i n θ 1 ∗ n (因为 ∣ S ∣ ∗ s i n θ 1 = ∣ S ⊥ ∣ ) = ∣ a ∣ ∗ ∣ S ⊥ ∣ ∗ n \begin{equation*} \begin{split} a × S & = |a| * |S|*sinθ_1 * n (因为|S|*sinθ_1 = |S⊥| )\\ & = |a| * |S⊥| * n \end{split} \end{equation*} a×S=aSsinθ1n(因为Ssinθ1=S⊥∣=aS⊥∣n
【GAMES101】作业1(提高)与框架理解

那么 S R O T ⊥ S_{ROT⊥} SROT就可以表示为
S R O T ⊥ = ∣ S ⊥ ∣ ∗ c o s θ ∗ b + ∣ S ⊥ ∣ ∗ s i n θ ∗ c = ∣ S ⊥ ∣ ∗ c o s θ ∗ S ⊥ ∣ S ⊥ ∣ + ∣ S ⊥ ∣ ∗ s i n θ ∗ ( a × b ) = c o s θ ∗ S ⊥ + ∣ S ⊥ ∣ ∗ s i n θ ∗ ( a × S ⊥ ∣ S ⊥ ∣ ) = c o s θ ∗ S ⊥ + s i n θ ∗ ( a × S ) = c o s θ ∗ ( S − a ∗ a T ∗ S ) + s i n θ ∗ ( a × S ) = c o s θ ∗ ( S − a ∗ a T ∗ S ) + s i n θ ∗ ( R a ∗ S ) ( R a 为向量 a 的叉乘矩阵) \begin{equation*} \begin{split} S_{ROT⊥} & = | S_⊥| * cosθ * b + | S_⊥| * sinθ * c\\ & = | S_⊥| * cosθ * \frac{S_⊥} {|S_⊥|} + | S_⊥| * sinθ * (a × b)\\ & = cosθ * S_⊥ + | S_⊥| * sinθ * (a × \frac{S_⊥} {|S_⊥|})\\ & = cosθ * S_⊥ + sinθ * (a × S)\\ & = cosθ * (S - a * a^T * S) + sinθ * (a × S)\\ & = cosθ * (S - a * a^T * S) + sinθ * (R_a * S) (R_a为向量a的叉乘矩阵) \end{split} \end{equation*} SROT=Scosθb+Ssinθc=ScosθSS+Ssinθ(a×b)=cosθS+Ssinθ(a×SS)=cosθS+sinθ(a×S)=cosθ(SaaTS)+sinθ(a×S)=cosθ(SaaTS)+sinθ(RaS)Ra为向量a的叉乘矩阵)
最后我们求得
S R O T = S R O T ⊥ + S R O T ∥ = c o s θ ∗ ( S − a ∗ a T ∗ S ) + s i n θ ∗ ( R a ∗ S ) + a ∗ a T ∗ S = c o s θ ∗ S + ( 1 − c o s θ ) ∗ ( a ∗ a T ∗ S ) + s i n θ ∗ ( R a ∗ S ) \begin{equation*} \begin{split} S_{ROT} & = S_{ROT⊥} + S_{ROT∥}\\ & = cosθ * (S - a * a^T * S) + sinθ * (R_a * S) + a * a^T * S \\ & = cosθ * S + (1 - cosθ) * (a * a^T * S) + sinθ * (R_a * S) \end{split} \end{equation*} SROT=SROT+SROT=cosθ(SaaTS)+sinθ(RaS)+aaTS=cosθS+(1cosθ)(aaTS)+sinθ(RaS)

中点画线算法

中点算法会在后面的课程提到,想提前了解的朋友可以看下这个ppt,看看中点算法是怎么推导的:
直线扫描转换-中点算法

源代码

附上源代码,网页看不清或者想要自己运行尝试修改的朋友可以自行下载:
CSDN:【GAMES101】作业1(提高)
GITHUB:【GAMES101】作业合集文章来源地址https://www.toymoban.com/news/detail-411116.html

到了这里,关于【GAMES101】作业1(提高)与框架理解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • GAMES101作业2

    在屏幕上画出一个实心三角形, 换言之,栅格化一个三角形。上一次作业中,在视口变化之后,我们调用了函数 rasterize_wireframe(const Triangle t)。但这一次,你需要自己填写并调用 函数 rasterize_triangle(const Triangle t)。 该函数的内部工作流程如下: 创建三角形的 2 维 bounding box。

    2024年02月16日
    浏览(43)
  • GAMES101 作业0

    课上提供的环境是Linux, 还需要安装Vitrual Box和创建虚拟机,省事就直接在Windows系统下Visual Studio下操作了。 简单的环境配置: 下载Eigen 的库 在工程属性中添加目录: 2处地方 注意: 刚添加完后,我新建main.cpp后, 引入头文件 路径也没有设置错啊,但是就是找不到。 直到看

    2024年02月16日
    浏览(42)
  • games101 作业3

    1.项目才打开时无法运行。 解决方法: 切换成c++17 解决方法引用: Games101 作业3 环境问题 - 知乎  注:知乎里面的关于越界限制的控制不适用,虽然可以解决部分作业的问题,但是在bump里面依然会出现越界错误。应该用以下大佬的代码。  2.出现越界错误   解决方法: 在头

    2023年04月25日
    浏览(39)
  • GAMES101 作业1

    作业pa1对应的是GAMES101课程Lecture02到Lecture04这三节课的内容,主要是用于巩固空间中的物体投影到相机平面的整个过程。 说在前面,本文是在左手系下进行讨论的。 粗略地看一遍我们可以知晓main函数的流程: ①设定一些基本的初始参数并初始化源代码给出的 光栅化类raste

    2024年02月09日
    浏览(35)
  • GAMES101:作业3

    附其他所有作业超链接如下: Games101 作业0: 作业0 Games101 作业1: 作业1 Games101 作业2: 作业2 Games101 作业3: 作业3 Games101 作业4: 作业4 Games101 作业5: 作业5 Games101 作业6: 作业6 Games101 作业7: 作业7 完整代码获取途径: https://github.com/liupeining/Games_101_homework 照旧把这段代码

    2024年02月04日
    浏览(41)
  • games101-3 BRDF101

    本文基于知乎Maple对brdf的文章,在此基础又收集了一些其它来源的关于brdf的文章,希望能够完全理解记忆相关知识 关于Jakub Boksansky的文章,看的过程中又去搜集了很多其它文章来理解,发现已经超出了我目前的知识厚度,因此只会简单的翻译一下我能理解的部分,感兴趣的

    2024年04月25日
    浏览(32)
  • 【GAMES101】03 Transformation

    1、Scale(缩放)  2、Reflection Matrix(反射矩阵)  3、Shear Matrix(剪切矩阵)  4、Rotation Matrix(旋转矩阵) 推导过程:     5、Translation Matrix(平移矩阵) 平移操作不属于线性变换的范畴。 引入 齐次坐标 ,通过增加维度,来将平移坐标写成同样的矩阵形式。 很显然,平移无

    2024年02月02日
    浏览(44)
  • Games101学习笔记1

    2023-08-10开始接触图形学 参考博客:GAMES101 梳理 / 个人向图形学笔记_games101笔记_river_of_sebajun的博客-CSDN博客  向量的长度 向量的单位化  向量的点乘 点乘结果是一个数字,叉乘结果是垂直已知向量的向量  b向量投影到a向量上  矩阵的乘法 二维变换 线性变换    切边  旋转

    2024年02月13日
    浏览(36)
  • Games101笔记-模型、视图、投影

    在旋转点,旋转矩阵的逆等于矩阵的转置 左边3*3是线性变换,右边一列是仿射变换(平移) 先线性再平移 x叉乘y得到z,y叉乘z得到x, xyz给任何两个可以得到另一个 (循环对称) z叉乘x得到y,所以y是反的 任意绕3d轴旋转可以分解为绕x,y,z轴旋转。三个角为欧拉角 罗德里格斯旋

    2024年03月19日
    浏览(52)
  • GAMES101 OpenCV环境安装

    作业2中用到了OpenCV 下载OpenCV 源码 OpenCV 4.5.4 下载OpenCV_contrib OpenCV_contrib 4.5.4 注意:这两个源码的版本号要一致 将源码解压后,不要放在有中文路径的目录 使用管理员权限打开CMake, 一开始未使用,发现点击Configure时会报错,使用管理员权限打开CMake后,点击Configure未报错。

    2024年02月17日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包