cloudy-raytracer/shader/cloudshader.cpp

141 lines
4.6 KiB
C++

#include "cloudshader.h"
#include "common/noise/cloudnoise.h"
#include "common/noise/perlinnoise.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 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
// Calculate step length
int noiseSamples = settings.densitySamples;
float stepLength = cloudLength / noiseSamples;
// Step through cloud
float transmittance = 1.0f;
Color cloudColor = Color(1, 1, 1);
for (int i = 0; i < noiseSamples; ++i)
{
// Get sample point
Vector3d samplePoint = hitPoint + i * stepLength * ray.direction;
// Get data at point
float sampleDensity = getCloudDensity(samplePoint) * stepLength;
// cloudColor += lightMarch(scene, samplePoint, ray);
transmittance *= exp(-sampleDensity * stepLength * settings.densityAbsorption);
if (transmittance <= TRANSMITTANCE_BREAK) break; // No need to continue
}
// 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 background * transmittance + (1.0f - transmittance) * cloudColor;
}
bool CloudShader::isTransparent() const
{
return true;
}
CloudShader::CloudShader(const CloudSettings &settings) : settings(settings),
cloudNoise(CloudNoise(NOISE_SIZE))
{
cloudNoise.invert = true;
}
float CloudShader::getCloudDensity(Vector3d point) const
{
point /= settings.scale;
float density = cloudNoise.getNoise(point);
// Treshold
density = std::max(0.0f, density - settings.densityTreshold) * settings.densityIntensity;
return density;
}
Color CloudShader::lightMarch(const Scene &scene, Vector3d position, const Ray &ray) const
{
Color cloudColor;
// For alle lights
for (const auto &light: scene.lights())
{
auto illumination = light->illuminate(scene, position);
// Get light direction
Vector3d lightDirection = normalized(illumination.direction); // Points from surface to light
// Get length of remaining cloud in light direction
float cloudLength = 0.0f;
Ray cloudRay = ray;
cloudRay.origin = position;
cloudRay.direction = lightDirection;
cloudRay.length = INFINITY;
cloudRay.primitive = nullptr;
// Find other end of cloud
if (!ray.primitive->intersect(cloudRay) || cloudRay.length == INFINITY)
{
// No cloud or at edge
continue;
}
cloudLength = cloudRay.length;
// Calculate step length
int lightSamples = settings.lightSamples;
float stepLength = cloudLength / lightSamples;
// Step through cloud
float transmittance = 0.0f;
for (int i = 0; i < lightSamples; ++i)
{
Vector3d samplePoint = position + i * stepLength * lightDirection;
float density = getCloudDensity(samplePoint) * stepLength;
transmittance *= exp(-density * settings.densityAbsorption);
if (transmittance <= TRANSMITTANCE_BREAK) break; // No need to continue
}
// float lightAbsorption = dotProduct(lightDirection, ray.direction); // Approaches 1 when light is parallel to ray
cloudColor += transmittance * illumination.color;
}
return cloudColor;
}