柏林噪声(Perlin Noise)

news2024/9/24 15:27:40

要获得看起来很酷的实心纹理,大多数人使用某种形式的Perlin噪声。Perlin噪声返回类似下图的噪声。

Perlin噪声的一个关键部分是它是可重复的:它接受一个3D点作为输入,并总是返回相同的随机数字。附近的点返回相似的数字。Perlin噪声的另一个重要部分是它要简单快速,因此通常作为一种技巧来实现。

Using Blocks of Random Numbers

使用随机数块返回albedo。文章中对于该噪声的实现如下:

  1. 首先定义了一个2的整数倍的数组大小L(文章中为255)
  2. 然后定义一个随机数组随机获得L个【0~1)的随机数。
  3. 分别计算perm_x数组和对应的y,z数组。
  4. 混乱数组中的(0~i)的下标所指向的(0~i)的数
  5. 最后按照以下方式得到albedo(其实也就是256个噪声点)
double noise(const point3& p) const {
    auto i = int(4*p.x()) & 255;
    auto j = int(4*p.y()) & 255;
    auto k = int(4*p.z()) & 255;

    return randfloat[perm_x[i] ^ perm_y[j] ^ perm_z[k]];
}

首先是随机选取256个噪声点

perlin(){
    for(int i=0;i<point_count;i++){
        randfloat[i] = random_double();
    }

}

然后对于perm数组,得到像素点的打乱的值。

private:
static const int point_count = 256;
double randfloat[point_count];
int perm_x[point_count];
int perm_y[point_count];
int perm_z[point_count];
static void perlin_generate_perm(int* p){
    for(int i=0;i<point_count;i++){
        p[i] = i;
    }
    permute(p,point_count);
}
static void permute(int* p,int n){
    for(int i=n-1;i>0;i--){
        int target = random_int(0,i);
        int temp = p[i];
        p[i] = target;
        p[target] = temp;
    }
}

最后在初始化阶段就需要去这样初始化。

perlin(){
    for(int i=0;i<point_count;i++){
        randfloat[i] = random_double();
    }
    perlin_generate_perm(perm_x);
    perlin_generate_perm(perm_y);
    perlin_generate_perm(perm_z);
}

以及对于传入一个点,返回其albedo.这里文章中是进行了缩放,将坐标乘4以至于实现表现的快速缩放。并且防止数组溢出,与255取模(&)下面的取反也是这个原因。

double noise(const Point3& p) const{
    int i = int(4*p.x()) & 255;
    int j = int(4*p.y()) & 255;
    int z = int(4*p.z()) & 255;

    return randfloat[perm_x[i]^perm_y[j]^perm_z[z]];
}

Perlin材质

实现了这样的Perlin方法了以后,我们就需要去根据这个Perlin类创建它的材质,也就是给定顶点后返回其albedo

class noise_texture : public texture{
public:
noise_texture(){}
color value(double u,double v,const Point3& p) const override{
    return color(1.0,1.0,1.0) * noise.noise(p);
}
private:
perlin noise;
};

在main方法中进行调用。

void perlin_spheres(){
    hittable_list world;
    shared_ptr<noise_texture> pertex = make_shared<noise_texture>();
    world.add(make_shared<sphere>(Point3(0,-1000,0),1000,make_shared<lambertian>(pertex)));
    world.add(make_shared<sphere>(Point3(0,2,0),2,make_shared<lambertian>(pertex)));
    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(13,2,3);
    cam.lookat   = Point3(0,0,0);
    cam.vup      = vec3(0,1,0);

    cam.defocus_angle = 0;

    cam.render(world);
}

看起来是比较粗糙的

Smoothing out the Result(平滑)

首先我们知道,我们的randfloat随机值是在一开始就固定好了。那么我们如何能够实现立体空间上的平滑,自然也就是属性的插值,得到一个立方体上每个点都平滑的效果。

这里使用了三线性插值。

最开始的时候,我们是对坐标直接取整。现在,我们设定我们的击中点在一个立方体内,对于这个立方体,其顶点的属性就是我们的randfloat中的顶点。

这里分别得到点在立方体内部的位置,和立方体的起始位置。

