风格化卡通体积云

Stylized cartoon volumetric clouds

Function getCloudColor(
    viewVector,      // Direction of gaze
    skyColor,        // Background sky color
    basePos,         // Ray origin
    samples,         // Number of samples
    umbral,          // Threshold used to remove low noise
    brightFactor,    // High light intensity
    dither,          // Jitter parameters
    CLOUD_PARAMS     // Various cloud layer constants such as plane, center, and thickness
):
    // 1. If your gaze is downward, return directly to the sky color.
    if viewVector.y0:
        return skyColor

    // 2. Calculate the intersection points t0 and t1 of the ray with the upper and lower cloud planes.
    t0 = (CLOUD_BOTTOM - basePos.y) / viewVector.y
    t1 = (CLOUD_TOP    - basePos.y) / viewVector.y

    // 3. Generate the sampling start point p and step size stepV
    p     = basePos + viewVector * t0
    pEnd  = basePos + viewVector * t1
    stepV = (pEnd - p) / samples
    p    += stepV * dither  // Add a small amount of jitter to reduce banding

    // 4. Sample along the ray, accumulating "cloud thickness" cv and "first hit depth" den
    cv       = 0          // Cumulative cloud volume components
    den      = 0          // Relative position upon first entry into the cloud layer (0~1)
    firstHit = true
    totalRange = (CLOUD_CENTER - CLOUD_BOTTOM) + (CLOUD_TOP - CLOUD_CENTER)

    for i in 0 .. samples-1:
        // 4.1 Sampling noise (height field)
        noiseHi = sampleNoiseHeight(p.xz, timeOffsetHigh)
        noiseLo = sampleNoiseHeight(p.zx, timeOffsetLow)  // Optional multi-layer mixing
        noise   = mixAndSmooth(noiseHi, noiseLo)

        // 4.2 Calculate the upper and lower boundaries of the cloud layer based on the threshold
        v    = (noise - umbral) / (1 - umbral)
        inf  = CLOUD_CENTER - v * (CLOUD_CENTER - CLOUD_BOTTOM)
        sup  = CLOUD_CENTER + v * (CLOUD_TOP    - CLOUD_CENTER)

        // 4.3 Determine if the current sampling point py is inside the cloud layer
        if inf < p.y < sup:
            cv += min(stepLength, sup - inf)
            if firstHit:
                den      = (sup - p.y) / totalRange
                firstHit = false
        else if withinSoftEdge(p.y, inf, sup, CLOUD_EDGE_WIDTH):
            // Edge transition: Add a little accumulation softly
            cv += softBlendAmount(p.y, inf, sup) 
            if firstHit:
                den      = (sup - p.y) / totalRange
                firstHit = false

        p += stepV

    // 5. Normalized cumulative value and depth
    opacity   = clamp(cv / (2 * CLOUD_EDGE_WIDTH / viewVector.y), 0, 1)
    density   = clamp(den, 0.0001, 1)

    // 6. Select colors by density: Low—Medium—High
    if density < 0.33:
        baseColor = COL_SH  // Cloud background
    else if density < 0.66:
        baseColor = COL_MD  // Mid-layer color
    else:
        baseColor = COL_HI  // Cloud Top Color

    // 7. Viewpoint Highlights: Only high-density areas are highlighted.
    if density > 0.66:
        highlight = dot(computeNormal(density), -viewVector)
        baseColor = Mix(baseColor, COL_HI, highlight * brightFactor)

    // 8. Self-shadow: The thicker the shadow, the darker it is.
    baseColor *= Mix(1.0, 0.85, density^2 * 0.5)

    // 9. Edge Stroke: Enhance the contour based on the depth field gradient
    edgeFactor = computeEdgeFactor(opacity)
    baseColor  = Mix(baseColor, OUTLINE_COLOR, edgeFactor * 0.3)

    // 10. Adjust the darkness at night according to the brightness of the sky.
    nightFactor = computeNightFactor(skyColor)
    baseColor  *= nightFactor

    // 11. Final blend: Cloud-covered background
    alpha = opacity * clamp((viewVector.y - 0.05) * 5.0, 0, 1)
    return Mix(skyColor, baseColor, alpha)
JavaScript

Comment

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.