Shader Toy - Planet
카테고리: Mini_Project
Shadertoy 의 glsl 코드를 hlsl로 매핑한 코드입니다.
🐥 Render Planet
float fbm(float3 x, float OCTAVES) {
float v = 0.0;
float a = 0.5;
float3 shift = float3(100, 100, 100);
for (int i=0; i<OCTAVES; ++i) {
v += a * noise(x);
x = x * 2.0 + shift;
a *= 0.5;
}
return v;
}
// ...
Material planetMaterial;
// X : input.position
X = mul(planetRotation, (X - planetCenter));
float3 surfaceLocation = normalize(X);
float mountain = clamp(1.0 - fbm(surfaceLocation * 4.0, 6) + (max(abs(surfaceLocation.y) - 0.6, 0.0)) * 0.03, 0.0, 1.0);
mountain = pow(mountain, 3) * 0.25 + 0.8;
const float water = 0.85;
float elevation = mountain;
float3 normal = normalize(cross(ddx(surfaceLocation * mountain), ddy(surfaceLocation * mountain)));
코드를 보면 우선 구 형태를 잡은 뒤 FBM(Fractal Brownian Motion)을 이용해 지형을 생성한다. FBM을 완전히 이해하지 못했지만 자연에서 발생하는 자연 침식을 노이즈와 프랙탈을 이용하여 구현한 것이라고 한다.
static const Material ROCK = {float3(0.5, 0.35, 0.15), 0.0, 0.0};
static const Material TREE = {float3(0.05, 1.15, 0.10), 0.2, 0.1};
static const Material SAND = {float3(1.0, 1.0, 0.85), 0.0, 0.0};
static const Material ICE = {float3(0.85, 1.00, 1.20), 0.2, 0.6};
// ...
float materialNoise = noise(surfaceLocation * 200.0);
float slope =
clamp(2.0 * (1.0 - dot(normal, surfaceLocation)), 0.0, 1.0);
bool iceCap = abs(surfaceLocation.y) + materialNoise * 0.2 > 0.98;
bool rock =
(elevation + materialNoise * 0.1 > 0.94) || (slope > 0.3);
bool mountainTop = (elevation + materialNoise * 0.05 -
slope * 0.05) > (planetMaxRadius * 0.92);
bool sand = (elevation < water + 0.006) &&
(noise(surfaceLocation * 8.0) > 0.3);
sand = sand || (elevation < 0.89) &&
(noise(surfaceLocation * 1.5) * 0.15 +
noise(surfaceLocation * 73.0) * 0.25 >
abs(surfaceLocation.y));
if (rock) {
planetMaterial = ROCK;
} else {
planetMaterial = TREE;
}
if (iceCap || mountainTop) {
planetMaterial = ICE;
} else if (!rock && sand) {
planetMaterial = SAND;
} else if (!rock && (iResolution.x > 420.0)) {
elevation += noise(surfaceLocation * 150.0) * 0.02;
}
if (!sand && !iceCap) {
planetMaterial.color *=
lerp(noise(surfaceLocation * 256.0), 1.0, 0.4);
}
Material을 정의한 뒤 생성된 지형을 적절히 분배한다. 기본 base material은 물이고, 산 꼭대기를 ice처리한 디테일이 인상적이다.
Global Illumination
Before | After |
---|---|
if (elevation < water) {
float relativeWaterDepth = min(1.0, (water - mountain) * 30.0);
const float waveMagnitude = 0.0014;
const float waveLength = 0.01;
const float3 shallowWaterColor = float3(0.4, 1.0, 1.9);
const float shallowWaveRefraction = 4.0;
float shallowWavePhase =
(surfaceLocation.y - mountain * shallowWaveRefraction) *
(1.0 / waveLength);
const float3 deepWaterColor = float3(0.0, 0.1, 0.7);
float deepWavePhase = (atan2(surfaceLocation.x, surfaceLocation.z) +
noise(surfaceLocation * 15.0) * 0.075) *
(1.5 / waveLength);
float wave = (cos(shallowWavePhase + iTime * 1.5) *
sqrt(1.0 - relativeWaterDepth) +
cos(deepWavePhase + iTime * 2.0) * 2.5 *
(1.0 - abs(surfaceLocation.y)) *
pow(relativeWaterDepth, 2)) *
waveMagnitude;
elevation = water + wave;
}
이제 파도를 추가해주고 (deep water, shallow water 구분해주는 깨알 디테일..)
Ray tracing을 이용해 전역 조명(direct + indirect) + 그림자까지 추가해준다.
Result
🐥 Render Background
// Background starfield
float galaxyClump =
(pow(noise(input.position.xy * (30.0 * invResolution.x)), 3.0) *
0.5 +
pow(noise(100.0 + input.position.xy * (15.0 * invResolution.x)),
5.0)) /
1.5;
float L_o_color =
galaxyClump * pow(hash(input.position.xy), 1500.0) * 80.0;
L_o = float3(L_o_color, L_o_color, L_o_color);
// Color stars
L_o.r *= sqrt(noise(input.position.xy) * 1.2);
L_o.g *= sqrt(noise(input.position.xy * 4.0));
// Twinkle
L_o *= noise(iTime * 0.5 + input.position.yx * 10.0);
float2 delta =
(input.position.xy - iResolution.xy * 0.5) * invResolution.y * 1.2; // strength
float atmosphereRadialAttenuation =
min(1.0, 0.06 * pow(max(0.0, 1.0 - (length(delta) - 0.9) / 0.9), 8));
// Gradient around planet
float radialNoise =
lerp(1.0, noise(normalize(delta) * 40.0 + iTime * 0.5), 0.14);
L_o += radialNoise * atmosphereRadialAttenuation * shadowedAtmosphere;
return float4(L_o, maxDistanceToPlanet);
- 배경 정도는 texture를 사용할 법 한데, 별 하나하나를 noise로 직접 구현했다!
- 이제 배경 위에 지구를 얹어주면
🐥 Render Clouds
float4 renderClouds(Ray eyeRay, float minDist, float maxDist,
float3 shadowedAtmosphere) {
const int maxSteps = 80;
const float stepSize = 0.012;
const float3 cloudColor = float3(0.95, 0.95, 0.95);
const float3 ambient = float3(0.9, 1.0, 1.0);
float planetShadow = clamp(
0.4 + dot(w_i, normalize(eyeRay.origin + eyeRay.direction * minDist)),
0.25, 1.0);
float4 result = float4(0.0, 0.0, 0.0, 0.0);
float t = maxDist;
for (int i = 0; i < maxSteps; ++i) {
if (t > minDist) {
float3 X = ((eyeRay.direction * t + eyeRay.origin) - planetCenter) *
(1.0 / planetMaxRadius);
float density = cloudDensity(X, iTime);
if (density > 0.0) {
const float eps = stepSize;
float wrapShading =
clamp(-(cloudDensity(X + w_i * eps, iTime) - density) *
(1.0 / eps),
-1.0, 1.0) *
0.5 +
0.5;
float AO = pow((dot(X, X) - 0.5) * 2.0, 8);
float3 L_o = cloudColor * (B_i * planetShadow * wrapShading *
lerp(1.0, AO, 0.5) +
ambient * AO);
L_o = lerp(L_o, shadowedAtmosphere,
min(0.5, pow(max(0.0, 1.0 - X.z), 2)));
density *= pow(
1.0 - abs(2.0 * length(X - planetCenter) -
(cloudMinRadius + planetMaxRadius)) *
(1.0 / (planetMaxRadius - cloudMinRadius)), 2);
result = lerp(result, float4(L_o, 1.0), density);
t += stepSize * 2.0;
}
t -= stepSize * 3.0;
} else {
return result;
}
}
return result;
}
Combine Planet and Clouds
🐥 Blurring
- 마지막으로 blurring! 아름답다 아릅다워~
댓글 남기기