games101——作业8

安装依赖

本次作业需要预先安装 OpenGL, Freetype 还有 RandR 这三个库。可以通过以下命令进行安装:

$ sudo apt install libglu1-mesa-dev freeglut3-dev mesa-common-dev

$ sudo apt install xorg-dev #会自动安装 libfreetype6-det

连接绳子的约束

在 rope.cpp 中, 实现 Rope 类的构造函数。这个构造函数应该可以创建一个新的绳子(Rope) 对象,该对象从 start 开始,end 结束,包含 num_nodes 个节点。也就是如下图所示:
每个结点都有质量,称为质点;质点之间的线段是一个弹簧。通过创建一系列的
质点和弹簧,你就可以创建一个像弹簧一样运动的物体。
pinned_nodes 设置结点的索引。这些索引对应结点的固定属性 (pinned attribute) 应该设置为真(他们是静止的)。对于每一个结点,你应该构造一个 Mass 对象,并在 Mass 对象的构造函数里设置质量和固定属性。(请仔细阅读代码,确定传递给构造函数的参数)。你应该在连续的两个结点之间创建一个弹簧,设置弹簧两端的结点索引和弹簧系数 k,请检查构造函数的签名以确定传入的参数。

 Rope::Rope(Vector2D start, Vector2D end, int num_nodes, float node_mass, float k, vector<int> pinned_nodes)
    {
        // TODO (Part 1): Create a rope starting at `start`, ending at `end`, and containing `num_nodes` nodes.
        for(int i=0; i<num_nodes; ++i) {
            Vector2D pos = start + (end - start) * ((double)i / ((double)num_nodes - 1.0));          
            masses.push_back(new Mass(pos, node_mass, false));
            // masses[i]->forces = Vector2D(0, 0);
        }

        for(int i=0; i<num_nodes-1; ++i) {
            springs.push_back(new Spring(masses[i], masses[i+1], k));
        }

//        Comment-in this part when you implement the constructor
       for (auto &i : pinned_nodes) {
           masses[i]->pinned = true;
       }
    }

运行 ./ropesim。你应该可以看到屏幕上画出绳子,但它不发生运动。


显式/半隐式欧拉法

胡克定律表示弹簧连接的两个质点之间的力和他们之间的距离成比例。也就是:


Rope::simulateEuler 中, 首先实现胡克定律。遍历所有的弹簧,对弹簧两端的质点施加正确的弹簧力。保证力的方向是正确的!对每个质点,累加所有的弹簧力。

		for (auto &s : springs)
        {
            // TODO (Part 2): Use Hooke's law to calculate the force on a node
            auto len = (s->m1->position - s->m2->position).norm();
            s->m1->forces += -s->k * (s->m1->position - s->m2->position) / len * (len - s->rest_length);
            s->m2->forces += -s->k * (s->m2->position - s->m1->position) / len * (len - s->rest_length); 
        }

一旦计算出所有的弹簧力,对每个质点应用物理定律:

显示欧拉法

下一个位置用当前速度计算得到

            if (!m->pinned)
            {
                // TODO (Part 2): Add the force due to gravity, then compute the new velocity and position
                auto a = m->forces / m->mass + gravity;
                m->position += m->velocity * delta_t; // For explicit method
                m->velocity += a * delta_t; 

                // TODO (Part 2): Add global damping
        	}

显式欧拉运行之后,会发现绳子飞了,因为显式欧拉不收敛或者不稳定

半隐式欧拉

半隐式欧拉使用下一时间的速度计算下一时间的位置

			if (!m->pinned)
            {
                // TODO (Part 2): Add the force due to gravity, then compute the new velocity and position
                auto a = m->forces / m->mass + gravity;                
                m->velocity += a * delta_t; 
                m->position += m->velocity * delta_t; // For semi-implicit method
                // TODO (Part 2): Add global damping
            }

运行 ./ropesim。结果如下

只有 3 个结点,看起来不够多。在 application.cpp 文件的最上方,你应该可以看到欧拉绳子和 Verlet 绳子的定义。改变两个绳子结点个数(默认为 3 个),比如 16 或者更多。这里使用 16 个结点,结果如下:

我们也可以使用 ./ropesim -s 32 来设置仿真中每帧的仿真步数为 32,默认是 64,我们发现小的步数,会更不容易趋于稳定(在后面加入摩擦力之后可以更容易看出),使用更大的步数,会更容易趋于稳定。
请添加图片描述


显式 Verlet

