当前位置: 首页 > news >正文

初始图形学(7)

上一章完成了相机类的实现,对之前所学的内容进行了封装与整理,现在要学习新的内容。

抗锯齿

我们放大之前渲染的图片,往往会发现我们渲染的图像边缘有尖锐的"阶梯"性质。这种阶梯状被称为"锯齿"。当真实的相机拍照时,边缘通常没有锯齿,这是因为真实的边缘时前景和背景颜色的混合,而不是简单的二值化。而且真实的世界是连续的,而我们渲染的图像是离散的,也就是说真实世界具有无限的分辨率 ,而我们的=图像的分辨率是有限的。我们可以通过对每个像素进行多次采样并取平均值,来近似实现此效果。

我们目前采用的采样方式被称为”点采样“,即在每个像素的中心发射一条射线来进行采样,但是同时也面临一个问题,当我们对较远的地方的图像进行采样时,可能会出现"非黑即白"的情况。但实际上我们应该看到的是灰色,而不是黑白分明的点。这是我们的眼睛很自然的对远处的图形进行了处理,而这种处理正是我们想要的效果。

所以为了解决这个问题,我们采用"多采样"的方式,来对我们的图片实现采样。我们需要对像素周围的光线进行采样,然后对采样的结果进行整合,以近似真实世界的连续效果。

为了实现这种效果,我们采用最为简单的方式,我们对以像素为中心的正方形区域进行采样,这个正方形区域会延伸到每个相邻的像素的一般位置。这个方法可能效果一般,但是便于实现,具体的内容可以参考文献像素不是一个小方块,下面是一个多采样草图

image.png

数学工具:随机数生成

为了实现函数的多采样,我们需要一个能够返回真实随机数的随机数生成器。这个函数为我们返回一个(0,1)的随机数,我们可以使用标准库<cstdlib>中的std::rand()函数,这个函数会返回一个[0,RAND_MAX]之间的随机整数。我们通过以下改动,可以得到真正的随机数函数,我们写在rtweekend.h中:

#include <cmath>
#include <cstdlib>
#include <iostream>
#include <limits>
#include <memory>
...
//实用函数
inline double degree_to_radius(double degrees){return degrees * pi / 180.0;
}
inline double random_double(){//返回[0,1)的数return std::rand() / (RAND_MAX + 1.0);
}
inline double random_double(double min,double max){//返回[min,max)的数return min + (max - min)*random_double();
}

不过由于rand()具有随机性较差等特点,所以在C++11标准下有其他的随机数函数写法:

...
#include <random>
...
inline double random_double(){static std::uniform_real_distribution<double> distribution(0.0,1.0);static std::mt19937 generator;return distribution(generator);
}
...

不过看不太懂就是了

使用多采样式生成像素

对于由多个样本组成的像素,我们将从像素周围的区域选择样本,并将颜色(光值)平均在一起

我们需要更新我们的write_color()函数以考虑我们的样本数量,不过在此之前,我们需要添加一个用于区间判断的辅助函数interval::clamp(x),以确保最终的结果的颜色分量保持在正确的[0,1]范围:

class interval{
public:
...
//包含double clamp(double x) const{if(x < min) return min;if(x > max) return max;return x;}

接下来我们更新write_color()函数,其包含区间的限制功能:

  void write_color(std::ostream& out,const color& pixel_color){auto r = pixel_color.x();auto g = pixel_color.y();auto b = pixel_color.z();//使用区间RGB[0,1]计算RGB值static const interval intensity(0.000,0.999);int rbyte = int (256*intensity.clamp(r));int gbyte = int (256*intensity.clamp(g));int bbyte = int (256*intensity.clamp(b));out << rbyte << ' ' << gbyte << ' ' << bbyte << '\n';
}

这样保证了我们的的rgb分量不会超出[0,1]的范围,这样更加安全。

接下来我们需要更新相机类,以定义和使用一个新的camera::get_ray(i,j)函数,该函数将为每个像素生成不同的样本。这个函数将使用一个新的辅助函数sample_squred(),该函数在以原点为中心的正方形内生成一个随机样本点。然后我们将这个正方形中的随机样本转换为我们当前正在采样的特定像素。

​
#ifndef RENDER_C___CAMERA_H
#define RENDER_C___CAMERA_H
​
#include "hittable.h"
​
class camera{
public:double aspect_radio = 1.0;      //图像的宽高比int    image_width = 100;       //图像宽度的像素数int    samples_per_pixel = 10;  //每个像素的采样次数
​void render(const hittable& world){initialize();
​std::cout << "P3\n" << image_width << " " << image_height << "\n255\n";for(int j=0;j<image_height;j++){std::clog << "\rScanlines remaining: " << (image_height - j) << ' ' << std::flush;for(int i=0;i<image_width;i++){color pixel_color(0,0,0);for(int sample = 0;sample < samples_per_pixel; sample++){ray r = get_ray(i,j);pixel_color += ray_color(r,world);}write_color(std::cout,pixel_color*pixel_samples_scale);}}std::clog << "\rDone.                   \n";}
​
private:int image_height;           //渲染图像的高度double pixel_samples_scale; //每次采样的颜色权重point3 camera_center;       //相机的中心point3 pixel00_loc;         //像素(0,0)的位置vec3 pixel_delta_u;         //向右的偏移值vec3 pixel_delta_v;         //向下的偏移值
​void initialize(){image_height = int(image_width/aspect_radio);image_height = (image_height < 1) ? 1 : image_height;
​pixel_samples_scale = 1.0 / samples_per_pixel;
​camera_center = point3 (0,0,0);
​//确认视窗的设置auto focal_length = 1.0;    //焦距设置auto viewport_height = 2.0;auto viewport_width = viewport_height*(double (image_width)/image_height);
​//视图边缘的向量计算auto viewport_u = vec3(viewport_width,0,0);auto viewport_v = vec3(0,-viewport_height,0);//计算视图的像素间的水平竖直增量pixel_delta_u = viewport_u/image_width;pixel_delta_v = viewport_v/image_height;
​//计算左上角第一个像素中心的坐标auto viewport_upper_left = camera_center - vec3(0,0,focal_length) - viewport_v/2 - viewport_u/2;pixel00_loc = viewport_upper_left + 0.5*(pixel_delta_u+pixel_delta_v);}
​ray get_ray(int i,int j){//构造一个从原点开始的随机采样射线,指向(i,j)像素
​auto offset = sample_square();auto pixel_sample = pixel00_loc + ((i+offset.x())*pixel_delta_u) + ((j+offset.y())*pixel_delta_v);
​auto ray_origin = camera_center;auto ray_direction = pixel_sample - ray_origin;
​return ray(ray_origin,ray_direction);}
​vec3 sample_square() const {//返回一个一个随机的点,在[-0.5,-0.5]~[+0.5,+0.5]的单位矩阵内return {random_double() - 0.5, random_double() - 0.5,0};}
​color ray_color(ray & r,const hittable& world){hit_record rec;if(world.hit(r,interval(0,infinity),rec)){return 0.5*(rec.normal + color(1,1,1));}
​vec3 unit_direction = unit_vector(r.direction());auto a = 0.5*(unit_direction.y()+1.0);return (1.0 - a)*color(1.0,1.0,1.0) + a*color(0.5,0.7,1.0);}
};
​
#endif //RENDER_C___CAMERA_H

这是新的camera,我们更新了get_ray()sample_square(),还有新的公有私有属性

接下来设置一下主函数的参数:

​
int main(){hittable_list world;world.add(make_shared<sphere>(point3(0,0,-1),0.5));world.add(make_shared<sphere>(point3(0,-100.5,-1),100));
​camera cam;
​cam.aspect_radio = 16.0/9.0;cam.image_width = 400;cam.samples_per_pixel = 100;    // 设置采样次数
​cam.render(world);
}

image.png

这里可以看到左图的锯齿明显减少了,我们的抗锯齿设置的十分成功,今天就到此为止了

http://www.xdnf.cn/news/321373.html

相关文章:

  • 2025-05-07-FFmpeg视频裁剪(尺寸调整,画面比例不变)
  • 系统思考:教育焦虑恶性循环分析
  • C语言初阶:数组
  • DeepSeek全域智能革命:从量子纠缠到星际文明的认知跃迁引言:认知边界的坍缩与重构
  • 解决leetcode第3537题填充特殊网格
  • CSS详细学习笔记
  • eclipse常用快捷键
  • Jmeter进行http接口测试
  • 使用VSCode在Windows 11上编译运行项目
  • 005 权限的理解
  • Linux上将conda环境VLLM服务注册为开机自启
  • Java 常用的 ORM框架(对象关系映射)
  • 企业级AI革命!私有化部署开源大模型:数据安全+自主可控,打造专属智能引擎
  • Ubuntu20.04安装使用ROS-PlotJuggler
  • 【MCP】客户端配置(ollama安装、qwen2.5:0.5b模型安装、cherry-studio安装配置)
  • C#与Halcon联合编程
  • 迁移学习:如何加速模型训练和提高性能
  • 锁相环HMC830的调试
  • 缓存替换算法与存储器管理的分页、分段、段页式管理联系
  • http Status 400 - Bbad request 网站网页经常报 HTTP 400 错误,清缓存后就好了的原因
  • 办公学习 效率提升 超级PDF处理软件 转换批量 本地处理
  • android 折叠屏开发适配全解析:多窗口、铰链处理与响应式布局
  • 回溯进阶(一):以全排列问题为例,来展示如何对回溯的纵向和横向进行操作
  • C++ 有哪些标准版本
  • eFish-SBC-RK3576工控板音频接口测试操作指南
  • ElementUI 表格el-table自适应高度设置
  • RISC-V架构深度解析
  • SPSS系统发育分析中的聚类相关part
  • Python案例实战《手势识别》
  • Linux:web服务nginx