Unity 使用柏林噪声程序化生成地形

这篇具有很好参考价值的文章主要介绍了Unity 使用柏林噪声程序化生成地形。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

参考教程链接
项目链接

柏林噪声函数简述

👇对噪声和柏林噪声不了解的可以看下面这个讲解。
柏林函数简介

  简单来说柏林噪声是一种连续的、渐变的噪声,不理解原理也无所谓,unity自带有Mathf.PerlinNoise(X-coordinate,Y-coordinate);我们可以根据这个来制作更有层次性的柏林噪声。你可以把这个函数理解为Unity提供了一张很大的随机平滑噪声图,我们可以通过(x,y)来在上面采样。

带有平滑的值噪声
unity使用柏林噪声图生成随机地形,图形学,unity
Unity自带的柏林噪声
unity使用柏林噪声图生成随机地形,图形学,unity

unity使用柏林噪声图生成随机地形,图形学,unity

  当我们形容一段波函数,振幅(Amplitude)指的是y轴,频率(Frequency)指的是x轴。

  如果只是这样一段是不够丰富,我们可以多设几段波,让他们在不同的频率下相加,更加丰富一点。例如下图,我们把原始的波函数看作主要的山脉轮廓,把2倍频率的看作巨石,把4倍频率的看作小石头,这样就可以做出主轮廓变化平缓,巨石出现频繁,小石头出现更加频繁的效果,最后把这三个参数相加就可以得到一个变化更加丰富的波形了。
  如果你理解了这点,你就懂了间隙度(lacunarity) 这个参数,通过这个参数,我们可以控制山脉高低变化的频率,把频率变成1 2 4,1 3 9 等等。

unity使用柏林噪声图生成随机地形,图形学,unity
  但又有一个问题,我们大石头、小石头对波的影响太大了,应该区分开,让大石头、小石头的占比更小一点。我们可以修改振幅来处理,例如大石头的振幅只有0.5,小石头的振幅只有0.25。
  如下图,可以看出大石头、小石头振幅发生了明显的变化,把他们相加,我们就得到了一条保留原来大致轮廓,又更有层次感的波,相对应的,我们引入一个新的参数 持久性(persistance) 来控制振幅。。
  注意间隙度(lacunarity)应该是大于1的,保证乘方后的频率是递增的,变化越来越快,
  持久性(persistance)是小于1的,保证乘方后振幅是递减的,对整体的影响越来越小。
unity使用柏林噪声图生成随机地形,图形学,unity
  你可能注意到图中还有另外一个参数 度(octave) ,在上述的例子中,我们用主轮廓、大石头、小石头三个波来解释对应的变化,但实际的时候,我们可以设置更多不同的波,就像中石头、更小的石头,当我们把度设为3的时候就是上述的例子,当我们增加度,就可以具体出更多的波来叠加。
  理解了lacunarity、persistance、octave这三个参数,就懂了我们对原始柏林函数的修改。cheer!

初始准备

  先来看看我们的起始项目
项目链接跳转

Noise:是我们后续写柏林噪声的主要地方,核心代码区,先写一个值噪声理解下原理,生成一个width*height的(0,1)随机数组。
MapDisplay:将上一步生成的二位噪声数组从(0,1)lerp转换到(0,0,0)到(255,255,255),转成纹理,赋值到材质球上。
MapGenerate:承载后续的各种参数,然后调用我们写的各种函数。
MapGeneratorEditor: 编辑器扩展,当我们点击Generate按钮,或者有参数改动时,调用GenerateMap函数来重新生成。

  这样我们就获得了一个可以实时展示生成噪声的Plane。
unity使用柏林噪声图生成随机地形,图形学,unity
  下面的注释中有,是使用Unity自带柏林噪声的版本
unity使用柏林噪声图生成随机地形,图形学,unity
  可以看出原始柏林函数已经做到了随机和平滑,但我们要做的不止于此,而是这样、这样的更丰富的噪声。甚至引入随机种子来让噪声“彻底”随机。

unity使用柏林噪声图生成随机地形,图形学,unity
unity使用柏林噪声图生成随机地形,图形学,unity

