diff --git a/doc/developer.md b/doc/developer.md index 60166ecbb31..fd6ef3cffca 100644 --- a/doc/developer.md +++ b/doc/developer.md @@ -28,6 +28,8 @@ Radium offers several cmake functions to configure and build your extension. - \subpage develmeshes - \subpage develanimation +- \subpage develcorematerials +- \subpage develcorerandom \page engine Engine diff --git a/doc/developer/corematerials.md b/doc/developer/corematerials.md new file mode 100644 index 00000000000..231bc845277 --- /dev/null +++ b/doc/developer/corematerials.md @@ -0,0 +1,24 @@ +\page develcorematerials Core Materials +[TOC] + +A Ra::Core::Material::MaterialModel is a way to control the appearance of an object when rendering. + +For now, a Ra::Core::Material::MaterialModel only provides the _Bidirectional Scattering Distribution function (BSDF)_ to be applied on an object. + +This _BSDF_ is composed of three methods. + +# Methods of the Ra::Core::Material + +## Evaluate BSDF + +This method, wich is defined as the operator `()` by the interface in Ra::Core::Material::MaterialModel gives the color value of the object for given ingoing, outgoing and surface normal directions. + +## Sample BSDF + +This method, named `sample()` by the interface in Ra::Core::Material::MaterialModel gives the outgoing direction according to the choosen reflectance model for a given ingoing and surface normal direction. + +A Ra::Core::Material::MaterialModel use a Ra::Core::Random::SphereSampler fed with a Ra::Core::Random::UniformGenerator in order to sample. To learn more about samplers and generators, please read the [Random](@ref develcorerandom) section. + +## Probability density function corresponding to BSDF + +This method, named `pdf()` by the interface in Ra::Core::Material::MaterialModel gives the probability for a pair of ingoing and outgoing direction to exist. diff --git a/doc/developer/corerandom.md b/doc/developer/corerandom.md new file mode 100644 index 00000000000..bd81a22ac8b --- /dev/null +++ b/doc/developer/corerandom.md @@ -0,0 +1,19 @@ +\page develcorerandom Core Random +[TOC] + +The `Ra::Core::Random` namespace contain two interfaces, Ra::Core::Random::UniformGenerator and Ra::Core::Random::SphereSampler. + +# Generators + +A generator is an implementation of the interface Ra::Core::Random::UniformGenerator. It provides an uniform random distribution between 0 and 1. The interface provides four methods to get a random scalar or a 2D, 3D or nD vector. By default, the vectors are built by calling n times the `get1D()` method. + +# Samplers + +A sampler follow the interface Ra::Core::Random::SphereSampler. A sphere sampler generate a direction from the center of the sphere to a point on the surface of the sphere according to a probability distribution. To achieve this, it has two methods. A method `getDir()` that maps an uniform generator into the probability distribution to generate a direction in the canonical frame and a method `pdf()` that compute the probability density function value of a direction with a given normal direction (both directions are in world frame). + +There is an example app called PlotImportanceSampler that can write samples from several samplers in a json file. The json file can then be read by a python script called plot-samples.py. +To launch this script do + +~~~{.bash} +python3 /path/to/plot-samples.py /path/to/jsonfile.json +~~~ diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index e9171f7f5a4..3b864d52caa 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -23,6 +23,7 @@ foreach( MaterialEdition ParameterEdition Picking + PlotImportanceSampler RawShaderMaterial SimpleAnimation SimpleSimulation diff --git a/examples/PlotImportanceSampler/CMakeLists.txt b/examples/PlotImportanceSampler/CMakeLists.txt new file mode 100644 index 00000000000..a8a1b3f1e92 --- /dev/null +++ b/examples/PlotImportanceSampler/CMakeLists.txt @@ -0,0 +1,61 @@ +cmake_minimum_required(VERSION 3.16) +cmake_policy(SET CMP0042 NEW) + +project(PlotImportanceSampler) + +# ------------------------------------------------------------------------------ +# set wanted application defaults for cmake settings +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release) +endif() +# Set default install location to installed- folder in build dir we do not want to +# install to /usr by default +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX + "${CMAKE_CURRENT_BINARY_DIR}/installed-${CMAKE_CXX_COMPILER_ID}-${CMAKE_BUILD_TYPE}" + CACHE PATH "Install path prefix, prepended onto install directories." FORCE + ) + message("Set install prefix to ${CMAKE_INSTALL_PREFIX}") +endif() +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_DISABLE_SOURCE_CHANGES ON) +set(CMAKE_DISABLE_IN_SOURCE_BUILD ON) + +# ------------------------------------------------------------------------------ + +find_package(Radium REQUIRED Core) + +# ------------------------------------------------------------------------------ + +set(app_sources main.cpp) +set(app_headers) +set(app_resources) + +# to install the app as a redistribuable bundle on macos, add MACOSX_BUNDLE when calling +# add_executable +add_executable(${PROJECT_NAME} ${app_sources} ${app_headers} ${app_uis} ${app_resources}) + +if("${CMAKE_CXX_COMPILER_ID}" MATCHES "MSVC") + target_compile_options( + ${PROJECT_NAME} + PRIVATE /MP + /W4 + /wd4251 + /wd4592 + /wd4127 + /Zm200 + $<$: + /Gw + /GS- + /GL + /GF + > + PUBLIC + ) +endif() + +target_link_libraries(${PROJECT_NAME} PUBLIC Radium::Core) + +# configure the application +configure_radium_app(NAME ${PROJECT_NAME}) diff --git a/examples/PlotImportanceSampler/main.cpp b/examples/PlotImportanceSampler/main.cpp new file mode 100644 index 00000000000..5a7ab122ffc --- /dev/null +++ b/examples/PlotImportanceSampler/main.cpp @@ -0,0 +1,51 @@ +// Include the samplers and a random generator +#include +#include +#include +#include + +#include +#include +#include +#include + +int main( int /*argc*/, char** /*argv*/ ) { + using namespace Ra::Core; + using json = nlohmann::json; + + //! [Creating the random generator to feed the samplers] + Random::MersenneTwisterGenerator generator = Random::MersenneTwisterGenerator(); + + //! [Creating the samplers] + std::vector uSamplesDir, cSamplesDir, bSamplesDir4, bSamplesDir16, bSamplesDir64, + bSamplesDir128; + + //! [Sampling (here 500 times) with each sampler] + for ( int i = 0; i < 500; i++ ) { + uSamplesDir.push_back( Random::UniformSphereSampler::getDir( &generator ).first ); + cSamplesDir.push_back( Random::CosineWeightedSphereSampler::getDir( &generator ).first ); + bSamplesDir4.push_back( Random::BlinnPhongSphereSampler::getDir( &generator, 4 ).first ); + bSamplesDir16.push_back( Random::BlinnPhongSphereSampler::getDir( &generator, 16 ).first ); + bSamplesDir64.push_back( Random::BlinnPhongSphereSampler::getDir( &generator, 64 ).first ); + bSamplesDir128.push_back( + Random::BlinnPhongSphereSampler::getDir( &generator, 128 ).first ); + } + + //! [Creating a json format to write the samples in and write to a file named "samples.json"] + json j = { { "UniformSamples", uSamplesDir }, + { "CosineWeightedSamples", cSamplesDir }, + { "BlinnPhongSamples 4", bSamplesDir4 }, + { "BlinnPhongSamples 16", bSamplesDir16 }, + { "BlinnPhongSamples 64", bSamplesDir64 }, + { "BlinnPhongSamples 128", bSamplesDir128 } }; + + std::ofstream o( "samples.json" ); + + o << std::setw( 4 ) << j << std::endl; + + o.close(); + + //! [Samples can now be displayed with the python script named "plot-samples.py"] + + return 0; +} diff --git a/examples/PlotImportanceSampler/plot-samples.py b/examples/PlotImportanceSampler/plot-samples.py new file mode 100644 index 00000000000..6c5a9fd90a5 --- /dev/null +++ b/examples/PlotImportanceSampler/plot-samples.py @@ -0,0 +1,36 @@ +import json +import matplotlib.pyplot as plt +import matplotlib.gridspec as gridspec +import numpy as np +import math +import sys + +""" +This python script is used to plot the samples writted to a json file by the main.cpp Radium example app in the PlotImportanceSampler folder. + +To launch this script use the command +python3 plot-samples.py /path/to/jsonfile.json +""" + +def plotDir(array, ax): + for p in array: + ax.scatter(p[0], p[1], p[2], c='#1f77b4', s=1) + +with open(sys.argv[1], 'r') as f: + data = json.load(f) + +size = len(data) + +fig = plt.figure(figsize=plt.figaspect(1/3)) +gs = gridspec.GridSpec(math.ceil(size/3), 3) + +i = 0 +for l in data: + ax = fig.add_subplot(gs[math.floor(i/3), i%3], projection='3d') + ax.set_title(l) + ax.set_zlim(0, 1) + arr = np.array(data[l]) + plotDir(arr, ax) + i += 1 + +plt.show() diff --git a/src/Core/Material/BlinnPhongMaterialModel.cpp b/src/Core/Material/BlinnPhongMaterialModel.cpp index 5ff3a48a53e..04120a60985 100644 --- a/src/Core/Material/BlinnPhongMaterialModel.cpp +++ b/src/Core/Material/BlinnPhongMaterialModel.cpp @@ -1,7 +1,8 @@ #include - #include +#include + namespace Ra { namespace Core { namespace Material { @@ -24,6 +25,80 @@ void BlinnPhongMaterialModel::displayInfo() const { print( hasNormalTexture(), " Normal Texture : ", m_texNormal ); print( hasOpacityTexture(), " Alpha Texture : ", m_texOpacity ); } + +Utils::Color Material::BlinnPhongMaterialModel::operator()( Vector3 w_i, + Vector3 w_o, + Vector3 normal, + Vector2 uv ) { + // diffuse lambertien component + Utils::Color diffuse = m_kd / M_PI; + + // Blinn-Phong specular component + Vector3 halfway = w_i + w_o; + halfway.normalize(); + Scalar specularIntensity = + ( m_ns + 2.0f ) / ( 2.0f * M_PI ) * std::pow( normal.dot( halfway ), m_ns ); + Utils::Color specular = m_ks * specularIntensity; + + // Combine the diffuse and specular components + Utils::Color bsdf = diffuse + specular; + + return bsdf; +} + +std::optional> +BlinnPhongMaterialModel::sample( Vector3 w_i, Vector3 normal, Vector3 tangent, Vector3 bitangent ) { + Vector3 halfway; + + Scalar distrib = m_generator.get()->get1D(); + + // diffuse part + if ( distrib < m_diffuseLuminance ) { + std::pair smpl = + Core::Random::CosineWeightedSphereSampler::getDir( m_generator.get() ); + Vector3 wo( + smpl.first.dot( tangent ), smpl.first.dot( bitangent ), smpl.first.dot( normal ) ); + std::pair result { wo, smpl.second }; + + return result; + } + // specular part + else if ( distrib < m_diffuseLuminance + m_specularLuminance ) { + std::pair smpl = + Core::Random::BlinnPhongSphereSampler::getDir( m_generator.get(), m_ns ); + Vector3 localMicroFacetNormal = smpl.first; + Vector3 microFacetNormal( localMicroFacetNormal.dot( tangent ), + localMicroFacetNormal.dot( bitangent ), + localMicroFacetNormal.dot( normal ) ); + Vector3 reflected = Core::Random::BlinnPhongSphereSampler::reflect( w_i, microFacetNormal ); + std::pair result { reflected, smpl.second }; + + return result; + } + else { // no next dir + return {}; + } +} + +Scalar BlinnPhongMaterialModel::pdf( Vector3 w_i, Vector3 w_o, Vector3 normal ) { + return std::clamp( m_diffuseLuminance * + Core::Random::CosineWeightedSphereSampler::pdf( w_o, normal ) + + m_specularLuminance * + Core::Random::BlinnPhongSphereSampler::pdf( w_i, w_o, normal, m_ns ), + 0_ra, + 1_ra ); +} + +void BlinnPhongMaterialModel::computeLuminance() { + Vector3 rgbToLuminance { 0.2126_ra, 0.7152_ra, 0.0722_ra }; + Scalar dIntensity = m_kd.rgb().dot( rgbToLuminance ); + Scalar sIntensity = m_ks.rgb().dot( rgbToLuminance ); + Scalar diffSpecNorm = std::max( 1_ra, dIntensity + sIntensity ); + + m_diffuseLuminance = dIntensity / diffSpecNorm; + m_specularLuminance = sIntensity / diffSpecNorm; +} + } // namespace Material } // namespace Core } // namespace Ra diff --git a/src/Core/Material/BlinnPhongMaterialModel.hpp b/src/Core/Material/BlinnPhongMaterialModel.hpp index 0fe93db1920..9c48c83898d 100644 --- a/src/Core/Material/BlinnPhongMaterialModel.hpp +++ b/src/Core/Material/BlinnPhongMaterialModel.hpp @@ -1,18 +1,28 @@ #pragma once -#include +#include +#include +#include #include +#include + namespace Ra { namespace Core { namespace Material { // RADIUM SUPPORTED MATERIALS -class RA_CORE_API BlinnPhongMaterialModel : public MaterialModel + +/// @brief Implementation of MaterialModel according to Blinn-Phong reflectance model. +class RA_CORE_API BlinnPhongMaterialModel : public SimpleMaterialModel { public: - explicit BlinnPhongMaterialModel( const std::string& name = "" ) : - MaterialModel( name, "BlinnPhong" ) {} + explicit BlinnPhongMaterialModel( + const std::string& name = "", + std::shared_ptr generator = + std::make_shared() ) : + SimpleMaterialModel( name, "BlinnPhong", generator ) {} + ~BlinnPhongMaterialModel() override = default; /// DEBUG @@ -20,31 +30,64 @@ class RA_CORE_API BlinnPhongMaterialModel : public MaterialModel /// QUERY - bool hasDiffuseTexture() const { return m_hasTexDiffuse; } - bool hasSpecularTexture() const { return m_hasTexSpecular; } bool hasShininessTexture() const { return m_hasTexShininess; } bool hasNormalTexture() const { return m_hasTexNormal; } - bool hasOpacityTexture() const { return m_hasTexOpacity; } + Utils::Color operator()( Vector3 w_i, Vector3 w_o, Vector3 normal, Vector2 uv ) override; + std::optional> + sample( Vector3 w_i, Vector3 normal, Vector3 tangent, Vector3 bitangent ) override; + Scalar pdf( Vector3 w_i, Vector3 w_o, Vector3 normal ) override; + + inline Utils::Color getSpecularColor() const { return m_ks; } + inline Scalar getShininess() const { return m_ns; } + inline Scalar getDiffuseLuminance() const { return m_diffuseLuminance; } + inline Scalar getSpecularLuminance() const { return m_specularLuminance; } + inline std::string getTexSpecular() const { return m_texSpecular; } + inline std::string getTexShininess() const { return m_texShininess; } + inline std::string getTexNormal() const { return m_texNormal; } + + inline void setDiffuseColor( Utils::Color color ) { + m_kd = color; + m_alpha = color.alpha(); + computeLuminance(); + } + + inline void setSpecularColor( Utils::Color color ) { + m_ks = color; + computeLuminance(); + } + + inline void setShininess( Scalar specular ) { m_ns = specular; } + inline void setTexSpecular( std::string texSpecular ) { + m_texSpecular = texSpecular; + m_hasTexSpecular = true; + } + inline void setTexShininess( std::string texShininess ) { + m_texShininess = texShininess; + m_hasTexShininess = true; + } + inline void setTexNormal( std::string texNormal ) { + m_texNormal = texNormal; + m_hasTexNormal = true; + } + + private: + /// @brief Compute luminance values for diffuse color and specular color. + void computeLuminance(); - /// DATA MEMBERS - Core::Utils::Color m_kd { 0.7_ra, 0.7_ra, 0.7_ra }; Core::Utils::Color m_ks { 0.3_ra, 0.3_ra, 0.3_ra }; Scalar m_ns { 64_ra }; - Scalar m_alpha { 1_ra }; - std::string m_texDiffuse; + Scalar m_diffuseLuminance; + Scalar m_specularLuminance; std::string m_texSpecular; std::string m_texShininess; std::string m_texNormal; - std::string m_texOpacity; - bool m_hasTexDiffuse { false }; bool m_hasTexSpecular { false }; bool m_hasTexShininess { false }; bool m_hasTexNormal { false }; - bool m_hasTexOpacity { false }; }; } // namespace Material diff --git a/src/Core/Material/MaterialModel.cpp b/src/Core/Material/MaterialModel.cpp index bfd24b816d7..f6f0cd5b574 100644 --- a/src/Core/Material/MaterialModel.cpp +++ b/src/Core/Material/MaterialModel.cpp @@ -1,3 +1,4 @@ +#include "MaterialModel.hpp" #include #include @@ -8,6 +9,7 @@ void MaterialModel::displayInfo() const { using namespace Core::Utils; // log LOG( logERROR ) << "MaterialModel : unknown material type : " << m_materialType; } + } // namespace Material } // namespace Core } // namespace Ra diff --git a/src/Core/Material/MaterialModel.hpp b/src/Core/Material/MaterialModel.hpp index 688ef8ff778..9c4f29d3a5f 100644 --- a/src/Core/Material/MaterialModel.hpp +++ b/src/Core/Material/MaterialModel.hpp @@ -1,17 +1,22 @@ #pragma once +#include +#include #include #include #include +#include +#include + namespace Ra { namespace Core { namespace Material { -/** @brief represent material model, loaded by a file loader. - * - */ +/// Texture management isn't implemented at all. + +/// @brief Interface used for discribing materials, compute bsdf and sample reflectance ray. class RA_CORE_API MaterialModel : public Utils::ObservableVoid { public: @@ -30,6 +35,32 @@ class RA_CORE_API MaterialModel : public Utils::ObservableVoid /// DEBUG virtual void displayInfo() const; + /// @brief Compute BSDF value for a set of directions and texture coordinates. + /// @param w_i incident direction in world space. + /// @param w_o outgoing direction in world space. + /// @param normal geometric normal direction in world space. + /// @param uv UV coordinates for texture mapping. + /// @return The color value of the bsdf. + virtual Utils::Color operator()( Vector3 w_i, Vector3 w_o, Vector3 normal, Vector2 uv ) = 0; + + /// @brief Sample reflectance direction for a set of directions. + /// @param w_i incident direction in world space. + /// @param normal normal to the surface in world space. + /// @param tangent tangent to the surface, perpendicular to normal, in world space. + /// @param bitangent bitangent to the surface, perpendicular to the normal and the tangent, in + /// world space. + /// @return optional pair composed of outgoing direction and probability density function value + /// associated with the direction. + virtual std::optional> + sample( Vector3 w_i, Vector3 normal, Vector3 tangent, Vector3 bitangent ) = 0; + + /// @brief Compute probability density function value associated with a set of directions. + /// @param w_i incident direction in world space. + /// @param w_o outgoing direction in world space. + /// @param normal normal to the surface in world space. + /// @return Scalar probability density function value associated with the directions. + virtual Scalar pdf( Vector3 w_i, Vector3 w_o, Vector3 normal ) = 0; + private: std::string m_materialType; std::string m_name; diff --git a/src/Core/Material/SimpleMaterialModel.cpp b/src/Core/Material/SimpleMaterialModel.cpp index 37e3248e21a..998496f0125 100644 --- a/src/Core/Material/SimpleMaterialModel.cpp +++ b/src/Core/Material/SimpleMaterialModel.cpp @@ -21,6 +21,18 @@ void SimpleMaterialModel::displayInfo() const { print( hasOpacityTexture(), " Alpha Texture : ", m_texOpacity ); } +Utils::Color +SimpleMaterialModel::operator()( Vector3 w_i, Vector3 w_o, Vector3 normal, Vector2 uv ) { + return m_kd; +} +std::optional> +SimpleMaterialModel::sample( Vector3 w_i, Vector3 normal, Vector3 tangent, Vector3 bitangent ) { + return {}; +} +Scalar SimpleMaterialModel::pdf( Vector3 w_i, Vector3 w_o, Vector3 normal ) { + return 0_ra; +} + void LambertianMaterialModel::displayInfo() const { using namespace Core::Utils; // log auto print = []( bool ok, const std::string& name, const auto& value ) { @@ -36,6 +48,33 @@ void LambertianMaterialModel::displayInfo() const { print( hasNormalTexture(), " Normal Texture : ", m_texNormal ); print( hasOpacityTexture(), " Alpha Texture : ", m_texOpacity ); } + +Utils::Color Material::LambertianMaterialModel::operator()( Vector3 w_i, + Vector3 w_o, + Vector3 normal, + Vector2 uv ) { + if ( w_i.dot( normal ) <= 0 || w_o.dot( normal ) <= 0 ) { return Utils::Color::Black(); } + + return m_kd / M_PI; +} + +std::optional> +LambertianMaterialModel::sample( Vector3 w_i, Vector3 normal, Vector3 tangent, Vector3 bitangent ) { + // sample point on hemisphere with cosine-weighted distribution + std::pair smpl = + Core::Random::CosineWeightedSphereSampler::getDir( m_generator.get() ); + + // transform sampled point from local to world coodinate system + Vector3 w_o( smpl.first.dot( tangent ), smpl.first.dot( bitangent ), smpl.first.dot( normal ) ); + std::pair result { w_o, smpl.second }; + + return result; +} + +Scalar LambertianMaterialModel::pdf( Vector3 w_i, Vector3 w_o, Vector3 normal ) { + return Core::Random::CosineWeightedSphereSampler::pdf( w_o, normal ); +} + } // namespace Material } // namespace Core } // namespace Ra diff --git a/src/Core/Material/SimpleMaterialModel.hpp b/src/Core/Material/SimpleMaterialModel.hpp index 83d3126344d..1463fb6c2d6 100644 --- a/src/Core/Material/SimpleMaterialModel.hpp +++ b/src/Core/Material/SimpleMaterialModel.hpp @@ -1,6 +1,9 @@ #pragma once #include +#include +#include +#include #include namespace Ra { @@ -8,11 +11,17 @@ namespace Core { namespace Material { // RADIUM SUPPORTED MATERIALS + +/// @brief Implementation of MaterialModel. This material doesn't compute lighting and only display +/// one color. class RA_CORE_API SimpleMaterialModel : public MaterialModel { protected: - SimpleMaterialModel( const std::string& name, const std::string type ) : - MaterialModel( name, type ) {} + SimpleMaterialModel( const std::string& name, + const std::string type, + std::shared_ptr generator = + std::make_shared() ) : + MaterialModel( name, type ), m_generator( std::move( generator ) ) {} public: explicit SimpleMaterialModel( const std::string& name = "" ) : MaterialModel( name, "Plain" ) {} @@ -25,20 +34,54 @@ class RA_CORE_API SimpleMaterialModel : public MaterialModel bool hasDiffuseTexture() const { return m_hasTexDiffuse; } bool hasOpacityTexture() const { return m_hasTexOpacity; } - /// DATA MEMBERS + Utils::Color operator()( Vector3 w_i, Vector3 w_o, Vector3 normal, Vector2 uv ) override; + std::optional> + sample( Vector3 w_i, Vector3 normal, Vector3 tangent, Vector3 bitangent ) override; + Scalar pdf( Vector3 inDir, Vector3 outDir, Vector3 normal ) override; + + inline Utils::Color getDiffuseColor() const { return m_kd; } + inline Scalar getAlpha() const { return m_alpha; } + inline std::string getTexDiffuse() const { return m_texDiffuse; } + inline std::string getTexOpacity() const { return m_texOpacity; } + + inline void setDiffuseColor( Utils::Color color ) { + m_kd = color; + m_alpha = color.alpha(); + } + inline void setAlpha( Scalar alpha ) { + m_alpha = alpha; + m_kd[3] = alpha; + } + inline void setTexDiffuse( std::string texDiffuse ) { + m_texDiffuse = texDiffuse; + m_hasTexDiffuse = true; + } + inline void setTexOpacity( std::string texOpacity ) { + m_texOpacity = texOpacity; + m_hasTexOpacity = true; + } + + protected: Core::Utils::Color m_kd { 0.9_ra, 0.9_ra, 0.9_ra }; Scalar m_alpha { 1_ra }; std::string m_texDiffuse; std::string m_texOpacity; bool m_hasTexDiffuse { false }; bool m_hasTexOpacity { false }; + + std::shared_ptr m_generator; }; +/// @brief Implementation of MaterialModel according to Lambertian reflectance model. class RA_CORE_API LambertianMaterialModel : public SimpleMaterialModel { public: - explicit LambertianMaterialModel( const std::string& name = "" ) : - SimpleMaterialModel( name, "Lambertian" ) {} + explicit LambertianMaterialModel( + const std::string& name = "", + std::shared_ptr generator = + std::make_shared() ) : + SimpleMaterialModel( name, "Lambertian", generator ) {} + ~LambertianMaterialModel() override = default; /// DEBUG @@ -47,7 +90,18 @@ class RA_CORE_API LambertianMaterialModel : public SimpleMaterialModel /// QUERY bool hasNormalTexture() const { return m_hasTexNormal; } - /// DATA MEMBERS + Utils::Color operator()( Vector3 w_i, Vector3 w_o, Vector3 normal, Vector2 uv ) override; + std::optional> + sample( Vector3 w_i, Vector3 normal, Vector3 tangent, Vector3 bitangent ) override; + Scalar pdf( Vector3 w_i, Vector3 w_o, Vector3 normal ) override; + + inline std::string getTexNormal() const { return m_texNormal; } + inline void setTexNormal( std::string texNormal ) { + m_texNormal = texNormal; + m_hasTexNormal = true; + } + + private: std::string m_texNormal; bool m_hasTexNormal { false }; }; diff --git a/src/Core/Random/BlinnPhongSphereSampler.cpp b/src/Core/Random/BlinnPhongSphereSampler.cpp new file mode 100644 index 00000000000..c450d0b8445 --- /dev/null +++ b/src/Core/Random/BlinnPhongSphereSampler.cpp @@ -0,0 +1,43 @@ +#include + +#include + +namespace Ra { +namespace Core { +namespace Random { + +std::pair +Ra::Core::Random::BlinnPhongSphereSampler::getDir( UniformGenerator* generator, Scalar shininess ) { + Vector3 dir; + Vector2 u = generator->get2D(); + + Scalar cosTheta = std::pow( 1_ra - u[0], 1_ra / ( shininess + 2 ) ); + Scalar sinTheta = std::sqrt( 1_ra - cosTheta * cosTheta ); + Scalar phi = 2 * Math::Pi * u[1]; + + dir[0] = sinTheta * std::cos( phi ); + dir[1] = sinTheta * std::sin( phi ); + dir[2] = cosTheta; + + return { dir, ( shininess + 2 ) * std::pow( 1_ra - u[0], shininess ) / ( 2 * Math::Pi ) }; +} + +Scalar BlinnPhongSphereSampler::pdf( Vector3 w_i, Vector3 w_o, Vector3 normal, Scalar shininess ) { + w_i.normalize(); + w_o.normalize(); + + Vector3 halfway = ( w_i + w_o ).normalized(); + Scalar cosTheta = normal.dot( halfway ); + + return ( shininess + 2 ) * std::pow( cosTheta, shininess ) / ( 2 * Math::Pi ); +} + +Vector3 BlinnPhongSphereSampler::reflect( Vector3 inDir, Vector3 normal ) { + inDir.normalize(); + normal.normalize(); + return ( -inDir + 2 * inDir.dot( normal ) * normal ).normalized(); +} + +} // namespace Random +} // namespace Core +} // namespace Ra diff --git a/src/Core/Random/BlinnPhongSphereSampler.hpp b/src/Core/Random/BlinnPhongSphereSampler.hpp new file mode 100644 index 00000000000..aa0c7fbcf64 --- /dev/null +++ b/src/Core/Random/BlinnPhongSphereSampler.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include + +namespace Ra { +namespace Core { +namespace Random { + +/// @brief Implementation of SphereSampler following the Blinn-Phong reflectance model. Inherit from +/// CosineWeightedSphereSampler to compute the lambertian component. +class RA_CORE_API BlinnPhongSphereSampler : public CosineWeightedSphereSampler +{ + public: + BlinnPhongSphereSampler() {}; + ~BlinnPhongSphereSampler() {}; + + /// @brief Static method to sample the sphere according to Blinn-Phong model. All computations + /// are done in canonical frame. + /// @param generator Uniform random generator used to sample. + /// @param shininess Blinn-Phong exponent. + /// @return Returns a pair made of sampled direction and probability density function value + /// associated. + static std::pair getDir( UniformGenerator* generator, Scalar shininess ); + + /// @brief Static method to compute probability density function value associated with a + /// outgoing direction and a normal vector according to Blinn-Phong reflectance model. + /// @param w_i ingoing direction in world frame. + /// @param w_o Outgoing direction in world frame. + /// @param normal Normal vector in world frame, gives the orientation of the sampled hemisphere. + /// @param shininess Blinn-Phong exponent. + /// @return Scalar probability. + static Scalar pdf( Vector3 w_i, Vector3 w_o, Vector3 normal, Scalar shininess ); + + /// @brief Compute the reflected direction of the inDir direction by the surface represented by + /// the normal vector. + /// @param inDir ingoing direction, must be in the same frame than normal. + /// @param normal normal to the surface that reflect, must be in the same frame than inDir. + /// @return reflected direction in the same frame than parameters. + static Vector3 reflect( Vector3 inDir, Vector3 normal ); +}; + +} // namespace Random +} // namespace Core +} // namespace Ra diff --git a/src/Core/Random/CosineWeightedSphereSampler.cpp b/src/Core/Random/CosineWeightedSphereSampler.cpp new file mode 100644 index 00000000000..2bcb8a13ba2 --- /dev/null +++ b/src/Core/Random/CosineWeightedSphereSampler.cpp @@ -0,0 +1,32 @@ +#include + +#include + +namespace Ra { +namespace Core { +namespace Random { + +std::pair +Ra::Core::Random::CosineWeightedSphereSampler::getDirImplem( UniformGenerator* generator ) { + Vector3 dir; + Vector2 u = generator->get2D(); + + Scalar cosTheta = std::cos( std::sqrt( u[0] ) ); + Scalar sinTheta = std::cos( Math::Pi / 2_ra - std::sqrt( u[0] ) ); + Scalar phi = 2 * Math::Pi * u[1]; + + dir[0] = sinTheta * std::cos( phi ); + dir[1] = sinTheta * std::sin( phi ); + dir[2] = cosTheta; + + return { dir, u[0] / Math::Pi }; +} + +Scalar CosineWeightedSphereSampler::pdfImplem( Vector3 dir, Vector3 normal ) { + dir.normalize(); + return dir.dot( normal ) / Math::Pi; +} + +} // namespace Random +} // namespace Core +} // namespace Ra diff --git a/src/Core/Random/CosineWeightedSphereSampler.hpp b/src/Core/Random/CosineWeightedSphereSampler.hpp new file mode 100644 index 00000000000..f855f9d31b0 --- /dev/null +++ b/src/Core/Random/CosineWeightedSphereSampler.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include + +namespace Ra { +namespace Core { +namespace Random { + +/// @brief Cosine weighted implementation of SphereSampler. Used for lambertian reflectance. +class RA_CORE_API CosineWeightedSphereSampler : public SphereSampler +{ + public: + CosineWeightedSphereSampler() {}; + ~CosineWeightedSphereSampler() {}; + + /// @brief Implementation of getDir method from SphereSampler. Also called by the same static + /// getDir method. + static std::pair getDirImplem( UniformGenerator* generator ); + + /// @brief Implementation of pdf method from SphereSampler. Also called by the same static pdf + /// method. + static Scalar pdfImplem( Vector3 dir, Vector3 normal ); +}; + +} // namespace Random +} // namespace Core +} // namespace Ra diff --git a/src/Core/Random/MersenneTwisterGenerator.cpp b/src/Core/Random/MersenneTwisterGenerator.cpp new file mode 100644 index 00000000000..d04ac43db39 --- /dev/null +++ b/src/Core/Random/MersenneTwisterGenerator.cpp @@ -0,0 +1,16 @@ +#include + +namespace Ra { +namespace Core { +namespace Random { + +MersenneTwisterGenerator::MersenneTwisterGenerator() : + m_randomEngine( std::mt19937( std::time( nullptr ) ) ) {} + +Scalar Ra::Core::Random::MersenneTwisterGenerator::get1D() { + return m_unifDistributionRand( m_randomEngine ); +} + +} // namespace Random +} // namespace Core +} // namespace Ra diff --git a/src/Core/Random/MersenneTwisterGenerator.hpp b/src/Core/Random/MersenneTwisterGenerator.hpp new file mode 100644 index 00000000000..655440a92a5 --- /dev/null +++ b/src/Core/Random/MersenneTwisterGenerator.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include + +#include + +namespace Ra { +namespace Core { +namespace Random { + +/// @brief Implementation of the UniformGenerator class using the std Mersenne Twister Generator +/// (std::mt19937). +class RA_CORE_API MersenneTwisterGenerator : public UniformGenerator +{ + public: + MersenneTwisterGenerator(); + ~MersenneTwisterGenerator() override = default; + + Scalar get1D() override; + + private: + std::mt19937 m_randomEngine; +}; + +} // namespace Random +} // namespace Core +} // namespace Ra diff --git a/src/Core/Random/SphereSampler.hpp b/src/Core/Random/SphereSampler.hpp new file mode 100644 index 00000000000..b3a4cb92a06 --- /dev/null +++ b/src/Core/Random/SphereSampler.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include +#include + +namespace Ra { +namespace Core { +namespace Random { + +/// @brief Interface used to sample a hemisphere. This class is a CRTP in order to benefit from +/// polymorphism and static methods. +/// @tparam T is the class that implement this interface. +template +class RA_CORE_API SphereSampler +{ + public: + SphereSampler() {}; + ~SphereSampler() {}; + + /// @brief Samples the sphere. Static method that calls the implemented one from the implemented + /// class T. All computations are done in canonical frame. + /// @param generator Uniform random generator used for sampling. + /// @return Returns a pair made of sampled direction and probability density function value + /// associated. + static std::pair getDir( UniformGenerator* generator ) { + return T::getDirImplem( generator ); + } + + /// @brief Compute probability density function value associated with a outgoing direction and a + /// normal vector. Static method that calls the implemented one from the implemented class T. + /// @param dir Outgoing direction in world frame. + /// @param normal Normal vector in world frame, gives the orientation of the sampled hemisphere. + /// @return Scalar probability. + static Scalar pdf( Vector3 dir, Vector3 normal ) { return T::pdfImplem( dir, normal ); } +}; + +} // namespace Random +} // namespace Core +} // namespace Ra diff --git a/src/Core/Random/UniformGenerator.cpp b/src/Core/Random/UniformGenerator.cpp new file mode 100644 index 00000000000..23f2a20ec6a --- /dev/null +++ b/src/Core/Random/UniformGenerator.cpp @@ -0,0 +1,29 @@ +#include + +namespace Ra { +namespace Core { +namespace Random { + +Vector2 Ra::Core::Random::UniformGenerator::get2D() { + return {get1D(), get1D()}; +} + +Vector3 Ra::Core::Random::UniformGenerator::get3D() { + return {get1D(), get1D(), get1D()}; +} + +VectorN Ra::Core::Random::UniformGenerator::getXD( int dim ) { + VectorN result; + + result.resize(dim, 1); + + for(int i = 0; i < dim; i++) { + result[i] = get1D(); + } + + return result; +} + +} // namespace Random +} // namespace Core +} // namespace Ra diff --git a/src/Core/Random/UniformGenerator.hpp b/src/Core/Random/UniformGenerator.hpp new file mode 100644 index 00000000000..5952b046ba4 --- /dev/null +++ b/src/Core/Random/UniformGenerator.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include + +#include + +namespace Ra { +namespace Core { +namespace Random { + +/// @brief Interface used for implementing uniform random generator. +class RA_CORE_API UniformGenerator +{ + public: + UniformGenerator() {}; + virtual ~UniformGenerator() = 0; + + /// @brief Virtual method used to get a one dimention random scalar. + /// @return random Scalar between 0 and 1. + virtual Scalar get1D() = 0; + + /// @brief Virtual method used to get a two dimension random vector. + /// default implementation calls get1D() twice. + /// @return random Vector2 between 0 and 1. + virtual Vector2 get2D(); + /// @brief Virtual method used to get a three dimension random vector. + /// default implementation calls get1D() three times. + /// @return random Vector3 between 0 and 1. + virtual Vector3 get3D(); + /// @brief Virtual method used to get a X dimension random vector. + /// default implementation calls get1D() X times. + /// @param dim int dimention of the vector. + /// @return random VectorN (with N corresponding to dim) between 0 and 1. + virtual VectorN getXD( int dim ); + + protected: + std::uniform_real_distribution m_unifDistributionRand { 0, 1 }; +}; + +} // namespace Random +} // namespace Core +} // namespace Ra diff --git a/src/Core/Random/UniformSphereSampler.cpp b/src/Core/Random/UniformSphereSampler.cpp new file mode 100644 index 00000000000..7823f900bad --- /dev/null +++ b/src/Core/Random/UniformSphereSampler.cpp @@ -0,0 +1,31 @@ +#include + +#include + +namespace Ra { +namespace Core { +namespace Random { + +std::pair +Ra::Core::Random::UniformSphereSampler::getDirImplem( UniformGenerator* generator ) { + Vector3 dir; + Vector2 u = generator->get2D(); + + Scalar cosTheta = std::cos( u[0] ); + Scalar sinTheta = std::cos( Math::Pi / 2_ra - u[0] ); + Scalar phi = 2 * Math::Pi * u[1]; + + dir[0] = sinTheta * std::cos( phi ); + dir[1] = sinTheta * std::sin( phi ); + dir[2] = cosTheta; + + return { dir, 1 / ( 2 * Math::Pi ) }; +} + +Scalar UniformSphereSampler::pdfImplem( Vector3 dir, Vector3 normal ) { + return 1 / ( 2 * Math::Pi ); +} + +} // namespace Random +} // namespace Core +} // namespace Ra diff --git a/src/Core/Random/UniformSphereSampler.hpp b/src/Core/Random/UniformSphereSampler.hpp new file mode 100644 index 00000000000..3a4c3a7abc3 --- /dev/null +++ b/src/Core/Random/UniformSphereSampler.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include + +namespace Ra { +namespace Core { +namespace Random { + +/// @brief Uniform implementation of SphereSampler. +class RA_CORE_API UniformSphereSampler : public SphereSampler +{ + public: + UniformSphereSampler() {}; + ~UniformSphereSampler() {}; + + /// @brief Implementation of getDir method from SphereSampler. Also called by the same static + /// getDir method. + static std::pair getDirImplem( UniformGenerator* generator ); + + /// @brief Implementation of pdf method from SphereSampler. Also called by the same static pdf + /// method. + static Scalar pdfImplem( Vector3 dir, Vector3 normal ); +}; + +} // namespace Random +} // namespace Core +} // namespace Ra diff --git a/src/Core/filelist.cmake b/src/Core/filelist.cmake index 101f98962d4..062dfc8ad7d 100644 --- a/src/Core/filelist.cmake +++ b/src/Core/filelist.cmake @@ -36,6 +36,11 @@ set(core_sources Material/BlinnPhongMaterialModel.cpp Material/MaterialModel.cpp Material/SimpleMaterialModel.cpp + Random/BlinnPhongSphereSampler.cpp + Random/CosineWeightedSphereSampler.cpp + Random/MersenneTwisterGenerator.cpp + Random/UniformGenerator.cpp + Random/UniformSphereSampler.cpp Resources/Resources.cpp Tasks/TaskQueue.cpp Utils/Attribs.cpp @@ -110,6 +115,12 @@ set(core_headers Math/Math.hpp Math/Quadric.hpp RaCore.hpp + Random/BlinnPhongSphereSampler.hpp + Random/CosineWeightedSphereSampler.hpp + Random/MersenneTwisterGenerator.hpp + Random/SphereSampler.hpp + Random/UniformGenerator.hpp + Random/UniformSphereSampler.hpp Resources/Resources.hpp Tasks/Task.hpp Tasks/TaskQueue.hpp diff --git a/src/Engine/Data/BlinnPhongMaterial.cpp b/src/Engine/Data/BlinnPhongMaterial.cpp index 126773a09ce..019158fc1d1 100644 --- a/src/Engine/Data/BlinnPhongMaterial.cpp +++ b/src/Engine/Data/BlinnPhongMaterial.cpp @@ -17,6 +17,7 @@ static const std::string materialName { "BlinnPhong" }; nlohmann::json BlinnPhongMaterial::s_parametersMetadata = {}; BlinnPhongMaterial::BlinnPhongMaterial( const std::string& instanceName ) : + m_coreMaterial( new Core::Material::BlinnPhongMaterialModel() ), Material( instanceName, materialName, Material::MaterialAspect::MAT_OPAQUE ) {} BlinnPhongMaterial::~BlinnPhongMaterial() { @@ -27,10 +28,13 @@ void BlinnPhongMaterial::updateRenderingParameters() { // update the rendering parameters auto& renderParameters = getParameters(); renderParameters.addParameter( "material.kd", m_kd ); + m_coreMaterial->setDiffuseColor( m_kd ); renderParameters.addParameter( "material.hasPerVertexKd", m_perVertexColor ); renderParameters.addParameter( "material.renderAsSplat", m_renderAsSplat ); renderParameters.addParameter( "material.ks", m_ks ); + m_coreMaterial->setSpecularColor( m_ks ); renderParameters.addParameter( "material.ns", m_ns ); + m_coreMaterial->setShininess( m_ns ); renderParameters.addParameter( "material.alpha", std::min( m_alpha, m_kd[3] ) ); Texture* tex = getTexture( BlinnPhongMaterial::TextureSemantic::TEX_DIFFUSE ); if ( tex != nullptr ) { renderParameters.addParameter( "material.tex.kd", tex ); } @@ -154,23 +158,25 @@ BlinnPhongMaterialConverter::operator()( const Ra::Core::Material::MaterialModel // static cst is safe here auto source = static_cast( toconvert ); - result->m_kd = source->m_kd; - result->m_ks = source->m_ks; - result->m_ns = source->m_ns; - result->m_alpha = source->m_alpha; + result->m_kd = source->getDiffuseColor(); + result->m_ks = source->getSpecularColor(); + result->m_ns = source->getShininess(); + result->m_alpha = source->getAlpha(); if ( source->hasDiffuseTexture() ) result->addTexture( BlinnPhongMaterial::TextureSemantic::TEX_DIFFUSE, - source->m_texDiffuse ); + source->getTexDiffuse() ); if ( source->hasSpecularTexture() ) result->addTexture( BlinnPhongMaterial::TextureSemantic::TEX_SPECULAR, - source->m_texSpecular ); + source->getTexSpecular() ); if ( source->hasShininessTexture() ) result->addTexture( BlinnPhongMaterial::TextureSemantic::TEX_SHININESS, - source->m_texShininess ); + source->getTexShininess() ); if ( source->hasOpacityTexture() ) - result->addTexture( BlinnPhongMaterial::TextureSemantic::TEX_ALPHA, source->m_texOpacity ); + result->addTexture( BlinnPhongMaterial::TextureSemantic::TEX_ALPHA, + source->getTexOpacity() ); if ( source->hasNormalTexture() ) - result->addTexture( BlinnPhongMaterial::TextureSemantic::TEX_NORMAL, source->m_texNormal ); + result->addTexture( BlinnPhongMaterial::TextureSemantic::TEX_NORMAL, + source->getTexNormal() ); return result; } diff --git a/src/Engine/Data/BlinnPhongMaterial.hpp b/src/Engine/Data/BlinnPhongMaterial.hpp index a41ae9ecee5..a5e77b18d51 100644 --- a/src/Engine/Data/BlinnPhongMaterial.hpp +++ b/src/Engine/Data/BlinnPhongMaterial.hpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -89,6 +90,8 @@ class RA_ENGINE_API BlinnPhongMaterial final : public Material, public Parameter inline bool isColoredByVertexAttrib() const override; + inline Core::Material::BlinnPhongMaterialModel* getCoreMaterial() { return m_coreMaterial; } + public: Core::Utils::Color m_kd { 0.7, 0.7, 0.7, 1.0 }; Core::Utils::Color m_ks { 0.3, 0.3, 0.3, 1.0 }; @@ -110,6 +113,7 @@ class RA_ENGINE_API BlinnPhongMaterial final : public Material, public Parameter std::map m_textures; std::map m_pendingTextures; static nlohmann::json s_parametersMetadata; + Core::Material::BlinnPhongMaterialModel* m_coreMaterial; /** * Add an new texture, from a given file, to control the specified BSDF parameter. diff --git a/src/Engine/Data/LambertianMaterial.cpp b/src/Engine/Data/LambertianMaterial.cpp index 3cd8a0e4d5f..6737e29344b 100644 --- a/src/Engine/Data/LambertianMaterial.cpp +++ b/src/Engine/Data/LambertianMaterial.cpp @@ -82,15 +82,15 @@ LambertianMaterialConverter::operator()( const Ra::Core::Material::MaterialModel // static cst is safe here auto source = static_cast( toconvert ); - result->m_color = source->m_kd; - result->m_alpha = source->m_alpha; + result->m_color = source->getDiffuseColor(); + result->m_alpha = source->getAlpha(); if ( source->hasDiffuseTexture() ) - result->addTexture( SimpleMaterial::TextureSemantic::TEX_COLOR, source->m_texDiffuse ); + result->addTexture( SimpleMaterial::TextureSemantic::TEX_COLOR, source->getTexDiffuse() ); if ( source->hasOpacityTexture() ) - result->addTexture( SimpleMaterial::TextureSemantic::TEX_MASK, source->m_texOpacity ); + result->addTexture( SimpleMaterial::TextureSemantic::TEX_MASK, source->getTexOpacity() ); if ( source->hasNormalTexture() ) - result->addTexture( SimpleMaterial::TextureSemantic::TEX_NORMAL, source->m_texNormal ); + result->addTexture( SimpleMaterial::TextureSemantic::TEX_NORMAL, source->getTexNormal() ); return result; } diff --git a/src/Engine/Data/PlainMaterial.cpp b/src/Engine/Data/PlainMaterial.cpp index c7c7b26d58e..a691a0cd2a3 100644 --- a/src/Engine/Data/PlainMaterial.cpp +++ b/src/Engine/Data/PlainMaterial.cpp @@ -81,13 +81,13 @@ Material* PlainMaterialConverter::operator()( const Ra::Core::Material::Material // static cst is safe here auto source = static_cast( toconvert ); - result->m_color = source->m_kd; - result->m_alpha = source->m_alpha; + result->m_color = source->getDiffuseColor(); + result->m_alpha = source->getAlpha(); if ( source->hasDiffuseTexture() ) - result->addTexture( SimpleMaterial::TextureSemantic::TEX_COLOR, source->m_texDiffuse ); + result->addTexture( SimpleMaterial::TextureSemantic::TEX_COLOR, source->getTexDiffuse() ); if ( source->hasOpacityTexture() ) - result->addTexture( SimpleMaterial::TextureSemantic::TEX_MASK, source->m_texOpacity ); + result->addTexture( SimpleMaterial::TextureSemantic::TEX_MASK, source->getTexOpacity() ); return result; } diff --git a/src/Engine/filelist.cmake b/src/Engine/filelist.cmake index 274f82178ba..5e11269aafc 100644 --- a/src/Engine/filelist.cmake +++ b/src/Engine/filelist.cmake @@ -1,6 +1,6 @@ # ---------------------------------------------------- # This file can be generated from a script: -# To do so, run "./generateFilelistForModule.sh Engine" +# To do so, run "./generateFilelistForModule.sh Engine" # from ./scripts directory # ---------------------------------------------------- @@ -116,6 +116,7 @@ set(engine_headers Scene/SystemDisplay.hpp ) + set(engine_shaders 2DShaders/Basic2D.vert.glsl 2DShaders/CircleBrush.frag.glsl diff --git a/src/Gui/filelist.cmake b/src/Gui/filelist.cmake index c312cf504fd..92f257238d0 100644 --- a/src/Gui/filelist.cmake +++ b/src/Gui/filelist.cmake @@ -1,6 +1,6 @@ # ---------------------------------------------------- # This file can be generated from a script: -# To do so, run "./generateFilelistForModule.sh Gui" +# To do so, run "./generateFilelistForModule.sh Gui" # from ./scripts directory # ---------------------------------------------------- @@ -87,10 +87,16 @@ set(gui_headers ) set(gui_uis - AboutDialog/AboutDialog.ui AboutDialog/RadiumHelpDialog.ui - SkeletonBasedAnimation/SkeletonBasedAnimationUI.ui Timeline/HelpDialog.ui Timeline/Timeline.ui + AboutDialog/AboutDialog.ui + AboutDialog/RadiumHelpDialog.ui + SkeletonBasedAnimation/SkeletonBasedAnimationUI.ui + Timeline/HelpDialog.ui + Timeline/Timeline.ui ) -set(gui_resources QtResources/RadiumQtResources.qrc - SkeletonBasedAnimation/SkeletonBasedAnimation.qrc Timeline/timeline.qrc +set(gui_resources + QtResources/RadiumQtResources.qrc + SkeletonBasedAnimation/SkeletonBasedAnimation.qrc + Timeline/timeline.qrc ) + diff --git a/src/Headless/filelist.cmake b/src/Headless/filelist.cmake index cc4128b6bd9..9d9276f8aa4 100644 --- a/src/Headless/filelist.cmake +++ b/src/Headless/filelist.cmake @@ -1,11 +1,14 @@ # ---------------------------------------------------- # This file can be generated from a script: -# To do so, run "./generateFilelistForModule.sh Headless" +# To do so, run "./generateFilelistForModule.sh Headless" # from ./scripts directory # ---------------------------------------------------- -set(headless_sources CLIBaseApplication.cpp CLIViewer.cpp OpenGLContext/EglOpenGLContext.cpp - OpenGLContext/GlfwOpenGLContext.cpp +set(headless_sources + CLIBaseApplication.cpp + CLIViewer.cpp + OpenGLContext/EglOpenGLContext.cpp + OpenGLContext/GlfwOpenGLContext.cpp ) set(headless_headers @@ -31,3 +34,4 @@ set(headless_headers OpenGLContext/OpenGLContext.hpp RaHeadless.hpp ) + diff --git a/src/IO/AssimpLoader/AssimpGeometryDataLoader.cpp b/src/IO/AssimpLoader/AssimpGeometryDataLoader.cpp index 74e3b7dced7..375fe651c82 100644 --- a/src/IO/AssimpLoader/AssimpGeometryDataLoader.cpp +++ b/src/IO/AssimpLoader/AssimpGeometryDataLoader.cpp @@ -237,17 +237,15 @@ void AssimpGeometryDataLoader::loadMaterial( const aiMaterial& material, aiColor4D color; aiString name; if ( AI_SUCCESS == material.Get( AI_MATKEY_COLOR_DIFFUSE, color ) ) { - plainMaterial->m_kd = assimpToCore( color ); + plainMaterial->setDiffuseColor( assimpToCore( color ) ); } if ( AI_SUCCESS == material.Get( AI_MATKEY_TEXTURE( aiTextureType_DIFFUSE, 0 ), name ) ) { - plainMaterial->m_texDiffuse = m_filepath + "/" + assimpToCore( name ); - plainMaterial->m_hasTexDiffuse = true; + plainMaterial->setTexDiffuse( m_filepath + "/" + assimpToCore( name ) ); } if ( AI_SUCCESS == material.Get( AI_MATKEY_TEXTURE( aiTextureType_OPACITY, 0 ), name ) ) { - plainMaterial->m_texOpacity = m_filepath + "/" + assimpToCore( name ); - plainMaterial->m_hasTexOpacity = true; + plainMaterial->setTexOpacity( m_filepath + "/" + assimpToCore( name ) ); } } break; @@ -259,28 +257,24 @@ void AssimpGeometryDataLoader::loadMaterial( const aiMaterial& material, aiColor4D color; aiString name; if ( AI_SUCCESS == material.Get( AI_MATKEY_COLOR_DIFFUSE, color ) ) { - lambertianMaterial->m_kd = assimpToCore( color ); + lambertianMaterial->setDiffuseColor( assimpToCore( color ) ); } if ( AI_SUCCESS == material.Get( AI_MATKEY_TEXTURE( aiTextureType_DIFFUSE, 0 ), name ) ) { - lambertianMaterial->m_texDiffuse = m_filepath + "/" + assimpToCore( name ); - lambertianMaterial->m_hasTexDiffuse = true; + lambertianMaterial->setTexDiffuse( m_filepath + "/" + assimpToCore( name ) ); } if ( AI_SUCCESS == material.Get( AI_MATKEY_TEXTURE( aiTextureType_OPACITY, 0 ), name ) ) { - lambertianMaterial->m_texOpacity = m_filepath + "/" + assimpToCore( name ); - lambertianMaterial->m_hasTexOpacity = true; + lambertianMaterial->setTexOpacity( m_filepath + "/" + assimpToCore( name ) ); } if ( AI_SUCCESS == material.Get( AI_MATKEY_TEXTURE( aiTextureType_NORMALS, 0 ), name ) ) { - lambertianMaterial->m_texNormal = m_filepath + "/" + assimpToCore( name ); - lambertianMaterial->m_hasTexNormal = true; + lambertianMaterial->setTexNormal( m_filepath + "/" + assimpToCore( name ) ); } // Assimp loads objs bump maps as height maps, gj bro if ( AI_SUCCESS == material.Get( AI_MATKEY_TEXTURE( aiTextureType_HEIGHT, 0 ), name ) ) { - lambertianMaterial->m_texNormal = m_filepath + "/" + assimpToCore( name ); - lambertianMaterial->m_hasTexNormal = true; + lambertianMaterial->setTexNormal( m_filepath + "/" + assimpToCore( name ) ); } } break; @@ -296,60 +290,54 @@ void AssimpGeometryDataLoader::loadMaterial( const aiMaterial& material, aiString name; if ( AI_SUCCESS == material.Get( AI_MATKEY_COLOR_DIFFUSE, color ) ) { - blinnPhongMaterial->m_kd = assimpToCore( color ); + blinnPhongMaterial->setDiffuseColor( assimpToCore( color ) ); } if ( AI_SUCCESS == material.Get( AI_MATKEY_COLOR_SPECULAR, color ) ) { - blinnPhongMaterial->m_ks = assimpToCore( color ); + blinnPhongMaterial->setSpecularColor( assimpToCore( color ) ); } if ( AI_SUCCESS == material.Get( AI_MATKEY_SHININESS, shininess ) ) { // Assimp gives the Phong exponent, we use the Blinn-Phong exponent - blinnPhongMaterial->m_ns = shininess * 4; + blinnPhongMaterial->setShininess( shininess * 4 ); } if ( AI_SUCCESS == material.Get( AI_MATKEY_OPACITY, opacity ) ) { // NOTE(charly): Due to collada way of handling objects that have an alpha map, we // must ensure // we do not have zeros in here. - blinnPhongMaterial->m_alpha = opacity < 1e-5 ? 1 : opacity; + blinnPhongMaterial->setAlpha( opacity < 1e-5 ? 1 : opacity ); } if ( AI_SUCCESS == material.Get( AI_MATKEY_TEXTURE( aiTextureType_DIFFUSE, 0 ), name ) ) { - blinnPhongMaterial->m_texDiffuse = m_filepath + "/" + assimpToCore( name ); - blinnPhongMaterial->m_hasTexDiffuse = true; + blinnPhongMaterial->setTexDiffuse( m_filepath + "/" + assimpToCore( name ) ); } if ( AI_SUCCESS == material.Get( AI_MATKEY_TEXTURE( aiTextureType_SPECULAR, 0 ), name ) ) { - blinnPhongMaterial->m_texSpecular = m_filepath + "/" + assimpToCore( name ); - blinnPhongMaterial->m_hasTexSpecular = true; + blinnPhongMaterial->setTexSpecular( m_filepath + "/" + assimpToCore( name ) ); } if ( AI_SUCCESS == material.Get( AI_MATKEY_TEXTURE( aiTextureType_SHININESS, 0 ), name ) ) { - blinnPhongMaterial->m_texShininess = m_filepath + "/" + assimpToCore( name ); - blinnPhongMaterial->m_hasTexShininess = true; + blinnPhongMaterial->setTexShininess( m_filepath + "/" + assimpToCore( name ) ); } if ( AI_SUCCESS == material.Get( AI_MATKEY_TEXTURE( aiTextureType_NORMALS, 0 ), name ) ) { - blinnPhongMaterial->m_texNormal = m_filepath + "/" + assimpToCore( name ); - blinnPhongMaterial->m_hasTexNormal = true; + blinnPhongMaterial->setTexNormal( m_filepath + "/" + assimpToCore( name ) ); } // Assimp loads objs bump maps as height maps, gj bro if ( AI_SUCCESS == material.Get( AI_MATKEY_TEXTURE( aiTextureType_HEIGHT, 0 ), name ) ) { - blinnPhongMaterial->m_texNormal = m_filepath + "/" + assimpToCore( name ); - blinnPhongMaterial->m_hasTexNormal = true; + blinnPhongMaterial->setTexNormal( m_filepath + "/" + assimpToCore( name ) ); } if ( AI_SUCCESS == material.Get( AI_MATKEY_TEXTURE( aiTextureType_OPACITY, 0 ), name ) ) { - blinnPhongMaterial->m_texOpacity = m_filepath + "/" + assimpToCore( name ); - blinnPhongMaterial->m_hasTexOpacity = true; + blinnPhongMaterial->setTexOpacity( m_filepath + "/" + assimpToCore( name ) ); } } } diff --git a/src/IO/filelist.cmake b/src/IO/filelist.cmake index 5ab4a4083d2..cb254c06e27 100644 --- a/src/IO/filelist.cmake +++ b/src/IO/filelist.cmake @@ -1,58 +1,74 @@ # ---------------------------------------------------- # This file can be generated from a script: -# To do so, run "./generateFilelistForModule.sh IO" +# To do so, run "./generateFilelistForModule.sh IO" # from ./scripts directory # ---------------------------------------------------- -set(io_sources CameraLoader/CameraLoader.cpp) +set(io_sources + CameraLoader/CameraLoader.cpp +) -set(io_headers CameraLoader/CameraLoader.hpp RaIO.hpp) +set(io_headers + CameraLoader/CameraLoader.hpp + RaIO.hpp +) if(RADIUM_IO_DEPRECATED) - list(APPEND io_sources deprecated/OBJFileManager.cpp deprecated/OFFFileManager.cpp) +list(APPEND io_sources + deprecated/OBJFileManager.cpp + deprecated/OFFFileManager.cpp +) - list(APPEND io_headers deprecated/FileManager.hpp deprecated/OBJFileManager.hpp - deprecated/OFFFileManager.hpp - ) +list(APPEND io_headers + deprecated/FileManager.hpp + deprecated/OBJFileManager.hpp + deprecated/OFFFileManager.hpp +) endif(RADIUM_IO_DEPRECATED) -if(RADIUM_IO_ASSIMP) - list( - APPEND - io_sources - AssimpLoader/AssimpAnimationDataLoader.cpp - AssimpLoader/AssimpCameraDataLoader.cpp - AssimpLoader/AssimpFileLoader.cpp - AssimpLoader/AssimpGeometryDataLoader.cpp - AssimpLoader/AssimpHandleDataLoader.cpp - AssimpLoader/AssimpLightDataLoader.cpp - ) - - list( - APPEND - io_headers - AssimpLoader/AssimpAnimationDataLoader.hpp - AssimpLoader/AssimpCameraDataLoader.hpp - AssimpLoader/AssimpFileLoader.hpp - AssimpLoader/AssimpGeometryDataLoader.hpp - AssimpLoader/AssimpHandleDataLoader.hpp - AssimpLoader/AssimpLightDataLoader.hpp - AssimpLoader/AssimpWrapper.hpp - ) - -endif(RADIUM_IO_ASSIMP) - -if(RADIUM_IO_TINYPLY) - list(APPEND io_sources TinyPlyLoader/TinyPlyFileLoader.cpp) - - list(APPEND io_headers TinyPlyLoader/TinyPlyFileLoader.hpp) - -endif(RADIUM_IO_TINYPLY) - -if(RADIUM_IO_VOLUMES) - list(APPEND io_sources VolumesLoader/VolumeLoader.cpp VolumesLoader/pvmutils.cpp) - - list(APPEND io_headers VolumesLoader/VolumeLoader.hpp VolumesLoader/pvmutils.hpp) - -endif(RADIUM_IO_VOLUMES) +if( RADIUM_IO_ASSIMP ) +list(APPEND io_sources + AssimpLoader/AssimpAnimationDataLoader.cpp + AssimpLoader/AssimpCameraDataLoader.cpp + AssimpLoader/AssimpFileLoader.cpp + AssimpLoader/AssimpGeometryDataLoader.cpp + AssimpLoader/AssimpHandleDataLoader.cpp + AssimpLoader/AssimpLightDataLoader.cpp +) + +list(APPEND io_headers + AssimpLoader/AssimpAnimationDataLoader.hpp + AssimpLoader/AssimpCameraDataLoader.hpp + AssimpLoader/AssimpFileLoader.hpp + AssimpLoader/AssimpGeometryDataLoader.hpp + AssimpLoader/AssimpHandleDataLoader.hpp + AssimpLoader/AssimpLightDataLoader.hpp + AssimpLoader/AssimpWrapper.hpp +) + +endif( RADIUM_IO_ASSIMP ) + +if( RADIUM_IO_TINYPLY ) +list(APPEND io_sources + TinyPlyLoader/TinyPlyFileLoader.cpp +) + +list(APPEND io_headers + TinyPlyLoader/TinyPlyFileLoader.hpp +) + +endif( RADIUM_IO_TINYPLY ) + +if( RADIUM_IO_VOLUMES ) +list(APPEND io_sources + VolumesLoader/VolumeLoader.cpp + VolumesLoader/pvmutils.cpp +) + +list(APPEND io_headers + VolumesLoader/VolumeLoader.hpp + VolumesLoader/pvmutils.hpp +) + +endif( RADIUM_IO_VOLUMES ) diff --git a/src/PluginBase/filelist.cmake b/src/PluginBase/filelist.cmake index e569f6b70f1..7e38da14a88 100644 --- a/src/PluginBase/filelist.cmake +++ b/src/PluginBase/filelist.cmake @@ -1,11 +1,21 @@ # ---------------------------------------------------- # This file can be generated from a script: -# To do so, run "./generateFilelistForModule.sh PluginBase" +# To do so, run "./generateFilelistForModule.sh PluginBase" # from ./scripts directory # ---------------------------------------------------- -set(pluginbase_sources RadiumPluginInterface.cpp) +set(pluginbase_sources + RadiumPluginInterface.cpp +) -set(pluginbase_headers PluginContext.hpp RaPluginBase.hpp RadiumPluginInterface.hpp) +set(pluginbase_headers + PluginContext.hpp + RaPluginBase.hpp + RadiumPluginInterface.hpp +) + +set(pluginbase_json + pluginMetaDataDebug.json + pluginMetaDataRelease.json +) -set(pluginbase_json pluginMetaDataDebug.json pluginMetaDataRelease.json) diff --git a/tests/unittest/CMakeLists.txt b/tests/unittest/CMakeLists.txt index aa26cd009d9..496468e1f74 100644 --- a/tests/unittest/CMakeLists.txt +++ b/tests/unittest/CMakeLists.txt @@ -22,6 +22,7 @@ set(test_src Core/obb.cpp Core/observer.cpp Core/polyline.cpp + Core/random.cpp Core/raycast.cpp Core/resources.cpp Core/string.cpp diff --git a/tests/unittest/Core/random.cpp b/tests/unittest/Core/random.cpp new file mode 100644 index 00000000000..9e3c36c8952 --- /dev/null +++ b/tests/unittest/Core/random.cpp @@ -0,0 +1,274 @@ +#include +#include +#include +#include +#include +#include + +#include + +using namespace Ra::Core; + +/// @brief Fake random generator class to test the samplers +class FakeGenerator : public Random::UniformGenerator +{ + public: + FakeGenerator() {}; + ~FakeGenerator() {}; + + Scalar get1D() override { return m_1D; } + + Vector2 get2D() override { return m_2D; } + + Vector3 get3D() override { return m_3D; } + + VectorN getXD( int dim ) override { return m_XD; } + + void set1D( Scalar s ) { m_1D = s; } + void set2D( Vector2 v ) { m_2D = v; } + void set3D( Vector3 v ) { m_3D = v; } + void setXD( VectorN v ) { m_XD = v; } + + private: + Scalar m_1D; + Vector2 m_2D; + Vector3 m_3D; + VectorN m_XD; +}; + +Vector3 uniformSamplerGetDir( FakeGenerator* fg ) { + Vector3 dir; + Vector2 u = fg->get2D(); + + Scalar cosTheta = std::cos( u[0] ); + Scalar sinTheta = std::cos( Math::Pi / 2_ra - u[0] ); + Scalar phi = 2 * Math::Pi * u[1]; + + dir[0] = sinTheta * std::cos( phi ); + dir[1] = sinTheta * std::sin( phi ); + dir[2] = cosTheta; + + return dir; +} + +Vector3 cosineWeightedSamplerGetDir( FakeGenerator* fg ) { + Vector3 dir; + Vector2 u = fg->get2D(); + + Scalar cosTheta = std::cos( std::sqrt( u[0] ) ); + Scalar sinTheta = std::cos( Math::Pi / 2_ra - std::sqrt( u[0] ) ); + Scalar phi = 2 * Math::Pi * u[1]; + + dir[0] = sinTheta * std::cos( phi ); + dir[1] = sinTheta * std::sin( phi ); + dir[2] = cosTheta; + + return dir; +} + +Vector3 blinnPhongSamplerGetDir( FakeGenerator* fg, Scalar shininess ) { + Vector3 dir; + Vector2 u = fg->get2D(); + + Scalar cosTheta = std::pow( 1_ra - u[0], 1_ra / ( shininess + 2_ra ) ); + Scalar sinTheta = std::sqrt( 1_ra - cosTheta * cosTheta ); + Scalar phi = 2 * Math::Pi * u[1]; + + dir[0] = sinTheta * std::cos( phi ); + dir[1] = sinTheta * std::sin( phi ); + dir[2] = cosTheta; + + return dir; +} + +Scalar blinnPhongPdf( Vector3 w_i, Vector3 w_o, Vector3 normal, Scalar shininess ) { + w_i.normalize(); + w_o.normalize(); + + Vector3 halfway = ( w_i + w_o ).normalized(); + Scalar cosTheta = normal.dot( halfway ); + + return ( shininess + 2_ra ) * std::pow( cosTheta, shininess ) / ( 2_ra * Math::Pi ); +} + +Vector3 blinnPhongReflect( Vector3 inDir, Vector3 normal ) { + inDir.normalize(); + normal.normalize(); + return ( -inDir + 2 * inDir.dot( normal ) * normal ).normalized(); +} + +TEST_CASE( "Core/Random/UniformSphereSampler" ) { + SECTION( "getDir" ) { + auto fg = FakeGenerator(); + fg.set2D( { 0_ra, 0_ra } ); + auto p = Random::UniformSphereSampler::getDir( &fg ); + Vector3 d = p.first; + Vector3 dtest = uniformSamplerGetDir( &fg ); + REQUIRE( Math::areApproxEqual( d.x(), dtest.x() ) ); + REQUIRE( Math::areApproxEqual( d.y(), dtest.y() ) ); + REQUIRE( Math::areApproxEqual( d.z(), dtest.z() ) ); + + fg.set2D( { 1_ra, 1_ra } ); + p = Random::UniformSphereSampler::getDir( &fg ); + d = p.first; + dtest = uniformSamplerGetDir( &fg ); + REQUIRE( Math::areApproxEqual( d.x(), dtest.x() ) ); + REQUIRE( Math::areApproxEqual( d.y(), dtest.y() ) ); + REQUIRE( Math::areApproxEqual( d.z(), dtest.z() ) ); + + fg.set2D( { 0.5_ra, 0.5_ra } ); + p = Random::UniformSphereSampler::getDir( &fg ); + d = p.first; + dtest = uniformSamplerGetDir( &fg ); + REQUIRE( Math::areApproxEqual( d.x(), dtest.x() ) ); + REQUIRE( Math::areApproxEqual( d.y(), dtest.y() ) ); + REQUIRE( Math::areApproxEqual( d.z(), dtest.z() ) ); + } + + SECTION( "pdf" ) { + Vector3 normal, dir; + Scalar usspdf; + + normal = { 0_ra, 0_ra, 1_ra }; + dir = { 0_ra, 0_ra, 1_ra }; + usspdf = Random::UniformSphereSampler::pdf( dir, normal ); + REQUIRE( Math::areApproxEqual( usspdf, 1_ra / ( 2_ra * Math::Pi ) ) ); + + dir = { 0_ra, 1_ra, 0_ra }; + usspdf = Random::UniformSphereSampler::pdf( dir, normal ); + REQUIRE( Math::areApproxEqual( usspdf, 1_ra / ( 2_ra * Math::Pi ) ) ); + + dir = { 1_ra, 0_ra, 0_ra }; + usspdf = Random::UniformSphereSampler::pdf( dir, normal ); + REQUIRE( Math::areApproxEqual( usspdf, 1_ra / ( 2_ra * Math::Pi ) ) ); + } +} + +TEST_CASE( "Core/Random/CosineWeightedSphereSampler" ) { + SECTION( "getDir" ) { + auto fg = FakeGenerator(); + fg.set2D( { 0_ra, 0_ra } ); + auto p = Random::CosineWeightedSphereSampler::getDir( &fg ); + Vector3 d = p.first; + Vector3 dtest = cosineWeightedSamplerGetDir( &fg ); + REQUIRE( Math::areApproxEqual( d.x(), dtest.x() ) ); + REQUIRE( Math::areApproxEqual( d.y(), dtest.y() ) ); + REQUIRE( Math::areApproxEqual( d.z(), dtest.z() ) ); + + fg.set2D( { 1_ra, 1_ra } ); + p = Random::CosineWeightedSphereSampler::getDir( &fg ); + d = p.first; + dtest = cosineWeightedSamplerGetDir( &fg ); + REQUIRE( Math::areApproxEqual( d.x(), dtest.x() ) ); + REQUIRE( Math::areApproxEqual( d.y(), dtest.y() ) ); + REQUIRE( Math::areApproxEqual( d.z(), dtest.z() ) ); + + fg.set2D( { 0.5_ra, 0.5_ra } ); + p = Random::CosineWeightedSphereSampler::getDir( &fg ); + d = p.first; + dtest = cosineWeightedSamplerGetDir( &fg ); + REQUIRE( Math::areApproxEqual( d.x(), dtest.x() ) ); + REQUIRE( Math::areApproxEqual( d.y(), dtest.y() ) ); + REQUIRE( Math::areApproxEqual( d.z(), dtest.z() ) ); + } + + SECTION( "pdf" ) { + Vector3 normal, dir; + Scalar usspdf; + + normal = { 0_ra, 0_ra, 1_ra }; + dir = { 0_ra, 0_ra, 1_ra }; + usspdf = Random::CosineWeightedSphereSampler::pdf( dir, normal ); + REQUIRE( Math::areApproxEqual( usspdf, 1_ra / Math::Pi ) ); + + dir = { 0_ra, 1_ra, 0_ra }; + usspdf = Random::CosineWeightedSphereSampler::pdf( dir, normal ); + REQUIRE( Math::areApproxEqual( usspdf, 0_ra ) ); + + dir = { 1_ra, 0_ra, 0.3_ra }; + usspdf = Random::CosineWeightedSphereSampler::pdf( dir, normal ); + REQUIRE( Math::areApproxEqual( usspdf, dir.normalized().dot( normal ) / Math::Pi ) ); + } +} + +TEST_CASE( "Core/Random/BlinnPhongSphereSampler" ) { + SECTION( "getDir" ) { + auto fg = FakeGenerator(); + fg.set2D( { 0_ra, 0_ra } ); + auto p = Random::BlinnPhongSphereSampler::getDir( &fg, 8_ra ); + Vector3 d = p.first; + Vector3 dtest = blinnPhongSamplerGetDir( &fg, 8_ra ); + REQUIRE( Math::areApproxEqual( d.x(), dtest.x() ) ); + REQUIRE( Math::areApproxEqual( d.y(), dtest.y() ) ); + REQUIRE( Math::areApproxEqual( d.z(), dtest.z() ) ); + + fg.set2D( { 1_ra, 1_ra } ); + p = Random::BlinnPhongSphereSampler::getDir( &fg, 32_ra ); + d = p.first; + dtest = blinnPhongSamplerGetDir( &fg, 32_ra ); + REQUIRE( Math::areApproxEqual( d.x(), dtest.x() ) ); + REQUIRE( Math::areApproxEqual( d.y(), dtest.y() ) ); + REQUIRE( Math::areApproxEqual( d.z(), dtest.z() ) ); + + fg.set2D( { 0.5_ra, 0.5_ra } ); + p = Random::BlinnPhongSphereSampler::getDir( &fg, 128_ra ); + d = p.first; + dtest = blinnPhongSamplerGetDir( &fg, 128_ra ); + REQUIRE( Math::areApproxEqual( d.x(), dtest.x() ) ); + REQUIRE( Math::areApproxEqual( d.y(), dtest.y() ) ); + REQUIRE( Math::areApproxEqual( d.z(), dtest.z() ) ); + } + + SECTION( "pdf" ) { + Vector3 normal; + Scalar pdf, testpdf; + + normal = { 0_ra, 0_ra, 1_ra }; + pdf = Random::BlinnPhongSphereSampler::pdf( + { 0_ra, 0_ra, 1_ra }, + Random::BlinnPhongSphereSampler::reflect( { 0_ra, 0_ra, 1_ra }, normal ), + normal, + 8_ra ); + testpdf = + blinnPhongPdf( { 0_ra, 0_ra, 1_ra }, + Random::BlinnPhongSphereSampler::reflect( { 0_ra, 0_ra, 1_ra }, normal ), + normal, + 8_ra ); + REQUIRE( Math::areApproxEqual( pdf, testpdf ) ); + + pdf = Random::BlinnPhongSphereSampler::pdf( + { 0_ra, 1_ra, 0_ra }, { 0.02_ra, -0.97_ra, 0.01_ra }, normal, 4_ra ); + testpdf = + blinnPhongPdf( { 0_ra, 1_ra, 0_ra }, { 0.02_ra, -0.97_ra, 0.01_ra }, normal, 4_ra ); + REQUIRE( Math::areApproxEqual( pdf, testpdf ) ); + + pdf = Random::BlinnPhongSphereSampler::pdf( + { 1_ra, 0_ra, 0.5_ra }, { -1.01_ra, 0.03_ra, 0.49_ra }, normal, 32_ra ); + testpdf = + blinnPhongPdf( { 1_ra, 0_ra, 0.5_ra }, { -1.01_ra, 0.03_ra, 0.49_ra }, normal, 32_ra ); + REQUIRE( Math::areApproxEqual( pdf, testpdf ) ); + } + + SECTION( "reflect" ) { + Vector3 normal = { 0_ra, 0_ra, 1_ra }; + + Vector3 reflected = + Random::BlinnPhongSphereSampler::reflect( { 0_ra, 0_ra, -1_ra }, normal ); + Vector3 testreflect = blinnPhongReflect( { 0_ra, 0_ra, -1_ra }, normal ); + REQUIRE( Math::areApproxEqual( reflected.x(), testreflect.x() ) ); + REQUIRE( Math::areApproxEqual( reflected.y(), testreflect.y() ) ); + REQUIRE( Math::areApproxEqual( reflected.z(), testreflect.z() ) ); + + reflected = Random::BlinnPhongSphereSampler::reflect( { 1_ra, 0_ra, 0_ra }, normal ); + testreflect = blinnPhongReflect( { 1_ra, 0_ra, 0_ra }, normal ); + REQUIRE( Math::areApproxEqual( reflected.x(), testreflect.x() ) ); + REQUIRE( Math::areApproxEqual( reflected.y(), testreflect.y() ) ); + REQUIRE( Math::areApproxEqual( reflected.z(), testreflect.z() ) ); + + reflected = Random::BlinnPhongSphereSampler::reflect( { 0.5_ra, 0.5_ra, -0.5_ra }, normal ); + testreflect = blinnPhongReflect( { 0.5_ra, 0.5_ra, -0.5_ra }, normal ); + REQUIRE( Math::areApproxEqual( reflected.x(), testreflect.x() ) ); + REQUIRE( Math::areApproxEqual( reflected.y(), testreflect.y() ) ); + REQUIRE( Math::areApproxEqual( reflected.z(), testreflect.z() ) ); + } +}