#include "cloudshader.h" Color CloudShader::shade(const Scene &scene, const Ray &ray) const { Vector3d hitPoint = ray.origin + ray.direction * ray.length; // Potentially add epsilon // Collect getNoise through the cloud float accumulatedNoise = 0.0f; float cloudLength = 0.0f; // Length of cloud in ray direction // Get background color behind cloud and information about the clouds length Ray cloudRay = ray; cloudRay.origin = ray.origin + (ray.length + REFR_EPS) * ray.direction; cloudRay.length = INFINITY; cloudRay.primitive = nullptr; // Get out of cloud primitive first if (ray.primitive->intersect(cloudRay)) { // Get length cloudLength = cloudRay.length; // Prepare ray for background color cloudRay.setRemainingBounces(cloudRay.getRemainingBounces() + 1); cloudRay.origin = cloudRay.origin + (cloudRay.length + REFR_EPS) * cloudRay.direction; cloudRay.length = INFINITY; cloudRay.primitive = nullptr; } Color background = scene.traceRay(cloudRay); if (cloudLength == 0.0f) return background; // No cloud or at edge // Get getNoise through cloud int const NOISE_SAMPLES = settings.densitySamples; float stepWeight = cloudLength / (float) NOISE_SAMPLES; for (int i = 0; i < NOISE_SAMPLES; ++i) { float progress = (float) i / (float) NOISE_SAMPLES; Vector3d samplePoint = hitPoint + progress * cloudLength * ray.direction; float sampleDensity = getCloudDensity(samplePoint); accumulatedNoise += sampleDensity * stepWeight; } // Pre-processs accumulated getNoise // accumulatedNoise /= (float) NOISE_SAMPLES; float cloudDensity = exp(-accumulatedNoise * settings.densityAbsorption); return background * cloudDensity; if (accumulatedNoise < 1 - settings.densityTreshold) { accumulatedNoise = 0; } else { // Fade out the getNoise accumulatedNoise = (accumulatedNoise - (1 - settings.densityTreshold)) / settings.densityTreshold; } // return accumulatedNoise * Color(1, 1, 1); // Use the getNoise to control the densityTreshold of the clouds Color cloud = background * (1.0f - accumulatedNoise) + settings.cloudColor * accumulatedNoise; // Use the getNoise to add some wispy details to the clouds cloud += settings.wispyColor * (accumulatedNoise * settings.wispyIntensity); // Add some ambient and diffuse lighting // cloud += scene.ambientLight() * material.ambient(); // for (const auto &light: scene.lights()) // { // Light::Illumination illumination = light->illuminate(scene, ray); // if (illumination.distance == 0.0f) continue; // Skip ambient light // float diffuse = dotProduct(normal, illumination.direction); // cloud += material.cloud() * illumination.cloud * diffuse; // } return cloud; } bool CloudShader::isTransparent() const { return true; } CloudShader::CloudShader(const CloudSettings &settings) : settings(settings), noise(WorleyNoise( NOISE_SIZE, NOISE_POINTS) ) { noise.invert = true; } float CloudShader::getCloudDensity(Vector3d point) const { point /= settings.scale; // Get corner points of the cube int x_min = (int) fitToNoise(floor(point.x)); int y_min = (int) fitToNoise(floor(point.y)); int z_min = (int) fitToNoise(floor(point.z)); int x_max = fitToNoise(x_min + 1); int y_max = fitToNoise(y_min + 1); int z_max = fitToNoise(z_min + 1); // Get the getNoise values at the corner points float n000 = noise.getNoise(x_min, y_min, z_min); float n001 = noise.getNoise(x_min, y_min, z_max); float n010 = noise.getNoise(x_min, y_max, z_min); float n011 = noise.getNoise(x_min, y_max, z_max); float n100 = noise.getNoise(x_max, y_min, z_min); float n101 = noise.getNoise(x_max, y_min, z_max); float n110 = noise.getNoise(x_max, y_max, z_min); float n111 = noise.getNoise(x_max, y_max, z_max); // Get fractions float fx = point.x - floor(point.x); float fy = point.y - floor(point.y); float fz = point.z - floor(point.z); // Interpolate float nx00 = n000 * (1 - fx) + n100 * fx; float nx01 = n001 * (1 - fx) + n101 * fx; float nx10 = n010 * (1 - fx) + n110 * fx; float nx11 = n011 * (1 - fx) + n111 * fx; float nxy0 = nx00 * (1 - fy) + nx10 * fy; float nxy1 = nx01 * (1 - fy) + nx11 * fy; float density = nxy0 * (1 - fz) + nxy1 * fz; // Treshold density = std::max(0.0f, density - settings.densityTreshold) * settings.densityIntensity; return density; } float CloudShader::fitToNoise(float point) const { float remainingValue = fmod(point, NOISE_SIZE); if (remainingValue < 0) { remainingValue += NOISE_SIZE; } return remainingValue; }