본문 바로가기
작심삼일/The Cosmic Robot

설날 프로젝트 - 절차적 행성 생성

by water_beetle 2025. 1. 27.

무작위로 생성된 행성들

 

 

1. 어떻게 구를 생성할 것 인가?

행성을 만들기 위해 여러 가지 구 생성 방식을 찾아봄.

(1) Lat-Lon 기반 구 생성

처음에는 위도와 경도를 기반으로 점을 분포시켜 구를 생성하려 했으나, 극지방에서 삼각형 크기가 지나치게 작아지는 문제가 발생하였음. 이로 인해 텍스처 왜곡이 심하고, 결과적으로 적합하지 않아 다른 방식을 사용하기로 결정.

(2) Fibonacci Triangle 방식

점들이 균일하게 분포되도록 Fibonacci 수열을 사용하여 구를 생성하는 방법.
점의 분포는 괜찮았지만, 삼각형을 생성하려면 돌로네 삼각분할(Delaunay Triangulation)을 계산해야 했기에 구현 난이도가 크게 증가하였다. 이 방식은 너무 복잡하여 제외함.

(3) 정이십면체 분할 방식

정이십면체를 기반으로 점을 세분화하여 구를 생성하는 방식.
삼각형 크기가 일정하게 유지되어 보기에는 좋았으나, 분할 수가 많아질수록 삼각형의 개수가 기하급수적으로 증가하였음.
내가 원하는 삼각형의 개수를 적절하게 조절할 수 없을 것 같아 제외.

(4) Cube Sphere 방식

최종적으로 Cube Sphere 방식을 선택하였음. 큐브의 각 면을 구형으로 변형하며 점들을 균일하게 분포시키는 방식으로, 구현이 비교적 간단하고 텍스처 왜곡도 최소화되었음.

사실 유튜브에 이 방식이 설명이 잘되어 있어서 ㅎㅎ...

 

 

이 때 생성한 구를 살펴보면서 그림자 부분이 깨져서 내가 Normal Vector를 잘못 넣었나 코드를 다시 읽어봤는데,

문제가 없어서 왜 그런지 구글링해보니까 Virtual Shadow Maps 이게 문제였다...

-> 바로 Shadow Maps로 바꾸니까 그림자가 깨지지 않고 잘 나옴.

 

2. 구에 다양한 Noise 추가하기

노이즈를 활용하여 행성 표면의 지형 변화를 구현함.

FastNoiseLite 라이브러리를 사용해 적용하였는데, 아직도 Noise에 대해서는 잘 모르겠다.

 

유튜브를 찾아보니까 막 삼각함수 이용해서 크레이터도 만들고, 진짜 행성처럼 자기가 직접 Noise Function을 만들어서

멋있게 구현하던데, 하루종일 FastNoiseLite를 만지고, 파라미터 값만 수정하다가 화가나서

그냥 대충 SimplexNoise만 사용해서 적용하였다.

 

// 지형 높이 계산
float FPlanetNoiseGenerator::GetHeight(const FVector& PointOnSphere)
{    
	//FVector WarpedPoint = ApplyDomainWarp(PointOnSphere);
	
    // 기본 노이즈
    float BaseHeight = GenerateBaseNoise(PointOnSphere);
    float MountainNoise = GenerateMountainNoise(PointOnSphere);

    // 최종 높이 조합
    float CombinedHeight = BaseHeight + MountainNoise;

    return CombinedHeight;
}

// 기본 노이즈
float FPlanetNoiseGenerator::GenerateBaseNoise(const FVector& Point) const
{
    float NoiseValue = BasicNoiseGenerator.GetNoise(Point.X * BaseNoiseScale, Point.Y * BaseNoiseScale, Point.Z * BaseNoiseScale);
    return NoiseValue * BaseNoiseIntensity;
}

// 산맥 노이즈
float FPlanetNoiseGenerator::GenerateMountainNoise(const FVector& Point) const
{
    float NoiseValue = RigidNoiseGenerator.GetNoise(Point.X * MountainNoiseScale, Point.Y * MountainNoiseScale, Point.Z * MountainNoiseScale);
    return (1.0f - FMath::Abs(NoiseValue)) * MountainIntensity;
}

 

 

 

3. Material 적용하기

이 때는 Material만 적용하면 끝날 줄 알았는데, 이제 한 50%한거였다.

 

1. Tiling texture

그냥 바로 Material을 적용하니까 아름다운 타일들이 계속 반복되어 너무 보기 흉해졌다....

 

이것도 찾아보니까 Tiling이라고 사람들이 유튜브에 해결법을 잔뜩 올려놨다.

-> Texture를 확대해서 부분 적용

-> Noise Texture를 적용시키기

-> 비슷한 Color 섞어서 Mix 시키기 등등...

 

유튜브 보면서 따라하다가 갑자기 현타가 또 와서 누가 구현해논거 없나... 찾아보니

Unreal Start Content에 Tiling 제거된 Material을 만들어 놨다는 사실을 알고 바로 만든거 삭제하고 사용함 ㅎㅎ..

 

 

4. Normal Vector 계산

Material을 적용하면서 조금 이상하게 적용되어

왜 그렇지 고민을 하던 중 내가 Normal Map을 잘못 계산해 넣고 있다는걸 깨달았다.

 

구를 만들 때 (구 Vertex 좌표 - 구 중심좌표)를 Normal Vector로 넣고 있었는데
Noise를 적용시켰으니까 그에 맞는 Normal Vector를 다시 계산해서 넣어줘야 했다!

 

