GAMES101:作业2记录

总览

在上次作业中,虽然我们在屏幕上画出一个线框三角形,但这看起来并不是那么的有趣。所以这一次我们继续推进一步——在屏幕上画出一个实心三角形,换言之,栅格化一个三角形。上一次作业中,在视口变化之后,我们调用了函数rasterize_wireframe(const Triangle& t)。但这一次,你需要自己填写并调用函数 rasterize_triangle(const Triangle& t)。该函数的内部工作流程如下:

  1. 创建三角形的 2 维 bounding box。
  2. 遍历此 bounding box 内的所有像素(使用其整数索引)。然后,使用像素中心的屏幕空间坐标来检查中心点是否在三角形内。
  3. 如果在内部,则将其位置处的插值深度值 (interpolated depth value) 与深度缓冲区 (depth buffer) 中的相应值进行比较。
  4. 如果当前点更靠近相机,请设置像素颜色并更新深度缓冲区 (depth buffer)。

你需要修改的函数如下:
rasterize_triangle(): 执行三角形栅格化算法

static bool insideTriangle(): 测试点是否在三角形内。你可以修改此函数的定义,这意味着,你可以按照自己的方式更新返回类型或函数参数。

因为我们只知道三角形三个顶点处的深度值,所以对于三角形内部的像素,我们需要用插值的方法得到其深度值。我们已经为你处理好了这一部分,因为有关这方面的内容尚未在课程中涉及。插值的深度值被储存在变量 z_interpolated 中。

请注意我们是如何初始化 depth buffer 和注意 z values 的符号。为了方便同学们写代码,我们将 z 进行了反转,保证都是正数,并且越大表示离视点越远。

在此次作业中,你无需处理旋转变换,只需为模型变换返回一个单位矩阵。最后,我们提供了两个 hard-coded 三角形来测试你的实现,如果程序实现正确,你将看到如下所示的输出图像:

在这里插入图片描述

编写代码

static bool insideTriangle()函数的实现

首先我们来实现static bool insideTriangle():判断是否在三角形内

在这里插入图片描述

在这里插入图片描述
我们只需要看 A P × A B AP\times AB AP×AB B P × B C BP\times BC BP×BC C P × C A CP\times CA CP×CA是否是同号的,如果是同号的就说明P点在三角形里面(说明P在这些线段 A B AB AB B C BC BC C A CA CA的同侧,这里的 × \times ×表示的是叉乘)。

注意课程所讲的像素的坐标的定义和虎书不太一样,我认为是定义在像素的角点,我们要求像素是否在三角形内,应该是判断像素的中心是否在三角形里面,如图下面的像素的坐标可以从(0,0)到(width-1, height-1),这样我们就知道像素的中心是定义在(x+0.5,y+0.5)

在这里插入图片描述
我这里把insideTriangle的定义重新修改了一下,函数的参数列表的x和y都定义成了float型,我们调用这个函数的时候直接传入像素的中心位置(即对应的像素坐标为(x0,y0),我们传入(x0+0.5,y0+0.5)),同时也方便我们后续使用MSAA的处理。

static bool insideTriangle(float x, float y, const Vector3f* _v)
{   // TODO : Implement this function to check if the point (x, y) is inside the triangle represented by _v[0], _v[1], _v[2]Eigen::Vector2f AP(x - _v[0].x(), y - _v[0].y());Eigen::Vector2f AB(_v[1].x() - _v[0].x(), _v[1].y()- _v[0].y());Eigen::Vector2f BP(x - _v[1].x(), y - _v[1].y());Eigen::Vector2f BC(_v[2].x()-_v[1].x(), _v[2].y()-_v[1].y());Eigen::Vector2f CP(x - _v[2].x(), y - _v[2].y());Eigen::Vector2f CA(_v[0].x() - _v[2].x(), _v[0].y() - _v[2].y());auto P_AB = AP.x() * AB.y() - AB.x() * AP.y();auto P_BC = BP.x() * BC.y() - BC.x() * BP.y();auto P_CA = CP.x() * CA.y() - CA.x() * CP.y();if ((P_AB >0 && P_BC >0 && P_CA >0) || (P_AB < 0 && P_BC < 0 && P_CA < 0)) {return true;}else {return false;}
}  

rasterize_triangle()函数的实现

