games101 hw1

原理

背景

将三维空间中一个旋转的三角形,显示在屏幕上。
假设三角形初始点位为 v0(2.0, 0.0, −2.0), v1(0.0, 2.0, −2.0), v2(−2.0, 0.0, −2.0), 你需要将这三个点的坐标变换为屏幕坐标并在屏幕上绘制出对应的线框三角形。

mvp

AI回答:
在计算机图形领域,MVP指的是模型-视图-投影矩阵(Model-View-Projection matrix)。它是将三维模型空间的点变换到二维屏幕上的关键变换矩阵链,包括以下几个步骤:

  1. 模型变换(Model Transformation):将物体从其本地模型空间变换到世界空间,比如平移、旋转、缩放。
  2. 视图变换(View Transformation):将世界空间变换到相机(观察者)空间,模拟相机位置和方向。
  3. 投影变换(Projection Transformation):将相机空间变换到裁剪空间,实现透视投影或正交投影。

总结:
MVP矩阵 = 投影矩阵 × 视图矩阵 × 模型矩阵


如果一个运动的物体要实现“动起来”,通常需要对物体应用变换(Transformation),即:

  • 平移(Translation):使物体移动到新位置
  • 旋转(Rotation):改变物体的方向
  • 缩放(Scaling):改变物体大小

这些变换可以组合在一起,形成一个变换矩阵,然后乘到模型矩阵上,实现物体在空间中的运动。

世界空间(World Space)是三维计算机图形中的一个坐标系,用来描述场景中的所有物体相对于一个统一的坐标系统。在这个空间里,场景中的每个物体都以其在整个场景中的位置、方向和大小进行定义。

简单来说:

  • 模型空间(Model Space):每个物体本地的坐标系统,描述物体的相对结构。
  • 世界空间(World Space):所有物体的模型空间经过模型变换(如平移、旋转、缩放)后,放在一个共同的坐标系中。
  • 相机空间(View Space):从相机的视角来看场景。
  • 裁剪空间/投影空间(Clip/Projection Space):经过投影变换后,用于最终渲染到屏幕。

一个运动的物体要“动起来”,通常需要修改模型矩阵,是因为模型矩阵控制着物体在模型空间到世界空间的变换。

具体来说:

  • 模型矩阵包含平移、旋转和缩放等变换,定义了物体在场景中的位置、方向和大小。
  • 改变模型矩阵就相当于“移动”物体,或者调整它的朝向和比例,从而实现物体在空间中的运动。

通过不断更新模型矩阵,物体就能在场景中“动起来”。

代码架构

rasterizer

用于实现基本的线框和三角形光栅化(绘制)功能,主要用于软件渲染管线中。核心内容包括加载顶点和索引数据、设置变换矩阵、绘制线条和三角形、以及管理帧缓冲和深度缓冲。

主要结构和功能简介

1. 枚举和类型定义
  • Buffers:定义了缓冲区类型(颜色、深度),支持按位操作组合。
  • Primitive:定义了图元类型(线、三角形)。
  • pos_buf_id、ind_buf_id:封装了顶点位置缓冲和索引缓冲的ID,确保类型安全。
2. 类:rasterizer
  • 成员变量:

    • 变换矩阵(model、view、projection)
    • 缓冲区(顶点位置、索引、帧缓冲、深度缓冲)
    • 窗口宽度和高度
    • 一个自动递增的ID生成器
  • 核心方法:

    • load_positions()load_indices():加载顶点和索引数据,返回对应的ID。
    • set_model(), set_view(), set_projection():设置变换矩阵。
    • clear():清空颜色缓冲和深度缓冲。
    • draw():绘制三角形(支持线框轮廓作业通过 rasterize_wireframe() 实现)。
    • set_pixel():设置像素颜色到帧缓冲。
    • frame_buffer():返回当前帧缓冲内容。
  • 私有方法:

    • draw_line():实现了 Bresenham 线段绘制算法,用于绘制线段。
    • rasterize_wireframe():绘制三角形的边框线。
    • get_index():根据像素点坐标计算缓冲区索引。

