Shader Toy - Planet

Date:     Updated:

카테고리:

태그:

Shadertoy 의 glsl 코드를 hlsl로 매핑한 코드입니다.


🐥 Render Planet

1

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
2 3


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

ezgif com-crop (9)


🐥 Render Background

4


// 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로 직접 구현했다!
  • 이제 배경 위에 지구를 얹어주면


ezgif com-crop (6)


🐥 Render Clouds

4


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;
}

Cloud 코드는 봐도 이해를 못하겠다.. 이 부분 역시 fbm을 이용하던데 좀 더 내공을 쌓고 다시 와야지..


Combine Planet and Clouds

5


🐥 Blurring

ezgif com-crop (10)

  • 마지막으로 blurring! 아름답다 아릅다워~



맨 위로 이동하기

Mini_Project 카테고리 내 다른 글 보러가기

댓글 남기기