[Unity] Planet Generation [4] - multiple noise filters

suhan0304·2024년 3월 6일
0
post-thumbnail

여러 노이즈 필터를 만들어보자.

Rigid noise filter

처음 만들 노이즈 필터는 꼭대기가 뾰족한 산과 비슷한 노이즈 필터를 만들어보자. sin 함수를 아래와 같은 과정을 통해 꼭대기가 뾰족한 산과 같은 그래프 형태를 만들 수 있다.

이 때 고도가 낮은 지형을 좀 더 평평하게 만들기 위해 제곱을 취한다.


기존 NoiseFilter를 SimpleNoiseFilter로 바꾼 후에 진행한다. SimpleNoiseFilter의 내용을 그대로 복사해준 후에 공식 부분을 위에서 세운 함수로 수정해준다.

RidgidNoiseFilter.cs

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

public class RidgidNoiseFilter : INoiseFilter
{
    NoiseSettings settings;
    Noise noise = new Noise();

    public RidgidNoiseFilter(NoiseSettings settings)
    {
        this.settings = settings;
    }

    public float Evaluate(Vector3 point)
    {
        float noiseValue = 0;
        float frequency = settings.baseRoughness;
        float amplitude = 1;
        float weight = 1;

        for (int i = 0; i < settings.numLayers; i++)
        {
            float v = 1 - Mathf.Abs(noise.Evaluate(point * frequency + settings.centre));
            v *= v;
            v *= weight;
            weight = v;

            noiseValue += v * amplitude;
            frequency *= settings.roughness;
            amplitude *= settings.persistence;
        }

        noiseValue = Mathf.Max(0, noiseValue - settings.minValue);
        return noiseValue * settings.strength;
    }
}

ShapeGenerator에서 SimpleNoiseFilter 말고도 다양한 NoiseFilter를 배열 형식으로 담을 수 있도록 하기 위해 INoiseFilter, NoiseFilterFactory 스크립트를 추가로 작성해준다.

INoiseFilter.cs

using System.Collections.Generic;
using UnityEngine;

public interface INoiseFilter 
{
    float Evaluate(Vector3 point);
}

그 다음에 RidgidNoiseFilter, SimpleNoiseFilter의 Interface를 설정해준 후에 NoiseSettings에서 해당 타입을 유지할 수 있도록 한다.

public class SimpleNoiseFilter : INoiseFilter
public class RidgidNoiseFilter : INoiseFilter

NoiseSettings.cs

public class NoiseSettings 
{
    public enum FilterType
    {
        Simple,
        Ridgid
    }

    public FilterType filterType;

    public float strength = 1;
    [Range(1, 8)]
    public int numLayers = 1;
    public float baseRoughness = 1;
    public float roughness = 2;
    public float persistence = .5f;
    public Vector3 centre;
    public float minValue;
}

이제 NoiseSettings의 FilterType을 확인해서 해당 타입에 맞는 노이즈 필터를 만들도록 NoiseFilterFactory를 작성한다.

NoiseFilterFactory.cs

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

public class NoiseFilterFactory
{
    public static INoiseFilter CreateNoiseFilter(NoiseSettings settings)
    {
        switch (settings.filterType)
        {
            case NoiseSettings.FilterType.Simple:
                return new SimpleNoiseFilter(settings);
            case NoiseSettings.FilterType.Ridgid:
                return new RidgidNoiseFilter(settings);
        }
        return null;
    }
}

이제 ShapeGenerator에서 SimpleNoiseFilter 배열이 아닌 INoiseFilter 배열을 사용한 후에 NoiseFilterFactory의 CreateNoiseFilter 메소드를 호출시켜서 NoiseFilter를 각각 타입에 맞도록 만들어준다.

ShapeGenerator.cs

ShapeSettings settings;
INoiseFilter[] noiseFilters;

public ShapeGenerator(ShapeSettings settings)
{
    this.settings = settings;
    noiseFilters = new INoiseFilter[settings.noiseLayers.Length];
    for(int i = 0; i < noiseFilters.Length; i++)
    {
        noiseFilters[i] = NoiseFilterFactory.CreateNoiseFilter(settings.noiseLayers[i].noiseSettings);
    }
}

이제 유니티로 돌아가 세 번째 노이즈 필터를 만들고 필터 타입을 Ridgid르 바꿔주면 뾰족한 산들이 만들어진다.

값을 잘 조절하면 산맥과 같은 모양처럼 만들 수 있다.

FirstLayerAsMask를 체크하면 실제 대륙에 산맥들이 있는 모양을 구현할 수 있다.


weightMultiplier를 설정해서 노이즈를 생성하면서 가중치를 점점 낮추도록 할 수 있다. 이 때 SimpleNoiseFilter가 아닐 때만 가중치 곱을 해주기 위해 아래와 같이 작성해준다.

NoiseSettings.cs

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

