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

光线追踪(纹理映射)

最近在跟着ray trace in one week来学习光线追踪(很多概念茅塞顿开)做到一半想着记录一下(比较随心)上面是之前的效果。ray trace in one week

Texture Coordinates for Spheres(球体纹理坐标)

u, v 纹理坐标指定了2D源图像(或某些2D参数化空间)上的位置。需要一种方式在3d物体上找到u,v坐标。这种映射是完全任意的,但通常你希望覆盖整个表面,并且能够以某种有意义的方式缩放、定位和拉伸2D图像。

对于球体而言,纹理坐标一般是基于球体的经度和纬度。我们计算球坐标中的 (θ, ϕ),其中 θ 是从底部极点(即从 -Y 方向)向上的角度(0~180度),而 ϕ 是围绕 Y 轴的角度(从 -X 到 +Z 到 +X 到 -Z 再回到 -X)(360度)。希望将 θ 和 ϕ 映射到纹理坐标 u 和 v,每个都在 [0,1] 范围内,其中 (u=0, v=0) 映射到纹理的左下角。因此,从 (θ, ϕ) 到 (u, v) 的归一化将是:

u = \frac{\phi }{2\pi }

v = \frac{\theta }{\pi }

将角度变化映射到对应的x,y,z的坐标上:

std::atan2(y, x),C++中一种double类型的反正切函数,返回值为弧度,是点(0,0)和(x,y)的连线与X轴正半轴的夹角,其值域为 [-π,π] (当y=0时,可以取到±π),且在第一二象限为正,在第三四象限为负。同理arccos是-y与单位向量1的夹角。

其中\phi的范围为[-π,π] ,此时映射回u为(-1/2~1/2)与实际的u(0~1)不符合,于是将其进行处理,

因为我们有等式:(atan2 函数是 arctan 函数的一种改进,它返回的是给定的 x 和 y 坐标值的反正切值,并且会根据 x 和 y 的符号来确定返回角度的象限。这里加上pai以后回到原来的象限)

所以:

注意此时的x,y,z的击中点都是单位向量点

static void get_sphere_uv(const Point3& p,double &u , double& v){auto theta = std::acos(-p.y());auto phi = std::atan2(-p.z(),p.x())+pi;u = phi / (2 *pi);v = theta / pi;
}
bool hit(const Ray& r,interval ray_t,hit_record& rec) const override{Point3 center = is_moving? sphere_center(r.time()) : center1;vec3 oc=center-r.origin();double a=r.direction().length_squared();double h= dot(r.direction(),oc);double c= oc.length_squared() - radius*radius;double discriminant= h * h - a * c;if(discriminant<0.0) return false;double sqrtd=std::sqrt(discriminant);double root=(h-sqrtd)/a;if(!ray_t.surrounds(root)){root=(h+sqrtd)/a;if(!ray_t.surrounds(root)){return false;}}rec.t=root;rec.p=r.at(root);vec3 outwrad_normal=unit_vector(rec.p-center);rec.set_face_normal(r,outwrad_normal);rec.mat=mat;get_sphere_uv(outwrad_normal,rec.u,rec.v);return true;}

从撞击点 P 开始,我们计算出表面坐标 (u, v)。然后我们使用这些坐标来索引我们的程序化实体纹理(例如大理石)。我们也可以读取一个图像,并使用二维 (u, v) 纹理坐标来索引图像。

对于一个(Nx*Ny)的图像,其(i,j)像素对应的纹理坐标为

Accessing Texture Image Data

接下来需要一个方式去读取图像。这里介绍stb_image。它将图像数据读入到一个32位浮点数的数组中。这些数值是打包的 RGB 值,每个分量的值在 [0,1] 的范围内(从黑色到纯白色)。此外,图像以线性颜色空间(gamma = 1)加载——这是我们进行所有计算所使用的颜色空间。

为了帮助我们更轻松地加载图像文件,我们提供了一个辅助类来管理这一切:rtw_image。它提供了一个辅助函数 —— pixel_data(int x, int y) —— 用于获取每个像素的8位RGB字节值。

首先我们需要下载stb_image.h

然后编写封装的类rtw_image

首先我需要一个初始化的定义,让我的程序从定义的目录中去加载图片,并且若加载失败还能输出错误信息。那么首先我需要一个函数去调用stb_image.h

rtw_image(const char* image_filename){// 从指定的文件加载图像数据。如果定义了 RTW_IMAGES 环境变量,// 则只在该目录中查找图像文件。如果找不到图像,// 首先从当前目录开始搜索指定的图像文件,然后在 images/ 子目录中搜索,// 接着是 父目录 的 images/ 子目录,然后是 那个父目录的父目录,依此类推,// 向上搜索六个级别。如果图像没有成功加载,// width() 和 height() 将返回 0。std::string filename = std::string(image_filename);auto imagedir = getenv("RTW_IMAGES");if(imagedir && load(std::string(imagedir) + "/" + filename)) return;if(load(filename)) return;if(load("images/"+filename)) return;if(load("../images/"+filename)) return;if(load("../../images/"+filename)) return;if(load("../../../images/"+filename)) return;if(load("../../../../images/"+filename)) return;if(load("../../../../../images/"+filename)) return;if(load("../../../../../../images/"+filename)) return;std::cerr<<"ERROR: Could not load image file'"<<image_filename<<"'.\n";
}
bool load(const std::string& filename){// 从给定的文件名加载线性(gamma=1)图像数据。// 如果加载成功,返回 true。// 结果数据缓冲区包含第一个像素的三个 [0.0, 1.0] // 范围内的浮点值(首先是红色,然后是绿色,然后是蓝色)。// 像素是连续的,从图像的宽度方向从左到右,接着是下一行,// 直到整个图像的高度。int n = bytes_per_pixel;fdata = stbi_loadf(filename.c_str(),&image_width,&image_height,&n,bytes_per_pixel);if (fdata == nullptr) return false;return true;
}

同时由于我们需要根据给定的图像上的像素的x,y的坐标来得到对应的像素的值,其中fdata返回的就是以行为优先级的每一列为bytes_per_pixel的值。于是这里我们需要手动记录每一行的像素值为多少。

bool load(const std::string& filename){// 从给定的文件名加载线性(gamma=1)图像数据。// 如果加载成功,返回 true。// 结果数据缓冲区包含第一个像素的三个 [0.0, 1.0] // 范围内的浮点值(首先是红色,然后是绿色,然后是蓝色)。// 像素是连续的,从图像的宽度方向从左到右,接着是下一行,// 直到整个图像的高度。int n = bytes_per_pixel;fdata = stbi_loadf(filename.c_str(),&image_width,&image_height,&n,bytes_per_pixel);if (fdata == nullptr) return false;bytes_per_scanline = image_width * bytes_per_pixel;convert_to_bytes();return true;
}

并且由于值都是[0.0,1.0]的范围,于是需要根据这个值来转换为0~255的范围。

static unsigned char float_to_byte(float value){if (value <= 0.0) return 0;if (value >= 1.0) return 255;return static_cast<unsigned char>(256.0 * value);//这里希望,比如0.99可以转换为255
}
void convert_to_bytes(){// Convert the linear floating point pixel data to bytes, storing the resulting byte// data in the `bdata` member.int total_bytes = image_width * image_height * bytes_per_pixel;bdata = new unsigned char[total_bytes];unsigned char *bptr = bdata;float *fptr = fdata;for (int i=0;i<total_bytes;i++,bptr++,fptr++){*bptr = float_to_byte(*fptr);}
}

最后我们需要一个函数可以根据我们提供的图像上的像素坐标得到我们实际需要的颜色值。

const unsigned char* pixel_data(int x,int y) const{static unsigned char magenta[] = {255,0,255};if(bdata == nullptr) return magenta;x = clamp(x,0,image_width);y = clamp(y,0,image_height);return bdata + y * bytes_per_scanline + x * bytes_per_pixel;}
static int clamp(int x,int low,int high){if (x<low) return low;if (x<high) return x;return high-1;}

rtw_image.h的总代码


