cloudy-raytracer/shader/cloudshader.cpp

149 lines
5 KiB
C++
Raw Normal View History

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