深度缓冲区作用

深度缓冲区(Depth Buffer,也叫Z缓冲区)用来管理和实现隐藏面消除,确保在渲染场景时,前面(离观察者近)的像素遮挡后面(离观察者远)的像素。

  • 隐藏面处理:在绘制每个像素时,比较当前像素的深度值(距离相机的远近)与已存储的深度值。
  • 遮挡关系:只保留离观察者更近的像素,屏蔽被遮挡的远处的像素,从而实现正确的遮挡关系。

具体流程

  1. 当绘制一个像素时,计算其深度(z值)。
  2. 将该深度值与深度缓冲区中对应位置的值进行比较。
  3. 如果新像素更接近(深度值小),则更新颜色和深度缓冲区中的值。
  4. 如果远离(深度值大),则跳过该像素,不更新。

代码中的表现

clear() 函数中,深度缓冲区被初始化为无穷大(std::numeric_limits<float>::infinity()),表示还没有进行任何像素绘制,所有像素的深度都比任何实际像素都远。

if ((buff & rst::Buffers::Depth) == rst::Buffers::Depth)
{std::fill(depth_buf.begin(), depth_buf.end(), std::numeric_limits<float>::infinity());
}

处理流程

  • 加载顶点和索引后,设置变换矩阵。
  • 调用 draw() 方法,进行模型-视图-投影变换,投影到屏幕空间。
  • 裁剪、归一化后,调用线段绘制或三角形光栅化函数。
  • 图元边界由 draw_line()rasterize_wireframe() 实现。

注释亮点

  • 使用 Eigen 作为线性代数库。
  • 提供类型安全的缓冲区识别方式。
  • 支持基础的线段绘制(Bresenham算法)。
  • 支持深度测试,启用深度缓冲区。
  • 简单、直接、易扩展的设计架构。

基础版

注:只显示三角形,不旋转

\\main.cpp 中只有下面的代码完成基础版while (key != 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));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);std::cout << "frame count: " << frame_count++ << '\n';if (key == 'a') {angle += 10;}else if (key == 'd') {angle -= 10;}}

我们根据代码重新理解一下上面的知识。

  1. 模型变换(Model Transformation):将物体从其本地模型空间变换到世界空间。可以看到get_model_matrix(angle)的函数只是构建了一个旋转矩阵。所以原本的三角形,即std::vector<Eigen::Vector3f> pos{{2, 0, -2}, {0, 2, -2}, {-2, 0, -2}};就是在世界坐标系下面表示的。旋转三角形,即直接在世界坐标系下面旋转,就可以做到在屏幕上旋转
  2. 视图变换(View Transformation):将世界空间变换到相机(观察者)空间,模拟相机位置和方向。代码在get_view_matrix(eye_pos)中,也可以看到相机的位置用世界坐标表示,但是经过视图变换后坐标系将以相机为原点。
  3. 投影变换(Projection Transformation):将相机空间变换到裁剪空间,实现透视投影或正交投影。需要实现:get_projection_matrix(float eye_fov, float aspect_ratio, float zNear, float zFar) 输入是 eyefov表示视野角度,aspect_ratio表示xy的比例。需要转换一下才能套公式。

三个矩阵怎么计算的很多博主都写过,我这里就不写了

在这里插入图片描述
正交基之间的过渡矩阵非常好求解
xyz 对应的正交基是(1,0,0) (0,1,0),(0,0,1) 所以矩阵A是单位矩阵E
$\hat{g} \times \hat{t} , \hat{t}, \hat{g} $是另一组正交基,矩阵 B = ( g ^ × t ^ , t ^ , g ^ ) B=(\hat{g} \times \hat{t} , \hat{t}, \hat{g}) B=(g^×t^,t^,g^)
则, A P = B ⇒ P = A − 1 B = E B = B AP=B \Rightarrow P=A^{-1}B = EB=B AP=BP=A1B=EB=B
而B是正交基组成的,它逆矩阵是它的转置