Verlet 是另一种精确求解所有约束的方法。这种方法的优点是只处理仿真中顶点的位置并且保证四阶精度。和欧拉法不同,Verlet积分按如下的方式来更新
下一步位置:

 void Rope::simulateVerlet(float delta_t, Vector2D gravity)
    {
        for (auto &s : springs)
        {
            // TODO (Part 3): Simulate one timestep of the rope using explicit Verlet (solving constraints)
            auto len = (s->m1->position - s->m2->position).norm();
            s->m1->forces += -s->k * (s->m1->position - s->m2->position) / len * (len - s->rest_length);
            s->m2->forces += -s->k * (s->m2->position - s->m1->position) / len * (len - s->rest_length);
        }

        for (auto &m : masses)
        {
            if (!m->pinned)
            {
                Vector2D temp_position = m->position;
                auto a = m->forces / m->mass + gravity;
                // TODO (Part 3.1): Set the new position of the rope mass
                m->position = temp_position + (temp_position - m->last_position) + a * delta_t * delta_t;
                m->last_position = temp_position;
            }
            m->forces = Vector2D(0, 0);
        }
    }
}

运行结果如下
除此之外,我们可以仿真弹簧系数无限大的弹簧。不用再考虑弹簧力,而是用解
约束的方法来更新质点位置:只要简单的移动每个质点的位置使得弹簧的长度保
持原长。修正向量应该和两个质点之间的位移成比例,方向为一个质点指向另一
质点。每个质点应该移动位移的一半。

只要对每个弹簧执行这样的操作,我们就可以得到稳定的仿真。为了使运动更加
平滑,每一帧可能需要更多的仿真次数。


阻尼

向显示 Verlet 方法积分的胡克定律中加入阻尼。现实中的弹簧不会永远跳动-因为动能会因摩擦而减小。阻尼系数设置为 0.00005, 加入阻尼之后质点位置更新如下:

for (auto &m : masses)
        {
            if (!m->pinned)
            {
                Vector2D temp_position = m->position;
                auto a = m->forces / m->mass + gravity;
                double damping_factor = 0.00005;
                // TODO (Part 4): Add global Verlet damping
                m->position = temp_position + (1 - damping_factor) * (temp_position - m->last_position) + a * delta_t * delta_t; 

                m->last_position = temp_position;
            }
            m->forces = Vector2D(0, 0);
        }
    }

./ropesim 会发现绳子最后趋于静止,由于能量损失
运行 ./ropesim -s 256 会发现静止的更快了
而向欧拉方法中加入阻尼,参考助教关于作业8的一些解答,直接使用 − k d v -k_dv kdv 作为阻尼,而不是相对速度

float kd = 0.005; // damping coefficient
if (!m->pinned)
            {
                // TODO (Part 2): Add the force due to gravity, then compute the new velocity and position

                // TODO (Part 2): Add global damping
                auto a = m->forces / m->mass + gravity - kd * m->velocity / m->mass;
                m->velocity += a * delta_t;
                m->position += m->velocity * delta_t;
            }

运行 ./ropesim 结果如下

在这部分的课程中,我们将专注于使用光线追踪来渲染图像。在光线追踪中 最重要的操作之一就是找到光线与物体的交点。一旦找到光线与物体的交点,就 可以执行着色并返回像素颜色。在这次作业中,我们需要实现两个部分:光线的 生成和光线与三角的相交。本次代码框架的工作流程为: 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 作业6 解决方案与资料 #### 完整的环境配置指南 对于 Mac M1 用户,在准备完成 Games101 的第六次作业前,建议先按照官方提供的《GAMES101 作业环境配置指北》来设置开发环境[^1]。这份文档不仅涵盖了如何安装必要的包管理工具以及依赖库,还提供了详细的编译指导——无论是通过命令行直接操作还是借助 Makefile 或者更复杂的 CMake 工具。 #### 关于光线追踪优化技巧 针对可能遇到的画面质量问题,比如由于减少反射次数而导致的噪点增加现象,可以通过调整采样策略加以改善。尽管降低最大递归深度确实有助于缓解性能瓶颈并防止光线路径过长引起的计算溢出,但这通常意味着每像素的有效样本数减少了,从而影响最终图像的质量[^2]。因此,考虑引入更加高效的随机化算法或是采用分层抽样的方法可能是更好的选择。 #### Lambda 表达式的应用实例 当涉及到复杂图形编程时,lambda 函数因其简洁性和灵活性而备受青睐。特别是在处理回调机制或需要临时定义一些短小精悍的操作逻辑场景下尤为有用。例如,在实现某种特定效果的过程中,如果希望某个局部作用域内的匿名函数可以直接访问外部上下文中声明过的变量,则可以利用 `&` 符号指定捕获列表,使得该 lambda 可以按引用方式获取这些资源,并对其进行修改[^3]。 #### 黑边问题及其修复措施 在渲染过程中偶尔会出现物体边缘出现不必要的黑色线条的情况,这通常是由于几何体之间的间隙或者是纹理映射不连续所造成的视觉伪影。对此类问题的一个常见修正手段是在着色器代码里加入额外判断条件,确保相邻表面之间平滑过渡;另外也可以尝试微调相机视角参数或者重新审视模型文件本身是否存在缺陷[^4]。 ```cpp // 示例:C++ 中使用 Lambda 捕获父级作用域中的变量 void example() { int value = 42; auto func = [&]() { std::cout << "Captured Value: " << ++value << '\n'; }; func(); // 输出 Captured Value: 43 并更新原值 } ```
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值