Clouds now interacting with lights, but especially sunlight is still buggy
This commit is contained in:
parent
9dd5878f31
commit
8a1ec659a2
4 changed files with 153 additions and 85 deletions
11
fancy1.cpp
11
fancy1.cpp
|
@ -31,9 +31,11 @@ int main()
|
||||||
scene.setBackgroundColor(Color(0.529f, 0.808f, 0.922f));
|
scene.setBackgroundColor(Color(0.529f, 0.808f, 0.922f));
|
||||||
|
|
||||||
// Add lights
|
// Add lights
|
||||||
auto mainLight = std::make_shared<SunLight>(Vector3d(-1.0f, -0.5f, -1.0f), 10.0f);
|
auto mainLight = std::make_shared<SunLight>(Vector3d(-1.0f, -0.5f, -1.0f), 10.0f, 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);
|
||||||
|
// scene.add(light);
|
||||||
|
|
||||||
// Add the bus
|
// Add the bus
|
||||||
// auto busShader = std::make_shared<ToneShader>(mainLight);
|
// auto busShader = std::make_shared<ToneShader>(mainLight);
|
||||||
|
@ -54,11 +56,8 @@ int main()
|
||||||
// Add box for volume shader
|
// Add box for volume shader
|
||||||
auto cloudSettings = CloudSettings();
|
auto cloudSettings = CloudSettings();
|
||||||
cloudSettings.scale = 15.0f;
|
cloudSettings.scale = 15.0f;
|
||||||
cloudSettings.densityIntensity = 10.0f;
|
|
||||||
cloudSettings.densityTreshold = 0.49f;
|
|
||||||
cloudSettings.densityAbsorption = 0.9f;
|
|
||||||
auto cloudShader = std::make_shared<CloudShader>(cloudSettings);
|
auto cloudShader = std::make_shared<CloudShader>(cloudSettings);
|
||||||
scene.add(std::make_shared<Box>(Vector3d(20.0f, 8.0f, 20.0f), Vector3d(50.0f, 5.0f, 50.0f), cloudShader));
|
scene.add(std::make_shared<Box>(Vector3d(20.0f, 10.0f, 20.0f), Vector3d(50.0f, 10.0f, 50.0f), cloudShader));
|
||||||
|
|
||||||
// build the tree
|
// build the tree
|
||||||
scene.buildTree();
|
scene.buildTree();
|
||||||
|
|
|
@ -36,25 +36,28 @@ Color CloudShader::shade(const Scene &scene, const Ray &ray) const
|
||||||
float stepLength = cloudLength / noiseSamples;
|
float stepLength = cloudLength / noiseSamples;
|
||||||
|
|
||||||
// Step through cloud
|
// Step through cloud
|
||||||
float transmittance = 1.0f;
|
float transmittance = 1;
|
||||||
Color cloudColor = Color(1, 1, 1);
|
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 samplePoint = hitPoint + i * stepLength * ray.direction;
|
Vector3d lengthDirection = i * stepLength * ray.direction;
|
||||||
|
Vector3d samplePoint = hitPoint + lengthDirection;
|
||||||
|
|
||||||
// Get data at point
|
// Get data at point
|
||||||
float sampleDensity = getCloudDensity(samplePoint) * stepLength;
|
float sampleDensity = getCloudDensity(samplePoint) * stepLength;
|
||||||
|
|
||||||
if (sampleDensity > REFR_EPS) {
|
if (sampleDensity > 0)
|
||||||
cloudColor += lightMarch(scene, samplePoint, ray);
|
{
|
||||||
|
cloudColor += lightMarch(scene, samplePoint, lengthDirection) * stepLength * sampleDensity;
|
||||||
}
|
}
|
||||||
|
|
||||||
transmittance *= exp(-sampleDensity * stepLength * settings.densityAbsorption);
|
transmittance *= exp(-sampleDensity * stepLength * settings.lightAbsorptionThroughCloud);
|
||||||
if (transmittance <= TRANSMITTANCE_BREAK) break; // No need to continue
|
|
||||||
|
if (transmittance < TRANSMITTANCE_BREAK) break; // Cloud is effectively opaque
|
||||||
}
|
}
|
||||||
|
|
||||||
return background * transmittance + (1.0f - transmittance) * cloudColor;
|
return background * transmittance + cloudColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CloudShader::isTransparent() const
|
bool CloudShader::isTransparent() const
|
||||||
|
@ -76,40 +79,60 @@ float CloudShader::getCloudDensity(Vector3d point) const
|
||||||
|
|
||||||
// Threshold
|
// Threshold
|
||||||
// TODO: Smooth out!
|
// 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;
|
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;
|
Color cloudColor;
|
||||||
|
|
||||||
// For alle lights
|
// For alle lights
|
||||||
for (const auto &light: scene.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
|
// Handle ambient lights
|
||||||
if (illumination.distance == 0.0f) {
|
if (illumination.distance == 0.0f)
|
||||||
|
{
|
||||||
cloudColor += illumination.color;
|
cloudColor += illumination.color;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Light ray
|
// Light ray
|
||||||
Ray lightRay;
|
Ray lightRay;
|
||||||
lightRay.origin = position;
|
lightRay.origin = currentInCloudPosition;
|
||||||
lightRay.direction = illumination.direction;
|
lightRay.direction = illumination.direction;
|
||||||
lightRay.length = 0; // Starting in cloud itself
|
lightRay.length = 0; // Starting in cloud itself
|
||||||
|
|
||||||
Color transparency = this->transparency(scene, lightRay, illumination.distance);
|
float density = this->rayDensity(lightRay, illumination.distance);
|
||||||
cloudColor += transparency * illumination.color;
|
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;
|
return cloudColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
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 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);
|
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;
|
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 || cloudRay.length <= 0)
|
if (ray.primitive != nullptr && !ray.primitive->intersect(cloudRay) || cloudRay.length == INFINITY ||
|
||||||
|
cloudRay.length <= 0)
|
||||||
{
|
{
|
||||||
// Something went wrong
|
// Something went wrong => No density
|
||||||
return Color(1, 1, 1);
|
return 0;
|
||||||
}
|
}
|
||||||
cloudLength = std::min(cloudRay.length, maxLength - ray.length);
|
cloudLength = std::min(cloudRay.length, maxLength - ray.length);
|
||||||
|
|
||||||
// Calculate step length
|
// Calculate step length
|
||||||
int noiseSamples = settings.lightSamples;
|
int noiseSamples = settings.lightSamples;
|
||||||
float stepLength = cloudLength / noiseSamples;
|
float stepLength = cloudLength / (float) noiseSamples;
|
||||||
|
|
||||||
// Step through cloud
|
// Step through cloud
|
||||||
float transmittance = 1.0f;
|
float density = 1.0f;
|
||||||
for (int i = 0; i < noiseSamples; ++i)
|
for (int i = 0; i < noiseSamples; ++i)
|
||||||
{
|
{
|
||||||
// Get sample point
|
// Get sample point
|
||||||
|
@ -143,11 +167,47 @@ Color CloudShader::transparency(const Scene &scene, const Ray &ray, float maxLen
|
||||||
// Get data at point
|
// Get data at point
|
||||||
float sampleDensity = getCloudDensity(samplePoint) * stepLength;
|
float sampleDensity = getCloudDensity(samplePoint) * stepLength;
|
||||||
|
|
||||||
transmittance *= exp(-sampleDensity * stepLength);
|
density += sampleDensity * stepLength;
|
||||||
if (transmittance <= TRANSMITTANCE_BREAK) break; // No need to continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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));
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,18 +8,20 @@
|
||||||
#include "common/noise/worleynoise.h"
|
#include "common/noise/worleynoise.h"
|
||||||
|
|
||||||
int const NOISE_SIZE = 128;
|
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
|
struct CloudSettings
|
||||||
{
|
{
|
||||||
int densitySamples = 100;
|
int densitySamples = 100;
|
||||||
int lightSamples = 20;
|
int lightSamples = 100;
|
||||||
float scale = 10;
|
float scale = 10;
|
||||||
float densityTreshold = 0.55f;
|
float densityOffset = -0.55f;
|
||||||
float densityIntensity = 2.5f;
|
float densityIntensity = 7.0f;
|
||||||
float densityAbsorption = 2;
|
float darknessThreshold = 0.07f;
|
||||||
float darknessThreshold = 0.1f;
|
float shadowLightAbsorption = 2;
|
||||||
float shadowIntensity = 0.8f;
|
float shadowIntensity = 0.8f;
|
||||||
|
float lightAbsorptionTowardsLight = 0.94f;
|
||||||
|
float lightAbsorptionThroughCloud = 0.85f;
|
||||||
};
|
};
|
||||||
|
|
||||||
class CloudShader : public Shader
|
class CloudShader : public Shader
|
||||||
|
@ -41,7 +43,13 @@ private:
|
||||||
|
|
||||||
float getCloudDensity(Vector3d point) const;
|
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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -9,56 +9,57 @@ RefractionShader::RefractionShader(float indexInside, float indexOutside, Color
|
||||||
Color RefractionShader::shade(Scene const &scene, Ray const &ray) const
|
Color RefractionShader::shade(Scene const &scene, Ray const &ray) const
|
||||||
{
|
{
|
||||||
// Circumvent getting environment map color into the mix
|
// 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
|
return Color(0.0f, 0.0f, 0.0f);
|
||||||
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);
|
|
||||||
|
// 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
|
float RefractionShader::remainingLightIntensity(float distanceThroughObject) const
|
||||||
|
|
Loading…
Reference in a new issue