auto u = p.x() - std::floor(p.x());
auto v = p.y() - std::floor(p.y());
auto w = p.z() - std::floor(p.z());

auto i = int(std::floor(p.x()));
auto j = int(std::floor(p.y()));
auto k = int(std::floor(p.z()));

然后定义一个数组去记录立方体的每个属性

for(int di=0;di<2;di++){
    for(int dj=0;dj<2;dj++){
        for(int dk=0;dk<2;dk++){
            c[di][dj][dk] = randfloat[
                perm_x[(i+di) & 255] ^
                perm_y[(j+dj) & 255] ^
                perm_z[(k+dk) & 255]
                ];
        }
    }
}
return trilinear_interp(c,u,v,w);

最后进行三线性插值

static double trilinear_interp(double c[2][2][2],double u, double v, double w){
    double x0_y0_z0_1 = c[0][0][0] * (1-w) + c[0][0][1] * w;
    double x0_y1_z0_1 = c[0][1][0] * (1-w) + c[0][1][1] * w;
    double x1_y0_z0_1 = c[1][0][0] * (1-w) + c[1][0][1] * w;
    double x1_y1_z0_1 = c[1][1][0] * (1-w) + c[1][1][1] * w;

    double x0_1_y1 = x0_y1_z0_1 * (1-u) + x1_y1_z0_1 * u;
    double x0_1_y0 = x0_y0_z0_1 * (1-u) + x1_y0_z0_1 * u;

    double accum = x0_1_y0 * (1-v) + x0_1_y1 * v;
    return accum;
}

文章中是这样

double accum =0.0;
for(int dx=0;dx<2;dx++){
    for(int dy=0;dy<2;dy++){
        for(int dz=0;dz<2;dz++){
            accum += ((dx*u + (1-dx) * (1-u)) * 
                (dy*v + (1-dy) * (1-v)) *
                (dz*w + (1-dz) * (1-w)))
                *c[dx][dy][dz];
        }
    }
}
return accum;

这两种方式是等价的

文章的平滑操作

平滑处理可以带来改进的结果,但其中仍然存在明显的网格特征。其中一些是马赫带效应,这是线性插值颜色的一个已知感知伪影。一个常用的技巧是使用赫尔曼三次样条来平滑插值。

double noise(const Point3& p) const{
    auto u = p.x() - std::floor(p.x());
    auto v = p.y() - std::floor(p.y());
    auto w = p.z() - std::floor(p.z());
    u = u * u * (3-2*u);
    v = v * v * (3-2*v);
    w = w * w * (3-2*w);
    auto i = int(std::floor(p.x()));
    auto j = int(std::floor(p.y()));
    auto k = int(std::floor(p.z()));
    double c[2][2][2];
    for(int di=0;di<2;di++){
        for(int dj=0;dj<2;dj++){
            for(int dk=0;dk<2;dk++){
                c[di][dj][dk] = randfloat[
                    perm_x[(i+di) & 255] ^
                    perm_y[(j+dj) & 255] ^
                    perm_z[(k+dk) & 255]
                    ];
            }
        }
    }
    return trilinear_interp(c,u,v,w);
}

Tweaking The Frequency(增大采样频率)

我们可以增大采样频率,让效果更加明显。其实就是加快它到达下一个状态的频率。

class noise_texture : public texture{
public:
    noise_texture(double scale) : scale(scale){}
    color value(double u,double v,const Point3& p) const override{
        return color(1.0,1.0,1.0) * noise.noise(scale*p);
    }
private:
    perlin noise;
    double scale;
};

Using Random Vectors on the Lattice Points

上面的结果看起来仍然有有一点块状。

在原始的 Perlin 噪声中,如果每个格点上的值只是一个随机浮点数,那么在进行插值计算时,会导致噪声图案显得比较块状。这是因为插值函数会直接在这些随机浮点数之间进行线性插值,而这些随机浮点数在整数的 x、y、z 格点上定义。因此,插值函数无法生成平滑过渡的噪声图案。我们来详细解释这一过程。

假设我们在三维空间中有一个输入点 p,它的坐标是 (x, y, z)。为了计算 p 处的噪声值,我们会:

  1. 确定 p 所在的单位立方体,即找到 p 周围最近的 8 个格点。
  2. 对每个格点,查找其对应的随机浮点数值。
  3. 使用三线性插值对这 8 个随机浮点数值进行插值,得到 p 处的噪声值。

在这个过程中,插值是基于整数的 x、y、z 格点上进行的。因为这些格点上的值是随机的浮点数,所以当插值函数在这些随机数之间进行插值时,产生的噪声值会在这些随机数值之间跳跃,导致噪声图案的块状效果。具体来说:

  • p 的坐标接近某个整数格点时,插值函数会更多地依赖于该格点的随机浮点数值。
  • 由于每个整数格点上的随机数值可能有很大的差异,这会导致在整数格点处的噪声值变化剧烈。
  • 这种变化在整个空间中重复出现,导致噪声图案呈现出块状效果。

Ken Perlin 的改进方法是使用随机单位向量而不是随机浮点数。这种方法能有效避免块状效果,原因如下:

  1. 随机单位向量:在每个格点上放置一个随机单位向量,而不是一个随机浮点数。这些单位向量可以代表不同的方向。
  2. 点积计算:对于每个输入点 p,计算 p 相对于每个格点的相对位置向量,然后与该格点的随机单位向量进行点积。这会生成一个标量值。
  3. 平滑插值:使用这些点积的结果进行插值,生成最终的噪声值。

通过这种方法,插值是在相对位置向量与随机单位向量的点积之间进行的,而不是直接在随机浮点数之间进行。这样一来,噪声值的变化不再局限于整数格点上,而是可以在整个单位立方体内平滑过渡,从而生成更加自然的噪声图案

perlin(){
    for(int i=0;i<point_count;i++){
        randvec[i] = unit_vector(vec3::random(-1,1));
    }
    perlin_generate_perm(perm_x);
    perlin_generate_perm(perm_y);
    perlin_generate_perm(perm_z);
}
static double perlin_interp(const vec3 c[2][2][2],double v,double u,double w){
    double uu = u * u * (3 - 2 * u);
    double vv = v * v * (3 - 2 * v);
    double ww = w * w * (3 - 2 * w);
    double accum = 0.0;
    for(int i=0;i<2;i++){
        for(int j=0;j<2;j++){
            for(int k=0;k<2;k++){
                vec3 weight(u-i,v-j,w-k);//相对位置
                accum += (i*u + (1-i)*(1-u)) *
                    (j*v + (1-j)*(1-v)) *
                    (k*w + (1-k)*(1-w)) *
                    dot(c[i][j][k],weight);
            }
        }
    }
    return accum;
}
class noise_texture : public texture{
public:
    noise_texture(double scale) : scale(scale){}
    color value(double u,double v,const Point3& p) const override{
        return color(1.0,1.0,1.0) * 0.5 * (1.0+noise.noise(scale*p));
    }
private:
    perlin noise;
    double scale;
};

Introducing Turbulence

通常使用具有多个叠加频率的复合噪声。这通常被称为Turbulence,是对噪声重复调用的总和。

double turb(const Point3& p, int depth) const{
        double accum = 0.0;
        Point3 temp_p = p;
        double weight = 1.0;
        for(int i=0;i<depth;i++){
            accum+=weight*noise(temp_p);
            weight *= 0.5;
            temp_p *= 2;
        }
        return std::fabs(accum);
    }
class noise_texture : public texture{
public:
noise_texture(double scale) : scale(scale){}
color value(double u,double v,const Point3& p) const override{
    return color(1.0,1.0,1.0)  * noise.turb(p,7);
}
private:
perlin noise;
double scale;
};

Adjusting the Phase

然而,通常Turbulence是间接使用的。例如,程序化实体纹理的“Hello World”是一个简单的类似大理石的纹理。基本思想是将颜色与类似正弦函数的东西成比例,并使用Turbulence来调整相位(使其在sin(x)中沿x方向移动),这使得条纹波动。注释掉直接的噪声和Turbulence,并给出类似大理石的效果是:

class noise_texture : public texture{
public:
noise_texture(double scale) : scale(scale){}
color value(double u,double v,const Point3& p) const override{
    return color(.5, .5, .5) * (1 + std::sin(scale * p.z() + 10 * noise.turb(p, 7)));
}
private:
perlin noise;
double scale;
};

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

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

相关文章

Spacedrive :开源跨平台文件管理器!!【送源码】

项目介绍 Spacedrive 是一款革命性的开源跨平台文件管理器&#xff0c;它采用先进的虚拟分布式文件系统 (VDFS) 技术&#xff0c;为你带来前所未有的文件管理体验。无论是云端服务还是离线硬盘&#xff0c;Spacedrive 都能将它们统一在一个易于使用的界面中&#xff0c;让你轻松…

超声波清洗机哪个牌子的好?推荐四款性价比高的超声波眼镜清洗机

眼镜是现代生活中的必需品&#xff0c;但很多人对如何正确清洁眼镜感到困扰。传统清洁方法可能会在清洗过程中损伤眼镜&#xff0c;比如用普通肥皂或清水清洗时容易划伤镜片。为了解决这个问题&#xff0c;家用眼镜超声波清洗机应运而生。然而&#xff0c;市场上品牌繁多&#…

亲测好用!2024年四大ai智能写作工具推荐!

在快节奏的工作生活中&#xff0c;我一直在寻找能够提升写作效率的工具。经过一番探索&#xff0c;我发现了四款AI智能写作工具。它们不仅极大地提升了我的写作速度&#xff0c;还保证了内容的质量。 笔灵AI写作 链接&#xff1a;https://ibiling.cn 作为一名自媒体人&#…

免费高清图片素材库,我推荐这8个~赶紧收藏

找高清图片素材就上这8 个网站&#xff0c;我强推。免费、付费、商用的素材都能找到&#xff0c;赶紧先收藏起来吧&#xff01; 菜鸟图库 美女图片|手机壁纸|风景图片大全|高清图片素材下载网 - 菜鸟图库 网站主要为新手设计师提供免费素材&#xff0c;这些素材的质量都很高…

CSDN博文中的数学公式显示——LaTeX数学公式一般语法

$MarkDown支持&#xff0c;HTML5文本也是支持$定制的。 (笔记模板由python脚本于2024年08月07日 21:18:36创建&#xff0c;本篇笔记适合初通Python&#xff0c;熟悉六大基本数据(str字符串、int整型、float浮点型、list列表、tuple元组、set集合、dict字典)的coder翻阅) 【学习…

explorer.exe没有注册类

管理员身份进入cmd面板 输入sfc /scannow 进行扫描&#xff0c;会自动修复异常文件 验证100% 后&#xff0c;输入 start explorer.exe 进行验证 这个问题就解决了

【Week-G6】CycleGAN-风格迁移网络-pytorch

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 | 接辅导、项目定制 本次学习内容为适用于风格迁移的CycleGAN网络 一、说明 本地文件目录&#xff1a; 程序修改的部分&#xff1a; 二、训练 等待训练完成…

相机标定——小孔成像、相机模型与坐标系

小孔成像 用一个带有小孔的板遮挡在墙体与物之间&#xff0c;墙体上就会形成物的倒影&#xff0c;我们把这样的现象叫小孔成像。 用一个带有小孔的板遮挡在墙体与物之间&#xff0c;墙体上就会形成物的倒影&#xff0c;我们把这样的现象叫小孔成像。前后移动中间的板&#xff…

聚贤国际商会成都分部盛大启航,助力中小企业共克时艰。

近日&#xff0c;聚贤国际商会成都分部在万众瞩目中正式成立&#xff0c;标志着聚贤国际在助力全球中小企业应对经济挑战、实现共同发展的征途上又迈出了坚实的一步。此次成都分部的设立&#xff0c;不仅是对聚贤国际全球布局的重要补充&#xff0c;更是对西南地区中小企业发展…

微信小程序开发【从0到1~入门篇完结】

目录 1.使用 npm 包1.1Vant Weapp1.2API Promise化传统回调函数风格Promise化优势注意事项实现API Promise化 2.全局数据共享3.分包4.案例--自定义tabBar 谢谢您能够坚持看到最后的一篇文章&#xff0c;读完这篇文章后&#xff0c;您已经学会了微信小程序开发的基础知识&#x…