所以这个要求是给定的 t ^ , g ^ \hat{t}, \hat{g} t^,g^是单位向量,且正交,才能推出老师给的公式

在这里插入图片描述

参考 :https://www.cnblogs.com/fijiisland/p/13993749.html

函数void rst::rasterizer::draw(...)解析

传入的参数是之前生成的“顶点坐标缓冲id”以及“顶点索引缓冲id”。
首先三角形有三个顶点

std::vector<Eigen::Vector3f> pos{{2, 0, -2}, {0, 2, -2}, {-2, 0, -2}};
auto pos_id = r.load_positions(pos);

把顶点使用map存起来,并返回顶点的id

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

然后这三个顶点有三个索引,同样存在map里面


std::vector<Eigen::Vector3i> ind{{0, 1, 2}};
auto ind_id = r.load_indices(ind);rst::ind_buf_id rst::rasterizer::load_indices(const std::vector<Eigen::Vector3i> &indices)
{auto id = get_next_id();// std::map<int, std::vector<Eigen::Vector3i>> ind_buf;ind_buf.emplace(id, indices);return {id};
}

使用的时候,用id在map里取顶点与索引

// 向draw函数传入之前生成的“顶点坐标缓冲id”以及“顶点索引缓冲id”
void rst::rasterizer::draw(rst::pos_buf_id pos_buffer, rst::ind_buf_id ind_buffer, rst::Primitive type)
{// 当前作业中,代码支持的图元(Primitive)类型仅为rst::Primitive::Triangle,即三角形if (type != rst::Primitive::Triangle){throw std::runtime_error("Drawing primitives other than triangle is not implemented yet!");}// 容易得出,auto推导出的类型为std::vector<Eigen::Vector3f>// buf取得对应的图元顶点坐标vectorauto& buf = pos_buf[pos_buffer.pos_id];// ind取得对应的图元顶点索引vectorauto& ind = ind_buf[ind_buffer.ind_id];// 下面会解释f1、f2的含义float f1 = (100 - 0.1) / 2.0;float f2 = (100 + 0.1) / 2.0;// 最终的变换矩阵为投影、视图、模型矩阵的点乘Eigen::Matrix4f mvp = projection * view * model;for (auto& i : ind){// 实例化一个TriangleTriangle t;// 构造一个元素为4行向量的数组v,向内插入mvp矩阵对顶点索引对应顶点坐标的变换点// 为了和mvp进行运算,将每个顶点坐标转为一个Eigen::Vector4f,并规定w值为1Eigen::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)};// 透视除法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;}// 将变换好的顶点坐标传入三角形实例tfor (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);}
}

三个顶点的位置都计算完毕,现在开始画三角形:

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());
}

这里使用的是Bresenham’s line drawing algorithm。就不多写了。
计算出画在屏幕上的点的位置之后使用函数:

void rst::rasterizer::set_pixel(const Eigen::Vector3f& point, const Eigen::Vector3f& color)
{//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-point.y())*width + point.x();frame_buf[ind] = color;
}

这里在图像上对应的位置填上颜色。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/news/909215.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Mybatis逆向工程详解(附源码文件)动态创建实体类、条件扩展类、Mapper接口、Mapper.xml映射文件

今天呢&#xff0c;博主的学习进度也是步入了Java Mybatis 框架&#xff0c;目前正在逐步杨帆旗航。 那么接下来就给大家出一期有关 Mybatis 逆向工程的教学&#xff0c;希望能对大家有所帮助&#xff0c;也特别欢迎大家指点不足之处&#xff0c;小生很乐意接受正确的建议&…

android studio中修改java逻辑对应配置的xml文件