代码实现部分

  回忆一下上面的内容,我们要做的是分octave次级,每次计算出新的noiseValue,然后叠加计算出最后的高度;
  所以我们noise的新框架大概是这样的

       for (int y = 0; y < mapHeight; y++)
       {
           for (int x = 0; x < mapWidth; x++)
           {
          	   	float noiseHeight = 0;  //高度,即最终该点的颜色值,将每一度的振幅相加来获得
               	
               	//分octaves次级
               for (int i = 0; i < octaves; i++)
               {
               	//1.根据persistance、lacunarity计算出新的 频率、振幅
   				//2.通过频率、振幅计算出新的噪声值noiseValue
   				
   				noiseHeight += noiseValue; //3.把每一度的值叠加起来,得到最终颜色
               }
               
               noiseMap[x, y] = noiseHeight;		//获取结果
           }
       }

  现在的柏林噪声生成代码中,Mathf.PerlinNoise(x,y),(x,y)就是代表频率,返回值代表振幅,加入振幅amplitude和频率frequency就转化成 Mathf.PerlinNoise(x * frequency, y * frequency) * amplitude 。

代码如下

        for (int y = 0; y < mapHeight; y++)
        {
            for (int x = 0; x < mapWidth; x++)
            {
                //2.使用unity的柏林函数
                float amplitude = 1;    //振幅
                float frequency = 1;    //频率
                float noiseHeight = 0;  //高度,即最终该点的颜色值,将每一度的振幅相加来获得
                //分octaves次级
                for (int i = 0; i < octaves; i++)
                {
                    float sampleX = x / scale * frequency;
                    float sampleY = y / scale * frequency;  //用频率影响采样点间隔

                    float perlinValue = Mathf.PerlinNoise(sampleX, sampleY);
                    noiseHeight += perlinValue * amplitude; //把每一度的值叠加起来,得到最终颜色

                    amplitude *= persistance;	//每次要对振幅和频率进行开方
                    frequency *= lacunarity;
                }
                noiseMap[x, y] = noiseHeight;	
            }
        }

现在我们来解决另一个问题,因为我们的噪声生成是随机的,可能出现这种情况
unity使用柏林噪声图生成随机地形,图形学,unity
噪声并没有占满[0,1],我们希望像下面这样把范围归一化到[0,1],保证不会出现过于明显的极端情况
unity使用柏林噪声图生成随机地形,图形学,unity
最终代码如下
noise.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public static class Noise 
{
    //octaves:度
    //persistance:持久性,控制振幅
    //lacunarity: 间隙度,控制频率
    public static float[,] GenerateNoiseMap(int mapWidth, int mapHeight,float scale, int octaves, float persistance, float lacunarity)
    {
        float[,] noiseMap = new float[mapWidth, mapHeight];

        //防止除以0,除以负数
        if (scale <= 0)
        {
            scale = 0.0001f;
        }

        float maxNoiseHeight = float.MinValue;
        float minNoiseHeight = float.MaxValue;

        for (int y = 0; y < mapHeight; y++)
        {
            for (int x = 0; x < mapWidth; x++)
            {
                //使用unity的柏林函数
                float amplitude = 1;    //振幅
                float frequency = 1;    //频率
                float noiseHeight = 0;  //高度,即最终该点的颜色值,将每一度的振幅相加来获得
                //分octaves次级
                for (int i = 0; i < octaves; i++)
                {
                    float sampleX = x / scale * frequency;
                    float sampleY = y / scale * frequency;  //用频率影响采样点间隔

                    float perlinValue = Mathf.PerlinNoise(sampleX, sampleY);
                    noiseHeight += perlinValue * amplitude; //振幅影响

                    amplitude *= persistance;	//更换新的频率和振幅
                    frequency *= lacunarity;
                }
                
                if (noiseHeight > maxNoiseHeight)
                {
                    maxNoiseHeight = noiseHeight;
                }
                else if (noiseHeight < minNoiseHeight)
                {
                    minNoiseHeight = noiseHeight;
                }
                noiseMap[x, y] = noiseHeight;
            }
        }
		
		//把噪声的范围从[minNoiseHeight,maxNoiseHeight]归一化到[0,1]
        for (int y = 0; y < mapHeight; y++)
        {
            for (int x = 0; x < mapWidth; x++)
            {
                noiseMap[x, y] = Mathf.InverseLerp(minNoiseHeight, maxNoiseHeight, noiseMap[x, y]);
            }
        }

        return noiseMap;
    }
}

  具体操作为,定义minNoiseHeight,maxNoiseHeight两个参数来记录整张噪声图的最大、最小值, 每计算一个像素点后,我们检查是否更新。最后使用Mathf.InverseLerp 函数来对所有点归一化,靠近minNoiseHeight的点结果靠近0,靠近maxNoiseHeight的更靠近1。

  至此,我们已经应用好了前面提到的理论,再更改下MapGenerate.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MapGenerate : MonoBehaviour
{
	public int mapWidth;
	public int mapHeight;
	public float noiseScale = 10f;

	[Range(1, 100)]
	public int octaves;			//度,需要大于1
	[Range(0, 1)]
	public float persistance;	//持久度,需要大于0,小于1
	[Range(1, 20)]
	public float lacunarity;    //间隙度,控制频率,需要大于1

	//是否开启在编辑器中自动更新
	public bool autoUpdate = true;
	public void GenerateMap()
	{
		//生成噪声
		float[,] noiseMap = Noise.GenerateNoiseMap(mapWidth, mapHeight,noiseScale,octaves,persistance,lacunarity);

		//显示在Plane上
		MapDisplay display = FindObjectOfType<MapDisplay>();
		display.DrawNoiseMap(noiseMap);
	}
}

  至此,我们就可以获得不错的噪声了(右侧是可参考的参数)
