如何在多个 perlin 噪声块之间平滑?
How to smooth between multiple perlin noise chunks?
块之间的平滑
所以我一直在开发一款 unity 游戏,希望将我的世界从 150x150 地图扩展到看似无限的程序世界。我的计划是以Perlin Noise为基础,使用0-1的不同值来判断地形类型。我遇到的问题 运行 是当我绘制块并相应地偏移时,我的块没有正确排列,这打破了无限世界的幻觉。
(见此处)
WorldChunk.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using Unity.Mathematics;
[System.Serializable]
public class WorldChunk
{
public int2 Position;
public int[,] Data;
public float[,] Sample;
public WorldChunk(int chunkSize = 16){
Data = new int[chunkSize, chunkSize];
Sample = new float[chunkSize, chunkSize];
}
}
WorldGenerator.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using Unity.Mathematics;
public class WorldGenerator : MonoBehaviour
{
// Base World Data
public int ChunkSize = 75;
public string Seed = "";
[Range(1f, 40f)]
public float PerlinScale = 10f;
// Pseudo Random Number Generator
private System.Random pseudoRandom;
// Chunk Data Split into Sections (Each Chunk having Coords (x, y))
public Dictionary<string, WorldChunk> chunks = new Dictionary<string, WorldChunk>();
//============================================================
// Set Warm-Up Data
//============================================================
private void Awake() {
// Get/Create Seed
if (Seed == ""){
Seed = GenerateRandomSeed();
}
// Get Random Number Generator
pseudoRandom = new System.Random(Seed.GetHashCode());
// Using to Clear while Making Test Adjustments
chunks.Clear();
// Generate Starting Chunk
for (int x = -1; x <= 1; x++)
{
for (int y = -1; y <= 1; y++)
{
// Draw Test Chunks
GenerateChunk(x, y);
}
}
}
//============================================================
// Generation Code
//============================================================
// ===
// Create New Chunks
// ===
public void GenerateChunk(int x, int y){
// Set Key to use
string key = $"{x},{y}";
// Check if key exists if not Generate New Chunk
if (!chunks.ContainsKey(key)){
// Add Chunk, Set Position in chunk grid (for calling and block data later), Then Generate data
chunks.Add(key, new WorldChunk(ChunkSize));
chunks[key].Position = new int2(x, y);
GenerateChunkData(chunks[key]);
}
}
// ===
// Fill Chunks with Perlin Data
// ===
private void GenerateChunkData(WorldChunk chunk){
// Set Offsets
float xOffset = (float)chunk.Position.x * ChunkSize;
float yOffset = (float)chunk.Position.y * ChunkSize;
// Set Data to Chunk
for (int x = 0; x < ChunkSize; x++)
{
for (int y = 0; y < ChunkSize; y++)
{
// Get Perlin Map
float px = (float)(x) / ChunkSize * PerlinScale + xOffset;
float py = (float)(y) / ChunkSize * PerlinScale + yOffset;
// Set Temp Sample For Testing (This will change for Map Data (Hills and Water) later)
chunk.Sample[x,y] = Mathf.PerlinNoise(px, py);
}
}
}
// ===
// Generate Random Seed of Length
// ===
private string GenerateRandomSeed(int maxCharAmount = 10, int minCharAmount = 10){
//Set Characters To Pick from
const string glyphs= "abcdefghijklmnopqrstuvwxyz0123456789";
//Set Length from min to max
int charAmount = UnityEngine.Random.Range(minCharAmount, maxCharAmount);
// Set output Variable
string output = "";
// Do Random Addition
for(int i=0; i<charAmount; i++)
{
output += glyphs[UnityEngine.Random.Range(0, glyphs.Length)];
}
// Output New Random String
return output;
}
//============================================================
// Draw Example
//============================================================
private void OnDrawGizmos() {
// Do this because I'm lazy and don't want to draw pixels to generated Sprites
Awake();
// For Each WorldChunk in the chunk Data
foreach (WorldChunk c in chunks.Values)
{
// Check if it exists (Foreach is stupid sometimes... When live editing)
if (c != null){
// Get World Positions for Chunk (Should probably Set to a Variable in the Chunk Data)
Vector3 ChunkPosition = new Vector3(c.Position.x * ChunkSize, c.Position.y * ChunkSize);
// For Each X & For Each Y in the chunk
for (int x = 0; x < ChunkSize; x++)
{
for (int y = 0; y < ChunkSize; y++)
{
// Get Cell position
Vector3 cellPos = new Vector3((ChunkPosition.x - ChunkSize/2f) + x, (ChunkPosition.y - ChunkSize/2f) + y);
// Get Temp Sample and set to color
float samp = c.Sample[x,y];
Gizmos.color = new Color(samp, samp, samp);
// Draw Tile as Sample black or white.
Gizmos.DrawCube(cellPos, Vector3.one);
}
}
// Size for Cubes
Vector3 size = new Vector3(ChunkSize, ChunkSize, 1f);
// Set Color Opaque Green
Gizmos.color = new Color(0f, 1f, 0f, 0.25f);
// Draw Chunk Borders (Disable to show issue)
// Gizmos.DrawWireCube(ChunkPosition, size);
}
}
}
}
我想在使用时指出:
// Get Perlin Map
float px = (float)(x + xOffset) / ChunkSize * PerlinScale;
float py = (float)(y + yOffset) / ChunkSize * PerlinScale;
而不是
// Get Perlin Map
float px = (float)(x) / ChunkSize * PerlinScale + xOffset;
float py = (float)(y) / ChunkSize * PerlinScale + yOffset;
一切都正确对齐,但柏林噪声只是重复。
对我来说,在块之间平滑以使所有内容匹配的最佳方法是什么?
有没有更好的写法?
编辑:
感谢 Draykoon D 的帮助!如果有人需要,这里是更新后的信息和指向 pastebin 上更新脚本的链接!
这是供任何需要的人使用的更新代码:
** WorldGenerator.cs**
** WorldGenerator.cs**
希望对您有所帮助!
您要查找的关键字是可拼接的。
但我有一个好消息要告诉你,像 perlin 这样的噪声函数本质上是周期性的。
因此,与其调用 ChunckSize * ChunkSize 噪声函数,不如调用它一次,然后对结果进行除法。
我会建议您阅读这个优秀的教程:
不要使用 Perlin 噪声。它严重偏向 45 度和 90 度方向。你的山丘都与这些对齐,并没有沿着更有趣的各种方向定向。您可以使用 Unity.mathematics.noise.snoise(float2)
但它的重复周期相当小,如果您不使用 Unity Burst 作业,它可能不会很快。 this is what I created/use/recommend,但这肯定不是唯一的选择!请注意,所有这些噪声的范围都是 -1 到 1,而不是 0 到 1,所以如果这很重要而不是 value=value*0.5+0.5;
重新调整它。
既然这样,要解决您的问题,您需要将块和生成的概念分开。总的来说这是个好主意,而且我始终相信尽可能多地从游戏玩法中隐藏后端实现细节(例如块)(例如避免可见边界)。每次生成块时,都应该在世界中找到它的起始坐标,以便坐标与其余坐标无缝连接。例如,如果块是 128x128,那么从 (0, 0) 开始的块应该有起始坐标 (0, 0),那么从 (0, 1) 开始的块应该有起始坐标 (0, 128)。然后,通过乘以您想要的频率将世界坐标转换为噪声坐标。
块之间的平滑
所以我一直在开发一款 unity 游戏,希望将我的世界从 150x150 地图扩展到看似无限的程序世界。我的计划是以Perlin Noise为基础,使用0-1的不同值来判断地形类型。我遇到的问题 运行 是当我绘制块并相应地偏移时,我的块没有正确排列,这打破了无限世界的幻觉。
(见此处)
WorldChunk.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using Unity.Mathematics;
[System.Serializable]
public class WorldChunk
{
public int2 Position;
public int[,] Data;
public float[,] Sample;
public WorldChunk(int chunkSize = 16){
Data = new int[chunkSize, chunkSize];
Sample = new float[chunkSize, chunkSize];
}
}
WorldGenerator.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using Unity.Mathematics;
public class WorldGenerator : MonoBehaviour
{
// Base World Data
public int ChunkSize = 75;
public string Seed = "";
[Range(1f, 40f)]
public float PerlinScale = 10f;
// Pseudo Random Number Generator
private System.Random pseudoRandom;
// Chunk Data Split into Sections (Each Chunk having Coords (x, y))
public Dictionary<string, WorldChunk> chunks = new Dictionary<string, WorldChunk>();
//============================================================
// Set Warm-Up Data
//============================================================
private void Awake() {
// Get/Create Seed
if (Seed == ""){
Seed = GenerateRandomSeed();
}
// Get Random Number Generator
pseudoRandom = new System.Random(Seed.GetHashCode());
// Using to Clear while Making Test Adjustments
chunks.Clear();
// Generate Starting Chunk
for (int x = -1; x <= 1; x++)
{
for (int y = -1; y <= 1; y++)
{
// Draw Test Chunks
GenerateChunk(x, y);
}
}
}
//============================================================
// Generation Code
//============================================================
// ===
// Create New Chunks
// ===
public void GenerateChunk(int x, int y){
// Set Key to use
string key = $"{x},{y}";
// Check if key exists if not Generate New Chunk
if (!chunks.ContainsKey(key)){
// Add Chunk, Set Position in chunk grid (for calling and block data later), Then Generate data
chunks.Add(key, new WorldChunk(ChunkSize));
chunks[key].Position = new int2(x, y);
GenerateChunkData(chunks[key]);
}
}
// ===
// Fill Chunks with Perlin Data
// ===
private void GenerateChunkData(WorldChunk chunk){
// Set Offsets
float xOffset = (float)chunk.Position.x * ChunkSize;
float yOffset = (float)chunk.Position.y * ChunkSize;
// Set Data to Chunk
for (int x = 0; x < ChunkSize; x++)
{
for (int y = 0; y < ChunkSize; y++)
{
// Get Perlin Map
float px = (float)(x) / ChunkSize * PerlinScale + xOffset;
float py = (float)(y) / ChunkSize * PerlinScale + yOffset;
// Set Temp Sample For Testing (This will change for Map Data (Hills and Water) later)
chunk.Sample[x,y] = Mathf.PerlinNoise(px, py);
}
}
}
// ===
// Generate Random Seed of Length
// ===
private string GenerateRandomSeed(int maxCharAmount = 10, int minCharAmount = 10){
//Set Characters To Pick from
const string glyphs= "abcdefghijklmnopqrstuvwxyz0123456789";
//Set Length from min to max
int charAmount = UnityEngine.Random.Range(minCharAmount, maxCharAmount);
// Set output Variable
string output = "";
// Do Random Addition
for(int i=0; i<charAmount; i++)
{
output += glyphs[UnityEngine.Random.Range(0, glyphs.Length)];
}
// Output New Random String
return output;
}
//============================================================
// Draw Example
//============================================================
private void OnDrawGizmos() {
// Do this because I'm lazy and don't want to draw pixels to generated Sprites
Awake();
// For Each WorldChunk in the chunk Data
foreach (WorldChunk c in chunks.Values)
{
// Check if it exists (Foreach is stupid sometimes... When live editing)
if (c != null){
// Get World Positions for Chunk (Should probably Set to a Variable in the Chunk Data)
Vector3 ChunkPosition = new Vector3(c.Position.x * ChunkSize, c.Position.y * ChunkSize);
// For Each X & For Each Y in the chunk
for (int x = 0; x < ChunkSize; x++)
{
for (int y = 0; y < ChunkSize; y++)
{
// Get Cell position
Vector3 cellPos = new Vector3((ChunkPosition.x - ChunkSize/2f) + x, (ChunkPosition.y - ChunkSize/2f) + y);
// Get Temp Sample and set to color
float samp = c.Sample[x,y];
Gizmos.color = new Color(samp, samp, samp);
// Draw Tile as Sample black or white.
Gizmos.DrawCube(cellPos, Vector3.one);
}
}
// Size for Cubes
Vector3 size = new Vector3(ChunkSize, ChunkSize, 1f);
// Set Color Opaque Green
Gizmos.color = new Color(0f, 1f, 0f, 0.25f);
// Draw Chunk Borders (Disable to show issue)
// Gizmos.DrawWireCube(ChunkPosition, size);
}
}
}
}
我想在使用时指出:
// Get Perlin Map
float px = (float)(x + xOffset) / ChunkSize * PerlinScale;
float py = (float)(y + yOffset) / ChunkSize * PerlinScale;
而不是
// Get Perlin Map
float px = (float)(x) / ChunkSize * PerlinScale + xOffset;
float py = (float)(y) / ChunkSize * PerlinScale + yOffset;
一切都正确对齐,但柏林噪声只是重复。
对我来说,在块之间平滑以使所有内容匹配的最佳方法是什么? 有没有更好的写法?
编辑:
感谢 Draykoon D 的帮助!如果有人需要,这里是更新后的信息和指向 pastebin 上更新脚本的链接!
这是供任何需要的人使用的更新代码: ** WorldGenerator.cs**
** WorldGenerator.cs**
希望对您有所帮助!
您要查找的关键字是可拼接的。
但我有一个好消息要告诉你,像 perlin 这样的噪声函数本质上是周期性的。 因此,与其调用 ChunckSize * ChunkSize 噪声函数,不如调用它一次,然后对结果进行除法。
我会建议您阅读这个优秀的教程:
不要使用 Perlin 噪声。它严重偏向 45 度和 90 度方向。你的山丘都与这些对齐,并没有沿着更有趣的各种方向定向。您可以使用
Unity.mathematics.noise.snoise(float2)
但它的重复周期相当小,如果您不使用 Unity Burst 作业,它可能不会很快。 this is what I created/use/recommend,但这肯定不是唯一的选择!请注意,所有这些噪声的范围都是 -1 到 1,而不是 0 到 1,所以如果这很重要而不是value=value*0.5+0.5;
重新调整它。既然这样,要解决您的问题,您需要将块和生成的概念分开。总的来说这是个好主意,而且我始终相信尽可能多地从游戏玩法中隐藏后端实现细节(例如块)(例如避免可见边界)。每次生成块时,都应该在世界中找到它的起始坐标,以便坐标与其余坐标无缝连接。例如,如果块是 128x128,那么从 (0, 0) 开始的块应该有起始坐标 (0, 0),那么从 (0, 1) 开始的块应该有起始坐标 (0, 128)。然后,通过乘以您想要的频率将世界坐标转换为噪声坐标。