I am a cloud god now
This commit is contained in:
parent
133f8444d2
commit
dd92e85861
3 changed files with 114 additions and 74 deletions
|
@ -32,7 +32,8 @@ int main()
|
||||||
// scene.setBackgroundColor(Color(1, 0.79f, 0.62f) * 0.8f);
|
// scene.setBackgroundColor(Color(1, 0.79f, 0.62f) * 0.8f);
|
||||||
|
|
||||||
// Add lights
|
// Add lights
|
||||||
auto mainLight = std::make_shared<SunLight>(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<SunLight>(Vector3d(0, -1.0f, 0), 1.0f, Color(1,1,1));//Color(1, 0.79f, 0.62f));
|
||||||
scene.add(mainLight);
|
scene.add(mainLight);
|
||||||
// scene.add(std::make_shared<AmbientLight>(0.3f));
|
// scene.add(std::make_shared<AmbientLight>(0.3f));
|
||||||
// auto light = std::make_shared<PointLight>(Vector3d(25.0f, 10.0f, 25.0f), 100.0f);
|
// auto light = std::make_shared<PointLight>(Vector3d(25.0f, 10.0f, 25.0f), 100.0f);
|
||||||
|
@ -56,9 +57,8 @@ int main()
|
||||||
|
|
||||||
// Add box for volume shader
|
// Add box for volume shader
|
||||||
auto cloudSettings = CloudSettings();
|
auto cloudSettings = CloudSettings();
|
||||||
cloudSettings.scale = 15.0f;
|
|
||||||
auto cloudShader = std::make_shared<CloudShader>(cloudSettings);
|
auto cloudShader = std::make_shared<CloudShader>(cloudSettings);
|
||||||
scene.add(std::make_shared<Box>(Vector3d(20.0f, 10.0f, 20.0f), Vector3d(50.0f, 10.0f, 50.0f), cloudShader));
|
scene.add(std::make_shared<Box>(Vector3d(20.0f, 15.0f, 20.0f), Vector3d(70.0f, 10.0f, 70.0f), cloudShader));
|
||||||
|
|
||||||
// build the tree
|
// build the tree
|
||||||
scene.buildTree();
|
scene.buildTree();
|
||||||
|
@ -73,7 +73,7 @@ int main()
|
||||||
// Render the scene
|
// Render the scene
|
||||||
SuperRenderer sr;
|
SuperRenderer sr;
|
||||||
sr.setSuperSamplingFactor(1);
|
sr.setSuperSamplingFactor(1);
|
||||||
sr.renderImage(scene, camera, 1024, 1024).save("result.png");
|
sr.renderImage(scene, camera, 256, 256).save("result.png");
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#include <cmath>
|
||||||
|
#include <iostream>
|
||||||
#include "cloudshader.h"
|
#include "cloudshader.h"
|
||||||
#include "common/noise/cloudnoise.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
|
if (cloudLength == 0.0f) return background; // No cloud or at edge
|
||||||
|
|
||||||
// Calculate step length
|
// Calculate step length
|
||||||
int noiseSamples = settings.densitySamples;
|
float stepLength = settings.densitySteps;
|
||||||
float stepLength = cloudLength / (float) noiseSamples;
|
int noiseSamples = std::floor(cloudLength / stepLength);
|
||||||
|
|
||||||
// Step through cloud
|
// Step through cloud
|
||||||
float accumulatedDensity = 0.0f;
|
float transmittance = 1.0f;
|
||||||
Color cloudColor = Color(0, 0, 0);
|
Color cloudColor = Color(0, 0, 0);
|
||||||
for (int i = 0; i < noiseSamples; ++i)
|
for (int i = 0; i < noiseSamples; ++i)
|
||||||
{
|
{
|
||||||
// Get sample point
|
// Get sample point
|
||||||
Vector3d lengthDirection = i * stepLength * ray.direction;
|
Vector3d lengthDirection = (float) i * stepLength * ray.direction;
|
||||||
Vector3d samplePoint = hitPoint + lengthDirection;
|
Vector3d samplePoint = hitPoint + lengthDirection;
|
||||||
|
|
||||||
// Get data at point
|
// 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;
|
return background * transmittance + cloudColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,17 +74,53 @@ CloudShader::CloudShader(const CloudSettings &settings) : settings(settings),
|
||||||
cloudNoise.invert = true;
|
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);
|
float density = cloudNoise.getNoise(point);
|
||||||
|
|
||||||
// Threshold
|
// Threshold
|
||||||
// TODO: Smooth out!
|
// TODO: Smooth out!
|
||||||
density = std::max(0.0f, density + settings.densityOffset) * settings.densityIntensity;
|
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,
|
Color CloudShader::lightMarch(const Scene &scene, Vector3d currentInCloudPosition, Vector3d lengthDistance,
|
||||||
|
@ -107,43 +143,33 @@ Color CloudShader::lightMarch(const Scene &scene, Vector3d currentInCloudPositio
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Light ray
|
// Light ray from object to light
|
||||||
Ray lightRay;
|
Ray lightRay;
|
||||||
lightRay.origin = currentInCloudPosition;
|
lightRay.origin = currentInCloudPosition;
|
||||||
lightRay.direction = illumination.direction;
|
lightRay.direction = -illumination.direction;
|
||||||
lightRay.primitive = cloudObject;
|
lightRay.primitive = cloudObject;
|
||||||
lightRay.length = 0; // Starting in cloud itself
|
lightRay.length = 0; // Starting in cloud itself
|
||||||
|
|
||||||
float density = this->rayDensity(lightRay, illumination.distance);
|
float density = this->rayDensity(lightRay, illumination.distance);
|
||||||
density *= settings.lightAbsorptionTowardsLight;
|
|
||||||
|
|
||||||
// Proper light calculation
|
// Proper light calculation
|
||||||
float transmittance = getDensityTransmittance(density);
|
float transmittance = calcBeer(density * settings.lightAbsorptionTowardsLight);
|
||||||
float scatter = scatterFactor(normalized(lengthDistance), illumination.direction);
|
|
||||||
|
|
||||||
float factor = transmittance;
|
transmittance *= phase(normalized(lengthDistance), lightRay.direction);
|
||||||
if (density > 0)
|
|
||||||
{
|
|
||||||
factor = settings.darknessThreshold +
|
|
||||||
(1.0f - settings.darknessThreshold) * factor * scatter;
|
|
||||||
}
|
|
||||||
|
|
||||||
cloudColor += factor * illumination.color;
|
cloudColor += transmittance * illumination.color;
|
||||||
}
|
}
|
||||||
|
|
||||||
return cloudColor;
|
float darknessFactor = settings.darknessThreshold +
|
||||||
}
|
(1.0f - settings.darknessThreshold);
|
||||||
|
return cloudColor * darknessFactor;
|
||||||
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
|
Color CloudShader::transparency(const Scene &scene, const Ray &ray, float maxLength) const
|
||||||
{
|
{
|
||||||
float density = rayDensity(ray, maxLength);
|
float density = rayDensity(ray, maxLength);
|
||||||
|
|
||||||
float transmittance = exp(-density * settings.shadowLightAbsorption);
|
float transmittance = calcBeer(density * settings.shadowLightAbsorption);
|
||||||
transmittance = 1 - (1 - transmittance) * settings.shadowIntensity;
|
transmittance = 1 - (1 - transmittance) * settings.shadowIntensity;
|
||||||
|
|
||||||
return Color(1, 1, 1) * transmittance;
|
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
|
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
|
// Determine length of cloud
|
||||||
Ray cloudRay = ray;
|
Ray cloudRay;
|
||||||
cloudRay.origin = startPoint;
|
cloudRay.origin = startPoint;
|
||||||
cloudRay.length = INFINITY;
|
cloudRay.length = INFINITY;
|
||||||
|
cloudRay.direction = ray.direction;
|
||||||
cloudRay.primitive = nullptr;
|
cloudRay.primitive = nullptr;
|
||||||
|
|
||||||
// Get out of cloud primitive first
|
// Get out of cloud primitive first
|
||||||
if (ray.primitive != nullptr && !ray.primitive->intersect(cloudRay) || cloudRay.length == INFINITY ||
|
if (ray.primitive != nullptr && !ray.primitive->intersect(cloudRay) || cloudRay.length == INFINITY ||
|
||||||
cloudRay.length <= 0)
|
cloudRay.length <= 0)
|
||||||
{
|
{
|
||||||
// Something went wrong => No density
|
// Assume ray started in cloud
|
||||||
return 0;
|
cloudRay.length = ray.length;
|
||||||
|
cloudRay.primitive = ray.primitive;
|
||||||
}
|
}
|
||||||
float cloudLength = std::min(cloudRay.length, maxLength - ray.length);
|
float cloudLength = std::min(cloudRay.length, maxLength - ray.length);
|
||||||
|
|
||||||
// Calculate step length
|
// Calculate step length
|
||||||
int noiseSamples = settings.lightSamples;
|
float stepLength = settings.densitySteps;
|
||||||
float stepLength = cloudLength / (float) noiseSamples;
|
int noiseSamples = std::floor(cloudLength / stepLength);
|
||||||
|
|
||||||
// Step through cloud
|
// Step through cloud
|
||||||
float density = 0.0f;
|
float density = 0.0f;
|
||||||
for (int i = 0; i < noiseSamples; ++i)
|
for (int i = 0; i < noiseSamples; ++i)
|
||||||
{
|
{
|
||||||
// Get sample point
|
// Get sample point
|
||||||
Vector3d samplePoint = startPoint + i * stepLength * ray.direction;
|
Vector3d samplePoint = startPoint + (float) i * stepLength * ray.direction;
|
||||||
|
|
||||||
// Get data at point
|
// 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
|
// 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;
|
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
|
// The angle between the visual and illumination rays
|
||||||
float cosTheta = dotProduct(visualRay, illuminationRay);
|
float cosTheta = dotProduct(visualRay, illuminationRay);
|
||||||
|
|
||||||
// The Dual-Lob Henyey-Greenstein function
|
// The Dual-Lob Henyey-Greenstein function
|
||||||
float blend = .5;
|
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]
|
// 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;
|
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));
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,22 +7,25 @@
|
||||||
#include "primitive/primitive.h"
|
#include "primitive/primitive.h"
|
||||||
#include "common/noise/worleynoise.h"
|
#include "common/noise/worleynoise.h"
|
||||||
|
|
||||||
int const NOISE_SIZE = 128;
|
int const NOISE_SIZE = 64;
|
||||||
float const TRANSMITTANCE_BREAK = 0.005f; // If transmittance goes below this limit, the cloud is considered opaque
|
float const TRANSMITTANCE_BREAK = 0.0001f; // If transmittance goes below this limit, the cloud is considered opaque
|
||||||
|
|
||||||
struct CloudSettings
|
struct CloudSettings
|
||||||
{
|
{
|
||||||
int densitySamples = 100;
|
float densitySteps = .2f; // .2f
|
||||||
int lightSamples = 100;
|
float scale = 30; // 30
|
||||||
float scale = 10;
|
float densityOffset = -.55f; // -.55f
|
||||||
float densityOffset = -0.57f;
|
float densityIntensity = 5.0f; // 5.0f
|
||||||
float densityIntensity = 7.0f; // 7.0f
|
float darknessThreshold = .1f; // .1f
|
||||||
float darknessThreshold = 0.1f; // 0.07f
|
float shadowIntensity = .6f; // .6f
|
||||||
float shadowIntensity = 0.6f;
|
float shadowLightAbsorption = 1; // 1
|
||||||
float shadowLightAbsorption = 1;
|
float lightAbsorptionTowardsLight = 0.3f; // .3f
|
||||||
float lightAbsorptionTowardsLight = 1.0f;
|
float lightAbsorptionThroughCloud = .8f; // .8f // How dark should the background be where the cloud is? Higher values mean darker background
|
||||||
float lightAbsorptionThroughCloud = 0.5f;
|
float phaseA = .5f; // .5f
|
||||||
float scatterWeight = 0.5f;
|
float phaseB = .3f; // .3f
|
||||||
|
float phaseOffset = .8f; // .8f
|
||||||
|
float phaseIntensity = 1.0f; // 1.0f
|
||||||
|
float edgeFadeOffDistance = .5f; // .5f
|
||||||
};
|
};
|
||||||
|
|
||||||
class CloudShader : public Shader
|
class CloudShader : public Shader
|
||||||
|
@ -32,6 +35,7 @@ public:
|
||||||
|
|
||||||
// Shader functions
|
// Shader functions
|
||||||
Color shade(Scene const &scene, Ray const &ray) const;
|
Color shade(Scene const &scene, Ray const &ray) const;
|
||||||
|
|
||||||
Color transparency(const Scene &scene, const Ray &ray, float maxLength) const override;
|
Color transparency(const Scene &scene, const Ray &ray, float maxLength) const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -42,17 +46,20 @@ private:
|
||||||
|
|
||||||
Noise cloudNoise;
|
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 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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue