cloudy-raytracer/shader/cloudshader.cpp

227 lines
7 KiB
C++
Raw Normal View History

#include "cloudshader.h"
2023-01-24 06:44:20 +01:00
#include "common/noise/cloudnoise.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
2023-01-24 11:18:03 +01:00
// Calculate step length
int noiseSamples = settings.densitySamples;
2023-01-27 05:22:54 +01:00
float stepLength = cloudLength / (float) noiseSamples;
2023-01-24 11:18:03 +01:00
// Step through cloud
2023-01-27 05:22:54 +01:00
float accumulatedDensity = 0.0f;
Color cloudColor = Color(0, 0, 0);
2023-01-24 11:18:03 +01:00
for (int i = 0; i < noiseSamples; ++i)
{
2023-01-24 11:18:03 +01:00
// Get sample point
Vector3d lengthDirection = i * stepLength * ray.direction;
Vector3d samplePoint = hitPoint + lengthDirection;
2023-01-24 11:18:03 +01:00
// Get data at point
float sampleDensity = getCloudDensity(samplePoint) * stepLength;
if (sampleDensity > 0)
{
2023-01-27 05:22:54 +01:00
cloudColor += lightMarch(scene, samplePoint, lengthDirection, ray.primitive) * sampleDensity;
}
2023-01-24 11:18:03 +01:00
2023-01-27 05:22:54 +01:00
accumulatedDensity += sampleDensity;
2023-01-24 11:18:03 +01:00
}
if (accumulatedDensity > 1)
cloudColor /= accumulatedDensity;
2023-01-27 05:22:54 +01:00
float transmittance = exp(-accumulatedDensity * settings.lightAbsorptionThroughCloud);
return background * transmittance + cloudColor;
}
bool CloudShader::isTransparent() const
{
return true;
}
CloudShader::CloudShader(const CloudSettings &settings) : settings(settings),
cloudNoise(CloudNoise(NOISE_SIZE))
{
2023-01-24 06:26:24 +01:00
cloudNoise.invert = true;
}
float CloudShader::getCloudDensity(Vector3d point) const
{
point /= settings.scale;
2023-01-24 06:26:24 +01:00
float density = cloudNoise.getNoise(point);
// Threshold
// TODO: Smooth out!
density = std::max(0.0f, density + settings.densityOffset) * settings.densityIntensity;
return density;
}
2023-01-24 19:38:12 +01:00
2023-01-27 05:22:54 +01:00
Color CloudShader::lightMarch(const Scene &scene, Vector3d currentInCloudPosition, Vector3d lengthDistance,
const Primitive *cloudObject) const
2023-01-24 19:38:12 +01:00
{
2023-01-27 05:22:54 +01:00
Color cloudColor = Color(0, 0, 0);
2023-01-24 19:38:12 +01:00
// For alle lights
for (const auto &light: scene.lights())
{
Ray ray = Ray(currentInCloudPosition - lengthDistance, normalized(lengthDistance));
ray.length = length(lengthDistance);
2023-01-27 05:22:54 +01:00
ray.primitive = cloudObject;
auto illumination = light->illuminate(scene, ray);
2023-01-24 19:38:12 +01:00
// Handle ambient lights
if (illumination.distance == 0.0f)
{
cloudColor += illumination.color;
continue;
}
2023-01-24 19:38:12 +01:00
// Light ray
Ray lightRay;
lightRay.origin = currentInCloudPosition;
lightRay.direction = illumination.direction;
2023-01-27 05:22:54 +01:00
lightRay.primitive = cloudObject;
lightRay.length = 0; // Starting in cloud itself
2023-01-24 19:38:12 +01:00
float density = this->rayDensity(lightRay, illumination.distance);
density *= settings.lightAbsorptionTowardsLight;
// Proper light calculation
2023-01-27 05:22:54 +01:00
float transmittance = getDensityTransmittance(density);
float scatter = scatterFactor(normalized(lengthDistance), illumination.direction);
2023-01-27 05:36:27 +01:00
float factor = transmittance;
if (density > 0)
{
factor = settings.darknessThreshold +
(1.0f - settings.darknessThreshold) * factor * scatter;
}
cloudColor += factor * illumination.color;
}
2023-01-24 19:38:12 +01:00
return cloudColor;
}
2023-01-24 19:38:12 +01:00
2023-01-27 05:22:54 +01:00
float CloudShader::getDensityTransmittance(float density) const
{
return exp(-density) * (1 - exp(-density * 2)) / 0.4f;
}
Color CloudShader::transparency(const Scene &scene, const Ray &ray, float maxLength) const
{
float density = rayDensity(ray, maxLength);
float transmittance = exp(-density * settings.shadowLightAbsorption);
transmittance = 1 - (1 - transmittance) * settings.shadowIntensity;
return Color(1, 1, 1) * transmittance;
}
float CloudShader::rayDensity(const Ray &ray, float maxLength) const
{
Vector3d startPoint = ray.origin + ray.direction * (ray.length + 0.0001f);
2023-01-24 19:38:12 +01:00
// Determine length of cloud
Ray cloudRay = ray;
cloudRay.origin = startPoint;
cloudRay.length = INFINITY;
cloudRay.primitive = nullptr;
2023-01-24 19:38:12 +01:00
// Get out of cloud primitive first
if (ray.primitive != nullptr && !ray.primitive->intersect(cloudRay) || cloudRay.length == INFINITY ||
cloudRay.length <= 0)
{
// Something went wrong => No density
return 0;
}
2023-01-26 20:30:59 +01:00
float cloudLength = std::min(cloudRay.length, maxLength - ray.length);
// Calculate step length
int noiseSamples = settings.lightSamples;
float stepLength = cloudLength / (float) noiseSamples;
// Step through cloud
2023-01-26 20:30:59 +01:00
float density = 0.0f;
for (int i = 0; i < noiseSamples; ++i)
{
// Get sample point
Vector3d samplePoint = startPoint + i * stepLength * ray.direction;
2023-01-24 19:38:12 +01:00
// Get data at point
2023-01-27 05:22:54 +01:00
density += getCloudDensity(samplePoint) * stepLength;
2023-01-24 19:38:12 +01:00
}
// If there is length left, check if it is in the cloud recursively
if (cloudLength < maxLength - ray.length)
{
Vector3d endPoint = startPoint + (cloudLength + REFR_EPS) * ray.direction;
Ray recursiveRay = cloudRay;
recursiveRay.origin = endPoint;
recursiveRay.length = 0;
recursiveRay.primitive = nullptr;
if (ray.primitive != nullptr && ray.primitive->intersect(recursiveRay) && recursiveRay.length > 0)
{
density += rayDensity(recursiveRay, maxLength - (cloudLength + REFR_EPS));
}
}
return density;
}
float CloudShader::scatterFactor(Vector3d visualRay, Vector3d illuminationRay) const
{
// The asymmetry parameter
2023-01-27 05:22:54 +01:00
float g = settings.scatterWeight;
// The angle between the visual and illumination rays
float cosTheta = dotProduct(visualRay, illuminationRay);
// The Dual-Lob Henyey-Greenstein function
float blend = .5;
2023-01-27 05:22:54 +01:00
float scatter = HenyeyGreenstein(cosTheta, g) * (1 - blend) + HenyeyGreenstein(cosTheta, -g) * blend;
// Clamp the result to the range [0, 1]
scatter = std::max(std::min(scatter, 1.0f), 0.0f);
return scatter;
}
float CloudShader::HenyeyGreenstein(float cosTheta, float g) const
{
float g2 = g * g;
return (1 - g2) / (4 * 3.1415f * pow(1 + g2 - 2 * g * (cosTheta), 1.5f));
2023-01-24 19:38:12 +01:00
}