diff --git a/code/projects/path_sobol.cpp b/code/projects/path_sobol.cpp new file mode 100644 index 0000000000000000000000000000000000000000..41cb35b7c564ac82175b29e7402b6396df3dc5b7 --- /dev/null +++ b/code/projects/path_sobol.cpp @@ -0,0 +1,380 @@ + + +#include <omp.h> +#include <chrono> +#include <random> +#include <stdint.h> + +#include "options.h" + +#include "vec.h" +#include "mat.h" + +#include "color.h" +#include "framebuffer.h" +#include "image.h" +#include "image_io.h" + +#include "gltf/gltf.h" +#include "gltf/scene.h" +#include "orbiter.h" + +#include "sampler.h" +#include "sampler_sobol.h" + +// utilise sobol' global +typedef Sobol Sampler; + +Options options; + + +Color direct( const Point& o, const Point& p, const Brdf& pbrdf, const Scene& scene, Sampler& rng ) +{ + Color color; + + /* strategie 1 : genere un point sur une source + */ + const Source& source= scene.sources.sample( rng.sample() ); + Point s= source.sample( rng.sample(), rng.sample() ); + Vector sn= source.n; + + Vector l= normalize( Vector(p, s) ); + float cos_theta= std::max(float(0), dot(pbrdf.world.n, l)); + float cos_theta_s= std::max(float(0), dot(sn, -l)); + float G= cos_theta_s / distance2(p, s); + float pdf= source.pdf(s) * scene.sources.pdf(source); + + if(pdf * cos_theta * cos_theta_s > 0 + && scene.visible( offset_point(p, pbrdf.world.n), offset_point(s, sn) )) + { + Color fr= pbrdf.f(l, normalize(o - p)); + color= color + source.emission * G * fr * cos_theta / pdf; + } + + /* strategie 2 : genere une direction en fonction de la matiere + + Vector l= pbrdf.sample( rng.sample(), rng.sample(), rng.sample(), normalize(o - p)); + float pdf= pbrdf.pdf( l, normalize(o - p) ); + float cos_theta= std::max(float(0), dot(pbrdf.world.n, l)); + if(pdf * cos_theta > 0 + && scene.visible(offset_point(p, pbrdf.world.n), l)) + { + Color fr= pbrdf.f(l, normalize(o - p)); + color= color + Color(0.02) * fr * cos_theta / pdf; + } + */ + + return color; +} + +// mis multi-sample, evalue les 2 integrales + ponderation de chaque echantillon +Color direct_mis( const Point& o, const Point& p, const Brdf& pbrdf, Hit& qhit, const Scene& scene, Sampler& rng ) +{ + Color color; + + // strategie 1 : selectionne une source + un point sur la source + { + const Source& source= scene.sources.sample( rng.sample() ); + Point s= source.sample( rng.sample(), rng.sample() ); + + Vector sn= source.n; + Vector l= normalize( Vector(p, s) ); + float cos_theta= std::max(float(0), dot(pbrdf.world.n, l)); + float cos_theta_s= std::max(float(0), dot(sn, -l)); + float G= cos_theta_s / distance2(p, s); + if(G * cos_theta > 0) + { + float pdf1= source.pdf(s) * scene.sources.pdf(source); + if(pdf1 > 0) + { + if(scene.visible( offset_point(p, pbrdf.world.n), offset_point(s, sn) ) ) + { + float pdf2= pbrdf.pdf(l, normalize( Vector(p, o) )) * G; + float w= (pdf1*pdf1) / (pdf1*pdf1 + pdf2*pdf2); // pdf1 et pdf2 sur les aires, power heuristic + + Color fr= pbrdf.f(l, normalize(o - p)); + color= color + w * source.emission * fr * G * cos_theta / pdf1; + } + } + } + } + + // strategie 2 : selectionne un terme de la brdf et genere une direcion + { + Vector l= pbrdf.sample( rng.sample(), rng.sample(), rng.sample(), normalize(o - p) ); + float pdf2= pbrdf.pdf(l, normalize(o - p) ); + if(pdf2 > 0) + { + Ray ray( offset_point(p, pbrdf.world.n), l ); + if(Hit hit= scene.intersect(ray)) + { + Color emission= scene.material(hit).emission; + if(emission.max() > 0) + { + Point s= ray.o + ray.d * hit.t; + Vector sn= scene.normal(hit); + float cos_theta= std::max(float(0), dot(pbrdf.world.n, l)); + float cos_theta_s= std::max(float(0), dot(sn, -l)); + float G= cos_theta_s / distance2(p, s); + if(G * cos_theta > 0) + { + pdf2= pdf2 * G; + float pdf1= 1 / scene.sources.area; + float w= (pdf2*pdf2) / (pdf1*pdf1 + pdf2*pdf2); // pdf1 et pdf2 sur les aires, power heuristic + + Color fr= pbrdf.f(l, normalize(o - p)); + color= color + w * emission * fr * G * cos_theta / pdf2; + } + } + else + // conserve le point pour prolonger le chemin... + qhit= hit; + } + } + } + + return color; +} + + +Color path( const int depth, const Point& o, const Point& p, const Brdf& pbrdf, const Scene& scene, Sampler& rng ) +{ + Hit qhit; + + //~ Color color= direct(o, p, pbrdf, scene, rng ); + Color color= direct_mis(o, p, pbrdf, qhit, scene, rng ); + if(depth == 0) + return color; + + // recuperer l'intersection mis du direct... cf direct_mis, elimine un rayon par rebond + if(qhit) + { + Point q= scene.position(qhit); + Vector l= normalize(q - p); + float pdf= pbrdf.pdf(l, normalize(o - p)); + if(pdf > 0) // ne devrait pas arriver... + { + Vector qn= scene.normal(qhit); + Brdf qbrdf= scene.brdf(qhit, qn); + + float cos_theta= std::max(float(0), dot(pbrdf.world.n, l)); + Color fr= pbrdf.f(l, normalize(o - p)); + return color + path(depth -1, p, q, qbrdf, scene, rng) * fr * cos_theta / pdf; + } + } + + /* sinon genere une direction pour prolonger le chemin... + Vector l= pbrdf.sample( rng.sample(), rng.sample(), rng.sample(), normalize(o - p) ); + float pdf= pbrdf.pdf(l, normalize(Vector(p, o))); + if(pdf > 0) + { + Ray ray( offset_point(p, pbrdf.world.n), l ); + if(Hit hit= scene.intersect(ray)) + { + Point q= scene.position(hit, ray); + Vector qn= scene.normal(hit); + Brdf qbrdf= scene.brdf(hit, qn); + + float cos_theta= std::max(float(0), dot(pbrdf.world.n, l)); + Color fr= pbrdf.f(l, normalize(o - p)); + return color + path(depth -1, p, q, qbrdf, scene, rng) * fr * cos_theta / pdf; + } + } + */ + + return color; +} + + + +struct alpha_filter +{ + const Scene& scene; + FCRNG& rng; + + alpha_filter( const Scene& _scene, FCRNG& _rng ) : scene(_scene), rng(_rng) {} + + bool operator() ( const Hit& hit ) const + { + const GLTFMaterial& material= scene.material(hit); + if(material.color_texture == -1) + return true; + if(scene.textures[material.color_texture].opaque) + return true; + + vec3 uv_lod= scene.texcoords_lod(hit); + Color diffuse= scene.textures[material.color_texture].sample(uv_lod.x, uv_lod.y, uv_lod.z); + + return (rng.sample() <= diffuse.a); + } +}; + + + +int main( const int argc, const char **argv ) +{ + options= Options(argv, argv + argc); + + Scene scene= read_scene(options.filename); + if(scene.gltf.nodes.empty()) + return 1; // pas de geometrie, pas d'image + + // transformations + Transform view; + Transform projection; + float aspect= 1; + + if(options.orbiter_filename) + { + Orbiter camera; + if(camera.read_orbiter(options.orbiter_filename) == false) + return 1; // pas de camera, pas d'image + + view= camera.view(); + projection= camera.projection(); + aspect= camera.aspect(); + } + else if(scene.gltf.cameras.size()) + { + view= scene.camera.view; + projection= scene.camera.projection; + aspect= scene.camera.aspect; + } + else + { + printf("[error] no glTF camera...\n"); + return 1; // pas de camera, pas d'image + } + + if(scene.sources.size() == 0) + { + printf("[error] no emissive triangles / light sources...\n"); + return 1; + } + + + // proportions largeur/hauteur de la camera gltf + int samples= options.samples; + int width= 1024; + int height= width / aspect; + Framebuffer image(width, height); + Image aov_normal(width, height); + Image aov_albedo(width, height); + + // charge les textures + parametres de filtrage en fonction de l'image + read_textures(options.filename, scene, width, height); + //~ read_textures(options.filename, scene, 256, 256); + + + // + Transform viewport= Viewport(image.width(), image.height()); + Transform inv= Inverse(viewport * projection * view); + + unsigned maxdims= 0; + unsigned lines= 0; + auto cpu_start= std::chrono::high_resolution_clock::now(); + + // determine les dimensions d'une image carre / puissance de 2 + int pow2= 1; + while(pow2 < width && pow2 < height) + pow2*= 2; + + printf("image %dx%d\n", width, height); + printf("square image %dx%d\n", pow2, pow2); + + // c'est parti ! parcours tous samples de la sequence de sobol + const size_t n= width*height*samples; + +#pragma omp parallel for schedule(dynamic, 4096) + for(size_t i= 0; i < n; i++) + { + Sobol rng; + rng.index(i); + // les dimensions 0, 1 de la sequence de Sobol' choisissent le pixel + float x= rng.sample(); + float y= rng.sample(); + + // rejette les echantillons en dehors de l'image + int py= y * pow2; + int px= x * pow2; + + if(py >= height) + continue; + if(px >= width) + continue; + + // generer le rayon + x= x * pow2; + y= y * pow2; + Point origine= inv(Point(x, y, 0)); + Point extremite= inv(Point(x, y, 1)); + Ray ray(origine, extremite); + + Color color; + Vector normal; + Color albedo; + + // todo : pour initialiser le rng d'alpha_filter, il faut recalculer le seed du pixel (px, py), cf path.cpp + + // calculer les intersections avec tous les triangles + if(Hit hit= scene.bvh.intersect(ray)) + //~ if(Hit hit= scene.bvh.intersect(ray, alpha_filter(scene, alpha_rng))) + { + Point p= scene.position(hit, ray); + Vector pn= scene.normal(hit); + + // matiere emissive, si la source est orientee vers la camera + if(dot(pn, ray.d) < 0) + { + const GLTFMaterial& material= scene.material(hit); + color= color + material.emission; + // suppose que les normales des sources sont correctes... + } + + if(dot(pn, ray.d) > 0) + // mais ce n'est pas toujours le cas pour le reste de la geometrie... + pn= -pn; + + Brdf brdf= scene.brdf(hit, pn); + //~ color= color + direct(origine, p, brdf, scene, rng); + color= color + path(options.depth, origine, p, brdf, scene, rng); + + normal= normal + pn; + albedo= albedo + brdf.f(pn, normalize(ray.o - p)); + } + + image.splat(px, py, color); // accumule l'echantillon + + aov_normal(px, py)= Color(normal / float(samples), 1); // force une couleur opaque... + aov_albedo(px, py)= Color(albedo / float(samples), 1); // force une couleur opaque... + + // #pragma omp compare // ne marche pas sur visual... + maxdims= std::max(rng.d, maxdims); + } + + printf("sampler dimensions %u\n", maxdims); + + auto cpu_stop= std::chrono::high_resolution_clock::now(); + auto cpu_time= std::chrono::duration_cast<std::chrono::milliseconds>(cpu_stop - cpu_start).count(); + printf("cpu %ds %03dms\n", int(cpu_time / 1000), int(cpu_time % 1000)); + + char tmp[1024]; + //~ sprintf(tmp, "%s-%05u-%04u.png", options.prefix, unsigned(cpu_time), samples); + sprintf(tmp, "%s-%04u.png", options.prefix, samples); + printf("writing '%s'...\n", tmp); + write_image_preview(image.flatten(), tmp); + + //~ sprintf(tmp, "%s-%05u-%04u.pfm", options.prefix, unsigned(cpu_time), samples); + sprintf(tmp, "%s-%04u.pfm", options.prefix, samples); + printf("writing '%s'...\n", tmp); + write_image_pfm(image.flatten(), tmp); + + sprintf(tmp, "%s-normals-%04u.pfm", options.prefix, samples); + write_image_pfm(aov_normal, tmp); + + sprintf(tmp, "%s-albedo-%04u.pfm", options.prefix, samples); + write_image_pfm(aov_albedo, tmp); + printf("\n"); + + return 0; +}