[System.Serializable]
public class NoiseSettings 
{
    public enum FilterType
    {
        Simple,
        Ridgid
    }
    public FilterType filterType;

    public SimpleNoiseSettings simpleNoiseSettings;
    public RidgidNoiseSettings ridgidNoiseSettings;

    [System.Serializable]
    public class SimpleNoiseSettings
    {
        public float strength = 1;
        [Range(1, 8)]
        public int numLayers = 1;
        public float baseRoughness = 1;
        public float roughness = 2;
        public float persistence = .5f;
        public Vector3 centre;
        public float minValue;
    }

    [System.Serializable]
    public class RidgidNoiseSettings : SimpleNoiseSettings
    {
        public float weightMultiplier = .8f;
    }
}

각각의 settings를 불러오는 곳에서 타입에 맞게 불러오도록 수정한다.

NoiseSettings.RidgidNoiseSettings settings;
Noise noise = new Noise();

public RidgidNoiseFilter(NoiseSettings.RidgidNoiseSettings settings)
{
    this.settings = settings;
}
NoiseSettings.SimpleNoiseSettings settings;
Noise noise = new Noise();

public SimpleNoiseFilter(NoiseSettings.SimpleNoiseSettings settings) 
{ 
    this.settings = settings; 
}
public class NoiseFilterFactory
{
    public static INoiseFilter CreateNoiseFilter(NoiseSettings settings)
    {
        switch (settings.filterType)
        {
            case NoiseSettings.FilterType.Simple:
                return new SimpleNoiseFilter(settings.simpleNoiseSettings);
            case NoiseSettings.FilterType.Ridgid:
                return new RidgidNoiseFilter(settings.ridgidNoiseSettings);
        }
        return null;
    }
}

editor scripts bt Brecht Lecluyes

속성에 따라서 editor를 수정하기위해 Brecht Lecluyes의 editor scripts를 사용한다.

NoiseSettings.cs

[ConditionalHide("filterType", 0)]
public SimpleNoiseSettings simpleNoiseSettings;
[ConditionalHide("filterType", 1)]
public RidgidNoiseSettings ridgidNoiseSettings;

이제 FilterType에 따라 인스펙터의 속 FilterType에 따라 Noise Settings가 하나만 나타나는 것을 확인할 수 있다.

이제 적절하게 값을 조절해서 산맥이 있는 대륙이 만들어진 행성을 아래와 같이 구현할 수 있다.


Resolution Update

해상도가 높아지면 NoiseSettings의 값들을 조금만 바꿔도 렌더링하는데 매우 오랜 시간이 걸린다. 한 번에 하나의 면만 렌더링하는 옵션을 행성에 추가해보자.

Planet.cs

public enum FaceRenderMask { All, Top, Bottom, Left, Right, Front, Back };
public FaceRenderMask faceRenderMask;

void Initialize()
{
    shapeGenerator = new ShapeGenerator(shapeSettings);

    if (meshFilters == null || meshFilters.Length == 0)
    {
        meshFilters = new MeshFilter[6];
    }
    terrainFaces = new TerrainFace[6];

    Vector3[] directions = { Vector3.up, Vector3.down, Vector3.left, Vector3.right, Vector3.forward, Vector3.back };

    for (int i = 0; i < 6; i++)
    {
        if (meshFilters[i] == null)
        {
            GameObject meshObj = new GameObject("mesh");
            meshObj.transform.parent = transform;

            meshObj.AddComponent<MeshRenderer>().sharedMaterial = new Material(Shader.Find("Standard"));
            meshFilters[i] = meshObj.AddComponent<MeshFilter>();
            meshFilters[i].sharedMesh = new Mesh();
        }

        terrainFaces[i] = new TerrainFace(shapeGenerator, meshFilters[i].sharedMesh, resolution, directions[i]);
        bool renderFace = faceRenderMask == FaceRenderMask.All || (int)faceRenderMask - 1 == i;
        meshFilters[i].gameObject.SetActive(renderFace);
    }
}

void GenerateMesh()
{
    for (int i = 0; i < 6;i++)
    {
        if (meshFilters[i].gameObject.activeSelf)
        {
            terrainFaces[i].ConstructMesh();
        }
    }
}

이제 유니티로 돌아가 컴파일 해준 후에 Planet의 FaceRenderMask의 값들을 수정해서 원하는 면만 렌더링할 수 있다. 이는 실제 인게임에서 유닛이 위치하고 보고있는 면만을 렌더링함으로써 resolution(해상도)가 높아져도 빠른 시간안에 렌더링이 가능하다는 장점이 있다.

해상도를 최대(256)으로 설정한 후에 Noise Settings의 Strength를 수정했을때 기존과 달리 빠르게 렌더링 되는 것을 확인할 수 있다.

다시 All로 바꿔주면 모든 면이 렌더링된다.

profile
Be Honest, Be Harder, Be Stronger

0개의 댓글