From 8a1ec659a238ae365fe464a2d1dc58ffcd3a9361 Mon Sep 17 00:00:00 2001 From: Maximilian Giller Date: Thu, 26 Jan 2023 05:04:30 +0100 Subject: [PATCH] Clouds now interacting with lights, but especially sunlight is still buggy --- fancy1.cpp | 11 ++-- shader/cloudshader.cpp | 108 ++++++++++++++++++++++++++++-------- shader/cloudshader.h | 22 +++++--- shader/refractionshader.cpp | 97 ++++++++++++++++---------------- 4 files changed, 153 insertions(+), 85 deletions(-) diff --git a/fancy1.cpp b/fancy1.cpp index f2c2eac..9600c1b 100644 --- a/fancy1.cpp +++ b/fancy1.cpp @@ -31,9 +31,11 @@ int main() scene.setBackgroundColor(Color(0.529f, 0.808f, 0.922f)); // Add lights - auto mainLight = std::make_shared(Vector3d(-1.0f, -0.5f, -1.0f), 10.0f); + auto mainLight = std::make_shared(Vector3d(-1.0f, -0.5f, -1.0f), 10.0f, Color(1, 0.79f, 0.62f)); scene.add(mainLight); - scene.add(std::make_shared(0.3f)); +// scene.add(std::make_shared(0.3f)); +// auto light = std::make_shared(Vector3d(25.0f, 10.0f, 25.0f), 100.0f); +// scene.add(light); // Add the bus // auto busShader = std::make_shared(mainLight); @@ -54,11 +56,8 @@ int main() // Add box for volume shader auto cloudSettings = CloudSettings(); cloudSettings.scale = 15.0f; - cloudSettings.densityIntensity = 10.0f; - cloudSettings.densityTreshold = 0.49f; - cloudSettings.densityAbsorption = 0.9f; auto cloudShader = std::make_shared(cloudSettings); - scene.add(std::make_shared(Vector3d(20.0f, 8.0f, 20.0f), Vector3d(50.0f, 5.0f, 50.0f), cloudShader)); + scene.add(std::make_shared(Vector3d(20.0f, 10.0f, 20.0f), Vector3d(50.0f, 10.0f, 50.0f), cloudShader)); // build the tree scene.buildTree(); diff --git a/shader/cloudshader.cpp b/shader/cloudshader.cpp index 11a8160..1a88fac 100644 --- a/shader/cloudshader.cpp +++ b/shader/cloudshader.cpp @@ -36,25 +36,28 @@ Color CloudShader::shade(const Scene &scene, const Ray &ray) const float stepLength = cloudLength / noiseSamples; // Step through cloud - float transmittance = 1.0f; - Color cloudColor = Color(1, 1, 1); + float transmittance = 1; + Color cloudColor = Color(0, 0, 0); for (int i = 0; i < noiseSamples; ++i) { // Get sample point - Vector3d samplePoint = hitPoint + i * stepLength * ray.direction; + Vector3d lengthDirection = i * stepLength * ray.direction; + Vector3d samplePoint = hitPoint + lengthDirection; // Get data at point float sampleDensity = getCloudDensity(samplePoint) * stepLength; - if (sampleDensity > REFR_EPS) { - cloudColor += lightMarch(scene, samplePoint, ray); + if (sampleDensity > 0) + { + cloudColor += lightMarch(scene, samplePoint, lengthDirection) * stepLength * sampleDensity; } - transmittance *= exp(-sampleDensity * stepLength * settings.densityAbsorption); - if (transmittance <= TRANSMITTANCE_BREAK) break; // No need to continue + transmittance *= exp(-sampleDensity * stepLength * settings.lightAbsorptionThroughCloud); + + if (transmittance < TRANSMITTANCE_BREAK) break; // Cloud is effectively opaque } - return background * transmittance + (1.0f - transmittance) * cloudColor; + return background * transmittance + cloudColor; } bool CloudShader::isTransparent() const @@ -76,40 +79,60 @@ float CloudShader::getCloudDensity(Vector3d point) const // Threshold // TODO: Smooth out! - density = std::max(0.0f, density - settings.densityTreshold) * settings.densityIntensity; + density = std::max(0.0f, density + settings.densityOffset) * settings.densityIntensity; return density; } -Color CloudShader::lightMarch(const Scene &scene, Vector3d position, const Ray &ray) const +Color CloudShader::lightMarch(const Scene &scene, Vector3d currentInCloudPosition, Vector3d lengthDistance) const { Color cloudColor; // For alle lights for (const auto &light: scene.lights()) { - auto illumination = light->illuminate(scene, position); + Ray ray = Ray(currentInCloudPosition - lengthDistance, normalized(lengthDistance)); + ray.length = length(lengthDistance); + auto illumination = light->illuminate(scene, ray); // Handle ambient lights - if (illumination.distance == 0.0f) { + if (illumination.distance == 0.0f) + { cloudColor += illumination.color; continue; } // Light ray Ray lightRay; - lightRay.origin = position; + lightRay.origin = currentInCloudPosition; lightRay.direction = illumination.direction; lightRay.length = 0; // Starting in cloud itself - Color transparency = this->transparency(scene, lightRay, illumination.distance); - cloudColor += transparency * illumination.color; + float density = this->rayDensity(lightRay, illumination.distance); + density *= settings.lightAbsorptionTowardsLight; + + // Proper light calculation + float transmittance = exp(-density) * (1 - exp(-density * 2)); + float scatter = scatterFactor(normalized(lengthDistance), illumination.direction); + + float factor = settings.darknessThreshold + (scatter * transmittance) * (1 - settings.darknessThreshold); // (transmittance * scatter) + cloudColor += factor * illumination.color; } return cloudColor; } 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); @@ -122,19 +145,20 @@ Color CloudShader::transparency(const Scene &scene, const Ray &ray, float maxLen cloudRay.primitive = nullptr; // Get out of cloud primitive first - if (ray.primitive != nullptr && !ray.primitive->intersect(cloudRay) || cloudRay.length == INFINITY || cloudRay.length <= 0) + if (ray.primitive != nullptr && !ray.primitive->intersect(cloudRay) || cloudRay.length == INFINITY || + cloudRay.length <= 0) { - // Something went wrong - return Color(1, 1, 1); + // Something went wrong => No density + return 0; } cloudLength = std::min(cloudRay.length, maxLength - ray.length); // Calculate step length int noiseSamples = settings.lightSamples; - float stepLength = cloudLength / noiseSamples; + float stepLength = cloudLength / (float) noiseSamples; // Step through cloud - float transmittance = 1.0f; + float density = 1.0f; for (int i = 0; i < noiseSamples; ++i) { // Get sample point @@ -143,11 +167,47 @@ Color CloudShader::transparency(const Scene &scene, const Ray &ray, float maxLen // Get data at point float sampleDensity = getCloudDensity(samplePoint) * stepLength; - transmittance *= exp(-sampleDensity * stepLength); - if (transmittance <= TRANSMITTANCE_BREAK) break; // No need to continue + density += sampleDensity * stepLength; } - transmittance = 1 - (1 - transmittance) * settings.shadowIntensity; + // 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; - return Color(1, 1, 1) * transmittance; + 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 + float g = 0.7f; + + // The angle between the visual and illumination rays + float cosTheta = dotProduct(visualRay, illuminationRay); + + // The Dual-Lob Henyey-Greenstein function + float blend = .5; + 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)); } diff --git a/shader/cloudshader.h b/shader/cloudshader.h index 82670c5..9b89ca7 100644 --- a/shader/cloudshader.h +++ b/shader/cloudshader.h @@ -8,18 +8,20 @@ #include "common/noise/worleynoise.h" int const NOISE_SIZE = 128; -int const TRANSMITTANCE_BREAK = 0.005f; // If transmittance goes below this limit, the cloud is considered opaque +float const TRANSMITTANCE_BREAK = 0.005f; // If transmittance goes below this limit, the cloud is considered opaque struct CloudSettings { int densitySamples = 100; - int lightSamples = 20; + int lightSamples = 100; float scale = 10; - float densityTreshold = 0.55f; - float densityIntensity = 2.5f; - float densityAbsorption = 2; - float darknessThreshold = 0.1f; + float densityOffset = -0.55f; + float densityIntensity = 7.0f; + float darknessThreshold = 0.07f; + float shadowLightAbsorption = 2; float shadowIntensity = 0.8f; + float lightAbsorptionTowardsLight = 0.94f; + float lightAbsorptionThroughCloud = 0.85f; }; class CloudShader : public Shader @@ -41,7 +43,13 @@ private: float getCloudDensity(Vector3d point) const; - Color lightMarch(const Scene &scene, Vector3d position, const Ray &ray) const; + Color lightMarch(const Scene &scene, Vector3d currentInCloudPosition, Vector3d lengthDistance) const; + + float rayDensity(const Ray &ray, float maxLength) const; + + float scatterFactor(Vector3d visualRay, Vector3d illuminationRay) const; + + float HenyeyGreenstein(float cosTheta, float g) const; }; diff --git a/shader/refractionshader.cpp b/shader/refractionshader.cpp index 8cfa49f..114f75c 100644 --- a/shader/refractionshader.cpp +++ b/shader/refractionshader.cpp @@ -9,56 +9,57 @@ RefractionShader::RefractionShader(float indexInside, float indexOutside, Color Color RefractionShader::shade(Scene const &scene, Ray const &ray) const { // Circumvent getting environment map color into the mix - if (ray.getRemainingBounces() > 0) + if (ray.getRemainingBounces() <= 0) { - // Get the normal of the primitive which was hit - Vector3d normalVector = ray.normal; - - // Calculate the index of refraction - float refractiveIndex = indexOutside / indexInside; - // What if we are already inside the object? - if (dotProduct(normalVector, ray.direction) > 0) - { - normalVector = -normalVector; - refractiveIndex = indexInside / indexOutside; - } - - // Using the notation from the lecture - float cosineTheta = dotProduct(normalVector, -ray.direction); - float cosinePhi = std::sqrt(1 + refractiveIndex * refractiveIndex * (cosineTheta * cosineTheta - 1)); - // Calculate t, the new ray direction - Vector3d t = refractiveIndex * ray.direction + (refractiveIndex * cosineTheta - cosinePhi) * normalVector; - - // Create the refraction ray - Ray refractionRay = ray; - // Reset the ray - refractionRay.length = INFINITY; - refractionRay.primitive = nullptr; - - // Check whether it is a refraction. - if (dotProduct(t, normalVector) <= 0.0) - { - refractionRay.origin = ray.origin + (ray.length + REFR_EPS) * ray.direction; - refractionRay.direction = normalized(t); - } else - { // Otherwise, it is a total reflection. - refractionRay.origin = ray.origin + (ray.length - REFR_EPS) * ray.direction; - // Next we get the reflection vector - Vector3d const reflectionVector = - ray.direction - 2.0f * dotProduct(normalVector, ray.direction) * normalVector; - - // Change the ray direction and origin - refractionRay.direction = normalized(reflectionVector); - } - - // Send out a new refracted ray into the scene - Color hitColor = scene.traceRay(refractionRay); - float lightRemaining = 1; - if (ray.primitive == refractionRay.primitive) lightRemaining = remainingLightIntensity(refractionRay.length); - - return hitColor * objectColor * lightRemaining; + return Color(0.0f, 0.0f, 0.0f); } - return Color(0.0f, 0.0f, 0.0f); + + // Get the normal of the primitive which was hit + Vector3d normalVector = ray.normal; + + // Calculate the index of refraction + float refractiveIndex = indexOutside / indexInside; + // What if we are already inside the object? + if (dotProduct(normalVector, ray.direction) > 0) + { + normalVector = -normalVector; + refractiveIndex = indexInside / indexOutside; + } + + // Using the notation from the lecture + float cosineTheta = dotProduct(normalVector, -ray.direction); + float cosinePhi = std::sqrt(1 + refractiveIndex * refractiveIndex * (cosineTheta * cosineTheta - 1)); + // Calculate t, the new ray direction + Vector3d t = refractiveIndex * ray.direction + (refractiveIndex * cosineTheta - cosinePhi) * normalVector; + + // Create the refraction ray + Ray refractionRay = ray; + // Reset the ray + refractionRay.length = INFINITY; + refractionRay.primitive = nullptr; + + // Check whether it is a refraction. + if (dotProduct(t, normalVector) <= 0.0) + { + refractionRay.origin = ray.origin + (ray.length + REFR_EPS) * ray.direction; + refractionRay.direction = normalized(t); + } else + { // Otherwise, it is a total reflection. + refractionRay.origin = ray.origin + (ray.length - REFR_EPS) * ray.direction; + // Next we get the reflection vector + Vector3d const reflectionVector = + ray.direction - 2.0f * dotProduct(normalVector, ray.direction) * normalVector; + + // Change the ray direction and origin + refractionRay.direction = normalized(reflectionVector); + } + + // Send out a new refracted ray into the scene + Color hitColor = scene.traceRay(refractionRay); + float lightRemaining = 1; + if (ray.primitive == refractionRay.primitive) lightRemaining = remainingLightIntensity(refractionRay.length); + + return hitColor * objectColor * lightRemaining; } float RefractionShader::remainingLightIntensity(float distanceThroughObject) const