이제 우리가 원하는 행성 표면은 모두 만들었다. 이제 이 행성 표면에 쉐이더를 적용시켜서 실제 지구처럼 보이도록 수정해보자. 이 때 각 지점의 지형 높이를 기준으로 색상을 선택하도록 하여 높이 고저차로 색을 수정해준다.
일단 먼저 Universal RP를 임포트해준다.
이제 아래와 같이 URP Asset, Lit Shader Graph, Material을 하나씩 생성해준다.
그 다음 ColorSettings 스크립트를 수정한다.
[CreateAssetMenu()]
public class ColorSettings : ScriptableObject
{
public Color planetColor;
public Material planetMaterial;
}
그 다음 Planet을 생성할 때 Mesh에서 기존 Standard를 찾아오는 부분을 제거하고 ColorSettings에서 선언해준 planetMaterial을 가져오도록 스크립트를 수정한다.
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>();
meshFilters[i] = meshObj.AddComponent<MeshFilter>();
meshFilters[i].sharedMesh = new Mesh();
}
meshFilters[i].GetComponent<MeshRenderer>().sharedMaterial = colorSettings.planetMaterial;
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);
최대 최소 고도를 구하기 위해 MinMax 클래스 스크립트를 아래와 작성해준 후에 높이에 따른 색상 조절 로직을 구현해보자.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MinMax
{
public float Min { get; private set; }
public float Max { get; private set; }
public MinMax()
{
Min = float.MaxValue;
Max = float.MinValue;
}
public void AddValue(float v)
{
if (v > Max)
{
Max = v;
}
if (v < Min)
{
Min = v;
}
}
}
public class ShapeGenerator
{
ShapeSettings settings;
INoiseFilter[] noiseFilters;
public MinMax elevationMinMax;
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);
}
elevationMinMax = new MinMax();
}
public Vector3 CalculatePoinOnPlanet(Vector3 pointOnUnitSphere)
{
float firstLayerValue = 0;
float elevation = 0;
if (noiseFilters.Length > 0)
{
firstLayerValue = noiseFilters[0].Evaluate(pointOnUnitSphere);
if (settings.noiseLayers[0].enabled)
{
elevation = firstLayerValue;
}
}
for (int i = 1; i < noiseFilters.Length; i++)
{
if (settings.noiseLayers[i].enabled)
{
float mask = (settings.noiseLayers[i].useFirstLayerAsMask) ? firstLayerValue : 1;
elevation += noiseFilters[i].Evaluate(pointOnUnitSphere) * mask;
}
}
elevation = settings.planetRadius * (1 + elevation);
elevationMinMax.AddValue(elevation);
return pointOnUnitSphere * elevation;
}
}
위처럼 작성해서 각 점들의 elevation을 계산할 때마다 elevationMinMax에 AddValue해주면서 고도의 최대, 최소값을 저장해둔다. 이제 이 높이를 기준으로 색상을 구현해보자.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ColorGenerator
{
ColorSettings settings;
public ColorGenerator(ColorSettings settings)
{
this.settings = settings;
}
public void UpdateElevation(MinMax elevationMinMax)
{
settings.planetMaterial.SetVector("_elevationMinMax", new Vector4(elevationMinMax.Min, elevationMinMax.Max));
}
}
이후에 Planet에서 ColorGenerator를 선언 후 초기화해주고 Mesh를 생성해준 후에 ColorGenerator의 UpdateElevation 메소드를 호출해준다.
ShapeGenerator shapeGenerator;
ColorGenerator colorGenerator;
void Initialize()
{
shapeGenerator = new ShapeGenerator(shapeSettings);
colorGenerator = new ColorGenerator(colorSettings);
//생략
}
void GenerateMesh()
{
for (int i = 0; i < 6;i++)
{
if (meshFilters[i].gameObject.activeSelf)
{
terrainFaces[i].ConstructMesh();
}
}
colorGenerator.UpdateElevation(shapeGenerator.elevationMinMax);
}
이제 Planet Shader를 수정해보자. 다음과 같이 ElevationMinMax를 선언해준후 reference를 _elevationMinMax로 수정해줘서 ColorGenerator에서 접근할 수 있도록 해준다. 그 다음 해당 Property를 Split해줘서 최소 최대를 따로 가져온다. 그 후에 texture 2D를 적용시켜서 해당 값을 Base Color로 설정해준다.
이제 Gradient 값을 ColorSettings에서 관리할 수 있도록 스크립트를 수정한다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu()]
public class ColorSettings : ScriptableObject
{
public Gradient gradient;
public Material planetMaterial;
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ColorGenerator
{
ColorSettings settings;
Texture2D texture;
const int textureResolution = 50;
public void UpdateSettings(ColorSettings settings)
{
this.settings = settings;
if (texture == null)
{
texture = new Texture2D(textureResolution, 1);
}
}
public void UpdateElevation(MinMax elevationMinMax)
{
settings.planetMaterial.SetVector("_elevationMinMax", new Vector4(elevationMinMax.Min, elevationMinMax.Max));
}
public void UpdateColors()
{
Color[] colors = new Color[textureResolution];
for (int i = 0;i < textureResolution; i++)
{
colors[i] = settings.gradient.Evaluate(i / (textureResolution - 1f));
}
texture.SetPixels(colors);
texture.Apply();
settings.planetMaterial.SetTexture("_texture", texture);
}
}
public void UpdateSettings(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);
}
elevationMinMax = new MinMax();
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Planet : MonoBehaviour {
[Range(2,256)]
public int resolution = 10;
public bool autoUpdate = true;
public enum FaceRenderMask { All, Top, Bottom, Left, Right, Front, Back };
public FaceRenderMask faceRenderMask;
public ShapeSettings shapeSettings;
public ColorSettings colorSettings;
[HideInInspector]
public bool shapeSettingFoldout;
[HideInInspector]
public bool colorSettingFoldout;
ShapeGenerator shapeGenerator = new ShapeGenerator();
ColorGenerator colorGenerator = new ColorGenerator();
[SerializeField, HideInInspector]
MeshFilter[] meshFilters;
TerrainFace[] terrainFaces;
private void OnValidate()
{
GeneratePlanet();
}
void Initialize()
{
shapeGenerator.UpdateSettings(shapeSettings);
colorGenerator.UpdateSettings(colorSettings);
//생략
}
void GenerateColors()
{
colorGenerator.UpdateColors();
}
}
이제 Gradient를 적절히 수정한 후에 Geneerate Planet을 누르면 아래와 같이 높이에 따른 색상 적용이 구현된다.
이제 진짜 지구처럼 보이는 것을 확인할 수 있다.