【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里提到过的罗德里格斯旋转公式了:
在这里插入图片描述根据给定的参数写出公式即可,这里要注意齐次坐标在这个公式应用时第四维的计算结果可能会影响最终成像,要记得改过来(推导过程见附录)。

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度:
在这里插入图片描述这是初始的图像:
在这里插入图片描述

这是按下三次‘A’(即绕z轴逆时针旋转30°)时的图像:
在这里插入图片描述

这是再按下四次‘D’(即顺时针旋转10°)时的图像:
在这里插入图片描述

这是再按下一次’R’(即逆时针旋转80°)时的图像:
在这里插入图片描述

可以自己尝试,比如绕(1,2,3)旋转10°的图像:
在这里插入图片描述

四、框架理解

这份作业框架包含了许多功能,提前了解的话有助于我们后续的学习,为方便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) 的颜色,并写入相应的帧缓冲区位置。
}

五、附件

罗德里格斯旋转公式推导

在这里插入图片描述

首先第一步,可以将原向量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
在这里插入图片描述

接下来再关注旋转后的向量 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
在这里插入图片描述

那么 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】作业合集

  • 32
    点赞
  • 61
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 10
    评论
在这部分的课程中,我们将专注于使用光线追踪来渲染图像。在光线追踪中 最重要的操作之一就是找到光线与物体的交点。一旦找到光线与物体的交点,就 可以执行着色并返回像素颜色。在这次作业中,我们需要实现两个部分:光线的 生成和光线与三角的相交。本次代码框架的工作流程为: 1. 从 main 函数开始。我们定义场景的参数,添加物体(球体或三角形)到场景 中,并设置其材质,然后将光源添加到场景中。 2. 调用 Render(scene) 函数。在遍历所有像素的循环里,生成对应的光线并将 返回的颜色保存在帧缓冲区(framebuffer)中。在渲染过程结束后,帧缓冲 区中的信息将被保存为图像。 3. 在生成像素对应的光线后,我们调用 CastRay 函数,该函数调用 trace 来 查询光线与场景中最近的对象的交点。 4. 然后,我们在此交点执行着色。我们设置了三种不同的着色情况,并且已经 为你提供了代码。 你需要修改的函数是: • Renderer.cpp 中的 Render():这里你需要为每个像素生成一条对应的光 线,然后调用函数 castRay() 来得到颜色,最后将颜色存储在帧缓冲区的相 应像素中。 • Triangle.hpp 中的 rayTriangleIntersect(): v0, v1, v2 是三角形的三个 顶点, orig 是光线的起点, dir 是光线单位化的方向向量。 tnear, u, v 是你需 要使用我们课上推导的 Moller-Trumbore 算法来更新的参数。
通过参与Games101作业3的学习和实践,我可以提供以下几点来提高自己的能力。 首先,通过完成作业任务,我可以更全面地了解游戏开发的流程和技术原理。在作业过程中,我需要学习和掌握各种游戏开发所需的基础知识,如图形学、物理模拟、算法等。这些知识将对我今后的游戏开发之路起到坚实的基础作用。 其次,通过作业中的编程实践,我可以提高自己的编程能力。在完成作业任务时,我需要运用所学的编程知识和技巧来实现游戏中的各种功能和效果。这样的实践将帮助我熟悉编程语言和工具的使用,提高我的编程能力和解决问题的能力。 此外,作业3还包括了一些团队协作的要素,如合作完成多人游戏开发任务。通过与他人的合作,我可以学习团队合作的重要性以及如何与他人有效地进行沟通和协作。这对我今后的职场发展将非常有帮助。 最后,通过作业3的学习和实践,我可以培养自己的创造力和创新能力。在游戏开发过程中,我将接触到各种不同的游戏设计思路和创意。这将激发我的自主思考和创造力,帮助我培养独立思考和解决问题的能力。 总之,通过参与Games101作业3的学习和实践,我将在游戏开发知识、编程能力、团队合作和创造力等方面得到提高。这将为我今后的游戏开发之路和职业生涯打下坚实的基础。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ycr的帐号

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值