初始图形学(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.mzph.cn/bicheng/80509.shtml

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

相关文章

vllm笔记

目录 vllm简介vllm解决了哪些问题&#xff1f;1. **瓶颈&#xff1a;KV 缓存内存管理低效**2. **瓶颈&#xff1a;并行采样和束搜索中的内存冗余**3. **瓶颈&#xff1a;批处理请求中的内存碎片化** 快速开始安装vllm开始使用离线推理启动 vLLM 服务器 支持的模型文本语言模型生…

访问网站提示“不安全”“有风险”怎么办?

访问网站提示“不安全”“有风险”有以下几种解决方案 一、理解警告类型 1.“不安全”提示&#xff08;HTTP网站&#xff09; 原因&#xff1a;网站未使用HTTPS加密&#xff0c;传输数据&#xff08;如密码、支付信息&#xff09;可能被窃取。 表现&#xff1a;浏览器地址栏显…

vue3的响应式设计原理

Vue 3 的响应式设计是其核心特性之一&#xff0c;依赖于 Proxy 和 依赖收集机制&#xff0c;相比 Vue 2 的 Object.defineProperty&#xff0c;Vue 3 的响应式系统更加高效、灵活且易于维护。 以下是 Vue 3 响应式设计的核心原理&#xff1a; 一、核心机制概览 使用 Proxy 实现…

C++模板笔记

Cpp模板笔记 文章目录 Cpp模板笔记1. 为什么要定义模板2. 模板的定义2.1 函数模板2.1.1 函数模板的重载2.1.2 头文件与实现文件形式&#xff08;重要&#xff09;2.1.3 模板的特化2.1.4 模板的参数类型2.1.5 成员函数模板2.1.6 使用模板的规则 2.2 类模板2.3 可变参数模板 模板…

递归函数(斐波那契数列0,1,1,2,3,5,8,13,21,34,55...)

目录 一、斐波那契数列&#xff08;兔子问题&#xff09; 二、迭代法&#xff08;用while循环推下一项 ) 三、递归函数 (函数的定义中调用函数自身的一种函数定义方式) 四、递归函数的底层逻辑推理 (二叉树推倒最左下节点回退法) 一、斐波那契数列&#xff08;兔子问题&…

光的本质(以暗物质维度粒子为介质的能量传导)

一、光的概要描述 1、光的本质是能量传导 空间中均匀分布着暗物质维度粒子。光不是粒子也不是波,而是没有质量和形态的能量,在临近暗物质粒子之间的一种能量传递。 2、光能传递类似牛顿钟摆(空间中的牛顿钟摆) 当光能能量骚动一个暗物质粒子后,该暗物质粒…

Open CASCADE学习|管道壳体生成

一、引言 在计算机辅助设计&#xff08;CAD&#xff09;和计算机图形学领域&#xff0c;OpenCASCADE 是一款功能强大的开源 3D 建模库。它提供了丰富的几何和拓扑建模工具&#xff0c;其中管道壳体&#xff08;Pipe Shell&#xff09;生成是其重要功能之一。管道壳体广泛应用于…

JS正则表达式介绍(JavaScript正则表达式)

文章目录 JavaScript正则表达式完全指南正则表达式基础元字符与特殊字符基本元字符. - 点号\d - 数字\D - 非数字\w - 单词字符\W - 非单词字符\s - 空白字符\S - 非空白字符 正则表达式标志常用标志详解g - 全局匹配i - 忽略大小写m - 多行匹配s - 点号匹配所有字符u - Unicod…

Kubernetes 使用 containerd 实现 GPU 支持及 GPU Operator 部署指南

目录 Kubernetes 使用 containerd 实现 GPU 支持及 GPU Operator 部署指南 一、为什么 containerd 是趋势&#xff1f; 二、目标 三、前提条件 四、方式一&#xff1a;containerd nvidia-container-toolkit&#xff08;基础方式&#xff09; 1️⃣ 安装 NVIDIA Containe…

leetcode 2918. 数组的最小相等和 中等

给你两个由正整数和 0 组成的数组 nums1 和 nums2 。 你必须将两个数组中的 所有 0 替换为 严格 正整数&#xff0c;并且满足两个数组中所有元素的和 相等 。 返回 最小 相等和 &#xff0c;如果无法使两数组相等&#xff0c;则返回 -1 。 示例 1&#xff1a; 输入&#xf…

猿人学第十二题-js入门

1. 链接 https://match.yuanrenxue.cn/match/12 2. 抓包分析 2.1. m参数 通过观察&#xff0c;只有m参数要解决&#xff1a; 3. 逆向分析 3.1. 跟栈 直接跟栈吧&#xff0c;一下就出结果了&#xff1a; 可以看到m其实很简单&#xff0c;就是固定字符串 当前页数&#xf…

双系统电脑中如何把ubuntu装进外接移动固态硬盘

电脑&#xff1a;win11 ubuntu22.04 实体机 虚拟机&#xff1a;VMware17 镜像文件&#xff1a;ubuntu-22.04.4-desktop-amd64.iso 或者 ubuntu20.4的镜像 外接固态硬盘1个 一、首先win11中安装vmware17 具体安装方法&#xff0c;网上很多教程 二、磁盘分区 1.在笔…

202535| Kafka架构与重要概念+幂等性+事务

好的&#xff01;以下是关于 Kafka 架构 以及其 重要概念 的详细介绍&#xff0c;结合 Mermaid 图形 和 表格&#xff0c;帮助你更好地理解各个概念的关系和作用。 Kafka 架构与重要概念 Kafka 是一个分布式消息系统&#xff0c;广泛应用于日志收集、流处理、事件驱动架构等场…

从0开始学习大模型--Day05--理解prompt工程

提示词工程原理 N-gram&#xff1a;通过统计&#xff0c;计算N个词共同出现的概率&#xff0c;从而预测下一个词是什么。 深度学习模型&#xff1a;有多层神经网络组成&#xff0c;可以自动从数据中学习特征&#xff0c;让模型通过不断地自我学习不断成长&#xff0c;直到模型…

Amazing晶焱科技:系统级 EOS 测试方法 - System Level EOS Testing Method

系统上常见的EOS测试端口以AC电源、电话线&#xff08;RJ11&#xff09;、同轴电缆&#xff08;coaxial cable&#xff09;以及以太网络&#xff08;RJ45&#xff09;最常见&#xff0c;这些端口因有机会布线至户外的关系&#xff0c;受到EOS/Surge冲击的几率也大大提升。因此电…

数据结构—(概述)

目录 一 数据结构&#xff0c;相关概念 1. 数据结构&#xff1a; 2. 数据(Data): 3. 数据元素(Data Element): 4. 数据项&#xff1a; 5. 数据对象(Data Object): 6. 容器&#xff08;container&#xff09;&#xff1a; 7. 结点&#xff08;Node&#xff09;&#xff…

Vue 两种导航方式

目录 一、声明式导航 二、编程式导航 三、两句话总结 一、声明式导航 1. 传参跳转&#xff1a; <router-link :to"/user?nameCHEEMS&id114514">Query传参 </router-link><router-link :to"/user?参数名1参数值1&参数名2参数值2&a…

QTableWidget实现多级表头、表头冻结效果

最终效果&#xff1a; 实现思路&#xff1a;如果只用一个表格的话写起来比较麻烦&#xff0c;可以考虑使用两个QTableWidget组合&#xff0c;把复杂的表头一个用QTableWidget显示&#xff0c;其他内容用另一个QTableWidget。 #include "mainwindow.h" #include &qu…

2025年客运从业资格证备考单选练习题

客运从业资格证备考单选练习题 1、从事道路旅客运输活动时&#xff0c;应当采取必要措施保证旅客的人身和财产安全&#xff0c;发生紧急情况时&#xff0c;首先应&#xff08; &#xff09;。 A. 抢救财产 B. 抢救伤员 C. 向公司汇报 答案&#xff1a;B 解析&#xff1a;…

python打卡day21

常见的降维算法 知识点回顾&#xff1a; LDA线性判别PCA主成分分析t-sne降维 之前学了特征降维的两个思路&#xff0c;特征筛选&#xff08;如树模型重要性、方差筛选&#xff09;和特征组合&#xff08;如SVD/PCA&#xff09;。 现在引入特征降维的另一种分类&#xff1a;无/有…