처음엔 인접한 Triangle들을 찾아 Normal을 다시 계산해야 하나 생각이 들었는데 이게 맞나 싶어

또 구글링 해보니까 누가 같은 질문을 해논게 있어 사람들 생각이 다 비슷하구나...라고 느꼇다.

 

https://math.stackexchange.com/questions/1071662/surface-normal-to-point-on-displaced-sphere

 

Surface normal to point on displaced sphere

I want to calculate the surface normal to a point on a deformed sphere. The surface of the sphere is displaced along its (original) normals by a function $f(\vec x)$. In mathematical terms: Let $...

math.stackexchange.com

gpt 돌려서 정리해달라고 한 후 읽어보니까, 수식이 막 섞여있어 어려운 내용인줄 알았는데 그냥 Vertex 좌표에 Epsilon값 더하고 빼서 미분값을 계산해 Normal Vector를 구하는 방법이였다.

 

FVector APlanetGenerator::CalculateNormal(const FVector& Vertex)
{
	// 구의 Normal Vector
	FVector UnitVector = Vertex.GetSafeNormal();
	
	const float Epsilon = (NoiseGenerator.BaseNoiseScale / 100.0f) * NoiseGenerator.Frequency;

	// 노이즈 변화량 측정을 위해 축을 약간 이동
	float NoiseXPlus = NoiseGenerator.GetHeight(FVector(Vertex.X + Epsilon, Vertex.Y, Vertex.Z));
	float NoiseXMinus = NoiseGenerator.GetHeight(FVector(Vertex.X - Epsilon, Vertex.Y, Vertex.Z));
    
	float NoiseYPlus = NoiseGenerator.GetHeight(FVector(Vertex.X, Vertex.Y + Epsilon, Vertex.Z));
	float NoiseYMinus = NoiseGenerator.GetHeight(FVector(Vertex.X, Vertex.Y - Epsilon, Vertex.Z));
    
	float NoiseZPlus = NoiseGenerator.GetHeight(FVector(Vertex.X, Vertex.Y, Vertex.Z + Epsilon));
	float NoiseZMinus = NoiseGenerator.GetHeight(FVector(Vertex.X, Vertex.Y, Vertex.Z - Epsilon));

	FVector Gradient;
	Gradient.X = (NoiseXPlus - NoiseXMinus) / 2;
	Gradient.Y = (NoiseYPlus - NoiseYMinus) / 2;
	Gradient.Z = (NoiseZPlus - NoiseZMinus) / 2;

	FVector H = Gradient - (FVector::DotProduct(Gradient, UnitVector) * UnitVector);
	FVector Normal = (UnitVector - H * PlanetRadius).GetSafeNormal();
	return Normal;
}

 

이 때 좀 고민했어야 하는 부분이 Epsilon 값을 어떻게 설정할 것인가였는데,

내가 Noise Function을 만들면서 Noise Scale, Frequency 를 사용하였는데 이 값을 고려해 Epsilon 값을 설정했어야 했다.

그래서 좀 노가다성으로 계산 수식을 바꿔가며 적용해보았는데 코드의 Epsilon 수식이 Normal Vector가 젤 깔끔했다.

 

Normal Vector가 잘 계산되는지 확인하기 위해 좀 극단적인 경우에서 Normal Vector들을 그려보니 잘 계산되는 것을 확인 할 수 있었다. 가파른 경사부분에서 위가 아닌 옆으로 그려지는 것을 확인 할 수 있다.

 

5. 경사에 따른 Material 적용

단색으로 Material을 적용하니까, 조금 밋밋한 부분이 있어 경사가 큰 부분, 평지 경우를 구분해서 각각의 경우에 Material을 적용하려고 하였다.

 

그래서 처음에는 그냥 Lerp 함수를 이용해 적용하니까, Material을 섞는거라 구분이 잘 되지 않아 보기 안좋았다.

내가 원하는 건 0~0.4는 Material A적용, 0.4~0.6는 Material A, Material B를 Mix, 0.6~1.0 에서는 Material B 적용 이런 방식이었다.

 

또 구글링.... 을 해보니까 WorldAlignBlend라는 Blueprint가 이미 있다고 해서 해당 함수를 사용해보니까
평지를 위한 Material이라 내 행성에 적용하니 제대로 적용되지 않았다.

어떻게 해야되나 Blueprint 구현 내용을 좀 읽어보니까 World Vector값을 수정하면 될 것 같아 구의 법선 벡터를 넣어주니

잘 적용된다!!

 

위 그림처럼 경사가 높은 지역은 바위 Material, 평지 부분은 풀 Material을 적용하였다. 

그런데 생각보다 예쁘지는 않은듯.

 

6. 우주 skybox 적용하기

행성은 다 만들었다. 

이제는 우주 배경이 필요해 적용해야한다. 또 유튜브 찬스를 빌려 해당 영상을 참고해 적용하였다.

우주 texture는 https://www.solarsystemscope.com/lab 여기꺼가 진국인것 같다.

 

 

글로 작성해보니까 별로 한게 없는것 같은데, 3일내내 이것만 한 것 같다.

결과물을 스크린샷으로 찍어 보니까 예뻐서 너무 만족스럽다!!!

 

'작심삼일 > The Cosmic Robot' 카테고리의 다른 글

반짝반짝 작은  (0) 2025.02.23
설날 프로젝트 - 공전과 자전  (0) 2025.01.31
설날 프로젝트 - 태양과 조명  (0) 2025.01.29
설날 프로젝트 - 행성과 중력  (0) 2025.01.27
설날 프로젝트 - 목표  (0) 2025.01.23

최근댓글

최근글

skin by © 2024 ttuttak