在android studio新建一个activity&#xff0c;自动生成一个xml&#xff0c;可以更改到另一个xml吗&#xff1f; 可以。 新建一个activity&#xff1a; 如下 上图中的activity_second为xml文件&#xff0c;SecondActivity为java文件。点击后&#xff0c; AndroidManifest.x…

@SchedulerLock处理Spring Task在分布式环境下的重复执行问题

本文大纲&#x1f4d6; 1、背景&#x1f342;2、SchedulerLock注解3、实现原理 1、背景&#x1f342; Spring生态下&#xff0c;日常开发定时任务&#xff0c;使用Spring Task框架还是很常见的选择&#xff0c;但Spring Task并不是为分布式环境设计的&#xff0c;分布式环境下…

Prompt工程指南中文版

Prompt-Engineering-Guide-zh Prompt工程指南中文版 github 本文翻译改编自 Dair-ai/Prompt-Engineering-Guide 中文原帖地址为 大型语言模型Prompt书写指南 为了方便理解以及补充缺少的知识点&#xff0c;内容有所添改。除非另有说明&#xff0c;本文中所有的例子都是使用te…

「pandas 与 numpy」数据分析与处理全流程【数据分析全栈攻略:爬虫+处理+可视化+报告】

- 第 106 篇 - Date: 2025 - 06 - 12 Author: 郑龙浩&#xff08;仟墨&#xff09; 文中使用的所有文件在文章顶部的资源展示 数据分析与处理 「pandas 与 numpy」 文章目录 数据分析与处理 「pandas 与 numpy」一了解数据处理1 数据处理2 数据分析第三方库 二 numpy1 基本介绍…

Fastapi + vue3 自动化测试平台(6):AI + Web UI的完美结合

&#x1f916;✨ AI Web自动化革命&#xff1a;用自然语言重塑测试体验&#xff01; 基于FastAPI Vue3的下一代Web自动化测试平台诞生&#xff01;将大语言模型 与 Web UI自动化深度结合&#xff0c;让测试脚本维护进入自然语言时代 —— 告别繁琐代码&#xff0c;拥抱智能测…

【QT】 QGraphicsItem 获取点坐标的几种方法

目录 1. 获取图元在场景中的位置 对于 QGraphicsEllipseItem&#xff08;点图元&#xff09; 2. 从 QMap 获取所有点坐标 3. 响应点击事件获取坐标 4. 获取选中点的坐标 5. 坐标转换说明 注意事项 在 Qt 图形视图框架中&#xff0c;从 QGraphicsItem&#xff08;特别是点…

伊吖学C笔记(6、数、求和、排列)

一、数 1.自然数、奇偶数 自然数也就是非负整数&#xff0c;C的循环语句很容易输出自然数&#xff0c;比如&#xff1a;输出100以内的自然数。 奇数、偶数也都是自然数&#xff1a; 2. 约数、因数 题目&#xff1a;一个数如果恰好等于它的因子之和&#xff0c;这个数就称为“…

SpringMVC与Struts2对比教学

SpringMVC 和 Struts2 就像武林中的两大门派&#xff0c;虽然都是处理 Web 请求的高手&#xff08;MVC 框架&#xff09;&#xff0c;但招式风格和内功心法大不相同。来&#xff0c;咱们用最接地气的方式掰扯掰扯&#xff0c;保准你笑着记住&#xff01; 核心区别一句话概括&a…

Nginx配置指南与最佳实践

Nginx 的配置文件通常位于 /etc/nginx/nginx.conf&#xff0c;并通过 include 指令加载其他目录&#xff08;如 /etc/nginx/conf.d/ 或 /etc/nginx/sites-enabled/&#xff09;中的配置片段。以下是一个结构化指南&#xff1a; 核心配置结构 # 全局配置 (主上下文) user nginx…

Apache 反向代理Unity服务器

Apache 反向代理Unity服务器 前言项目使用PHPStudy开启服务修改配置文件修改配置负载均衡&#xff08;可选&#xff09;重启 总结 前言 使用Unity开了个后台服务器&#xff0c;但是另一个Java服务器进行大量异步请求时会导致服务器回复过慢&#xff0c;所以开一个Apache缓冲一…

