Commit cbfc9266 authored by Johannes Braun's avatar Johannes Braun
Browse files

Finally finished BRDF/BTDF/Emit/Lambert stuff...

parent 6cacbe38
......@@ -60,132 +60,150 @@ SampledMaterial getMaterialParameters(const in Material material, const in Verte
return sampled_material;
}
bool isTotalReflection(float ior_in, float ior_out, vec3 incoming, vec3 micro_normal)
BSDFResult computeReflection(const in SampledMaterial sampled_material, const in Ray ray_in, const in vec3 microfacet_normal, const in Vertex vertex)
{
float c = abs(dot(incoming, micro_normal));
return c * c < -(ior_out / ior_in) + 1;
const vec3 incoming = normalize(-ray_in.direction.xyz);
const vec3 normal = faceforward(vertex.normal.xyz, -incoming, vertex.normal.xyz);
// Compute BRDF
const vec3 reflected = normalize(2 * abs(dot(microfacet_normal, incoming)) * microfacet_normal - incoming);
const vec3 outgoing = dot(reflected, normal) >= 0 ? reflected : reflect(reflected, normal);
const vec3 microfacet_normal_corrected = normalize(outgoing + incoming);
const float m_dot_in = dot(microfacet_normal_corrected, incoming);
const float cos_theta = dot(microfacet_normal_corrected, normal);
const float m_dot_out = dot(microfacet_normal_corrected, outgoing);
const float distribution = ggxDistribution(cos_theta, sampled_material.roughness);
const float geometry = ggxGeometry(incoming, outgoing, normal, microfacet_normal_corrected, sampled_material.roughness);
// Compute the microsurface normal probability and with that the final probability density function
// Clamp up denominator to not divide by zero.
const float jacobian = 1 / abs(4 * m_dot_out);
const float probability_m = distribution * cos_theta;
BSDFResult brdf_result;
brdf_result.evaluation = vec3(0); // No diffuse if reflecting
brdf_result.bsdf_id = eReflect;
brdf_result.probability_density = probability_m * jacobian;
brdf_result.radiance = mix(vec3(1), sampled_material.base, sampled_material.metallic) * geometry * distribution / (4 * m_dot_in * m_dot_out);
brdf_result.generated_ray = ray_in;
brdf_result.generated_ray.direction = outgoing;
brdf_result.generated_ray.origin = vertex.position.xyz + 1e-3f * brdf_result.generated_ray.direction;
return brdf_result;
}
BSDFResult computeTransmission(const in SampledMaterial sampled_material, const in Ray ray_in, const in vec3 microfacet_normal, const in Vertex vertex, float ior_in, float ior_out)
{
const float eta = ior_in / ior_out;
const vec3 incoming = normalize(-ray_in.direction.xyz);
const vec3 normal = faceforward(vertex.normal.xyz, -incoming, vertex.normal.xyz);
const bool entering = dot(incoming, vertex.normal.xyz) > 0;
// Compute BTDF
const float c = abs(dot(incoming, microfacet_normal));
vec3 outgoing = (eta * c - sign(dot(incoming, normal)) * sqrt(1 + eta*(c * c - 1))) * microfacet_normal - eta * incoming;
vec3 microfacet_normal_corrected = microfacet_normal;
const float m_dot_in = abs(dot(microfacet_normal_corrected, incoming));
const float cos_theta = abs(dot(microfacet_normal_corrected, normal));
const float m_dot_out = dot(microfacet_normal_corrected, outgoing);
const float n_dot_in = dot(normal, incoming);
const float n_dot_out = dot(normal, outgoing);
const float distribution = ggxDistribution(abs(dot(microfacet_normal_corrected, normal)), sampled_material.roughness);
const float geometry = ggxGeometry(incoming, outgoing, normal, microfacet_normal_corrected, sampled_material.roughness);
const float ior_out_2 = ior_out * ior_out;
const float jacobian_denominator = pow(ior_in * m_dot_in + abs(ior_out * m_dot_out), 2);
const float probability_m = distribution * cos_theta;
//Take the absolute value as m_dot_out_refract is negative
const float jacobian = ior_out_2 * abs(m_dot_out) / jacobian_denominator;
BSDFResult btdf_result;
btdf_result.evaluation = vec3(0); // No diffuse if reflecting
btdf_result.bsdf_id = eTransmit;
btdf_result.probability_density = probability_m * jacobian;
btdf_result.radiance = abs(m_dot_in * m_dot_out / (n_dot_in * n_dot_out)) * ior_out_2 * sampled_material.base * geometry * distribution / (jacobian_denominator);
btdf_result.generated_ray = ray_in;
btdf_result.generated_ray.direction = outgoing;
btdf_result.generated_ray.origin = vertex.position.xyz + 1e-3f * btdf_result.generated_ray.direction;
return btdf_result;
}
BSDFResult computeLambert(const in SampledMaterial sampled_material, const in Ray ray_in, const in Vertex vertex, const vec2 random)
{
const vec3 incoming = normalize(-ray_in.direction.xyz);
const vec3 normal = faceforward(vertex.normal.xyz, -incoming, vertex.normal.xyz);
// Sample lambertian diffuse
BSDFResult lambert_result;
lambert_result.evaluation = (1-sampled_material.metallic)*sampled_material.base * ONE_OVER_PI;
lambert_result.generated_ray = ray_in;
lambert_result.generated_ray.direction = normalize(localToWorld(randCosineHemisphere(random.x, random.y), normal));
lambert_result.probability_density = abs(dot(vec4(lambert_result.generated_ray.direction, 0), vertex.normal)) * ONE_OVER_PI;
lambert_result.radiance = (1-sampled_material.metallic)*sampled_material.base * ONE_OVER_PI;
lambert_result.bsdf_id = eDiffuse;
// offset by newly generated direction
lambert_result.generated_ray.origin = vertex.position.xyz + 1e-3f * lambert_result.generated_ray.direction;
return lambert_result;
}
BSDFResult computeEmission(const in SampledMaterial sampled_material, const in Ray ray_in)
{
BSDFResult emit_result;
emit_result.generated_ray = ray_in;
emit_result.radiance = sampled_material.emission * sampled_material.base;
emit_result.bsdf_id = eEmit;
return emit_result;
}
BSDFResult computeBSDF(const in Material material, const in vec2 random, const in Vertex vertex, const in Ray ray_in)
{
const SampledMaterial sampled_material = material.getMaterialParameters(vertex);
if(sampled_material.emission != 0)
{
BSDFResult result;
//Use the pixel coordinates of the incoming ray.
result.generated_ray = ray_in;
result.radiance = sampled_material.emission * sampled_material.base;
result.bsdf_id = eEmit;
return result;
}
// inverse incoming direction
vec3 incoming = normalize(-ray_in.direction);
const vec3 incoming = normalize(-ray_in.direction);
// Turn around normal if looking at it from the backside
vec3 normal = faceforward(vertex.normal.xyz, -incoming, vertex.normal.xyz);
// Flip around normal if looking at it from the backside
const vec3 normal = faceforward(vertex.normal.xyz, -incoming, vertex.normal.xyz);
// Importance sampling: compute an importance sample {phi, theta} and generate a local (z-up) microsurface normal, as well as an according normal distribution
HemisphereSample hemisphere_sample = ggxSample(random, sampled_material.roughness);
vec3 microfacet_normal = localToWorld(hemisphere_sample.micro_normal, normal);
microfacet_normal = dot(microfacet_normal, incoming) < 0 ? reflect(-microfacet_normal, normal) : microfacet_normal;
// Importance sampling: compute an importance sample {phi, theta} and generate a global microsurface normal along the given normal
const HemisphereSample hemisphere_sample = ggxSample(random, sampled_material.roughness, incoming, normal);
// Schlick's fresnel approximation with the material's normal response
float normal_response = normalResponse(sampled_material.ior, sampled_material.metallic);
float fresnel = fresnelSchlick(dot(incoming, microfacet_normal), normal_response);
const float fresnel = fresnelSchlick(dot(incoming, hemisphere_sample.micro_normal), normalResponse(sampled_material.ior, sampled_material.metallic));
const float k_e = clamp(sampled_material.emission, 0, 1);
float k_s = fresnel;
float k_rest = 1-k_s;
float k_t = k_rest * sampled_material.transmission;
float k_t = (1-k_s) * sampled_material.transmission;
float k_d = 1 - k_s - k_t;
const float one_minus_k_e = 1-k_e;
k_s *= one_minus_k_e;
k_t *= one_minus_k_e;
k_d *= one_minus_k_e;
float roulette_border_specular = k_s;
float roulette_border_transmit = k_s + k_t;
// Compute needed refraction values
const bool enters = dot(incoming, vertex.normal.xyz) > 0;
float rnd = rand(int(12301 * random.x + 99373 * random.y));
const float ior_in = enters ? 1 : sampled_material.ior;
const float ior_out = enters ? sampled_material.ior : 1;
// Compute needed refraction values
bool enters = dot(incoming, vertex.normal.xyz) > 0;
float ior_in = enters ? 1 : sampled_material.ior;
float ior_out = enters ? sampled_material.ior : 1;
float eta = ior_in / ior_out;
if(rnd < k_s || (k_t > 0 && rnd < k_s + k_t && isTotalReflection(ior_in, ior_out, incoming, microfacet_normal))){
// Compute BRDF
vec3 reflected = normalize(2 * abs(dot(microfacet_normal, incoming)) * microfacet_normal - incoming);
vec3 outgoing = dot(reflected, normal) >= 0 ? reflected : reflect(reflected, normal);
microfacet_normal = normalize(outgoing + incoming);
float m_dot_in = abs(dot(microfacet_normal, incoming));
float cos_theta = abs(dot(microfacet_normal, normal));
float m_dot_out = abs(dot(microfacet_normal, outgoing));
float distribution = ggxDistribution(cos_theta, sampled_material.roughness);
float geometry = ggxGeometry(incoming, outgoing, normal, microfacet_normal, sampled_material.roughness);
// Compute the microsurface normal probability and with that the final probability density function
// Clamp up denominator to not divide by zero.
float jacobian = 1 / max(abs(4 * m_dot_out), 0.00001f);
float probability_m = distribution * cos_theta;
BSDFResult brdf_result;
brdf_result.evaluation = vec3(0); // No diffuse if reflecting
brdf_result.bsdf_id = eReflect;
brdf_result.probability_density = probability_m * jacobian;
brdf_result.radiance = mix(vec3(1), sampled_material.base, sampled_material.metallic) * geometry * distribution / (4 * m_dot_in * m_dot_out);
brdf_result.generated_ray = ray_in;
brdf_result.generated_ray.direction = outgoing;
brdf_result.generated_ray.origin = vertex.position.xyz + 1e-3f * brdf_result.generated_ray.direction;
return brdf_result;
}
else if(rnd < k_s + k_t)
{
// Compute BTDF
float c = abs(dot(incoming, microfacet_normal));
vec3 outgoing = (eta * c - sign(dot(incoming, normal)) * sqrt(1 + eta*(c * c - 1))) * microfacet_normal - eta * incoming;
float m_dot_in = abs(dot(microfacet_normal, incoming));
float cos_theta = abs(dot(microfacet_normal, normal));
float m_dot_out = abs(dot(microfacet_normal, outgoing));
float distribution = ggxDistribution(abs(dot(microfacet_normal, normal)), sampled_material.roughness);
float geometry = ggxGeometry(incoming, outgoing, normal, microfacet_normal, sampled_material.roughness);
float ior_out_2 = ior_out * ior_out;
float jacobian_denominator = ior_in * m_dot_in + ior_out * abs(m_dot_out);
jacobian_denominator *= jacobian_denominator;
float probability_m = distribution * cos_theta;
//Take the absolute value as m_dot_out_refract is negative
float jacobian = abs(ior_out_2 * abs(m_dot_out) / jacobian_denominator);
BSDFResult btdf_result;
btdf_result.evaluation = vec3(0); // No diffuse if reflecting
btdf_result.bsdf_id = eTransmit;
btdf_result.probability_density = probability_m * jacobian;
btdf_result.radiance = abs(m_dot_in * m_dot_out / (m_dot_in * m_dot_out)) * ior_out_2 * sampled_material.base * geometry * distribution / jacobian_denominator;
btdf_result.generated_ray = ray_in;
btdf_result.generated_ray.direction = outgoing;
btdf_result.generated_ray.origin = vertex.position.xyz + 1e-3f * btdf_result.generated_ray.direction;
return btdf_result;
}
else
// Russian roulette.
const float roulette_random = rand(int(12301 * random.x + 99373 * random.y));
const bool emits = roulette_random < k_e;
const bool reflects = roulette_random < k_s || (k_t > 0 && roulette_random < k_s + k_t && isTotalReflection(ior_in, ior_out, incoming, hemisphere_sample.micro_normal));
const bool transmits = roulette_random < k_s + k_t;
const uint bsdf_type = emits ? eEmit : (reflects ? eReflect : (transmits ? eTransmit : eDiffuse));
switch(bsdf_type)
{
// Sample lambertian diffuse
BSDFResult lambert_result;
lambert_result.evaluation = (1-sampled_material.metallic)*sampled_material.base * ONE_OVER_PI;
lambert_result.generated_ray = ray_in;
lambert_result.generated_ray.direction = normalize(localToWorld(randCosineHemisphere(random.x, random.y), normal));
lambert_result.probability_density = abs(dot(vec4(lambert_result.generated_ray.direction, 0), vertex.normal)) * ONE_OVER_PI;
lambert_result.radiance = (1-sampled_material.metallic)*sampled_material.base * ONE_OVER_PI;
lambert_result.bsdf_id = eDiffuse;
// offset by newly generated direction
lambert_result.generated_ray.origin = vertex.position.xyz + 1e-3f * lambert_result.generated_ray.direction;
return lambert_result;
default: return sampled_material.computeLambert(ray_in, vertex, random);
case eEmit: return sampled_material.computeEmission(ray_in);
case eReflect: return sampled_material.computeReflection(ray_in, hemisphere_sample.micro_normal, vertex);
case eTransmit: return sampled_material.computeTransmission(ray_in, hemisphere_sample.micro_normal, vertex, ior_in, ior_out);
}
}
......
......@@ -77,4 +77,10 @@ vec3 localToWorld(const in vec3 vector, const in vec3 normal) {
return normalize(u * vector.x + v * vector.y + w * vector.z);
}
bool isTotalReflection(float ior_in, float ior_out, vec3 incoming, vec3 micro_normal)
{
float c = abs(dot(incoming, micro_normal));
return c * c < -(ior_out / ior_in) + 1;
}
#endif //!INCLUDE_UTIL_GEOMETRY_GLSL
......@@ -92,10 +92,17 @@ float ggxGeometry(const in vec3 vector_in, const in vec3 vector_out, const in ve
}
// Samples an importance sample, a z-up local microsurface normal and the fitting normal distribution
HemisphereSample ggxSample(const in vec2 random, const in float roughness)
HemisphereSample ggxSample(const in vec2 random, const in float roughness, const in vec3 incoming, const in vec3 normal)
{
HemisphereSample hemisphere_sample;
// Sample microfacet normal in tangent space
hemisphere_sample.micro_normal = ggxImportantHemisphereSample(random, roughness, hemisphere_sample.importance);
// Then convert it to world space and flip towards the viewer.
hemisphere_sample.micro_normal = localToWorld(hemisphere_sample.micro_normal, normal);
hemisphere_sample.micro_normal = dot(hemisphere_sample.micro_normal, incoming) < 0 ? reflect(-hemisphere_sample.micro_normal, normal) : hemisphere_sample.micro_normal;
return hemisphere_sample;
}
......
......@@ -28,7 +28,7 @@ const fs::path engine_settings_path = files::asset("/preferences/default.xml");
const fs::path pathtracer_settings_path = files::asset("/preferences/pathtracer_default.xml");
const fs::path skybox_files_path = files::asset("/textures/ryfjallet/");
const fs::path env_audio_path = files::asset("/audio/env.wav");
const fs::path startup_scene_path = files::asset("/meshes/scenery/sphere.dae");
const fs::path startup_scene_path = files::asset("/meshes/scenery/material_testscene.dae");
// My little all-enclosing pathtracer pointer
std::unique_ptr<raytrace::Pathtracer> pathtracer = nullptr;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment