使用柏林噪声生成随机地图

news2025/6/8 14:36:15

简单介绍柏林噪声

柏林噪声(Perlin Noise)是一种由 Ken Perlin 在1983年提出的梯度噪声(Gradient Noise)算法,用于生成自然、连续的随机值。它被广泛用于计算机图形学中模拟自然现象(如地形、云层、火焰等),是程序化生成(Procedural Generation)的核心技术之一。

柏林噪声的核心特点

  1. 连续性
    噪声值在空间中是平滑过渡的,没有突兀的跳变(与白噪声不同)。
  2. 可控随机性
    通过相同的输入(坐标+种子)总能得到相同的输出,适合可预测的生成。
  3. 分形扩展性
    可通过多层级叠加(Octaves)生成复杂细节(即分形噪声/Fractal Noise)。

也就是说,柏林噪声提供的是一个确定性的随机结果
更重要的是,它是连续的(输入连续的X,返回的Y也是连续的),这一点对于生成随机地形是非常有意义的。
而且还可以通过多次采样(通过参数控制采样粒度和影响强度)后进行叠加,为随机结果增加更多的细节。

用柏林噪声生成随机地图

假设我们定义好了地图的长宽,然后我们也有了一个柏林噪声的采样器(网上找一找就有),这时候就可以遍历地图上每一个地块,然后进行柏林噪声的采样,大概代码可能长这样(没写这个版本)

 			for (int _y = 0; _y < mMapManager.MapHeight; _y++)
            {
                for (int _x = 0; _x < mMapManager.MapWidth; _x++)
                {
                    float _sampleValue = mPerlinNoise.Noise(_x, _y) * 2f - 1f;
                    //可能还有一堆存储结果、记录极致之类的操作
                }
            }
            //最后还要对结果做一下归一化处理。。。

好,这就是最简单的版本了。但是这样有两个问题:一是我们缺少控制手段,结果完全依赖于柏林噪声的原始分辨率,二是结果虽然是随机的,但丰富度不够,也就是缺乏细节。

emmm…什么叫缺乏细节呢,文字描述起来比较费劲,直接看图吧。
这是一张缺乏细节的噪声图:
在这里插入图片描述
这是一张不缺乏细节的噪声图:
在这里插入图片描述
这样看起来就直观一些了。

那么接下来我们就开始增加细节。

首先,我们可以先增加一个ScaleParam的控制参数,通过这个控制参数对采样点(x,y)进行缩放,可以理解为修改了变化的频率,在相同变化幅度下,当频率变大,整个波形就会变得更扁平,也就是地形更平坦。这样我们就可以控制整个地图的基础地形特点了。

大概的代码长下面这样:

		
            //影响频率,提供整个地图基础的细节丰富程度(平坦程度,决定大的基础地形)
            float _invScaleParam = 1f / (float)mMapManager.ScaleParam;
            
            for (int i = 0; i < mMapManager.MapHeight; i++)
            {
                for (int j = 0; j < mMapManager.MapWidth; j++)
                {
                    
                    float _x = (j - _halfWidth) * _invScaleParam;
                    float _y = (i - _halfHeight) * _invScaleParam;
                    float _sampleValue = mPerlinNoise.Noise(_x, _y) * 2f - 1f;
                    
                    //还有一堆存储结果、记录极致之类的操作
                }
            }
            
			//同样需要归一化处理
			

在此之上,可以添加参数继续增加更加丰富的细节,这个过程说白了就是经过多轮采样,比如第一轮采样决定了基础的地形特点,第二轮采样增加山丘与平原的差异,第三轮采样。。。

所以,我们需要一个控制进行几轮采样的参数Octaves,并且为每一轮采样提供一个独立的类似ScaleParam一样调整波形频率的参数_frequency,整个多轮采样是一个从粗犷到细致的过程(从大的地形到小的细节),显而易见,每一轮采样结果对最终结果的贡献程度是由差别,因此还需要一个_amplitude来影响波形的振幅,从而做到单独控制每一轮采样对地形的修改幅度。

最后,刚才提到过了,_frequency和_amplitude都是针对每一轮采样动态变化的,因此还需要两个参数来分别控制它们在多轮采样间是如何变化的。

于是最终的结果就变成了下面这样:

using UnityEngine;

namespace MapRandom.PerlinNoise
{
    public class TestPerlinNoiseMap : MonoBehaviour
    {
        public Transform MapRoot;
        public int MapWidth = 100;
        public int MapHeight = 100;
        public float Seed = 1f;
        public int ScaleParam = 20;
        public int Octaves = 3;
        public float Persistance = 0.5f;
        public float Lacunarity = 2f;
        
        private MapGenerator mMapGenerator;
        private MapNodeData[,] mMapData;

        private void Update()
        {
            if (Input.GetKeyUp(KeyCode.F2))
            {
                CreateMap();    
            }
        }

        private void CreateMap()
        {
            MapRoot.position = Vector3.zero;
            MapRoot.rotation = Quaternion.identity;
            MapRoot.localScale = Vector3.one;
            EnsureMapGenerator();
            mMapData = mMapGenerator.CreateMap();
        }

        private void EnsureMapGenerator()
        {
            if (null != mMapGenerator) return;
            mMapGenerator = new MapGenerator(this);
        }

        private void OnDrawGizmos()
        {
            if (null == mMapData) return;
            
            for (int _y = 0; _y < MapHeight; _y++)
            {
                for (int _x = 0; _x < MapWidth; _x++)
                {
                    MapNodeData _mapNodeData = mMapData[_x, _y];
                    Gizmos.color = _mapNodeData.Color;
                    Gizmos.DrawCube(new Vector3(_x, 0, _y), Vector3.one);
                }
            }
        }
    }
}
using UnityEngine;

namespace MapRandom.PerlinNoise
{
    public class MapNodeData
    {
        public float X;
        public float Y;
        public float Height;
        public Color32 Color = UnityEngine.Color.white;

        public MapNodeData(float _x, float _y, float _height)
        {
            X = _x;
            Y = _y;
            Height = _height;
        }

        public void FormatColor()
        {
            if (Height < 0.25f)
            {
                //深海
                Color = UnityEngine.Color.blue;
            }
            else if (Height < 0.5f)
            {
                //浅海
                Color = UnityEngine.Color.cyan;
            }
            else if (Height < 0.55f)
            {
                //平原
                Color = UnityEngine.Color.yellow;
            }
            else if (Height < 0.75f)
            {
                //矮山(林地)
                Color = UnityEngine.Color.green;
            }
            else
            {
                //高山(雪山)
                Color = UnityEngine.Color.white;
            }
        }
    }

    public class MapGenerator
    {
        private TestPerlinNoiseMap mMapManager;
        private PerlinNoise mPerlinNoise;

        public MapGenerator(TestPerlinNoiseMap _mapManager)
        {
            mMapManager = _mapManager;
            mPerlinNoise = new PerlinNoise();
        }

        public MapNodeData[,] CreateMap()
        {
            mPerlinNoise.Seed(mMapManager.Seed);
            
            MapNodeData[,] _retMap = new MapNodeData[mMapManager.MapWidth, mMapManager.MapHeight];
            float _maxValue = float.MinValue, _minValue = float.MaxValue;
            
            float _halfWidth = mMapManager.MapWidth * 0.5f;
            float _halfHeight = mMapManager.MapHeight * 0.5f;
            //影响频率,提供整个地图基础的细节丰富程度(平坦程度,决定大的基础地形)
            float _invScaleParam = 1f / mMapManager.ScaleParam;
            
            //扫描每一个地块
            for (int i = 0; i < mMapManager.MapHeight; i++)
            {
                for (int j = 0; j < mMapManager.MapWidth; j++)
                {
                    float _noiseValue = 0f;
                    //影响频率,在基础地形一致的情况下,每一层可以通过这个参数额外提供一些细节丰富度
                    float _frequency = 1f;
                    //影响振幅,控制每一层细节对原始地形的修改幅度
                    float _amplitude = 1f;
                    
                    //逐层添加细节
                    for (int k = 0; k < mMapManager.Octaves; k++)
                    {
                        float _x = (j - _halfWidth) * _invScaleParam * _frequency;
                        float _y = (i - _halfHeight) * _invScaleParam * _frequency;
                        float _sampleValue = mPerlinNoise.Noise(_x, _y) * 2f - 1f;
                        _noiseValue += _sampleValue * _amplitude;

                        _frequency *= mMapManager.Lacunarity;
                        _amplitude *= mMapManager.Persistance;
                    }
                    
                    //记录最终结果
                    MapNodeData _mapNode = _retMap[j, i];
                    if (null == _mapNode)
                    {
                        _mapNode = new MapNodeData(j, i, _noiseValue);
                        _retMap[j, i] = _mapNode;
                    }
                    else
                    {
                        _mapNode.Height = _noiseValue;
                    }
                    
                    //记录极值,用于归一化处理
                    _maxValue = Mathf.Max(_maxValue, _noiseValue);
                    _minValue = Mathf.Min(_minValue, _noiseValue);
                }
            }

            float _fixParam = 1f / (_maxValue - _minValue);
            for (int i = 0; i < mMapManager.MapHeight; i++)
            {
                for (int j = 0; j < mMapManager.MapWidth; j++)
                {
                    MapNodeData _mapNode = _retMap[j, i];
                    _mapNode.Height = (_mapNode.Height - _minValue) * _fixParam;
                    _mapNode.FormatColor();
                }
            }
            
            return _retMap;
        }
        

    }
}