【力扣 简单 C++】94. 二叉树的中序遍历

目录 题目 解法一&#xff1a;递归 解法二&#xff1a;迭代 解法三&#xff1a;Morris遍历 题目 解法一&#xff1a;递归 class Solution { private:void traverse(TreeNode* root, vector<int>& inorder){if (!root)return;traverse(root->left, inorder);i…

idea2024版本设置TODO快捷键

直接开干&#xff1a; 首先打开File–>Settings…–>Editor–>Live Templates 复制文本&#xff1a;//wk TODO $data$ 定义自定义todo使用范围&#xff1a; 设置自定义todo的过滤器&#xff1a; 正式开始设置todo的过滤器&#xff1a; 复制文本&#xff1a; \bwk TO…

云原生核心技术 (12/12): 终章:使用 GitLab CI 将应用自动部署到 K8s (保姆级教程)

大家好&#xff0c;欢迎来到《云原生核心技术》系列的最终章&#xff01; 我们一起走过了漫长而充实的旅程。从 Docker 的集装箱&#xff0c;到 K8s 这座自动化的数字港口&#xff1b;从部署单个 Pod&#xff0c;到构建复杂的有状态应用。现在&#xff0c;我们站在了实现全自动…

DEVICENET转MODBUS TCP网关连接ABB机器人配置案例

在工业自动化场景中&#xff0c;DeviceNet和Modbus TCP是两种常见的通信协议。DeviceNet通常用于连接现场设备&#xff08;如传感器、执行器等&#xff09;&#xff0c;而Modbus TCP则广泛应用于以太网环境下的远程监控和数据采集。当需要将基于DeviceNet协议的ABB机器人集成到…

达梦数据库单机部署dmhs同步复制(dm8->kafka)

本文讨论了达梦数据实时同步软件DMHS的相关内容&#xff0c;包括概念总结、环境模拟及部署实现从达梦数据库到Kafka队列的同步复制。关键要点包括&#xff1a; 1.DMHS系统概述&#xff1a; 达梦公司推出的异构环境高性能数据库实时同步系统&#xff0c;可应用于应急、容灾等多…

爬虫+动态代理助力 AI 训练数据采集

文章目录 引言新手之选&#xff1a;网页抓取API可靠之选&#xff1a;动态住宅代理总结 引言 近年来&#xff0c;AI 技术飞速发展&#xff0c;很多朋友都投身于 AI 模型的训练。然而&#xff0c;相较于模型的获取&#xff0c;高质量的数据往往更加难以收集。一方面&#xff0…

OpenEuler服务器警告邮件自动化发送:原理、配置与安全实践

OpenEuler服务器警告邮件自动化发送&#xff1a;原理、配置与安全实践 在服务器的运维管理过程中&#xff0c;及时感知系统异常状态至关重要。当OpenEuler系统运行时&#xff0c;将服务器的警告信息实时推送至邮箱&#xff0c;能帮助运维人员快速响应潜在问题&#xff0c;保障…

使用vite-plugin-html在 HTML 文件中动态注入数据,如元数据、环境变量、标题

vite-plugin-html 是一个用于 Vite 构建工具的插件&#xff0c;它可以帮助你在构建过程中动态注入一些 HTML 内容&#xff0c;比如标题、元数据、环境变量等。通过使用这个插件&#xff0c;你可以根据项目的配置和环境变量自动生成带有动态内容的 HTML 文件&#xff0c;适用于 …

学习笔记087——Java接口和抽象类的区别和使用

文章目录 1、主要区别2、使用场景2.1 使用接口的情况&#xff1a;2.1 使用抽象类的情况&#xff1a; 3、Java 8及以后的接口增强4、设计建议 1、主要区别 特性接口(Interface)抽象类(Abstract Class)定义方式使用interface关键字使用abstract class关键字方法实现Java 8前不能…