From dd92e858616b44b7dbf2225a2ac93ed223bff0f5 Mon Sep 17 00:00:00 2001 From: Maximilian Giller Date: Sun, 29 Jan 2023 04:25:14 +0100 Subject: [PATCH] I am a cloud god now --- fancy1.cpp | 8 +-- shader/cloudshader.cpp | 137 +++++++++++++++++++++++++---------------- shader/cloudshader.h | 43 +++++++------ 3 files changed, 114 insertions(+), 74 deletions(-) diff --git a/fancy1.cpp b/fancy1.cpp index 86b4830..72c753e 100644 --- a/fancy1.cpp +++ b/fancy1.cpp @@ -32,7 +32,8 @@ int main() // scene.setBackgroundColor(Color(1, 0.79f, 0.62f) * 0.8f); // Add lights - auto mainLight = std::make_shared(Vector3d(-1.0f, -0.5f, -1.0f), 2.0f, Color(1,1,1));//Color(1, 0.79f, 0.62f)); + // Alternate direction Vector3d(1.0f, -0.5f, 1.0f) + auto mainLight = std::make_shared(Vector3d(0, -1.0f, 0), 1.0f, Color(1,1,1));//Color(1, 0.79f, 0.62f)); scene.add(mainLight); // scene.add(std::make_shared(0.3f)); // auto light = std::make_shared(Vector3d(25.0f, 10.0f, 25.0f), 100.0f); @@ -56,9 +57,8 @@ int main() // Add box for volume shader auto cloudSettings = CloudSettings(); - cloudSettings.scale = 15.0f; auto cloudShader = std::make_shared(cloudSettings); - scene.add(std::make_shared(Vector3d(20.0f, 10.0f, 20.0f), Vector3d(50.0f, 10.0f, 50.0f), cloudShader)); + scene.add(std::make_shared(Vector3d(20.0f, 15.0f, 20.0f), Vector3d(70.0f, 10.0f, 70.0f), cloudShader)); // build the tree scene.buildTree(); @@ -73,7 +73,7 @@ int main() // Render the scene SuperRenderer sr; sr.setSuperSamplingFactor(1); - sr.renderImage(scene, camera, 1024, 1024).save("result.png"); + sr.renderImage(scene, camera, 256, 256).save("result.png"); return 0; } diff --git a/shader/cloudshader.cpp b/shader/cloudshader.cpp index 5c9ae60..8491d58 100644 --- a/shader/cloudshader.cpp +++ b/shader/cloudshader.cpp @@ -1,3 +1,5 @@ +#include +#include #include "cloudshader.h" #include "common/noise/cloudnoise.h" @@ -32,34 +34,32 @@ Color CloudShader::shade(const Scene &scene, const Ray &ray) const if (cloudLength == 0.0f) return background; // No cloud or at edge // Calculate step length - int noiseSamples = settings.densitySamples; - float stepLength = cloudLength / (float) noiseSamples; + float stepLength = settings.densitySteps; + int noiseSamples = std::floor(cloudLength / stepLength); // Step through cloud - float accumulatedDensity = 0.0f; + float transmittance = 1.0f; Color cloudColor = Color(0, 0, 0); for (int i = 0; i < noiseSamples; ++i) { // Get sample point - Vector3d lengthDirection = i * stepLength * ray.direction; + Vector3d lengthDirection = (float) i * stepLength * ray.direction; Vector3d samplePoint = hitPoint + lengthDirection; // Get data at point - float sampleDensity = getCloudDensity(samplePoint) * stepLength; + float sampleDensity = getCloudDensity(samplePoint, ray.primitive) * stepLength; - if (sampleDensity > 0) + if (sampleDensity <= 0) { - cloudColor += lightMarch(scene, samplePoint, lengthDirection, ray.primitive) * sampleDensity; + continue; } - accumulatedDensity += sampleDensity; + cloudColor += lightMarch(scene, samplePoint, lengthDirection, ray.primitive) * sampleDensity * transmittance; + transmittance *= calcBeer(sampleDensity * settings.lightAbsorptionThroughCloud); + + if (transmittance < TRANSMITTANCE_BREAK) break; } - if (accumulatedDensity > 1) - cloudColor /= accumulatedDensity; - - float transmittance = exp(-accumulatedDensity * settings.lightAbsorptionThroughCloud); - return background * transmittance + cloudColor; } @@ -74,17 +74,53 @@ CloudShader::CloudShader(const CloudSettings &settings) : settings(settings), cloudNoise.invert = true; } -float CloudShader::getCloudDensity(Vector3d point) const +float CloudShader::getCloudDensity(Vector3d point, Primitive const *primitive) const { - point /= settings.scale; + //! Requires the unscaled point + float edgeDensity = getEdgeDensity(point, primitive); + point /= settings.scale; float density = cloudNoise.getNoise(point); // Threshold // TODO: Smooth out! density = std::max(0.0f, density + settings.densityOffset) * settings.densityIntensity; - return density; + return density * edgeDensity; +} + +float CloudShader::getEdgeDensity(const Vector3d &point, const Primitive *primitive) const +{ + if (primitive == nullptr) + { + return 1; + } + + Vector3d size = Vector3d(0, 0, 0); + size.x = primitive->maximumBounds(0) - primitive->minimumBounds(0); + size.y = primitive->maximumBounds(1) - primitive->minimumBounds(1); + size.z = primitive->maximumBounds(2) - primitive->minimumBounds(2); + + Vector3d center = Vector3d(0, 0, 0); + center.x = primitive->maximumBounds(0) + primitive->minimumBounds(0); + center.y = primitive->maximumBounds(1) + primitive->minimumBounds(1); + center.z = primitive->maximumBounds(2) + primitive->minimumBounds(2); + center /= 2; + + Vector3d distance = point - center; + distance.x = std::abs(distance.x); + distance.y = std::abs(distance.y); + distance.z = std::abs(distance.z); + + Vector3d distanceFromEdge = size / 2 - distance; + distanceFromEdge.x = std::max(0.0f, distanceFromEdge.x); + distanceFromEdge.y = std::max(0.0f, distanceFromEdge.y); + distanceFromEdge.z = std::max(0.0f, distanceFromEdge.z); + + float fallOff = std::min(distanceFromEdge.x, std::min(distanceFromEdge.y, distanceFromEdge.z)) / + settings.edgeFadeOffDistance; + + return std::min(1.0f, fallOff); } Color CloudShader::lightMarch(const Scene &scene, Vector3d currentInCloudPosition, Vector3d lengthDistance, @@ -107,43 +143,33 @@ Color CloudShader::lightMarch(const Scene &scene, Vector3d currentInCloudPositio continue; } - // Light ray + // Light ray from object to light Ray lightRay; lightRay.origin = currentInCloudPosition; - lightRay.direction = illumination.direction; + lightRay.direction = -illumination.direction; lightRay.primitive = cloudObject; lightRay.length = 0; // Starting in cloud itself float density = this->rayDensity(lightRay, illumination.distance); - density *= settings.lightAbsorptionTowardsLight; // Proper light calculation - float transmittance = getDensityTransmittance(density); - float scatter = scatterFactor(normalized(lengthDistance), illumination.direction); + float transmittance = calcBeer(density * settings.lightAbsorptionTowardsLight); - float factor = transmittance; - if (density > 0) - { - factor = settings.darknessThreshold + - (1.0f - settings.darknessThreshold) * factor * scatter; - } + transmittance *= phase(normalized(lengthDistance), lightRay.direction); - cloudColor += factor * illumination.color; + cloudColor += transmittance * illumination.color; } - return cloudColor; -} - -float CloudShader::getDensityTransmittance(float density) const -{ - return exp(-density) * (1 - exp(-density * 2)) / 0.4f; + float darknessFactor = settings.darknessThreshold + + (1.0f - settings.darknessThreshold); + return cloudColor * darknessFactor; } Color CloudShader::transparency(const Scene &scene, const Ray &ray, float maxLength) const { float density = rayDensity(ray, maxLength); - float transmittance = exp(-density * settings.shadowLightAbsorption); + float transmittance = calcBeer(density * settings.shadowLightAbsorption); transmittance = 1 - (1 - transmittance) * settings.shadowIntensity; return Color(1, 1, 1) * transmittance; @@ -151,36 +177,38 @@ Color CloudShader::transparency(const Scene &scene, const Ray &ray, float maxLen float CloudShader::rayDensity(const Ray &ray, float maxLength) const { - Vector3d startPoint = ray.origin + ray.direction * (ray.length + 0.0001f); + Vector3d startPoint = ray.origin + ray.direction * (ray.length + REFR_EPS); // Determine length of cloud - Ray cloudRay = ray; + Ray cloudRay; cloudRay.origin = startPoint; cloudRay.length = INFINITY; + cloudRay.direction = ray.direction; cloudRay.primitive = nullptr; // 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; + // Assume ray started in cloud + cloudRay.length = ray.length; + cloudRay.primitive = ray.primitive; } float cloudLength = std::min(cloudRay.length, maxLength - ray.length); // Calculate step length - int noiseSamples = settings.lightSamples; - float stepLength = cloudLength / (float) noiseSamples; + float stepLength = settings.densitySteps; + int noiseSamples = std::floor(cloudLength / stepLength); // Step through cloud float density = 0.0f; for (int i = 0; i < noiseSamples; ++i) { // Get sample point - Vector3d samplePoint = startPoint + i * stepLength * ray.direction; + Vector3d samplePoint = startPoint + (float) i * stepLength * ray.direction; // Get data at point - density += getCloudDensity(samplePoint) * stepLength; + density += getCloudDensity(samplePoint, ray.primitive) * stepLength; } // If there is length left, check if it is in the cloud recursively @@ -201,26 +229,31 @@ float CloudShader::rayDensity(const Ray &ray, float maxLength) const return density; } -float CloudShader::scatterFactor(Vector3d visualRay, Vector3d illuminationRay) const +float CloudShader::phase(Vector3d visualRay, Vector3d illuminationRay) const { - // The asymmetry parameter - 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; - float scatter = HenyeyGreenstein(cosTheta, g) * (1 - blend) + HenyeyGreenstein(cosTheta, -g) * blend; + float phaseBlend = calcDualHenyeyGreenstein(cosTheta, settings.phaseA) * (1 - blend) + + calcDualHenyeyGreenstein(cosTheta, -settings.phaseB) * blend; // Clamp the result to the range [0, 1] - scatter = std::max(std::min(scatter, 1.0f), 0.0f); + phaseBlend = std::max(std::min(phaseBlend, 1.0f), 0.0f); - return scatter; + return settings.phaseOffset + phaseBlend * settings.phaseIntensity; } -float CloudShader::HenyeyGreenstein(float cosTheta, float g) const +float CloudShader::calcBeer(float d) +{ + float beer = std::exp(-d); + return beer; +} + + +float CloudShader::calcDualHenyeyGreenstein(float cosTheta, float g) { float g2 = g * g; - return (1 - g2) / (4 * 3.1415f * pow(1 + g2 - 2 * g * (cosTheta), 1.5f)); + return (1 - g2) / (4 * 3.1415f * std::pow(1 + g2 - 2 * g * (cosTheta), 1.5f)); } diff --git a/shader/cloudshader.h b/shader/cloudshader.h index a7c1cce..7830a6e 100644 --- a/shader/cloudshader.h +++ b/shader/cloudshader.h @@ -7,22 +7,25 @@ #include "primitive/primitive.h" #include "common/noise/worleynoise.h" -int const NOISE_SIZE = 128; -float const TRANSMITTANCE_BREAK = 0.005f; // If transmittance goes below this limit, the cloud is considered opaque +int const NOISE_SIZE = 64; +float const TRANSMITTANCE_BREAK = 0.0001f; // If transmittance goes below this limit, the cloud is considered opaque struct CloudSettings { - int densitySamples = 100; - int lightSamples = 100; - float scale = 10; - float densityOffset = -0.57f; - float densityIntensity = 7.0f; // 7.0f - float darknessThreshold = 0.1f; // 0.07f - float shadowIntensity = 0.6f; - float shadowLightAbsorption = 1; - float lightAbsorptionTowardsLight = 1.0f; - float lightAbsorptionThroughCloud = 0.5f; - float scatterWeight = 0.5f; + float densitySteps = .2f; // .2f + float scale = 30; // 30 + float densityOffset = -.55f; // -.55f + float densityIntensity = 5.0f; // 5.0f + float darknessThreshold = .1f; // .1f + float shadowIntensity = .6f; // .6f + float shadowLightAbsorption = 1; // 1 + float lightAbsorptionTowardsLight = 0.3f; // .3f + float lightAbsorptionThroughCloud = .8f; // .8f // How dark should the background be where the cloud is? Higher values mean darker background + float phaseA = .5f; // .5f + float phaseB = .3f; // .3f + float phaseOffset = .8f; // .8f + float phaseIntensity = 1.0f; // 1.0f + float edgeFadeOffDistance = .5f; // .5f }; class CloudShader : public Shader @@ -32,6 +35,7 @@ public: // Shader functions Color shade(Scene const &scene, Ray const &ray) const; + Color transparency(const Scene &scene, const Ray &ray, float maxLength) const override; private: @@ -42,17 +46,20 @@ private: Noise cloudNoise; - float getCloudDensity(Vector3d point) const; + float getCloudDensity(Vector3d point, const Primitive *primitive = nullptr) const; - Color lightMarch(const Scene &scene, Vector3d currentInCloudPosition, Vector3d lengthDistance, const Primitive *cloudObject) const; + Color lightMarch(const Scene &scene, Vector3d currentInCloudPosition, Vector3d lengthDistance, + const Primitive *cloudObject) const; float rayDensity(const Ray &ray, float maxLength) const; - float scatterFactor(Vector3d visualRay, Vector3d illuminationRay) const; + float phase(Vector3d visualRay, Vector3d illuminationRay) const; - float HenyeyGreenstein(float cosTheta, float g) const; + static float calcDualHenyeyGreenstein(float cosTheta, float g) ; - float getDensityTransmittance(float density) const; + static float calcBeer(float d) ; + + float getEdgeDensity(const Vector3d &point, const Primitive *primitive) const; };