生成的效果如下:
在这里插入图片描述

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

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

相关文章

C++课设:实现简易文件加密工具(凯撒密码、异或加密、Base64编码)

名人说&#xff1a;路漫漫其修远兮&#xff0c;吾将上下而求索。—— 屈原《离骚》 创作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 专栏介绍&#xff1a;《编程项目实战》 目录 一、初识文件加密&#xff1a;为什么需要…

H_Prj06_03 8088单板机串口读取8088ROM复位内存

1.8088CPU复位时&#xff0c;CSFFFFH,IP0000H,因此在ROM的逻辑地址FFFF&#xff1a;0000&#xff08;FFF0H&#xff09;处一般要防止一个长跳转指令LJMP&#xff08;机器码位EAH&#xff09; 2.写一个完整的8086汇编程序&#xff0c;通过查询方式检测串口接收符串‘r’&#x…

构建 MCP 服务器:第 3 部分 — 添加提示

这是我们构建 MCP 服务器的四部分教程的第三部分。在第一部分中&#xff0c;我们使用基本资源创建了第一个MCP 服务器&#xff1b;在第二部分中&#xff0c;我们添加了资源模板并改进了代码组织。现在&#xff0c;我们将进一步重构代码并添加提示功能。 什么是 MCP 提示&#…

基于React + FastAPI + LangChain + 通义千问的智能医疗问答系统

&#x1f4cc; 文章摘要&#xff1a; 本文详细介绍了如何在前端通过 Fetch 实现与 FastAPI 后端的 流式响应通信&#xff0c;并支持图文多模态数据上传。通过构建 multipart/form-data 请求&#xff0c;配合 ReadableStream 实时读取 AI 回复内容&#xff0c;实现类似 ChatGPT…

C# 中替换多层级数据的 Id 和 ParentId,保持主从或父子关系不变

在C#中替换多层级数据的Id和ParentId&#xff0c;同时保持父子关系不变&#xff0c;可以通过以下步骤实现&#xff1a; 创建旧Id到新Id的映射&#xff1a;遍历所有节点&#xff0c;为每个旧Id生成唯一的新Id&#xff0c;并存储在字典中。 替换节点的Id和ParentId&#xff1a;…

PG 分区表的缺陷

简介 好久没发文&#xff0c;是最近我实在不知道写点啥。随着国产化进程&#xff0c;很多 oracle 都在进行迁移&#xff0c;最近遇到了一个分区表迁移之后唯一性的问题。oracle 数据库中创建主键或者唯一索引&#xff0c;不需要引用分区键&#xff0c;但是 PG 就不行&#xff…

从Copilot到Agent,AI Coding是如何进化的?

编程原本是一项具有一定门槛的技能&#xff0c;但借助 AI Coding 产品&#xff0c;新手也能写出可运行的代码&#xff0c;非专业人员如业务分析师、产品经理&#xff0c;也能在 AI 帮助下直接生成简单应用。 这一演变对软件产业产生了深远影响。当 AI 逐步参与代码生成、调试乃…

Qt(part 2)1、Qwindow(菜单栏,工具栏,状态栏),铆接部件,核心部件 ,2、添加资源文件 3、对话框

1、Qwindow tips&#xff1a;1&#xff0c;首先为什么创建出的对象基本都是指针形式&#xff0c;个人觉得是对象树的原因&#xff08;自动释放内存&#xff09;&#xff0c;指针来访问成员函数->的形式。2&#xff0c;菜单栏只能一个的&#xff0c;放窗口基本Set&#xff0c…

【图片识别Excel】批量提取图片中的文字,图片设置识别区域,识别后将文字提取并保存Excel表格,基于WPF和OCR识别的应用

应用场景 在办公自动化、文档处理、数据录入等场景中&#xff0c;经常需要从大量图片中提取文字信息。例如&#xff1a; 批量处理扫描的表单、合同、发票等文档从图片集中提取特定区域的文字数据将纸质资料快速转换为电子文本并整理归档 通过设置识别区域&#xff0c;可以精…

Qt/C++学习系列之Excel使用记录