我们根据提示来进行操作:

  1. 创建三角形的 2 维 bounding box。这一步很好实现,三角形被限制在一个bounding box,我们需要找到这个长方形的四个边,我们遍历的时候可以先遍历行再遍历列,所以需要知道这个长方形x坐标和y坐标的范围:
  • 长方形x坐标的对应的左边界是三角形的三个角点x坐标的最小值
  • 长方形x坐标的对应的右边界是三角形的三个角点x坐标的最大值
  • 长方形y坐标的对应的下边界是三角形的三个角点y坐标的最小值
  • 长方形y坐标的对应的上边界是三角形的三个角点y坐标的最大值

对应的代码是:

auto v = t.toVector4();// TODO : Find out the bounding box of current triangle.
int bounding_box_left_x = std::min(v[0].x(), std::min(v[1].x(), v[2].x()));
int bounding_box_right_x = std::max(v[0].x(), std::max(v[1].x(), v[2].x()));
int bounding_box_bottom_y = std::min(v[0].y(), std::min(v[1].y(), v[2].y()));
int bounding_box_top_y = std::max(v[0].y(), std::max(v[1].y(), v[2].y()));
  1. 遍历此 bounding box 内的所有像素(使用其整数索引)。然后,使用像素中心的屏幕空间坐标来检查中心点是否在三角形内。

知道了bounding box的边界,我们就可以进行遍历了:

for (int x = bounding_box_left_x; x <= bounding_box_right_x; x++) {for (int y = bounding_box_bottom_y; y <= bounding_box_top_y; y++) {if (insideTriangle(x, y, t.v)) {
//代码逻辑
}
  1. 如果在内部,则将其位置处的插值深度值 (interpolated depth value) 与深度缓冲区 (depth buffer) 中的相应值进行比较。

现在我们实现比较的逻辑,首先我们需要求出插值的深度值(这部分已经给我们了),然后和缓冲区的值进行比较:

if (insideTriangle(x + 0.5, y + 0.5, t.v)) {// If so, use the following code to get the interpolated z value.   auto[alpha, beta, gamma] = computeBarycentric2D(x + 0.5, y + 0.5, t.v);float w_reciprocal = 1.0/(alpha / v[0].w() + beta / v[1].w() + gamma / v[2].w());float z_interpolated = alpha * v[0].z() / v[0].w() + beta * v[1].z() / v[1].w() + gamma * v[2].z() / v[2].w();z_interpolated *= w_reciprocal;if (z_interpolated <  depth_buf[get_index(x, y)]) {//代码逻辑}
}

这里使用的computeBarycentric2D 对应的是下面的公式:

在这里插入图片描述

下面是虎书的公式,两者是差不多的:

在这里插入图片描述
然后我们把这三个系数对应乘上三角形三个点的深度就是插值以后的深度了。

其对应的代码是

static std::tuple<float, float, float> computeBarycentric2D(float x, float y, const Vector3f* v)
{float c1 = (x*(v[1].y() - v[2].y()) + (v[2].x() - v[1].x())*y + v[1].x()*v[2].y() - v[2].x()*v[1].y()) / (v[0].x()*(v[1].y() - v[2].y()) + (v[2].x() - v[1].x())*v[0].y() + v[1].x()*v[2].y() - v[2].x()*v[1].y());float c2 = (x*(v[2].y() - v[0].y()) + (v[0].x() - v[2].x())*y + v[2].x()*v[0].y() - v[0].x()*v[2].y()) / (v[1].x()*(v[2].y() - v[0].y()) + (v[0].x() - v[2].x())*v[1].y() + v[2].x()*v[0].y() - v[0].x()*v[2].y());float c3 = (x*(v[0].y() - v[1].y()) + (v[1].x() - v[0].x())*y + v[0].x()*v[1].y() - v[1].x()*v[0].y()) / (v[2].x()*(v[0].y() - v[1].y()) + (v[1].x() - v[0].x())*v[2].y() + v[0].x()*v[1].y() - v[1].x()*v[0].y());return {c1,c2,c3};
}
  1. 如果当前点更靠近相机,请设置像素颜色并更新深度缓冲区 (depth buffer)。

更靠近相机,由于这里的深度的值都设置为了正值,所以我们定义更靠近相机就是z_interpolated小于depth_buf对应的值。我们把深度缓冲区的深度更新为z_interpolated,把对应像素的颜色定义为三角形的颜色。

if (z_interpolated <  depth_buf[get_index(x, y)) {depth_buf[get_index(x, y)] = z_interpolated;// TODO : set the current pixel (use the set_pixel function) to the color of the triangle (use getColor function) if it should be painted.set_pixel(Eigen::Vector3f(x, y, z_interpolated),  t.getColor());}

这里还需要注意一个小bug,如果单单用上面的代码运行会发现图形覆盖顺序不对:

这一点在讨论里有讲述
Home,Forums,GAMES在线课程(现代计算机图形学入门)i讨论区,Hw2的疑问

在这里插入图片描述
这样才是z越大表示越远。所以我们这里需要修改一下(原来没有添加vert.z()的负号):

vert.z() = -vert.z() * f1 + f2;

完整的代码是:

void rst::rasterizer::rasterize_triangle(const Triangle& t) {auto v = t.toVector4();// TODO : Find out the bounding box of current triangle.int bounding_box_left_x = std::min(v[0].x(), std::min(v[1].x(), v[2].x()));int bounding_box_right_x = std::max(v[0].x(), std::max(v[1].x(), v[2].x()));int bounding_box_bottom_y = std::min(v[0].y(), std::min(v[1].y(), v[2].y()));int bounding_box_top_y = std::max(v[0].y(), std::max(v[1].y(), v[2].y()));/*without MSAA*/// iterate through the pixel and find if the current pixel is inside the trianglefor (int x = bounding_box_left_x; x <= bounding_box_right_x; x++) {for (int y = bounding_box_bottom_y; y <= bounding_box_top_y; y++) {if (insideTriangle(x + 0.5, y + 0.5, t.v)) {auto[alpha, beta, gamma] = computeBarycentric2D(x + 0.5, y + 0.5, t.v);float w_reciprocal = 1.0/(alpha / v[0].w() + beta / v[1].w() + gamma / v[2].w());float z_interpolated = alpha * v[0].z() / v[0].w() + beta * v[1].z() / v[1].w() + gamma * v[2].z() / v[2].w();z_interpolated *= w_reciprocal;if (z_interpolated <  depth_buf[get_index(x, y)) {depth_buf[get_index(x, y)] = z_interpolated;// TODO : set the current pixel (use the set_pixel function) to the color of the triangle (use getColor function) if it should be painted.set_pixel(Eigen::Vector3f(x, y, z_interpolated),  t.getColor());}}}}
}

可以看到三角形的效果渲染出来了,但是这里会出现锯齿:

在这里插入图片描述

在这里插入图片描述

然后是进阶的部分,我们使用 2x2 MSAA 缓解走样问题,即对每个像素进行 2x2 超采样,看一个像素的四个部分,有几个部分在三角形内,就把该点像素颜色乘上对应占比。(其实就是进行一次模糊操作)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

初代的代码没有维护每一个子采样点的深度,深度用的还是中心点的深度,而颜色对应是乘上了百分比:

void rst::rasterizer::rasterize_triangle(const Triangle& t) {auto v = t.toVector4();// TODO : Find out the bounding box of current triangle.int bounding_box_left_x = std::min(v[0].x(), std::min(v[1].x(), v[2].x()));int bounding_box_right_x = std::max(v[0].x(), std::max(v[1].x(), v[2].x()));int bounding_box_bottom_y = std::min(v[0].y(), std::min(v[1].y(), v[2].y()));int bounding_box_top_y = std::max(v[0].y(), std::max(v[1].y(), v[2].y()));/*with MSAA*/// iterate through the pixel and find if the current pixel is inside the triangleint inNumber;float rd[4][2] ={{0.25, 0.25}, {0.25, 0.75},{0.75, 0.25},{0.75, 0.75}};for (int x = bounding_box_left_x; x <= bounding_box_right_x; x++) {for (int y = bounding_box_bottom_y; y <= bounding_box_top_y; y++) {inNumber = 0;for (int i = 0; i < 4; i++){if (insideTriangle(x+rd[i][0], y+rd[i][1], t.v)) {inNumber++;}    }if (inNumber > 0){auto[alpha, beta, gamma] = computeBarycentric2D(x + 0.5, y + 0.5, t.v);float z_interpolated = alpha * v[0].z() / v[0].w() + beta * v[1].z() / v[1].w() + gamma * v[2].z() / v[2].w();z_interpolated *= w_reciprocal;if (z_interpolated < depth_buf[get_index(x, y)]) {depth_buf[get_index(x, y)] = z_interpolated;// TODO : set the current pixel (use the set_pixel function) to the color of the triangle (use getColor function) if it should be painted.Eigen::Vector3f pixelColor;pixelColor << inNumber * t.getColor() / 4;set_pixel(Eigen::Vector3f(x, y, z_interpolated), pixelColor);}}}}}

但是这样渲染出来的是有黑边的。

在这里插入图片描述
在这里插入图片描述

这个讨论把黑边介绍得很好。

Home › Forums › GAMES在线课程(现代计算机图形学入门)讨论区 › 提高部分的解决方案 – 答案就在作业文档中

在这里插入图片描述
在这里插入图片描述

改进的方法就是我们要记录采样点的深度,像素的颜色根据采样点的颜色取均值,而深度检测的对象也是采样点

rasterizer.hpp定义采样点的颜色和采样点的深度的数组

std::vector<Eigen::Vector3f> sample_frame_buf;
std::vector<float> sample_depth_buf;

仿照原来类定义的frame_bufdepth_buf添加操作:

rst::rasterizer::rasterizer(int w, int h) : width(w), height(h)
{frame_buf.resize(w * h);depth_buf.resize(w * h);//MSAAsample_frame_buf.resize(4 * w * h);sample_depth_buf.resize(4 * w * h);
}
void rst::rasterizer::clear(rst::Buffers buff)
{if ((buff & rst::Buffers::Color) == rst::Buffers::Color){std::fill(frame_buf.begin(), frame_buf.end(), Eigen::Vector3f{0, 0, 0});//MSAA sample_frame_bufstd::fill(sample_frame_buf.begin(), sample_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());//MSAA sample_depth_bufstd::fill(sample_depth_buf.begin(), sample_depth_buf.end(), std::numeric_limits<float>::infinity());}
}

并且在rasterizer.cpp定义了一个新函数(注意在rasterizer.hpp加上声明)方便我们获取采样点的索引:

int rst::rasterizer::get_sample_index(int x, int y)
{return (2 * height -1 -y) * 2 * width + x;
}

接着我们就可以实现MSAA了:

void rst::rasterizer::rasterize_triangle(const Triangle& t) {int inNumber;float x_sample;float y_sample;int sample_index[4];std::vector<Eigen::Vector3f> pixelvec;float rd[4][2] ={{0.25, 0.25}, {0.25, 0.75},{0.75, 0.25},{0.75, 0.75}};//定义四个采样点的中心距离for (int x = bounding_box_left_x; x <= bounding_box_right_x; x++) {for (int y = bounding_box_bottom_y; y <= bounding_box_top_y; y++) {inNumber = 0;for (int i = 0; i < 4; i++){x_sample = x + rd[i][0];y_sample = y + rd[i][1];sample_index[i] =  get_sample_index(int(2 * x_sample), int(2 * y_sample));if (insideTriangle(x_sample, y_sample, t.v)) {// If so, use the following code to get the interpolated z value.   auto[sample_alpha, sample_beta, sample_gamma] = computeBarycentric2D(x_sample, y_sample, t.v);float sample_w_reciprocal = 1.0/(sample_alpha / v[0].w() + sample_beta / v[1].w() + sample_gamma / v[2].w());float sample_z_interpolated = sample_alpha * v[0].z() / v[0].w() + sample_beta * v[1].z() / v[1].w() + sample_gamma * v[2].z() / v[2].w();sample_z_interpolated *= sample_w_reciprocal;if (sample_z_interpolated <  sample_depth_buf[sample_index[i]]) {sample_depth_buf[sample_index[i]] = sample_z_interpolated;// TODO : set the current pixel (use the set_pixel function) to the color of the triangle (use getColor function) if it should be painted.sample_frame_buf[sample_index[i]] = t.getColor();inNumber = inNumber + 1;}}    }if (inNumber > 0){// TODO : set the current pixel (use the set_pixel function) to the color of the triangle (use getColor function) if it should be painted.Eigen::Vector3f pixelColor;pixelColor <<  (sample_frame_buf[sample_index[0]] + sample_frame_buf[sample_index[1]] + sample_frame_buf[sample_index[2]] + sample_frame_buf[sample_index[3]])/ 4;set_pixel(Eigen::Vector3f(x, y, 0), pixelColor);}}} 
}  

图形的边缘就比较柔和了

在这里插入图片描述
明显看到边缘比较柔和了,而且是没有黑边的。

在这里插入图片描述

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

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

相关文章

二叉树在线OJ

二叉树的构建及遍历 本题目的要求是&#xff1a; 输入一个数组&#xff0c;里面存放了若干个字符&#xff0c;#代表了空指针&#xff0c;数组中的顺序是 是先序遍历&#xff0c;然后要求你用中序输出 首先我们要做的就是构造结构体&#xff1a; typedef struct TreeNode {char…

inux基础项目开发1:量产工具——业务系统(七)

前言&#xff1a; 前面我们已经构造出来显示系统、输入系统、文字系统、UI系统、页面系统&#xff0c;这个项目百分之八十需要实现的都已经构建出来了&#xff0c;最后让我们对这个项目进行最后一项系统的搭建&#xff0c;也就是业务系统&#xff0c;说到业务大家应该就知道我们…

解决ZED SDK安装后不可用,出现“核心已转储”的闪退问题

在陈述问题简单回顾下ZED SDK安装的步骤 ZED的运行需要显卡支持&#xff0c;cuda加速&#xff0c;因此需要提前安装好显卡驱动以及对应的cuda和cudnn&#xff0c;基础工作在此不再赘述&#xff0c;以下步骤默认已经完成上述准备工作。 建议新建一个虚拟环境以限定ZED使用的py…

飞行员兄弟

飞行员兄弟 思路&#xff1a; 这里一共有16个格子&#xff0c;如果暴力的话也就是2^16次方种排列组合。 这题和之前的开关不一样&#xff0c;这题是会影响到周围很多格子&#xff0c;而开关那题可以利用上方只改变一个的操作来解题&#xff0c;这题我想到的就是暴搜&#xff…

阿里微服务质量保障系列:性能监控最佳实践

建设一体化性能监控平台 随着互联网技术的不断发展&#xff0c;企业的业务规模和复杂度也在不断增加。为了保证业务的稳定性和可靠性&#xff0c;企业需要对其系统进行全面的性能监控。而一体化性能监控就是一种集成了多种监控工具和技术的综合性监控方案&#xff0c;可以帮助…

电源需要考虑的因素

做产品的都离不开电源&#xff0c;产品出问题也首先检查供电是否正常。今天给大家分享的是做好一个电源需要考虑哪些因素。 一&#xff0e; 描述输入电压影响输出电压几个指标形式 1&#xff0e; 稳压系数 A&#xff0e;稳压系数&#xff1a;表示负载不变时&#xff0c;稳压电源…

深度解析:整数和浮点数在内存中的存储

深度解析&#xff1a;整数和浮点数在内存中的存储 引言 在计算机科学中&#xff0c;理解整数和浮点数在内存中的存储方式是深入学习的关键一步。这篇博客将带你深入探讨整数和浮点数的内存表示&#xff0c;并通过代码实例详细解析其存储结构。 整数的内存存储 对于整形来说&a…

Redis命令详解

文章目录 Key&#xff08;键&#xff09; DEL EXISTS EXPIRE EXPIREAT PEXPIRE PEXPIREAT PERSIST KEYS TTL PTTL RENAME RENAMENX TYPE SCAN HSCAN SSCAN ZSCAN DUMP String&#xff08;字符串&#xff09; SET GET INCR DECR MSET MGET APPEND SETNX STRLEN INCRBY DECRBY IN…

Spark大数据集群日常开发过程遇到的异常及解决思路汇总

原创/朱季谦 在开发Spark任务过程中&#xff0c;遇到过不少新人经常可能会遇到的坑&#xff0c;故而将这些坑都总结了下来&#xff0c;方便日后遇到时&#xff0c;可以快速定位解决&#xff0c;壁面耗费过多时间在查找问题之上。 一、出现java.lang.IllegalAccessError: tried…

阻抗匹配电阻原理及其应用

一、匹配电阻的作用 1、阻抗匹配 当信号频率比较高&#xff0c;上升沿比较陡时&#xff0c;电子信号经过阻抗不同的地方时也会产设反射。 PCB的单线阻抗一般会设计成50Ω&#xff0c;发射端阻抗一般是17到40&#xff0c;而接收端一般是MOS管的输入&#xff0c;阻抗是比较大的…

【字符串匹配】【KMP算法】Leetcode 28 找出字符串中第一个匹配项的下标

【字符串匹配】【KMP算法】Leetcode 28 找出字符串中第一个匹配项的下标 &#xff08;1&#xff09;前缀和后缀&#xff08;2&#xff09;前缀表&#xff08;最长相同的前缀和后缀的长度&#xff09;&#xff08;3&#xff09;匹配过程示意&#xff08;4&#xff09;next数组的…

分享几个国内免费使用的 gpt 网站

可放心阅读点击&#xff0c;无邀请链接、邀请码等 今天主要分享几个个免费的GPT网站。 1、智晓星AiStar 智晓星AiStar 个人长期自用&#xff0c;界面简单&#xff0c;使用比较方便&#xff0c;支持验证码、微信登录。 对于免费用户来说&#xff0c;登录之后每天可以使用50…

系统地自学 Python

文章目录 如何系统地自学 Python1. 选择合适的 Python 版本2. 安装 Python 和必要的工具3. 学习 Python 的基础知识4. 学习 Python 的高级特性5. Python 的应用领域6. 保持良好的学习习惯 如何系统地自学 Python Python 是一种广泛使用的编程语言&#xff0c;它具有简洁、易读、…

flutter使用动态路由传参的最小案例

flutter中使用动态路由传递参数的封装案例&#xff0c;子组件页面只需要接收arguments参数即可&#xff0c;参数是一个map&#xff0c;里面包含有所需要的参数&#xff0c;类似于json。在MaterialApp中配置onGenerateRoute&#xff0c;然后动态判断传递参数&#xff1a; route…

Rust国内sparse镜像源配置

文章目录 1. 遇到问题1.1 问题现象1.2 解决办法 2. 重新设置最新 sparse源3. 更多参考资料3.1 字节源3.2 ustc 源3.3 清华源3.4 其他人的总结 1. 遇到问题 有好一阵子没有更新源和安装软件了&#xff0c; 使用ustc的源&#xff0c; 更新了好一阵子&#xff0c; 最后安装居然还出…

费解的开关

费解的开关 模拟一下开关的过程&#xff1a; 直接对第一行进行开关灯即可&#xff0c;那么第一行开关等的方案有多少个呢&#xff1f; 可以第一个想到的是5次&#xff0c;但实际上是25次&#xff0c;因为没有规定说只能开关一次吧。 那么如何获得这32种方案呢&#xff1f; 可…

十五届海峡两岸电视主持新秀大会竞赛流程

海峡两岸电视主持新秀会是两岸电视媒体共同举办的一项活动&#xff0c;旨在为两岸年轻的电视主持人提供一个展示才华的舞台&#xff0c;促进两岸文化交流和青年交流。本届新秀会是第十二届海峡两岸电视艺术节的重要活动之一。本次竞赛赛制流程如下&#xff1a; &#xff08;1&…

springboot助农管理系统

springboot助农管理系统 成品项目已经更新&#xff01;同学们可以打开链接查看&#xff01;需要定做的及时联系我&#xff01;专业团队定做&#xff01;全程包售后&#xff01; 2000套项目视频链接&#xff1a;https://pan.baidu.com/s/1N4L3zMQ9nNm8nvEVfIR2pg?pwdekjv 提…

初探Java之旅:探寻Java的奥秘

✨个人主页&#xff1a;全栈程序猿的CSDN博客 &#x1f4a8;系列专栏&#xff1a;Java从入门到精通 ✌座右铭&#xff1a;编码如诗&#xff0c;Bug似流星&#xff0c;持续追求优雅的代码&#xff0c;解决问题如同星辰般自如 在计算机编程的世界中&#xff0c;有一门被誉为“千变…

分享87个节日PPT,总有一款适合您

分享87个节日PPT&#xff0c;总有一款适合您 87个节日PPT下载链接&#xff1a;https://pan.baidu.com/s/1eUxA59uQ-hZWWpFzzDuCkQ?pwd6666 提取码&#xff1a;6666 Python采集代码下载链接&#xff1a;采集代码.zip - 蓝奏云 学习知识费力气&#xff0c;收集整理更不易…