unity使用柏林噪声图生成随机地形,图形学,unity

添加随机性

  随机的思路是对采样加上随机偏移,我们之前用这样的方式来采样

        for (int y = 0; y < mapHeight; y++)
        {
            for (int x = 0; x < mapWidth; x++)
            {
				float perlinValue = Mathf.PerlinNoise(x, y);
			}
		}

  结果就是永远是在[0,0] 到 [mapWidth,mapHeight]这个范围进行采样,如果我们这样修改

float perlinValue = Mathf.PerlinNoise(x + RandomOffset.x, y + RandomOffset.y);

  就可以把采样范围从[0,0] ~ [mapWidth,mapHeight] 随机到 [RandomOffset.x,RandomOffset.y] ~ [mapWidth + RandomOffset.x, mapHeight + RandomOffset.y]上。

  在此基础上,我们可以添加一个手动的Vector2 offset,令RandomOffset += offset,这样我们也可以手动简单地偏移噪声。
  代码大致如下

		//引入两个新变量 随机种子seed ,还有自定义偏移offset
		System.Random prng = new System.Random(seed);
		
		//即上文提到的RandomOffset,我们需要对每一个度都随机一个新的采样点
		Vector2[] octaveOffsets = new Vector2[octaves];	
		
		for (int i = 0; i < octaves; i++)  
		{
			float offsetX = prng.Next(-100000, 100000) + offset.x;	//RandomOffset += offset
			float offsetY = prng.Next(-100000, 100000) + offset.y;
			octaveOffsets[i] = new Vector2(offsetX, offsetY);
		}
		
		for (int y = 0; y < mapHeight; y++)
        {
            for(int x = 0; x < mapWidth; x++)
            {
				float amplitude = 1;
				float frequency = 1;
				float noiseHeight = 0;

				for (int i = 0; i < octaves; i++)
				{
					float sampleX = x / scale * frequency + octaveOffsets[i].x;
					float sampleY = y / scale * frequency + octaveOffsets[i].y;
					
					float perlinValue = Mathf.PerlinNoise(sampleX, sampleY);	
				}
			}
		}

项目链接跳转

这里我们还更改了另一个地方,x - halfWidth

float sampleX = (x-halfWidth) / scale * frequency + octaveOffsets[i].x; 

我们之前定义了一个NoiseScale参数来对噪声图进行缩放,但是以右上角为锚点进行缩放的。
这是因为x / scale ,是以(0,0)点(即右上角)为锚点进行缩放,而(x-halfWidth) / scale 相当于把(halfWidth,halfHeight)偏移到(0,0)点,即图像中点作为(0,0)锚点。
unity使用柏林噪声图生成随机地形,图形学,unity
unity使用柏林噪声图生成随机地形,图形学,unity

  那么用这个噪声可以做什么呢,我们只要添加一点点小功能,就可以实现各种效果。
unity使用柏林噪声图生成随机地形,图形学,unity
unity使用柏林噪声图生成随机地形,图形学,unity
具体做法可参考教程,源文件也放到github上了。文章来源地址https://www.toymoban.com/news/detail-614356.html