Qt/C学习系列之Excel使用记录 前言The process was ended forcefully.解决方式断点查语句问题 总结 前言 在项目中解析条目达50多条&#xff0c;并且都需要将对应的结果进行显示。为了将结果显示的更加清晰&#xff0c;考虑采用QTableWidget进行表格设置&#xff0c;而在使用过…

跳转指令四维全解:从【call/jmp 】的时空法则到内存迷宫导航术

一、核心概念&#xff1a;代码世界的空间定位法则 在汇编世界里&#xff0c;我们可以把内存想象成一栋巨大的图书馆&#xff1a; CS&#xff08;代码段寄存器&#xff09; 楼层编号 IP&#xff08;指令指针&#xff09; 房间编号 当前执行位置 CS:IP&#xff08;如3楼201…

LabVIEW实时系统数据监控与本地存储

基于LabVIEW Real-Time 模块&#xff0c;面向工业自动化、嵌入式测控等场景&#xff0c;提供实时数据采集、监控与本地存储的完整实现路径。通过分层任务调度、TDMS 文件格式应用及跨平台兼容性设计&#xff0c;确保系统在实时性、可靠性与数据管理效率间达到平衡。文中以 Comp…

从 Revit 到 3DTiles:GISBox RVT 切片器如何让建筑图元在 Web 端展示

在GIS&#xff08;地理信息系统&#xff09;行业蓬勃发展的当下&#xff0c;数据处理与展示的效率和精准度成为关键。GISBox作为一款功能强大的一站式三维GIS数据编辑、转换、发布平台&#xff0c;凭借其独特的“RVT切片器”功能&#xff0c;在RVT图元处理方面也有着不俗的表现…

Appium+python自动化(十二)- Android UIAutomator

Android团队在4.1版本&#xff08;API 16&#xff09;中推出了一款全新的UI自动化测试工具UiAutomator&#xff0c;用来帮助开发人员更有效率的完成App的Debug工作&#xff0c;同时对于测试人员也是一大福音&#xff0c;为什么这么说呢&#xff1f; UiAutomator提供了以下两种…

QQ邮箱发送验证码(Springboot)

一、邮箱发送服务准备 在qq邮箱的设置中选择账号下开启服务。 开启时可能会有短信验证&#xff0c;开启后显示验证码之类的一串英文&#xff0c;复制保存起来&#xff0c;在配置文件中会使用到。 二、后端依赖及配置 依赖 在pom.yml文件中添加相关依赖&#xff0c;redis的…

【EF Core】 EF Core并发控制:乐观锁与悲观锁的应用

文章目录 前言一、并发的风险二、EF Core中的并发控制方式2.1 开放式并发&#xff08;乐观锁&#xff09;2.1.1 应用程序管理的属性并发令牌2.1.2 数据库生成的并发令牌 2.2 悲观锁 总结 前言 实际的生产环境中&#xff0c;我们经常能遇到数据库由多个应用程序同时使用。每个程…

Harmony核心:动态方法修补与.NET游戏Mod开发

一、Harmony的核心定位与设计哲学 Harmony是一个运行时动态方法修补库&#xff0c;专为修改已编译的.NET/Mono应用程序而设计&#xff0c;尤其适用于游戏Mod开发。其核心创新在于&#xff1a; 非破坏性修改&#xff1a;保留原始方法完整性&#xff0c;避免直接替换或覆盖。多…

【Java开发日记】说一说 SpringBoot 中 CommandLineRunner

目录 1、CommandLineRunner SpringBoot中CommandLineRunner的作用 简单例子 多个类实现CommandLineRunner接口执行顺序的保证 通过实现Ordered接口实现控制执行顺序 通过Order注解实现控制执行顺序 Order 作用 2、ApplicationRunner 3、传递参数 4、源码跟踪 run()方…

全面理解 Linux 内核性能问题:分类、实战与调优策略

在 Linux 系统&#xff08;特别是嵌入式或服务器环境&#xff09;中&#xff0c;性能问题往往错综复杂、表象多变。只有对常见性能问题进行系统归类、理解其症状与根源&#xff0c;才能有效定位和解决。本文将围绕八大类核心性能问题&#xff0c;结合实战示例&#xff0c;逐类分…

算法-多条件排序

1、数对排序的使用 pair<ll,ll> a[31];//cmp为比较规则 ll cmp(pair<ll,ll>a,pair<ll,ll>b){if(a.first!b.first)return a.first>b.first;else return a.second<b.second; }//按照比较规则进行排序 sort(a1,a31,cmp); 2、具体例题 输入样例&#xff1…