GHOST重装系统分区丢失:成因解析与高效数据恢复指南

开篇&#xff1a;GHOST重装下的数据挑战 在计算机技术日新月异的今天&#xff0c;GHOST重装系统以其高效、便捷的特点&#xff0c;成为了众多用户解决系统问题、快速恢复系统环境的首选方案。然而&#xff0c;这一看似简单的操作背后&#xff0c;却隐藏着数据安全的巨大挑战&a…

ip地址冲突会影响整个网络吗

在数字化时代&#xff0c;网络已成为连接世界的桥梁&#xff0c;而IP地址则是这座桥梁上不可或缺的“门牌号”。然而&#xff0c;当这个独特的身份标识出现冲突时&#xff0c;整个网络的稳定运行将面临严峻挑战。IP地址冲突&#xff0c;这一看似微小的技术问题&#xff0c;实则…

【数据结构】算法的时间复杂度与空间复杂度

计算机考研408-数据结构笔记本之——第一章 绪论 1.2 算法和算法评价 1.2.2 算法效率的度量 算法效率的度量是通过时间复杂度和空间复杂度来描述的。 1.空间复杂度 算法的空间复杂度S(n)定义为该算法所需的存储空间&#xff0c;它是问题规模n的函数&#xff0c;记为 S(n) …

CCF编程能力等级认证GESP—C++7级—20240629

CCF编程能力等级认证GESP—C7级—20240629 单选题&#xff08;每题 2 分&#xff0c;共 30 分&#xff09;判断题&#xff08;每题 2 分&#xff0c;共 20 分&#xff09;编程题 (每题 25 分&#xff0c;共 50 分)黑白翻转区间乘积 单选题&#xff08;每题 2 分&#xff0c;共 …

【鸿蒙开发基础学习】UIAbility 组件生命周期

UIAbility 组件生命周期 概述 当用户打开、切换和返回到对应应用时&#xff0c;应用中的 UIAbility 实例会在其生命周期的不同状态之间转换。UIAbility 类提供了一系列回调&#xff0c;通过这些回调可以知道当前 UIAbility 实例的某个状态发生改变&#xff0c;会经过 UIAbili…

DeDeCMS

DeDeCMS 环境部署 同意协议 环境检测 参数配置 漏洞复现 姿势⼀&#xff1a;通过⽂件管理器上传WebShell #前台 http://192.168.4.139/dedecms/index.php?upcache1 #后台 http://192.168.4.139/dedecms/dede/ #信息 username:admin password:admin 上传文件 成功上传 …

如何在Chrome、Edge、360、Firefox等浏览器查看网站SSL证书信息?

在如今的网络环境中&#xff0c;保障网络安全、数据安全尤其重要&#xff0c;市面上大部分网站都部署了SSL证书以实现HTTPS加密保护数据传输安全以及验证网站身份&#xff0c;确保网站安全可信。那么如何查看网站的SSL证书信息&#xff1f;接下来&#xff0c;我们将详细介绍如何…

Java零基础之多线程篇:讲解死锁和资源管理

哈喽&#xff0c;各位小伙伴们&#xff0c;你们好呀&#xff0c;我是喵手。运营社区&#xff1a;C站/掘金/腾讯云&#xff1b;欢迎大家常来逛逛 今天我要给大家分享一些自己日常学习到的一些知识点&#xff0c;并以文字的形式跟大家一起交流&#xff0c;互相学习&#xff0c;一…

Qt 跨平台支持任务栏进度条以及自绘消息通知显示

一.效果图 win效果图 mac效果图 二.测试demo地址&#xff1a; testwindowbar: 跨平台的任务栏进度条

语言模型-预训练模型(三)

预训练-BERT 传统方法与预训练方法的比较预训练方式 --BERT训练任务模型结构BERT结构-EmbeddingBERT结构-EncoderBERT结构-self-attention 传统方法与预训练方法的比较 思想解读&#xff1a; 预训练的概念就和我们人读书和工作一样&#xff1b;先是通过基础教育到大学毕业&…