光线追踪(纹理映射)

news2024/11/16 8:39:58

最近在跟着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 coordinates
        int 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;
}

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

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

相关文章

K-means聚类算法原理解析

度量最小距离 对于 K-means 聚类算法而言&#xff0c;找到质心是一项既核心又重要的任务&#xff0c;找到质心才可以划分出距离质心最近样本点。从数学角度来讲就是让簇内样本点到达各自质心的距离总和最小。通过数学定义&#xff0c;我们将“质心”具象化&#xff0c;既然要使…

使用SpringBoot+Vue3开发项目(2)---- 设计文章分类的相关接口及页面

目录 一.所用技术栈&#xff1a; 二.后端开发&#xff1a; 1.文章分类列表渲染&#xff1a; 2.新增文章分类&#xff1a; 3.编辑文章分类&#xff1a; 4.删除文章分类 &#xff1a; 5.完整三层架构后端代码&#xff1a; &#xff08;1&#xff09;Controller层&#xff1a…

学习大数据DAY31 Python基础语法4和基于Python中的MySQL 编程

目录 Python 库 模块 time&datetime 库 连接 MySQL 操作 结构操作 数据增删改操作 数据查询操作 上机练习 7 面向对象 OOP 封装 继承 三层架构---面向对象思想模型层 数据层 业务逻辑显示层 上机练习 8 三层架构开发豆瓣网 关于我对 AI 写代码的看法&#xf…

大模型技术在企业应用中的实践与优化

【导读】大模型技术更新层出不穷&#xff0c;但对于众多企业及开发者而言&#xff0c;更为关键的命题则是如何进行应用落地&#xff0c;实现真正的智能化转型。本文系统且深入地探讨了大模型在企业应用中的关键环节和技术要点。从构建高质量的专属数据集、选择适宜的微调策略&a…

天线增益测试方法之射频器件S参数测试软件

天线增益的精确测量对于优化无线信号传输至关重要。NSAT-1000射频器件S参数测试软件作为针对S参数的测试设备&#xff0c;大幅提高了测试精度和效率。本文将为大家介绍该软件在天线增益测试方面的具体操作流程。 一、准备工作 在测试天线增益之前&#xff0c;需要准备好测试软件…

【启明智显分享】Model3A 7寸TFT触摸彩屏智能电压力锅解决方案

随着智能家居市场的快速发展&#xff0c;电压力锅作为厨房电器的代表之一&#xff0c;正逐步向智能化、高端化转型。为了进一步提升用户体验&#xff0c;增强产品竞争力&#xff0c;我们推出基于Model3A 7寸触摸彩屏电压力锅解决方案。该方案旨在通过Model3A芯片的强大性能与7寸…

24/8/5算法笔记 BGD,SGD,MGD梯度下降

今日对比不同梯度下降的代码 1.BGD大批量梯度下降(一元一次&#xff09; 首先导入库 import numpy as npimport matplotlib.pyplot as plt 随机生成线性回归函数 Xnp.random.rand(100,1)w,bnp.random.randint(1,10,size2)#增加噪声&#xff0c;更像真实数据 #numoy广播机制…

mysql的安装与基本操作

1、centos7 中安装 mysql 8.x&#xff08;1&#xff09;下载安装包 wget https://downloads.mysql.com/archives/get/p/23/file/mysql-8.0.33-1.el7.x86_64.rpm-bundle.tar&#xff08;2&#xff09;解压 tar -xf mysql-8.0.33-1.el7.x86_64.rpm-bundle.tar&#xff08;3&…

PXE实验-使用kickstart批量自动部署操作系统

实验准备&#xff1a;rhel7.9具备图形界面的虚拟机&#xff0c;虚拟机网络配置可用&#xff0c;VMware 中NAT的DHCP功能关闭&#xff0c;虚拟机中yum源已配置好 1.在虚拟机中安装kickstart并且启动图形制作工具 yum install system-config-kickstart.noarch -y system-config…

【第13章】Spring Cloud之Gateway全局异常处理

文章目录 前言一、异常处理1. 响应实体类2. 异常处理类 二、单元测试1. 无可用路由2. 服务不可用 总结 前言 网关作为我们对外服务的入口起着至关重要的作用&#xff0c;我们必须保证网关服务的稳定性&#xff0c;下面来为网关服务增加异常处理机制。 一、异常处理 1. 响应实…

动态规划.

目录 &#xff08;一&#xff09;递归到动规的一般转化方法 &#xff08;二&#xff09;动规解题的一般思路 1. 将原问题分解为子问题 2. 确定状态 3. 确定一些初始状态&#xff08;边界状态&#xff09;的值 4. 确定状态转移方程 &#xff08;三&#xff09;能用动规解…

小程序 发布流程

1&#xff1a; 点击HbuilderX 菜单栏上的 发行> 小程序-微信&#xff08;适用于uni-app&#xff09; 2: 第二步&#xff1a; 需要再弹出框中填写发布系小程序的名称和AppId 之后&#xff0c; 点击发行按钮。 3&#xff1a;在Hbuilder 的控制台中 查看小程序发布编译的进度。…

VMware17下载与安装

1.下载 通过百度网盘分享的文件&#xff1a;VMware17 链接&#xff1a;https://pan.baidu.com/s/1gCine3d3Rp_l3NYAu5-ojg 提取码&#xff1a;ek25 --来自百度网盘超级会员V3的分享 2.安装

k8s(六)---pod

六、pod&#xff08;k8s中最小的调度单元&#xff09; pod中可以有一个或多个容器 1、官网 2、简介 Pod是k8s中最小的调度单元、Pod具有命名空间隔离性 3、如何创建一个Pod资源&#xff08;主要两种方式&#xff09; 1&#xff09;kubctl run ①kubectl run nginx–imagereg…

k8s(七)---标签

一、标签&#xff08;适用于资源定位&#xff09; label是一对key和value,创建标签后&#xff0c;方便对资源进行分组管理。 1.帮助 kubectl label --help 2.打标签 pod 针对于pod打标签 key是env&#xff0c;value是test kubectl label po nginx envtest 给pod打标签 3.查看 k…

Qcustomplot绘制实时动态曲线??

&#x1f3c6;本文收录于《CSDN问答解惑-专业版》专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收…

uviewPlus 组件库的使用

文章目录 1、 1、 全局引入样式文件 &#xff08;该语句是文档中提及但是不存在的语句&#xff09;

mysql的安装配置与基础用户使用

第五周 周一 早 mysql安装配置 1.官网下载或者wget [rootmysql ~]# ls anaconda-ks.cfg initserver.sh mysql-8.0.33-1.el7.x86_64.rpm-bundle.tar mysql-community-client-8.0.33-1.el7.x86_64.rpm mysql-community-client-plugins-8.0.33-1.el7.x86_64.rpm mysql-c…

Dockerfile 容器镜像制作 私有仓库

Dockerfile 概述 制作镜像 FROM CMD # ENTRYPOINT 与 CMD 执行方式为 ${ENTRYPOINT} ${-${CMD}} apache 镜像 nginx 镜像 php-fpm 镜像 docker 私有仓库

单位工作邮箱如何实现快速开通

单位工作邮箱如何实现快速开通&#xff1f;单位工作邮箱快速开通需分析需求、选合适服务商、备材料、注册验证配置MX记录、创账户。开通前需测试邮件收发、功能及安全&#xff0c;确保稳定运行。本文将详细介绍单位工作邮箱的前期准备以及快速开通的流程。 一、需求分析与规划…