到了这里,关于Unity 使用柏林噪声程序化生成地形的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • (2023,3D 场景生成器 Infinigen)使用程序化生成的无限逼真世界

    Infinite PhotorealisticWorlds using Procedural Generation 公众号:EDPJ 目录 0. 摘要 1. 简介 2. 相关工作 3. 方法 4. 实验  参考 S. 总结 S.1 主要思想 S.2 方法 S.3 场景生成 我们介绍 Infinigen,一个自然世界逼真 3D 场景的程序生成器。 Infinigen 完全是程序化的:从形状到纹理的每一个资产(asse

    2024年02月16日
    浏览(46)
  • 虚幻官方项目《CropOut》技术解析 之 程序化岛屿生成器(IslandGenerator)

    开个新坑详细分析一下虚幻官方发布的《CropOut》,文章会同步发布到我在知乎|CSDN的专栏里 《CropOut》的技术亮点之一就是全部关卡都是随机生成的,本篇文章将重点解析游戏中岛屿的随机生成方式,先列举几个技术: 用Geometry Script构造几何体 为Geometry Script几何体赋材

    2024年02月12日
    浏览(28)
  • 虚幻引擎程序化资源生成框架PCG 之 UPCGBlueprintElement源码笔记(二)数据流

    PCG节点处理的是数据流,也就是点云,点云到底是啥?笼统地说就是一个个携带着信息的点组成的集合。但是在具体是使用过程中,我们还得了解这些”携带着信息的点“是如何被层层包装起来的。本文中老王就和大家一边拆解源代码一边做实验,尝试着深入理解一下PCG中的

    2024年02月13日
    浏览(49)
  • 【unity实战】Unity2D TileMap+柏林噪声生成随机地图(附源码)

    我的上一篇文章介绍了TileMap的使用,主要是为我这篇做一个铺垫,看过上一篇文章的人,应该已经很好的理解TileMap的使用了,这里我就不需要过多的解释一些繁琐而基础的知识了,省去很多时间。所有没看过上一篇文章的小伙伴我强烈建议先去看看:

    2024年02月03日
    浏览(39)
  • 【unity小技巧】Unity2D TileMap+柏林噪声生成随机地图(附源码)

    我的上一篇文章介绍了TileMap的使用,主要是为我这篇做一个铺垫,看过上一篇文章的人,应该已经很好的理解TileMap的使用了,这里我就不需要过多的解释一些繁琐而基础的知识了,省去很多时间。所有没看过上一篇文章的小伙伴我强烈建议先去看看:

    2024年02月07日
    浏览(25)
  • 虚幻引擎程序化资源生成框架PCG(Procedural Content Generation Framework) 之 PCG基础

    可以和Houdini说拜拜了 Unreal Engine 5.2全新推出了程序化资源生成框架即Procedural Content Generation Framework下文简称PCG, 开发者可以通过PCG程序快速生成宏大且复杂的游戏场景。以往这种程序化资源(场景)的生成需要借助Houdini来实现,有了PCG,真的可以和Houdini说拜拜了! 优点:

    2024年02月11日
    浏览(39)
  • 【实现100个unity游戏之20】制作一个2d开放世界游戏,TileMap+柏林噪声生成随机地图(附源码)

    我的上一篇文章介绍了TileMap的使用,主要是为我这篇做一个铺垫,看过上一篇文章的人,应该已经很好的理解TileMap的使用了,这里我就不需要过多的解释一些繁琐而基础的知识了,省去很多时间。所有没看过上一篇文章的小伙伴我强烈建议先去看看:

    2024年01月20日
    浏览(35)
  • 什么是程序化交易

    大到量化、程序化、高频交易、套利交易、主观投资这些基本的概念,小到网格交易、条件单、T+0、ETF套利、期现套利、算法拆单交易、打板策略等具体的投资方式。如果没有接触过这些,很容易混淆。 程序化交易: 指通过既定程序或特定软件,自动生成或执行交易指令的

    2024年02月06日
    浏览(39)
  • 柏林噪声分形&幻想大陆地图生成

    之前介绍过perlin噪声的实现,现在应用实践一下——程序化生成幻想大陆 这里使用的是perlin噪声倍频技术(也称分形噪声),详情传送门:柏林噪声算法 代码示例使用的是shadertoy的语法规则,shandertoy传送门:ShaderToy 地形轮廓的生成主要依靠噪声,来看倍频相关代码(for迭代

    2024年02月07日
    浏览(30)
  • 基于URP的程序化天空盒

    参考来源:   天空盒教程第 1 部分 |开尔文·范·霍恩 (kelvinvanhoorn.com) 【程序化天空盒】过程记录02:云扰动 边缘光 消散效果_九九345的博客-CSDN博客 程序化天空盒实现昼夜变换 - 知乎 (zhihu.com) 一、太阳          目标:改变光的方向,使天空球旋转(日夜交替);光的正方

    2024年02月15日
    浏览(49)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包