#ifndef RTW_STB_IMAGE_H
#define RTW_STB_IMAGE_H
// Disable strict warnings for this header from the Microsoft Visual C++ compiler.
#ifdef _MSC_VER#pragma warning(push,0);
#endif
#define STB_IMAGE_IMPLEMENTATION
#define STBI_FAILURE_USERMSG
#include "extern/stb_image.h"
#include <cstdlib>
#include <iostream>
class rtw_image{
public:rtw_image(){}rtw_image(const char* image_filename){// 从指定的文件加载图像数据。如果定义了 RTW_IMAGES 环境变量,// 则只在该目录中查找图像文件。如果找不到图像,// 首先从当前目录开始搜索指定的图像文件,然后在 images/ 子目录中搜索,// 接着是 父目录 的 images/ 子目录,然后是 那个父目录的父目录,依此类推,// 向上搜索六个级别。如果图像没有成功加载,// width() 和 height() 将返回 0。std::string filename = std::string(image_filename);auto imagedir = getenv("RTW_IMAGES");if(imagedir && load(std::string(imagedir) + "/" + filename)) return;if(load(filename)) return;if(load("images/"+filename)) return;if(load("../images/"+filename)) return;if(load("../../images/"+filename)) return;if(load("../../../images/"+filename)) return;if(load("../../../../images/"+filename)) return;if(load("../../../../../images/"+filename)) return;if(load("../../../../../../images/"+filename)) return;std::cerr<<"ERROR: Could not load image file'"<<image_filename<<"'.\n";}bool load(const std::string& filename){// 从给定的文件名加载线性(gamma=1)图像数据。// 如果加载成功,返回 true。// 结果数据缓冲区包含第一个像素的三个 [0.0, 1.0] // 范围内的浮点值(首先是红色,然后是绿色,然后是蓝色)。// 像素是连续的,从图像的宽度方向从左到右,接着是下一行,// 直到整个图像的高度。int n = bytes_per_pixel;fdata = stbi_loadf(filename.c_str(),&image_width,&image_height,&n,bytes_per_pixel);if (fdata == nullptr) return false;bytes_per_scanline = image_width * bytes_per_pixel;convert_to_bytes();return true;}int width() const {return (fdata == nullptr) ? 0 : image_width;}int height() const {return (fdata == nullptr) ? 0 : image_height;}const unsigned char* pixel_data(int x,int y) const{static unsigned char magenta[] = {255,0,255};if(bdata == nullptr) return magenta;x = clamp(x,0,image_width);y = clamp(y,0,image_height);return bdata + y * bytes_per_scanline + x * bytes_per_pixel;}
private:const int bytes_per_pixel = 3;float    *fdata = nullptr; // Linear floating point pixel data 每个像素的0~1的值unsigned char *bdata = nullptr;  Linear 8-bit pixel data 转换为8bit的颜色值int       image_width = 0;int       image_height = 0;int       bytes_per_scanline = 0;static int clamp(int x,int low,int high){if (x<low) return low;if (x<high) return x;return high-1;}static unsigned char float_to_byte(float value){if (value <= 0.0) return 0;if (value >= 1.0) return 255;return static_cast<unsigned char>(256.0 * value);//这里希望,比如0.99可以转换为255}void convert_to_bytes(){// Convert the linear floating point pixel data to bytes, storing the resulting byte// data in the `bdata` member.int total_bytes = image_width * image_height * bytes_per_pixel;bdata = new unsigned char[total_bytes];unsigned char *bptr = bdata;float *fptr = fdata;for (int i=0;i<total_bytes;i++,bptr++,fptr++){*bptr = float_to_byte(*fptr);}}
};
// Restore MSVC compiler warnings
#ifdef _MSC_VER#pragma warning (pop)
#endif#endif

应用

首先需要修改texture.h的代码,之前的纹理都是固定颜色的,现在的纹理使用的是图片的纹理颜色。

class image_texture : public texture{
public:image_texture(const char* image_filename) : image(image_filename){}color value(double u,double v,const Point3& p) const override{// If we have no texture data, then return solid cyan as a debugging aid.if(image.height() <= 0) return color(0,1,1);// Clamp input texture coordinates to [0,1] x [1,0]u = interval(0,1).clamp(u);v = 1.0 - interval(0,1).clamp(v);// Flip V to image coordinatesint i = int(u*image.width());int j = int(v*image.height());auto pixel = image.pixel_data(i,j);double color_scale = 1.0 / 255.0;return color(color_scale*pixel[0],color_scale*pixel[1],color_scale*pixel[2]);}
private:rtw_image image;
};

做一个地球

void earth(){hittable_list world;shared_ptr<image_texture> earth_texture = make_shared<image_texture>("../images/earthmap.jpg");shared_ptr<lambertian> earth_surface = make_shared<lambertian>(earth_texture);shared_ptr<sphere> globe = make_shared<sphere>(Point3(0,0,0),2,earth_surface);world.add(globe);camera cam;cam.aspect_ratio = 16.0 / 9.0;cam.image_width = 400;cam.samples_per_pixel = 100;cam.max_depth = 50;cam.vfov = 20;cam.lookfrom = Point3(0,0,12);cam.lookat = Point3(0,0,0);cam.vup = vec3(0,1,0);cam.defocus_angle = 0;cam.render(world);
}
int main(){earth();return 0;
}

相关文章:

光线追踪(纹理映射)

最近在跟着ray trace in one week来学习光线追踪&#xff08;很多概念茅塞顿开&#xff09;做到一半想着记录一下&#xff08;比较随心&#xff09;上面是之前的效果。ray trace in one week Texture Coordinates for Spheres&#xff08;球体纹理坐标&#xff09; u, v 纹理…...

传统产品经理VS现在AI产品经理,你要学习的太多了,超详细收藏我这一篇就够了

传统产品经理想要转行成为AI产品经理&#xff0c;需要经历一系列的学习和实践过程。下面是一份详细的学习路线图&#xff0c;旨在帮助你顺利转型。 学习路线图 了解AI基础知识 AI概览&#xff1a;阅读《人工智能&#xff1a;一种现代的方法》这样的书籍&#xff0c;以获得对AI…...

C#使用Socket实现TCP服务器端

1、TCP服务器实现代码 using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading; using System.Threading.Tasks;namespace PtLib.TcpServer {public delegate void Tcp…...

MTK联发科MT8766/MT8166安卓核心板性能参数对

MT8766核心板 采用联发科四核2G主频芯片方案&#xff0c;国内4G全网通。12nm先进工艺&#xff0c;支持 Android9.0系统。 GPU 采用超强 IMG GE8300 ,主频600MHz。支持高速LPDDR4/X&#xff0c;主频高达1600MHz。支持EMMC5.1。标配 WIFI 802.11 ac/abgn&#xff0c;BT 5.0。 支持…...

ps绘制动图

ps绘制动图教程&#xff08;简易版&#xff09;-直播gif动态效果图 第一步 打开ps绘制几个简单的长方形 第二步 将图层转化为智能图层 第三部 在窗口找到时间轴创建时间轴 第五步 通过变换来鼠标控制图像的变化并打下结束点 第六部 通过图像中的图像大小控制gif的大小 第七部 …...

AI学习指南机器学习篇-强化学习和深度学习简介

AI学习指南机器学习篇-强化学习和深度学习简介 强化学习和深度学习基本概念回顾 强化学习是一种机器学习方法&#xff0c;其目标是让智能体通过与环境的交互来学习最优的行为策略。在强化学习中&#xff0c;智能体不需要标记的训练数据&#xff0c;而是通过试错来提升自己的表…...

yolov8 bytetrack onnx模型推理

原文&#xff1a;yolov8 bytetrack onnx模型推理 - 知乎 (zhihu.com) 一、pt模型转onnx from ultralytics import YOLO# Load a model model YOLO(weights/yolov8s.pt) # load an official model # model YOLO(path/to/best.pt) # load a custom trained# Export the mod…...

ImageNet数据集和CIFAR-10数据集

一、为什么需要大量数据集 人工智能其实就是大数据的时代&#xff0c;无论是目标检测、图像分类、还是现在植入我们生活的推荐系统&#xff0c;“喂入”神经网络的数据越多&#xff0c;则识别效果越好、分类越准确。因此开源大型数据集的研究团队为人工智能的发展做了大量贡献…...

Go语言编程大全,web微服务数据库十大专题精讲

本课程主要从数据结构、Go Module 依赖管理、IO编程、数据库编程、消息队列、加密技术与网络安全、爬虫与反爬虫、web开发、微服务通用技术、Kitex框架等方面讲解~ 链接&#xff1a;https://pan.quark.cn/s/d65337a0e60d...

【LabVIEW学习篇 - 13】:队列

文章目录 队列 队列 队列通常情况下是一种先入先出&#xff08;FIFO&#xff1a;First in First out&#xff09;的数据结构&#xff0c;常用作数据缓存&#xff0c;通过队列结构可以保证数据有序的传递&#xff0c;避免竞争和冲突。 案例&#xff1a;利用队列&#xff0c;模…...

大语言模型综述泛读之Large Language Models: A Survey

摘要 这篇文章主要回顾了一些最突出的LLMs(GPT, LLaMA, PaLM)并讨论了它们的特点、贡献和局限性,就如何构建增强LLMs做了一个技术概述,然后调研了为LLM训练、微调和评估而准备的N多种流行数据集,审查了使用的LLM评价指标,在一组有代表性的基准上比较了几个流行的LLMs;最…...

奇偶函数的性质及运算

目录 定义 注意 特征 运算 拓展 定义 设函数f(x)的定义域D&#xff1b; 如果对于函数定义域D内的任意一个x&#xff0c;都有f(-x)&#xff0d;f&#xff08;x&#xff09;&#xff0c;那么函数f&#xff08;x&#xff09;就叫做奇函数。如果对于函数定义域D内的任意一个x…...

代码随想录 day 32 动态规划

第九章 动态规划part01 今天正式开始动态规划&#xff01; 理论基础 无论大家之前对动态规划学到什么程度&#xff0c;一定要先看 我讲的 动态规划理论基础。 如果没做过动态规划的题目&#xff0c;看我讲的理论基础&#xff0c;会有感觉 是不是简单题想复杂了&#xff1f; …...

支持目标检测的框架有哪些

目标检测是计算机视觉领域的一个重要任务&#xff0c;许多深度学习框架都提供了对目标检测的支持。以下是一些广泛使用的支持目标检测的深度学习框架&#xff1a; 1. TensorFlow TensorFlow 是一个广泛使用的开源深度学习框架&#xff0c;由Google开发。它提供了TensorFlow O…...

原神自定义倒计时

<!DOCTYPE html> <html lang"zh-CN"><head><meta charset"UTF-8"><title>原神倒计时</title><style>* {margin: 0;padding: 0;box-sizing: border-box;user-select: none;body {background: #0b1b2c;}}header {…...

top命令实时监测Linux进程

top命令可以动态实时显示Linux进程信息&#xff0c;方便观察频繁换进换出的内存的进程变化。 top命令执行示例如下&#xff1a; 其中&#xff0c;第一行表示系统当前时间、系统的运行时间、登录的用户数目、系统的平均负载&#xff08;最近1分钟&#xff0c;最近5分钟&#xff…...

Rust 所有权

所有权 Rust的核心特性就是所有权所有程序在运行时都必须管理他们使用计算机内存的方式 有些语言有垃圾收集机制&#xff0c;在程序运行时&#xff0c;他们会不断地寻找不再使用的内存在其他语言中&#xff0c;程序员必须显式的分配和释放内存 Rust采用了第三种方式&#xff1…...

Python面试题:结合Python技术,如何使用PyTorch进行动态计算图构建

PyTorch 是一个流行的深度学习框架&#xff0c;它通过动态计算图&#xff08;Dynamic Computation Graphs&#xff09;来支持自动微分&#xff08;Autograd&#xff09;。动态计算图的特点是每次前向传播时都会构建新的计算图&#xff0c;这使得它非常灵活&#xff0c;适合处理…...

基于RHEL7的服务器批量安装

目录 一、项目要求 二、实验环境 三、生成kickstart自动化安装脚本 四、搭建dhcp服务并测试kickstart脚本 五、搭建pxe网络安装环境实现服务器自动部署 ​编辑 六、测试 一、项目要求 1.使用kickstart编写自动化安装脚本 2.搭建dhcp服务并测试kickstart脚本 3.搭建px…...

C. Light Switches

文章目录 C. Light Switches题意&#xff1a;解题思路&#xff1a;解题代码&#xff1a; C. Light Switches 原题链接 题意&#xff1a; 房间的灯最初均为关闭状态&#xff0c;安装芯片后&#xff0c;它会每隔k分钟改变一次房间的灯光状态&#xff0c;即会打开灯光k分钟&…...

Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动

一、前言说明 在2011版本的gb28181协议中&#xff0c;拉取视频流只要求udp方式&#xff0c;从2016开始要求新增支持tcp被动和tcp主动两种方式&#xff0c;udp理论上会丢包的&#xff0c;所以实际使用过程可能会出现画面花屏的情况&#xff0c;而tcp肯定不丢包&#xff0c;起码…...

【Oracle APEX开发小技巧12】

有如下需求&#xff1a; 有一个问题反馈页面&#xff0c;要实现在apex页面展示能直观看到反馈时间超过7天未处理的数据&#xff0c;方便管理员及时处理反馈。 我的方法&#xff1a;直接将逻辑写在SQL中&#xff0c;这样可以直接在页面展示 完整代码&#xff1a; SELECTSF.FE…...

MongoDB学习和应用(高效的非关系型数据库)

一丶 MongoDB简介 对于社交类软件的功能&#xff0c;我们需要对它的功能特点进行分析&#xff1a; 数据量会随着用户数增大而增大读多写少价值较低非好友看不到其动态信息地理位置的查询… 针对以上特点进行分析各大存储工具&#xff1a; mysql&#xff1a;关系型数据库&am…...

Java如何权衡是使用无序的数组还是有序的数组

在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…...

Python爬虫实战:研究feedparser库相关技术

1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的信息资源。RSS(Really Simple Syndication)作为一种标准化的信息聚合技术,被广泛用于网站内容的发布和订阅。通过 RSS,用户可以方便地获取网站更新的内容,而无需频繁访问各个网站。 然而,互联网…...

Nuxt.js 中的路由配置详解

Nuxt.js 通过其内置的路由系统简化了应用的路由配置&#xff0c;使得开发者可以轻松地管理页面导航和 URL 结构。路由配置主要涉及页面组件的组织、动态路由的设置以及路由元信息的配置。 自动路由生成 Nuxt.js 会根据 pages 目录下的文件结构自动生成路由配置。每个文件都会对…...

今日科技热点速览

&#x1f525; 今日科技热点速览 &#x1f3ae; 任天堂Switch 2 正式发售 任天堂新一代游戏主机 Switch 2 今日正式上线发售&#xff0c;主打更强图形性能与沉浸式体验&#xff0c;支持多模态交互&#xff0c;受到全球玩家热捧 。 &#x1f916; 人工智能持续突破 DeepSeek-R1&…...

.Net Framework 4/C# 关键字(非常用,持续更新...)

一、is 关键字 is 关键字用于检查对象是否于给定类型兼容,如果兼容将返回 true,如果不兼容则返回 false,在进行类型转换前,可以先使用 is 关键字判断对象是否与指定类型兼容,如果兼容才进行转换,这样的转换是安全的。 例如有:首先创建一个字符串对象,然后将字符串对象隐…...

企业如何增强终端安全?

在数字化转型加速的今天&#xff0c;企业的业务运行越来越依赖于终端设备。从员工的笔记本电脑、智能手机&#xff0c;到工厂里的物联网设备、智能传感器&#xff0c;这些终端构成了企业与外部世界连接的 “神经末梢”。然而&#xff0c;随着远程办公的常态化和设备接入的爆炸式…...

搭建DNS域名解析服务器(正向解析资源文件)

正向解析资源文件 1&#xff09;准备工作 服务端及客户端都关闭安全软件 [rootlocalhost ~]# systemctl stop firewalld [rootlocalhost ~]# setenforce 0 2&#xff09;服务端安装软件&#xff1a;bind 1.配置yum源 [rootlocalhost ~]# cat /etc/yum.repos.d/base.repo [Base…...