├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── README.md ├── include └── Covariance │ ├── Covariance2D.hpp │ ├── Covariance4D.hpp │ ├── InvCovariance4D.hpp │ └── Matrix.hpp ├── tests ├── Covariance4D.cpp └── InvCovariance4D.cpp └── tutorials ├── common.hpp ├── opengl.hpp ├── tutorial1.cpp ├── tutorial2.cpp ├── tutorial2.hpp └── tutorial2gl.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | .vscode 3 | tags 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "modules/tinyexr"] 2 | path = modules/tinyexr 3 | url = https://github.com/syoyo/tinyexr.git 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.2) 2 | project (CovarianceTracing CXX) 3 | 4 | # For debug build 5 | set(CMAKE_BUILD_TYPE RELEASE) 6 | 7 | if ( CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) 8 | add_definitions ("-Wall -pedantic -Wno-deprecated -Wno-deprecated-declarations") 9 | add_definitions ("-Ofast -ffast-math -fopenmp") 10 | endif ( CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) 11 | 12 | include_directories ("include" "modules" ".") 13 | 14 | # Add dependencies 15 | find_package(OpenGL) 16 | find_package(GLUT) 17 | find_package(OpenMP) 18 | 19 | # Add main test suite 20 | add_executable (TestCovariance4D tests/Covariance4D.cpp) 21 | add_executable (TestInvCovariance4D tests/InvCovariance4D.cpp) 22 | target_compile_features(TestCovariance4D PRIVATE cxx_range_for) 23 | target_compile_features(TestInvCovariance4D PRIVATE cxx_range_for) 24 | 25 | enable_testing() 26 | add_test(TestCovariance4D TestCovariance4D) 27 | add_test(TestInvCovariance4D TestInvCovariance4D) 28 | 29 | add_executable (Tutorial1 tutorials/tutorial1.cpp) 30 | target_compile_features(Tutorial1 PRIVATE cxx_range_for) 31 | add_executable (Tutorial2 tutorials/tutorial2.cpp) 32 | target_compile_features(Tutorial2 PRIVATE cxx_range_for) 33 | 34 | if(OPENGL_FOUND AND GLUT_FOUND) 35 | add_definitions("-DGL_GLEXT_PROTOTYPES") 36 | include_directories("${OPENGL_INCLUDE_DIR}/Headers" ${GLUT_INCLUDE_DIR}) 37 | add_executable(Tutorial2gl tutorials/tutorial2gl.cpp) 38 | target_compile_features(Tutorial2gl PRIVATE cxx_range_for) 39 | target_link_libraries(Tutorial2gl ${OPENGL_gl_LIBRARY} ${GLUT_glut_LIBRARY}) 40 | endif(OPENGL_FOUND AND GLUT_FOUND) 41 | 42 | # Add OpenMP support 43 | if ( OPENMP_FOUND ) 44 | target_compile_options(Tutorial1 PUBLIC ${OpenMP_CXX_FLAGS}) 45 | target_compile_options(Tutorial2 PUBLIC ${OpenMP_CXX_FLAGS}) 46 | target_compile_options(Tutorial2gl PUBLIC ${OpenMP_CXX_FLAGS}) 47 | if (NOT WIN32 ) 48 | set_target_properties(Tutorial1 PROPERTIES LINK_FLAGS ${OpenMP_CXX_FLAGS}) 49 | set_target_properties(Tutorial2 PROPERTIES LINK_FLAGS ${OpenMP_CXX_FLAGS}) 50 | set_target_properties(Tutorial2gl PROPERTIES LINK_FLAGS ${OpenMP_CXX_FLAGS}) 51 | endif (NOT WIN32 ) 52 | endif( OPENMP_FOUND ) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Covariance tracing 2 | 3 | This repository contains the source code to use [Covariance tracing](https://hal.inria.fr/hal-00814164) in a rendering engine. This C++ code is header-only and has no dependency except of STL. 4 | 5 | To use covariance tracing in your software, simply include the corresponding header file available in `include` and specify the two arguments of the template class (floatting point representation and Vector class): 6 | 7 | #include 8 | 9 | Covariance::Covariance4D cov; 10 | 11 | See the different tutorials and source code documentation to get a better view on how to use this class in your code. To build the tutorials, remember to load the `tinyexr` git submodule. 12 | -------------------------------------------------------------------------------- /include/Covariance/Covariance2D.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Covariance { 4 | 5 | /*! The 2D isotropic version of Covariance Tracing. 6 | * This code assumes that light transport in 2 dimensions and only 7 | * store a 2D covariance matrix for space and angles. 8 | */ 9 | struct Covariance2D { 10 | 11 | float matrix[3]; 12 | 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /include/Covariance/Covariance4D.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // STL includes 4 | #include 5 | #include 6 | #include 7 | 8 | // Local includes 9 | #include "Matrix.hpp" 10 | 11 | #define COV_MAX_FLOAT 1.0E+5 12 | #define COV_MIN_FLOAT 1.0E-5 13 | 14 | #define USE_WOODBURY_IDENTITY 15 | 16 | namespace Covariance { 17 | 18 | /* The 4D version of Covariance Tracing. 19 | * This is the timeless version of Covariance Tracing. It allows to compute 20 | * covariance information of the local lightfield around the central ray 21 | * position (Belcour 2013). 22 | * 23 | * The covariance matrix represent the covariance of the local light field 24 | * in the (X, Y, U, V) local coordinate frame. 25 | * 26 | * The covariance matrix is a 4x4 symetric Matrix and we only deal with the 27 | * upper triangle for all the operations. Variance terms are at indices 28 | * 0, 2, 5, 9. 29 | * 30 | * The matrix is indexed the following way: 31 | * 32 | * C = ( 0 1 3 6) 33 | * ( * 2 4 7) 34 | * ( * * 5 8) 35 | * ( * * * 9) 36 | * 37 | * Thus the full indexing of the matrix is the following: 38 | * 39 | * C = ( 0 1 3 6) 40 | * ( 1 2 4 7) 41 | * ( 3 4 5 8) 42 | * ( 6 7 8 9) 43 | */ 44 | template 45 | struct Covariance4D { 46 | 47 | std::array matrix; 48 | Vector x, y, z; 49 | 50 | 51 | //////////////////////// 52 | // Atomic operators // 53 | //////////////////////// 54 | 55 | /* Travel operator 56 | * 57 | * 'd' distance of travel along the central ray 58 | */ 59 | inline void Travel(Float d) { 60 | ShearAngleSpace(d, d); 61 | } 62 | 63 | /* Curvature operator 64 | * 65 | * 'kx' curvature along the X direction 66 | * 'ky' curvature along the Y direction 67 | */ 68 | inline void Curvature(Float kx, Float ky) { 69 | ShearSpaceAngle(kx, ky); 70 | } 71 | 72 | /* Cosine operator 73 | * 74 | * 'wz' The incident direction's elevation in the local frame 75 | */ 76 | inline void Cosine(Float wz) { 77 | const Float theta = acos(wz); 78 | const Float dist = std::abs(0.5*M_PI-theta); 79 | const Float frequ = 2.0 / M_PI; 80 | const Float freqv = 1.0 / fmax(dist, 1.0E-10); 81 | matrix[5] += frequ*frequ; 82 | matrix[9] += freqv*freqv; 83 | } 84 | 85 | /* Reflection operator 86 | * 87 | * 'suu' the covariance of the BRDF along the X axis. 88 | * 'svv' the covariance of the BRDF along the Y axis. 89 | */ 90 | inline void Reflection(Float suu, Float svv) { 91 | if(suu < COV_MAX_FLOAT && svv < COV_MAX_FLOAT) { 92 | ProductUV(fmax(suu, COV_MIN_FLOAT), fmax(svv, COV_MIN_FLOAT)); 93 | } 94 | } 95 | 96 | 97 | ///////////////////////////// 98 | // Local Frame alignment // 99 | ///////////////////////////// 100 | 101 | /* Perform the projection of the incomming lightfield on the surface with 102 | * normal n. This function assumes that the surface normal is in the 103 | * opposite direction to the main vector of the lightfield. 104 | * 105 | * 'n' the surface normal. 106 | */ 107 | inline void Projection(const Vector& n) { 108 | 109 | const auto cx = Vector::Dot(x, n); 110 | const auto cy = Vector::Dot(y, n); 111 | 112 | // Rotate the Frame to be aligned with plane. 113 | const Float alpha = (cx != 0.0) ? atan2(cx, cy) : 0.0; 114 | const Float c = cos(alpha), s = -sin(alpha); 115 | Rotate(c, s); 116 | 117 | // Scale the componnent that project by the cosine of the ray direction 118 | // and the normal. 119 | const Float cosine = Vector::Dot(z, n); 120 | ScaleY(std::abs(cosine)); 121 | 122 | // Update direction vectors. 123 | x = c*x + s*y; 124 | z = (cosine < 0.0f) ? -n : n; 125 | y = (cosine < 0.0f) ? Vector::Cross(x, z) : Vector::Cross(z, x); 126 | } 127 | 128 | /* Perform the projection of a lightfield defined on a surface to an 129 | * outgoing direction. This function assumes that the lightfield main 130 | * vector is the surface normal and that the outgoing vector is in the 131 | * same direction. 132 | * 133 | * 'd' the outgoing direction. 134 | */ 135 | inline void InverseProjection(const Vector& d) { 136 | 137 | const auto cx = Vector::Dot(x, d); 138 | const auto cy = Vector::Dot(y, d); 139 | 140 | // Rotate the Frame to be aligned with plane. 141 | const Float alpha = (cx != 0.0) ? atan2(cx, cy) : 0.0; 142 | const Float c = cos(alpha), s = -sin(alpha); 143 | Rotate(c, s); // Rotate of -alpha 144 | 145 | // Scale the componnent that project by the inverse cosine of the ray 146 | // direction and the normal. 147 | const Float cosine = Vector::Dot(z, d); 148 | if(cosine < 0.0f) { 149 | ScaleV(-1.0f); 150 | ScaleU(-1.0f); 151 | } 152 | ScaleY(1.0/fmax(fabs(cosine), COV_MIN_FLOAT)); 153 | 154 | // Update direction vectors. 155 | x = c*x + s*y; 156 | z = d; 157 | y = Vector::Cross(z, x); 158 | } 159 | 160 | 161 | /* Note: for the case of tracking the local frame of the light field 162 | * performing the symmetry makes not difference since the local frame 163 | * is ajusted with respect to symmetry. 164 | */ 165 | inline void Symmetry() { 166 | matrix[3] = -matrix[3]; 167 | matrix[4] = -matrix[4]; 168 | matrix[6] = -matrix[6]; 169 | matrix[7] = -matrix[7]; 170 | } 171 | 172 | 173 | //////////////////////// 174 | // Matrix scaling // 175 | //////////////////////// 176 | 177 | inline void ScaleX(Float alpha) { 178 | matrix[0] *= alpha*alpha; 179 | matrix[1] *= alpha; 180 | matrix[3] *= alpha; 181 | matrix[6] *= alpha; 182 | } 183 | 184 | inline void ScaleY(Float alpha) { 185 | matrix[1] *= alpha; 186 | matrix[2] *= alpha*alpha; 187 | matrix[4] *= alpha; 188 | matrix[7] *= alpha; 189 | } 190 | 191 | inline void ScaleU(Float alpha) { 192 | matrix[3] *= alpha; 193 | matrix[4] *= alpha; 194 | matrix[5] *= alpha*alpha; 195 | matrix[8] *= alpha; 196 | } 197 | 198 | inline void ScaleV(Float alpha) { 199 | matrix[6] *= alpha; 200 | matrix[7] *= alpha; 201 | matrix[8] *= alpha; 202 | matrix[9] *= alpha*alpha; 203 | } 204 | 205 | 206 | //////////////////////// 207 | // Matrix shearing // 208 | //////////////////////// 209 | 210 | // Shear the Spatial (x,y) domain by the Angular (u,v). 211 | // \param cx amount of shear along the x direction. 212 | // \param cy amount of shear along the y direction. 213 | inline void ShearSpaceAngle(Float cx, Float cy) { 214 | matrix[0] += (matrix[5]*cx - 2*matrix[3])*cx; 215 | matrix[1] += matrix[8]*cx*cy - (matrix[4]*cy + matrix[6]*cx); 216 | matrix[2] += (matrix[9]*cy - 2*matrix[7])*cy; 217 | matrix[3] -= matrix[5]*cx; 218 | matrix[4] -= matrix[8]*cy; 219 | matrix[6] -= matrix[8]*cx; 220 | matrix[7] -= matrix[9]*cy; 221 | } 222 | 223 | // Shear the angular (U, V) domain by the spatial (X, Y) domain. 224 | // \param cu amount of shear along the U direction. 225 | // \param cy amount of shear along the V direction. 226 | inline void ShearAngleSpace(Float cu, Float cv) { 227 | matrix[5] += (matrix[0]*cu - 2*matrix[3])*cu; 228 | matrix[3] -= matrix[0]*cu; 229 | matrix[8] += matrix[1]*cu*cv - (matrix[4]*cv + matrix[6]*cu); 230 | matrix[4] -= matrix[1]*cv; 231 | matrix[6] -= matrix[1]*cu; 232 | matrix[9] += (matrix[2]*cv - 2*matrix[7])*cv; 233 | matrix[7] -= matrix[2]*cv; 234 | } 235 | 236 | 237 | //////////////////////// 238 | // Matrix rotation // 239 | //////////////////////// 240 | 241 | // alpha rotation angle 242 | inline void Rotate(Float alpha) { 243 | const Float c = cos(alpha); 244 | const Float s = sin(alpha); 245 | Rotate(c, s); 246 | } 247 | 248 | // 'c' the cosine of the rotation angle 249 | // 's' the sine of the rotation angle 250 | inline void Rotate(Float c, Float s) { 251 | const Float cs = c*s; 252 | const Float c2 = c*c; 253 | const Float s2 = s*s; 254 | 255 | const Float cov_xx = matrix[0]; 256 | const Float cov_xy = matrix[1]; 257 | const Float cov_yy = matrix[2]; 258 | const Float cov_xu = matrix[3]; 259 | const Float cov_yu = matrix[4]; 260 | const Float cov_uu = matrix[5]; 261 | const Float cov_xv = matrix[6]; 262 | const Float cov_yv = matrix[7]; 263 | const Float cov_uv = matrix[8]; 264 | const Float cov_vv = matrix[9]; 265 | 266 | // Rotation of the space 267 | matrix[0] = c2 * cov_xx + 2*cs * cov_xy + s2 * cov_yy; 268 | matrix[1] = (c2-s2) * cov_xy + cs * (cov_yy - cov_xx); 269 | matrix[2] = c2 * cov_yy - 2*cs * cov_xy + s2 * cov_xx; 270 | 271 | // Rotation of the angle 272 | matrix[5] = c2 * cov_uu + 2*cs * cov_uv + s2 * cov_vv; 273 | matrix[8] = (c2-s2) * cov_uv + cs * (cov_vv - cov_uu); 274 | matrix[9] = c2 * cov_vv - 2*cs * cov_uv + s2 * cov_uu; 275 | 276 | // Covariances 277 | matrix[3] = c2 * cov_xu + cs * (cov_xv + cov_yu) + s2 * cov_yv; 278 | matrix[4] = c2 * cov_yu + cs * (cov_yv - cov_xu) - s2 * cov_xv; 279 | matrix[6] = c2 * cov_xv + cs * (cov_yv - cov_xu) - s2 * cov_yu; 280 | matrix[7] = c2 * cov_yv - cs * (cov_xv + cov_yu) + s2 * cov_xu; 281 | } 282 | 283 | 284 | //////////////////////// 285 | // Matrix inverse // 286 | //////////////////////// 287 | 288 | /* Compute the inverse of the covariance matrix. 289 | * 290 | * We add an epsilon to the diagonal in order to ensure that the matrix 291 | * can be inverted. 292 | * 293 | * 'inverse' needs to be a 4x4 preallocated matrix (16 Floats).. 294 | */ 295 | void InverseMatrix(Float* inverse) const { 296 | // Compute the inverse matrix. We need to add an epsilon to the 297 | // diagonal in order to ensure that the matrix can be inverted. 298 | inverse[ 0] = matrix[ 0] + COV_MIN_FLOAT; 299 | inverse[ 1] = matrix[ 1]; inverse[ 4] = matrix[ 1]; 300 | inverse[ 5] = matrix[ 2] + COV_MIN_FLOAT; 301 | inverse[ 2] = matrix[ 3]; inverse[ 8] = matrix[ 3]; 302 | inverse[ 6] = matrix[ 4]; inverse[ 9] = matrix[ 4]; 303 | inverse[10] = matrix[ 5] + COV_MIN_FLOAT; 304 | inverse[ 3] = matrix[ 6]; inverse[12] = matrix[ 6]; 305 | inverse[ 7] = matrix[ 7]; inverse[13] = matrix[ 7]; 306 | inverse[11] = matrix[ 8]; inverse[14] = matrix[ 8]; 307 | inverse[15] = matrix[ 9] + COV_MIN_FLOAT; 308 | 309 | if(!Inverse(inverse, 4)) { throw 1; } 310 | } 311 | 312 | 313 | 314 | //////////////////////// 315 | // Product of signals // 316 | //////////////////////// 317 | 318 | // Evaluate the covariance matrix of the product of the local lightfield 319 | // and a angularly varying only signal (like a BSDF). 320 | // 321 | // 'su' is the sigma u of the inverse angular signal's covariance matrix. 322 | // 'sv' the sigma v of the inverse angular signal's covariance matrix. 323 | inline void ProductUV(Float su, Float sv) { 324 | 325 | if(su == 0.0 && sv == 0.0) { 326 | return; 327 | } 328 | 329 | #ifdef USE_WOODBURY_IDENTITY 330 | const Float cov_xu = matrix[3]; 331 | const Float cov_yu = matrix[4]; 332 | const Float cov_uu = matrix[5]; 333 | const Float cov_xv = matrix[6]; 334 | const Float cov_yv = matrix[7]; 335 | const Float cov_uv = matrix[8]; 336 | const Float cov_vv = matrix[9]; 337 | 338 | // The following is an application of the Woodbury matrix identity for 339 | // a rank 2 C matrix. See: 340 | // http://en.wikipedia.org/wiki/Woodbury_matrix_identity 341 | const Float sig_u = cov_uu+su, sig_v = cov_vv+sv; 342 | const Float og = (sig_u*sig_v - cov_uv*cov_uv); 343 | const Float g = 1.0f / og; 344 | 345 | matrix[0] -= g*(cov_xu*(sig_v*cov_xu-cov_uv*cov_xv) + 346 | cov_xv*(sig_u*cov_xv-cov_uv*cov_xu)); 347 | 348 | matrix[1] -= g*(cov_yu*(sig_v*cov_xu-cov_uv*cov_xv) + 349 | cov_yv*(sig_u*cov_xv-cov_uv*cov_xu)); 350 | matrix[2] -= g*(cov_yu*(sig_v*cov_yu-cov_uv*cov_yv) + 351 | cov_yv*(sig_u*cov_yv-cov_uv*cov_yu)); 352 | 353 | matrix[3] -= g*(cov_uu*(sig_v*cov_xu-cov_uv*cov_xv) + 354 | cov_uv*(sig_u*cov_xv-cov_uv*cov_xu)); 355 | matrix[4] -= g*(cov_uu*(sig_v*cov_yu-cov_uv*cov_yv) + 356 | cov_uv*(sig_u*cov_yv-cov_uv*cov_yu)); 357 | matrix[5] -= g*(cov_uu*(sig_v*cov_uu-cov_uv*cov_uv) + 358 | cov_uv*(sig_u*cov_uv-cov_uv*cov_uu)); 359 | 360 | matrix[6] -= g*(cov_uv*(sig_v*cov_xu-cov_uv*cov_xv) + 361 | cov_vv*(sig_u*cov_xv-cov_uv*cov_xu)); 362 | matrix[7] -= g*(cov_uv*(sig_v*cov_yu-cov_uv*cov_yv) + 363 | cov_vv*(sig_u*cov_yv-cov_uv*cov_yu)); 364 | matrix[8] -= g*(cov_uv*(sig_v*cov_uu-cov_uv*cov_uv) + 365 | cov_vv*(sig_u*cov_uv-cov_uv*cov_uu)); 366 | matrix[9] -= g*(cov_uv*(sig_v*cov_uv-cov_uv*cov_vv) + 367 | cov_vv*(sig_u*cov_vv-cov_uv*cov_uv)); 368 | #else 369 | // This method computes the inverse of the covariance matrix 370 | // explicitely, adds the inverse of the BRDF matrix to the 371 | // angular component, and then invert the matrix again to obtain 372 | // the covariance matrix. This is very slow method to check 373 | // the Woodbury formula. 374 | 375 | Float inverse[16]; 376 | inverse[ 0] = matrix[ 0] + COV_MIN_FLOAT; 377 | inverse[ 1] = matrix[ 1]; inverse[ 4] = matrix[ 1]; 378 | inverse[ 5] = matrix[ 2] + COV_MIN_FLOAT; 379 | inverse[ 2] = matrix[ 3]; inverse[ 8] = matrix[ 3]; 380 | inverse[ 6] = matrix[ 4]; inverse[ 9] = matrix[ 4]; 381 | inverse[10] = matrix[ 5] + COV_MIN_FLOAT; 382 | inverse[ 3] = matrix[ 6]; inverse[12] = matrix[ 6]; 383 | inverse[ 7] = matrix[ 7]; inverse[13] = matrix[ 7]; 384 | inverse[11] = matrix[ 8]; inverse[14] = matrix[ 8]; 385 | inverse[15] = matrix[ 9] + COV_MIN_FLOAT; 386 | 387 | if(!Inverse(inverse, 4)) { throw 1; } 388 | inverse[10] += 1.0f/std::max(sv, COV_MIN_FLOAT); 389 | inverse[15] += 1.0f/std::max(su, COV_MIN_FLOAT); 390 | 391 | if(!Inverse(inverse, 4)) { throw 1; } 392 | matrix[ 0] = inverse[ 0]; 393 | matrix[ 1] = inverse[ 1]; 394 | matrix[ 2] = inverse[ 5]; 395 | matrix[ 3] = inverse[ 2]; 396 | matrix[ 4] = inverse[ 6]; 397 | matrix[ 5] = inverse[10]; 398 | matrix[ 6] = inverse[ 3]; 399 | matrix[ 7] = inverse[ 7]; 400 | matrix[ 8] = inverse[11]; 401 | matrix[ 9] = inverse[15]; 402 | #endif 403 | } 404 | 405 | ///////////////////////////// 406 | // Add covariance together // 407 | ///////////////////////////// 408 | 409 | void Add(const Covariance4D& cov, Float L1=1.0f, Float L2=1.0f) { 410 | const Float L = L1+L2; 411 | if(L <= 0.0f) return; 412 | 413 | for(unsigned short i=0; i<10; ++i) { 414 | matrix[i] = (L1*matrix[i] + L2*cov.matrix[i]) / L; 415 | } 416 | } 417 | 418 | //////////////////////////// 419 | // Spatio-angular Filters // 420 | //////////////////////////// 421 | 422 | /* Compute the spatio-angular extent of the space related to the 423 | * covariance matrix's filter. The extent is provided as vectors 424 | * 'Dx', 'Dy', and 'Du', 'Dv'. Those vectors are the main axis of 425 | * the filter's fooprint. 426 | 427 | * Vector like 'Dx' and 'Dy' are expressed in the local tangent frame 428 | * x, y using the first two components: 429 | * Dx = [x, y, 0] 430 | 431 | * To extract Dx, Dy, Du and Dv, we do an eigen-decomposition of the 432 | * covariance matrix and use the normalized eigen-vectors as the axis 433 | * of the extent and the eigen-values are the squared extent of the 434 | * polygonal shape. 435 | * 436 | * This spatio-angular polygonal shape can be used to specify an 437 | * equivalent ray differential [Igehy 1999]. 438 | */ 439 | void Extent(Vector& Dx, Vector& Dy, Vector& Du, Vector& Dv) const { 440 | // Compute the inverse matrix. 441 | Float inverse[16]; 442 | this->InverseMatrix(inverse); 443 | 444 | // T = trace and D = det of the spatial submatrix 445 | Float T = inverse[0]+inverse[5]; 446 | Float D = inverse[0]*inverse[5] - inverse[1]*inverse[1]; 447 | 448 | // Solve the 2nd order polynomial roots of p(l) = l^2 - l T + D. 449 | // This gives us the eigen values. 450 | Float d = 0.25*T*T - D; 451 | if(d < 0.0) { throw 1; } // No solution exists 452 | Float l1 = 0.5*T + sqrt(d); 453 | Float l2 = 0.5*T - sqrt(d); 454 | 455 | if(abs(inverse[1]) > COV_MIN_FLOAT) { 456 | Dx.x = l1 - inverse[5]; 457 | Dx.y = inverse[1]; 458 | Dx.z = 0.0; 459 | Dy.x = l2 - inverse[5]; 460 | Dy.y = inverse[1]; 461 | Dy.z = 0.0; 462 | 463 | Dx.Normalize(); 464 | Dy.Normalize(); 465 | 466 | Dx = sqrt(l1)/(2.0*M_PI) * Dx; 467 | Dy = sqrt(l2)/(2.0*M_PI) * Dy; 468 | } else { 469 | Dx.x = sqrt(inverse[0])/(2.0*M_PI); 470 | Dx.y = 0.0; 471 | Dx.z = 0.0; 472 | Dy.x = 0.0; 473 | Dy.y = sqrt(inverse[5])/(2.0*M_PI); 474 | Dy.z = 0.0; 475 | } 476 | 477 | // T = trace and D = det of the angluar submatrix 478 | T = inverse[10]+inverse[15]; 479 | D = inverse[10]*inverse[15] - inverse[11]*inverse[11]; 480 | 481 | // Solve the 2nd order polynomial roots of p(l) = l^2 - l T + D. 482 | // This gives us the eigen values. 483 | d = 0.25*T*T - D; 484 | if(d < 0.0) { throw 1; } // No solution exists 485 | l1 = 0.5*T + sqrt(d); 486 | l2 = 0.5*T - sqrt(d); 487 | 488 | if(abs(inverse[11]) > COV_MIN_FLOAT) { 489 | Du.x = l1 - inverse[15]; 490 | Du.y = inverse[11]; 491 | Du.z = 0.0; 492 | Dv.x = l2 - inverse[15]; 493 | Dv.y = inverse[11]; 494 | Dv.z = 0.0; 495 | 496 | Du.Normalize(); 497 | Dv.Normalize(); 498 | 499 | Du = sqrt(l1)/(2.0*M_PI) * Du; 500 | Dv = sqrt(l2)/(2.0*M_PI) * Dv; 501 | } else { 502 | Du.x = sqrt(inverse[10])/(2.0*M_PI); 503 | Du.y = 0.0; 504 | Du.z = 0.0; 505 | Dv.x = 0.0; 506 | Dv.y = sqrt(inverse[15])/(2.0*M_PI); 507 | Dv.z = 0.0; 508 | } 509 | } 510 | 511 | 512 | ///////////////////// 513 | // Spatial Filters // 514 | ///////////////////// 515 | 516 | /* Compute the spatial filter in primal space. This resumes to computing 517 | * the inverse of the spatial submatrix from the inverse covariance 518 | * matrix in frequency space. 519 | */ 520 | void SpatialFilter(Float& sxx, Float& sxy, Float& syy) const { 521 | 522 | // Compute the inverse matrix. We need to add an epsilon to the 523 | // diagonal in order to ensure that the matrix can be inverted. 524 | Float inverse[16]; 525 | inverse[ 0] = matrix[ 0] + COV_MIN_FLOAT; 526 | inverse[ 1] = matrix[ 1]; inverse[ 4] = matrix[ 1]; 527 | inverse[ 5] = matrix[ 2] + COV_MIN_FLOAT; 528 | inverse[ 2] = matrix[ 3]; inverse[ 8] = matrix[ 3]; 529 | inverse[ 6] = matrix[ 4]; inverse[ 9] = matrix[ 4]; 530 | inverse[10] = matrix[ 5] + COV_MIN_FLOAT; 531 | inverse[ 3] = matrix[ 6]; inverse[12] = matrix[ 6]; 532 | inverse[ 7] = matrix[ 7]; inverse[13] = matrix[ 7]; 533 | inverse[11] = matrix[ 8]; inverse[14] = matrix[ 8]; 534 | inverse[15] = matrix[ 9] + COV_MIN_FLOAT; 535 | 536 | if(!Inverse(inverse, 4)) { throw 1; } 537 | 538 | // The outgoing filter is the inverse submatrix of this inverse 539 | // matrix. 540 | Float det = (inverse[0]*inverse[5]-inverse[1]*inverse[1]) / pow(2.0*M_PI, 2); 541 | sxx = inverse[5] / det; 542 | syy = inverse[0] / det; 543 | sxy = -inverse[1] / det; 544 | } 545 | 546 | /* Compute the spatial extent of the space related to the equivalent 547 | * spatial filter to the covariance matrix. The extent is provided as 548 | * 'Dx' and 'Dy', the main axis of the filter's fooprint. 549 | * 550 | * 'Dx' and 'Dy' are express using the first two components: 551 | * Dx = [x, y, 0] 552 | * as they represent direction in the local frame x,y. 553 | * 554 | * To extract Dx and Dy, we do an eigen-decomposition of the 555 | * covariance matrix and use the normalized eigen-vectors as the 556 | * axis of the extent and the eigen-values are the squared extent 557 | * of the polygonal shape. 558 | */ 559 | void SpatialExtent(Vector& Dx, Vector& Dy) const { 560 | // Compute the inverse matrix. We need to add an epsilon to the 561 | // diagonal in order to ensure that the matrix can be inverted. 562 | Float inverse[16]; 563 | inverse[ 0] = matrix[ 0] + COV_MIN_FLOAT; 564 | inverse[ 1] = matrix[ 1]; inverse[ 4] = matrix[ 1]; 565 | inverse[ 5] = matrix[ 2] + COV_MIN_FLOAT; 566 | inverse[ 2] = matrix[ 3]; inverse[ 8] = matrix[ 3]; 567 | inverse[ 6] = matrix[ 4]; inverse[ 9] = matrix[ 4]; 568 | inverse[10] = matrix[ 5] + COV_MIN_FLOAT; 569 | inverse[ 3] = matrix[ 6]; inverse[12] = matrix[ 6]; 570 | inverse[ 7] = matrix[ 7]; inverse[13] = matrix[ 7]; 571 | inverse[11] = matrix[ 8]; inverse[14] = matrix[ 8]; 572 | inverse[15] = matrix[ 9] + COV_MIN_FLOAT; 573 | 574 | if(!Inverse(inverse, 4)) { throw 1; } 575 | 576 | // T = trace and D = det of the spatial submatrix 577 | Float T = inverse[0]+inverse[5]; 578 | Float D = inverse[0]*inverse[5] - inverse[1]*inverse[1]; 579 | 580 | // Solve the 2nd order polynomial roots of p(l) = l^2 - l T + D. 581 | // This gives us the eigen values. 582 | Float d = 0.25*T*T - D; 583 | if(d < 0.0) { throw 1; } // No solution exists 584 | Float l1 = 0.5*T + sqrt(d); 585 | Float l2 = 0.5*T - sqrt(d); 586 | 587 | if(abs(inverse[1]) > COV_MIN_FLOAT) { 588 | Dx.x = l1 - inverse[5]; 589 | Dx.y = inverse[1]; 590 | Dx.z = 0.0; 591 | Dy.x = l2 - inverse[5]; 592 | Dy.y = inverse[1]; 593 | Dy.z = 0.0; 594 | 595 | Dx.Normalize(); 596 | Dy.Normalize(); 597 | 598 | Dx = sqrt(l1)/(2.0*M_PI) * Dx; 599 | Dy = sqrt(l2)/(2.0*M_PI) * Dy; 600 | } else { 601 | Dx.x = sqrt(inverse[0])/(2.0*M_PI); 602 | Dx.y = 0.0; 603 | Dx.z = 0.0; 604 | Dy.x = 0.0; 605 | Dy.y = sqrt(inverse[5])/(2.0*M_PI); 606 | Dy.z = 0.0; 607 | } 608 | } 609 | 610 | 611 | ///////////////////// 612 | // Angular Filters // 613 | ///////////////////// 614 | 615 | /* Compute the angular filter in primal space. The angular filter is 616 | * the 2D Gaussian parameters 'suu', 'suv', 'svv' such that the filter 617 | * is written as: 618 | * f(u,v) = exp(- 0.5 (suu u^2 + 2 suv u v + svv v^2)) 619 | * in the local tangent frame or directions.. 620 | * 621 | * This resumes to computing the inverse of the spatial submatrix from 622 | * the inverse covariance matrix in frequency space. 623 | */ 624 | void AngularFilter(Float& suu, Float& suv, Float& svv) const { 625 | 626 | // Compute the inverse matrix. We need to add an epsilon to the 627 | // diagonal in order to ensure that the matrix can be inverted. 628 | Float inverse[16]; 629 | inverse[ 0] = matrix[ 0] + COV_MIN_FLOAT; 630 | inverse[ 1] = matrix[ 1]; inverse[ 4] = matrix[ 1]; 631 | inverse[ 5] = matrix[ 2] + COV_MIN_FLOAT; 632 | inverse[ 2] = matrix[ 3]; inverse[ 8] = matrix[ 3]; 633 | inverse[ 6] = matrix[ 4]; inverse[ 9] = matrix[ 4]; 634 | inverse[10] = matrix[ 5] + COV_MIN_FLOAT; 635 | inverse[ 3] = matrix[ 6]; inverse[12] = matrix[ 6]; 636 | inverse[ 7] = matrix[ 7]; inverse[13] = matrix[ 7]; 637 | inverse[11] = matrix[ 8]; inverse[14] = matrix[ 8]; 638 | inverse[15] = matrix[ 9] + COV_MIN_FLOAT; 639 | 640 | if(!Inverse(inverse, 4)) { throw 1; } 641 | 642 | // The outgoing filter is the inverse submatrix of this inverse 643 | // matrix. 644 | Float det = (matrix[5]*matrix[9]-matrix[8]*matrix[8]) / pow(2.0*M_PI,2); 645 | if(det > 0.0) { 646 | suu = matrix[9] / det; 647 | svv = matrix[5] / det; 648 | suv = -matrix[8] / det; 649 | } else { 650 | suu = COV_MAX_FLOAT; 651 | svv = COV_MAX_FLOAT; 652 | suv = 0.0; 653 | } 654 | } 655 | 656 | /* Compute the angular extent of the angular component of the equivalent 657 | * spatial filter to the covariance matrix. The extent is provided as 658 | * 'Du' and 'Dv', the main axis of the filter's fooprint. 659 | * 660 | * 'Du' and 'Dv' are express using the first two components: 661 | * Du = [u, v, 0] 662 | * as they represent direction in the local frame x,y. 663 | * 664 | * To extract Du and Dv, we do an eigen-decomposition of the 665 | * covariance matrix and use the normalized eigen-vectors as the 666 | * axis of the extent and the eigen-values are the squared extent 667 | * of the polygonal shape. 668 | */ 669 | void AngularExtent(Vector& Du, Vector& Dv) const { 670 | // Compute the inverse matrix. We need to add an epsilon to the 671 | // diagonal in order to ensure that the matrix can be inverted. 672 | Float inverse[16]; 673 | inverse[ 0] = matrix[ 0] + COV_MIN_FLOAT; 674 | inverse[ 1] = matrix[ 1]; inverse[ 4] = matrix[ 1]; 675 | inverse[ 5] = matrix[ 2] + COV_MIN_FLOAT; 676 | inverse[ 2] = matrix[ 3]; inverse[ 8] = matrix[ 3]; 677 | inverse[ 6] = matrix[ 4]; inverse[ 9] = matrix[ 4]; 678 | inverse[10] = matrix[ 5] + COV_MIN_FLOAT; 679 | inverse[ 3] = matrix[ 6]; inverse[12] = matrix[ 6]; 680 | inverse[ 7] = matrix[ 7]; inverse[13] = matrix[ 7]; 681 | inverse[11] = matrix[ 8]; inverse[14] = matrix[ 8]; 682 | inverse[15] = matrix[ 9] + COV_MIN_FLOAT; 683 | 684 | if(!Inverse(inverse, 4)) { throw 1; } 685 | 686 | // T = trace and D = det of the spatial submatrix 687 | Float T = inverse[10]+inverse[15]; 688 | Float D = inverse[10]*inverse[15] - inverse[11]*inverse[11]; 689 | 690 | // Solve the 2nd order polynomial roots of p(l) = l^2 - l T + D. 691 | // This gives us the eigen values. 692 | Float d = 0.25*T*T - D; 693 | if(d < 0.0) { throw 1; } // No solution exists 694 | Float l1 = 0.5*T + sqrt(d); 695 | Float l2 = 0.5*T - sqrt(d); 696 | 697 | if(abs(inverse[11]) > COV_MIN_FLOAT) { 698 | Du.x = l1 - inverse[15]; 699 | Du.y = inverse[11]; 700 | Du.z = 0.0; 701 | Dv.x = l2 - inverse[15]; 702 | Dv.y = inverse[11]; 703 | Dv.z = 0.0; 704 | 705 | Du.Normalize(); 706 | Dv.Normalize(); 707 | 708 | Du = sqrt(l1)/(2.0*M_PI) * Du; 709 | Dv = sqrt(l2)/(2.0*M_PI) * Dv; 710 | } else { 711 | Du.x = sqrt(inverse[10])/(2.0*M_PI); 712 | Du.y = 0.0; 713 | Du.z = 0.0; 714 | Dv.x = 0.0; 715 | Dv.y = sqrt(inverse[15])/(2.0*M_PI); 716 | Dv.z = 0.0; 717 | } 718 | } 719 | 720 | /* Compute the volume (in frequency domain) spanned by the matrix. 721 | * Note: this volume should always be positive. 722 | */ 723 | Float Volume() const { 724 | 725 | Float fmatrix[16]; 726 | fmatrix[ 0] = matrix[ 0] + COV_MIN_FLOAT; 727 | fmatrix[ 1] = matrix[ 1]; fmatrix[ 4] = matrix[ 1]; 728 | fmatrix[ 5] = matrix[ 2] + COV_MIN_FLOAT; 729 | fmatrix[ 2] = matrix[ 3]; fmatrix[ 8] = matrix[ 3]; 730 | fmatrix[ 6] = matrix[ 4]; fmatrix[ 9] = matrix[ 4]; 731 | fmatrix[10] = matrix[ 5] + COV_MIN_FLOAT; 732 | fmatrix[ 3] = matrix[ 6]; fmatrix[12] = matrix[ 6]; 733 | fmatrix[ 7] = matrix[ 7]; fmatrix[13] = matrix[ 7]; 734 | fmatrix[11] = matrix[ 8]; fmatrix[14] = matrix[ 8]; 735 | fmatrix[15] = matrix[ 9] + COV_MIN_FLOAT; 736 | 737 | return Determinant(fmatrix, 4); 738 | } 739 | 740 | ///////////////////// 741 | // Constructors // 742 | ///////////////////// 743 | 744 | Covariance4D() { 745 | matrix = { 0.0f, 746 | 0.0f, 0.0f, 747 | 0.0f, 0.0f, 0.0f, 748 | 0.0f, 0.0f, 0.0f, 0.0f}; 749 | } 750 | Covariance4D(Float sxx, Float syy, Float suu, Float svv) { 751 | matrix = { sxx, 752 | 0.0f, syy, 753 | 0.0f, 0.0f, suu, 754 | 0.0f, 0.0f, 0.0f, svv}; 755 | } 756 | Covariance4D(std::array matrix, const Vector& z) : 757 | matrix(matrix), z(z) { 758 | Vector::Frame(z, x, y); 759 | } 760 | Covariance4D(std::array matrix, 761 | const Vector& x, 762 | const Vector& y, 763 | const Vector& z) : 764 | matrix(matrix), x(x), y(y), z(z) {} 765 | }; 766 | } 767 | -------------------------------------------------------------------------------- /include/Covariance/InvCovariance4D.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // STL includes 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | // Local includes 10 | #include "Matrix.hpp" 11 | 12 | #define INVCOV_MAX_FLOAT 1.0E+10 13 | #define INVCOV_MIN_FLOAT 1.0E-10 14 | 15 | namespace Covariance { 16 | 17 | /* The 4D version of Covariance Tracing using the inverse matrix. This is 18 | * the timeless version of Covariance Tracing. It allows to compute 19 | * covariance information of the local lightfield around the central ray 20 | * position (Belcour 2013). Instead of tracking the covariance matrix, it 21 | * updates the inverse of this matrix. This is more stable when no occlusion 22 | * is accounted. 23 | * 24 | * The covariance matrix represent the covariance of the local light field 25 | * in the (X, Y, U, V) local coordinate frame. 26 | * 27 | * The covariance matrix is a 4x4 symetric Matrix and we only deal with the 28 | * upper triangle for all the operations. Variance terms are at indices 29 | * 0, 2, 5, 9. 30 | * 31 | * The matrix is indexed the following way: 32 | * 33 | * C = ( 0 1 3 6) 34 | * ( * 2 4 7) 35 | * ( * * 5 8) 36 | * ( * * * 9) 37 | * 38 | * Thus the full indexing of the matrix is the following: 39 | * 40 | * C = ( 0 1 3 6) 41 | * ( 1 2 4 7) 42 | * ( 3 4 5 8) 43 | * ( 6 7 8 9) 44 | */ 45 | template 46 | struct InvCovariance4D { 47 | 48 | std::array matrix; 49 | Vector x, y, z; 50 | 51 | 52 | //////////////////////// 53 | // Atomic operators // 54 | //////////////////////// 55 | 56 | /* Travel operator 57 | * 58 | * 'd' distance of travel along the central ray 59 | */ 60 | inline void Travel(Float d) { 61 | //ShearAngleSpace(-d, -d); 62 | ShearSpaceAngle(-d, -d); 63 | } 64 | 65 | /* Curvature operator 66 | * 67 | * 'kx' curvature along the X direction 68 | * 'ky' curvature along the Y direction 69 | */ 70 | inline void Curvature(Float kx, Float ky) { 71 | ShearAngleSpace(-kx, -ky); 72 | //ShearSpaceAngle(-kx, -ky); 73 | } 74 | 75 | /* Cosine operator 76 | * This operator would require to invert the matrix and perform an 77 | * addition to it. 78 | * 79 | * 'wz' The incident direction's elevation in the local frame 80 | */ 81 | inline void Cosine(Float wz) { 82 | } 83 | 84 | /* Reflection operator 85 | * 86 | * 'suu' the covariance of the BRDF along the X axis. 87 | * 'svv' the covariance of the BRDF along the Y axis. 88 | */ 89 | inline void Reflection(Float suu, Float svv) { 90 | matrix[5] += 1.0f/std::max(svv, INVCOV_MIN_FLOAT); 91 | matrix[9] += 1.0f/std::max(suu, INVCOV_MIN_FLOAT); 92 | } 93 | 94 | 95 | ///////////////////////////// 96 | // Local Frame alignment // 97 | ///////////////////////////// 98 | 99 | /* Perform the projection of the incomming lightfield on the surface with 100 | * normal n. This function assumes that the surface normal is in the 101 | * opposite direction to the main vector of the lightfield. 102 | * 103 | * 'n' the surface normal. 104 | */ 105 | inline void Projection(const Vector& n) { 106 | const auto cx = Vector::Dot(x, n); 107 | const auto cy = Vector::Dot(y, n); 108 | 109 | // Rotate the Frame to be aligned with plane. 110 | const Float alpha = (cx != 0.0) ? atan2(cx, cy) : 0.0; 111 | const Float c = cos(alpha), s = -sin(alpha); 112 | Rotate(c, s); 113 | 114 | // Scale the componnent that project by the cosine of the ray direction 115 | // and the normal. 116 | const Float cosine = Vector::Dot(z, n); 117 | ScaleY(1.0/fmax(fabs(cosine), INVCOV_MIN_FLOAT)); 118 | 119 | // Update direction vectors. 120 | x = c*x + s*y; 121 | z = (cosine < 0.0f) ? -n : n; 122 | y = (cosine < 0.0f) ? Vector::Cross(x, z) : Vector::Cross(z, x); 123 | } 124 | 125 | /* Perform the projection of a lightfield defined on a surface to an 126 | * outgoing direction. This function assumes that the lightfield main 127 | * vector is the surface normal and that the outgoing vector is in the 128 | * same direction. 129 | * 130 | * 'd' the outgoing direction. 131 | */ 132 | inline void InverseProjection(const Vector& d) { 133 | 134 | const auto cx = Vector::Dot(x, d); 135 | const auto cy = Vector::Dot(y, d); 136 | 137 | // Rotate the Frame to be aligned with plane. 138 | const Float alpha = (cx != 0.0) ? atan2(cx, cy) : 0.0; 139 | const Float c = cos(alpha), s = -sin(alpha); 140 | Rotate(c, s); // Rotate of -alpha 141 | 142 | // Scale the componnent that project by the inverse cosine of the ray 143 | // direction and the normal. 144 | const Float cosine = Vector::Dot(z, d); 145 | if(cosine < 0.0f) { 146 | ScaleV(-1.0f); 147 | ScaleU(-1.0f); 148 | } 149 | ScaleY(std::abs(cosine)); 150 | 151 | // Update direction vectors. 152 | x = c*x + s*y; 153 | z = d; 154 | y = Vector::Cross(z, x); 155 | } 156 | 157 | 158 | /* Note: for the case of tracking the local frame of the light field 159 | * performing the symmetry makes not difference since the local frame 160 | * is ajusted with respect to symmetry. 161 | */ 162 | inline void Symmetry() { 163 | matrix[3] = -matrix[3]; 164 | matrix[4] = -matrix[4]; 165 | matrix[6] = -matrix[6]; 166 | matrix[7] = -matrix[7]; 167 | } 168 | 169 | 170 | //////////////////////// 171 | // Matrix scaling // 172 | //////////////////////// 173 | 174 | inline void ScaleX(Float alpha) { 175 | matrix[0] *= alpha*alpha; 176 | matrix[1] *= alpha; 177 | matrix[3] *= alpha; 178 | matrix[6] *= alpha; 179 | } 180 | 181 | inline void ScaleY(Float alpha) { 182 | matrix[1] *= alpha; 183 | matrix[2] *= alpha*alpha; 184 | matrix[4] *= alpha; 185 | matrix[7] *= alpha; 186 | } 187 | 188 | inline void ScaleU(Float alpha) { 189 | matrix[3] *= alpha; 190 | matrix[4] *= alpha; 191 | matrix[5] *= alpha*alpha; 192 | matrix[8] *= alpha; 193 | } 194 | 195 | inline void ScaleV(Float alpha) { 196 | matrix[6] *= alpha; 197 | matrix[7] *= alpha; 198 | matrix[8] *= alpha; 199 | matrix[9] *= alpha*alpha; 200 | } 201 | 202 | 203 | //////////////////////// 204 | // Matrix shearing // 205 | //////////////////////// 206 | 207 | // Shear the Spatial (x,y) domain by the Angular (u,v). 208 | // \param cx amount of shear along the x direction. 209 | // \param cy amount of shear along the y direction. 210 | inline void ShearSpaceAngle(Float cx, Float cy) { 211 | matrix[0] += (matrix[5]*cx - 2*matrix[3])*cx; 212 | matrix[1] += matrix[8]*cx*cy - (matrix[4]*cy + matrix[6]*cx); 213 | matrix[2] += (matrix[9]*cy - 2*matrix[7])*cy; 214 | matrix[3] -= matrix[5]*cx; 215 | matrix[4] -= matrix[8]*cy; 216 | matrix[6] -= matrix[8]*cx; 217 | matrix[7] -= matrix[9]*cy; 218 | } 219 | 220 | // Shear the angular (U, V) domain by the spatial (X, Y) domain. 221 | // \param cu amount of shear along the U direction. 222 | // \param cy amount of shear along the V direction. 223 | inline void ShearAngleSpace(Float cu, Float cv) { 224 | matrix[5] += (matrix[0]*cu - 2*matrix[3])*cu; 225 | matrix[3] -= matrix[0]*cu; 226 | matrix[8] += matrix[1]*cu*cv - (matrix[4]*cv + matrix[6]*cu); 227 | matrix[4] -= matrix[1]*cv; 228 | matrix[6] -= matrix[1]*cu; 229 | matrix[9] += (matrix[2]*cv - 2*matrix[7])*cv; 230 | matrix[7] -= matrix[2]*cv; 231 | } 232 | 233 | 234 | //////////////////////// 235 | // Matrix rotation // 236 | //////////////////////// 237 | 238 | // alpha rotation angle 239 | inline void Rotate(Float alpha) { 240 | const Float c = cos(alpha); 241 | const Float s = sin(alpha); 242 | Rotate(c, s); 243 | } 244 | 245 | // 'c' the cosine of the rotation angle 246 | // 's' the sine of the rotation angle 247 | inline void Rotate(Float c, Float s) { 248 | const Float cs = c*s; 249 | const Float c2 = c*c; 250 | const Float s2 = s*s; 251 | 252 | const Float cov_xx = matrix[0]; 253 | const Float cov_xy = matrix[1]; 254 | const Float cov_yy = matrix[2]; 255 | const Float cov_xu = matrix[3]; 256 | const Float cov_yu = matrix[4]; 257 | const Float cov_uu = matrix[5]; 258 | const Float cov_xv = matrix[6]; 259 | const Float cov_yv = matrix[7]; 260 | const Float cov_uv = matrix[8]; 261 | const Float cov_vv = matrix[9]; 262 | 263 | // Rotation of the space 264 | matrix[0] = c2 * cov_xx + 2*cs * cov_xy + s2 * cov_yy; 265 | matrix[1] = (c2-s2) * cov_xy + cs * (cov_yy - cov_xx); 266 | matrix[2] = c2 * cov_yy - 2*cs * cov_xy + s2 * cov_xx; 267 | 268 | // Rotation of the angle 269 | matrix[5] = c2 * cov_uu + 2*cs * cov_uv + s2 * cov_vv; 270 | matrix[8] = (c2-s2) * cov_uv + cs * (cov_vv - cov_uu); 271 | matrix[9] = c2 * cov_vv - 2*cs * cov_uv + s2 * cov_uu; 272 | 273 | // Covariances 274 | matrix[3] = c2 * cov_xu + cs * (cov_xv + cov_yu) + s2 * cov_yv; 275 | matrix[4] = c2 * cov_yu + cs * (cov_yv - cov_xu) - s2 * cov_xv; 276 | matrix[6] = c2 * cov_xv + cs * (cov_yv - cov_xu) - s2 * cov_yu; 277 | matrix[7] = c2 * cov_yv - cs * (cov_xv + cov_yu) + s2 * cov_xu; 278 | } 279 | 280 | 281 | //////////////////////// 282 | // Matrix inverse // 283 | //////////////////////// 284 | 285 | /* Compute the inverse of the covariance matrix. 286 | * 287 | * We add an epsilon to the diagonal in order to ensure that the matrix 288 | * can be inverted. 289 | * 290 | * 'inverse' needs to be a 4x4 preallocated matrix (16 Floats).. 291 | */ 292 | void InverseMatrix(Float* inverse) const { 293 | // Compute the inverse matrix. We need to add an epsilon to the 294 | // diagonal in order to ensure that the matrix can be inverted. 295 | inverse[ 0] = matrix[ 0] + COV_MIN_FLOAT; 296 | inverse[ 1] = matrix[ 1]; inverse[ 4] = matrix[ 1]; 297 | inverse[ 5] = matrix[ 2] + COV_MIN_FLOAT; 298 | inverse[ 2] = matrix[ 3]; inverse[ 8] = matrix[ 3]; 299 | inverse[ 6] = matrix[ 4]; inverse[ 9] = matrix[ 4]; 300 | inverse[10] = matrix[ 5] + COV_MIN_FLOAT; 301 | inverse[ 3] = matrix[ 6]; inverse[12] = matrix[ 6]; 302 | inverse[ 7] = matrix[ 7]; inverse[13] = matrix[ 7]; 303 | inverse[11] = matrix[ 8]; inverse[14] = matrix[ 8]; 304 | inverse[15] = matrix[ 9] + COV_MIN_FLOAT; 305 | 306 | if(!Inverse(inverse, 4)) { throw 1; } 307 | } 308 | 309 | 310 | //////////////////////// 311 | // Product of signals // 312 | //////////////////////// 313 | 314 | /* Evaluate the covariance matrix of the product of the local lightfield 315 | * and a angularly varying only signal (like a BSDF). 316 | * 317 | * 'su' is the sigma u of the inverse angular signal's covariance matrix. 318 | * 'sv' is the sigma v of the inverse angular signal's covariance matrix. 319 | */ 320 | inline void ProductUV(Float su, Float sv) { 321 | matrix[5] += 1.0f/su; 322 | matrix[9] += 1.0f/sv; 323 | } 324 | 325 | ///////////////////////////// 326 | // Add covariance together // 327 | ///////////////////////////// 328 | 329 | /* Add two covariance matrices together. Since we are using inverse 330 | * covariance storage here, we need to invert the matrices before doing 331 | * the addition (geometric mean). Using this routine is not recommended, 332 | * prefer storing covariances directly using 'Covariance4D' if you need 333 | * to perform many additions. 334 | */ 335 | void Add(const InvCovariance4D& cov, Float L1=1.0f, Float L2=1.0f) { 336 | const Float L = L1+L2; 337 | if(L <= 0.0f) return; 338 | 339 | // Compute the inverse matrix 340 | Float inverse[16]; 341 | this->InverseMatrix(inverse); 342 | 343 | // Compute the inverse of matrix cov.matrix 344 | Float inverseB[16]; 345 | cov.InverseMatrix(inverseB); 346 | 347 | // Add the two covariance matrices together 348 | for(unsigned short i=0; i<15; ++i) { 349 | inverse[i] = (L1*inverse[i] + L2*inverseB[i]) / L; 350 | } 351 | 352 | // Get the inverse covariance back 353 | if(!Inverse(inverse, 4)) { throw 1; } 354 | matrix[ 0] = inverse[ 0]; 355 | matrix[ 1] = inverse[ 1]; 356 | matrix[ 2] = inverse[ 5]; 357 | matrix[ 3] = inverse[ 2]; 358 | matrix[ 4] = inverse[ 6]; 359 | matrix[ 5] = inverse[10]; 360 | matrix[ 6] = inverse[ 3]; 361 | matrix[ 7] = inverse[ 7]; 362 | matrix[ 8] = inverse[11]; 363 | matrix[ 9] = inverse[15]; 364 | } 365 | 366 | 367 | //////////////////////////// 368 | // Spatio-angular Filters // 369 | //////////////////////////// 370 | 371 | /* Compute the spatio-angular extent of the space related to the 372 | * covariance matrix's filter. The extent is provided as vectors 373 | * 'Dx', 'Dy', and 'Du', 'Dv'. Those vectors are the main axis of 374 | * the filter's fooprint. 375 | 376 | * Vector like 'Dx' and 'Dy' are expressed in the local tangent frame 377 | * x, y using the first two components: 378 | * Dx = [x, y, 0] 379 | 380 | * To extract Dx, Dy, Du and Dv, we do an eigen-decomposition of the 381 | * covariance matrix and use the normalized eigen-vectors as the axis 382 | * of the extent and the eigen-values are the squared extent of the 383 | * polygonal shape. 384 | * 385 | * This spatio-angular polygonal shape can be used to specify an 386 | * equivalent ray differential [Igehy 1999]. 387 | */ 388 | void Extent(Vector& Dx, Vector& Dy, Vector& Du, Vector& Dv) const { 389 | // T = trace and D = det of the spatial submatrix 390 | Float T = matrix[0]+matrix[2]; 391 | Float D = matrix[0]*matrix[2] - matrix[1]*matrix[1]; 392 | 393 | // Solve the 2nd order polynomial roots of p(l) = l^2 - l T + D. 394 | // This gives us the eigen values. 395 | Float d = 0.25*T*T - D; 396 | if(d < 0.0) { throw 1; } // No solution exists 397 | Float l1 = 0.5*T + sqrt(d); 398 | Float l2 = 0.5*T - sqrt(d); 399 | 400 | if(abs(matrix[1]) > COV_MIN_FLOAT) { 401 | Dx.x = l1 - matrix[2]; 402 | Dx.y = matrix[1]; 403 | Dx.z = 0.0; 404 | Dy.x = l2 - matrix[2]; 405 | Dy.y = matrix[1]; 406 | Dy.z = 0.0; 407 | 408 | Dx.Normalize(); 409 | Dy.Normalize(); 410 | 411 | Dx = sqrt(l1)/(2.0*M_PI) * Dx; 412 | Dy = sqrt(l2)/(2.0*M_PI) * Dy; 413 | } else { 414 | Dx.x = sqrt(matrix[0])/(2.0*M_PI); 415 | Dx.y = 0.0; 416 | Dx.z = 0.0; 417 | Dy.x = 0.0; 418 | Dy.y = sqrt(matrix[2])/(2.0*M_PI); 419 | Dy.z = 0.0; 420 | } 421 | 422 | // T = trace and D = det of the angluar submatrix 423 | T = matrix[5]+matrix[9]; 424 | D = matrix[5]*matrix[9] - matrix[8]*matrix[8]; 425 | 426 | // Solve the 2nd order polynomial roots of p(l) = l^2 - l T + D. 427 | // This gives us the eigen values. 428 | d = 0.25*T*T - D; 429 | if(d < 0.0) { throw 1; } // No solution exists 430 | l1 = 0.5*T + sqrt(d); 431 | l2 = 0.5*T - sqrt(d); 432 | 433 | if(abs(matrix[8]) > COV_MIN_FLOAT) { 434 | Du.x = l1 - matrix[9]; 435 | Du.y = matrix[8]; 436 | Du.z = 0.0; 437 | Dv.x = l2 - matrix[9]; 438 | Dv.y = matrix[8]; 439 | Dv.z = 0.0; 440 | 441 | Du.Normalize(); 442 | Dv.Normalize(); 443 | 444 | Du = sqrt(l1)/(2.0*M_PI) * Du; 445 | Dv = sqrt(l2)/(2.0*M_PI) * Dv; 446 | } else { 447 | Du.x = sqrt(matrix[5])/(2.0*M_PI); 448 | Du.y = 0.0; 449 | Du.z = 0.0; 450 | Dv.x = 0.0; 451 | Dv.y = sqrt(matrix[9])/(2.0*M_PI); 452 | Dv.z = 0.0; 453 | } 454 | } 455 | 456 | 457 | ///////////////////// 458 | // Spatial Filters // 459 | ///////////////////// 460 | 461 | /* Compute the spatial filter in primal space. The spatial filter is 462 | * the 2D Gaussian parameters 'sxx', 'sxy', 'syy' such that the filter 463 | * is written as: 464 | * f(u,v) = exp(- 0.5 (sxx u^2 + 2 sxy u v + syy v^2)) 465 | * 466 | * This resumes to computing the inverse of the spatial submatrix from 467 | * the inverse covariance matrix in frequency space. 468 | */ 469 | void SpatialFilter(Float& sxx, Float& sxy, Float& syy) const { 470 | 471 | // The outgoing filter is the inverse submatrix of this inverse 472 | // matrix. 473 | Float det = (matrix[0]*matrix[2]-matrix[1]*matrix[1]) / pow(2.0*M_PI,2); 474 | if(det > 0.0) { 475 | sxx = matrix[2] / det; 476 | syy = matrix[0] / det; 477 | sxy = -matrix[1] / det; 478 | } else { 479 | sxx = INVCOV_MAX_FLOAT; 480 | syy = INVCOV_MAX_FLOAT; 481 | sxy = 0.0; 482 | } 483 | } 484 | 485 | /* Compute the spatial extent of the space related to the equivalent 486 | spatial filter to the covariance matrix. The extent is provided as 487 | 'Dx' and 'Dy', the main axis of the filter's fooprint. 488 | 489 | 'Dx' and 'Dy' are express using the first two components: 490 | Dx = [x, y, 0] 491 | as they represent direction in the local frame x,y. 492 | 493 | To extract Dx and Dy, we do an eigen-decomposition of the 494 | covariance matrix and use the normalized eigen-vectors as the 495 | axis of the extent and the eigen-values are the squared extent 496 | of the polygonal shape. 497 | */ 498 | void SpatialExtent(Vector& Dx, Vector& Dy) const { 499 | // T = trace and D = det of the spatial submatrix 500 | Float T = matrix[0]+matrix[2]; 501 | Float D = matrix[0]*matrix[2] - matrix[1]*matrix[1]; 502 | 503 | // Solve the 2nd order polynomial roots of p(l) = l^2 - l T + D. 504 | // This gives us the eigen values. 505 | Float d = 0.25*T*T - D; 506 | if(d < 0.0) { throw 1; } // No solution exists 507 | Float l1 = 0.5*T + sqrt(d); 508 | Float l2 = 0.5*T - sqrt(d); 509 | 510 | if(abs(matrix[1]) > INVCOV_MIN_FLOAT) { 511 | Dx.x = l1 - matrix[2]; 512 | Dx.y = matrix[1]; 513 | Dx.z = 0.0; 514 | Dy.x = l2 - matrix[2]; 515 | Dy.y = matrix[1]; 516 | Dy.z = 0.0; 517 | 518 | Dx.Normalize(); 519 | Dy.Normalize(); 520 | 521 | Dx = sqrt(l1)/(2.0*M_PI) * Dx; 522 | Dy = sqrt(l2)/(2.0*M_PI) * Dy; 523 | } else { 524 | Dx.x = sqrt(matrix[0])/(2.0*M_PI); 525 | Dx.y = 0.0; 526 | Dx.z = 0.0; 527 | Dy.x = 0.0; 528 | Dy.y = sqrt(matrix[2])/(2.0*M_PI); 529 | Dy.z = 0.0; 530 | } 531 | } 532 | 533 | 534 | ///////////////////// 535 | // Angular Filters // 536 | ///////////////////// 537 | 538 | /* Compute the angular filter in primal space. The angular filter is 539 | * the 2D Gaussian parameters 'suu', 'suv', 'svv' such that the filter 540 | * is written as: 541 | * f(u,v) = exp(- 0.5 (suu u^2 + 2 suv u v + svv v^2)) 542 | * in the local tangent frame or directions.. 543 | * 544 | * This resumes to computing the inverse of the spatial submatrix from 545 | * the inverse covariance matrix in frequency space. 546 | */ 547 | void AngularFilter(Float& suu, Float& suv, Float& svv) const { 548 | 549 | // The outgoing filter is the inverse submatrix of this inverse 550 | // matrix. 551 | Float det = (matrix[5]*matrix[9]-matrix[8]*matrix[8]) / pow(2.0*M_PI,2); 552 | if(det > 0.0) { 553 | suu = matrix[9] / det; 554 | svv = matrix[5] / det; 555 | suv = -matrix[8] / det; 556 | } else { 557 | suu = INVCOV_MAX_FLOAT; 558 | svv = INVCOV_MAX_FLOAT; 559 | suv = 0.0; 560 | } 561 | } 562 | 563 | /* Compute the angular extent of the angular component of the equivalent 564 | spatial filter to the covariance matrix. The extent is provided as 565 | 'Du' and 'Dv', the main axis of the filter's fooprint. 566 | 567 | 'Du' and 'Dv' are express using the first two components: 568 | Du = [u, v, 0] 569 | as they represent direction in the local frame x,y. 570 | 571 | To extract Du and Dv, we do an eigen-decomposition of the 572 | covariance matrix and use the normalized eigen-vectors as the 573 | axis of the extent and the eigen-values are the squared extent 574 | of the polygonal shape. 575 | */ 576 | void AngularExtent(Vector& Du, Vector& Dv) const { 577 | // T = trace and D = det of the spatial submatrix 578 | Float T = matrix[10]+matrix[15]; 579 | Float D = matrix[10]*matrix[15] - matrix[11]*matrix[11]; 580 | 581 | // Solve the 2nd order polynomial roots of p(l) = l^2 - l T + D. 582 | // This gives us the eigen values. 583 | Float d = 0.25*T*T - D; 584 | if(d < 0.0) { throw 1; } // No solution exists 585 | Float l1 = 0.5*T + sqrt(d); 586 | Float l2 = 0.5*T - sqrt(d); 587 | 588 | if(abs(matrix[11]) > INVCOV_MIN_FLOAT) { 589 | Du.x = l1 - matrix[15]; 590 | Du.y = matrix[11]; 591 | Du.z = 0.0; 592 | Dv.x = l2 - matrix[15]; 593 | Dv.y = matrix[11]; 594 | Dv.z = 0.0; 595 | 596 | Du.Normalize(); 597 | Dv.Normalize(); 598 | 599 | Du = sqrt(l1)/(2.0*M_PI) * Du; 600 | Dv = sqrt(l2)/(2.0*M_PI) * Dv; 601 | } else { 602 | Du.x = sqrt(matrix[10])/(2.0*M_PI); 603 | Du.y = 0.0; 604 | Du.z = 0.0; 605 | Dv.x = 0.0; 606 | Dv.y = sqrt(matrix[15])/(2.0*M_PI); 607 | Dv.z = 0.0; 608 | } 609 | } 610 | 611 | /* Compute the volume (in frequency domain) spanned by the matrix. 612 | * Note: this volume should always be positive. 613 | */ 614 | Float Volume() const { 615 | 616 | Float fmatrix[16]; 617 | fmatrix[ 0] = matrix[ 0] + INVCOV_MIN_FLOAT; 618 | fmatrix[ 1] = matrix[ 1]; fmatrix[ 4] = matrix[ 1]; 619 | fmatrix[ 5] = matrix[ 2] + INVCOV_MIN_FLOAT; 620 | fmatrix[ 2] = matrix[ 3]; fmatrix[ 8] = matrix[ 3]; 621 | fmatrix[ 6] = matrix[ 4]; fmatrix[ 9] = matrix[ 4]; 622 | fmatrix[10] = matrix[ 5] + INVCOV_MIN_FLOAT; 623 | fmatrix[ 3] = matrix[ 6]; fmatrix[12] = matrix[ 6]; 624 | fmatrix[ 7] = matrix[ 7]; fmatrix[13] = matrix[ 7]; 625 | fmatrix[11] = matrix[ 8]; fmatrix[14] = matrix[ 8]; 626 | fmatrix[15] = matrix[ 9] + INVCOV_MIN_FLOAT; 627 | 628 | return 1.0f/Determinant(fmatrix, 4); 629 | } 630 | 631 | ///////////////////// 632 | // Constructors // 633 | ///////////////////// 634 | 635 | InvCovariance4D() { 636 | matrix = { INVCOV_MAX_FLOAT, 637 | 0.0f, INVCOV_MAX_FLOAT, 638 | 0.0f, 0.0f, INVCOV_MAX_FLOAT, 639 | 0.0f, 0.0f, 0.0f, INVCOV_MAX_FLOAT}; 640 | } 641 | InvCovariance4D(Float sxx, Float syy, Float suu, Float svv) { 642 | matrix = { 1.0/std::max(sxx, INVCOV_MIN_FLOAT), 643 | 0.0f, 1.0/std::max(syy, INVCOV_MIN_FLOAT), 644 | 0.0f, 0.0f, 1.0/std::max(suu, INVCOV_MIN_FLOAT), 645 | 0.0f, 0.0f, 0.0f, 1.0/std::max(svv, INVCOV_MIN_FLOAT)}; 646 | } 647 | InvCovariance4D(std::array matrix, const Vector& z) : 648 | matrix(matrix), z(z) { 649 | Vector::Frame(z, x, y); 650 | } 651 | InvCovariance4D(std::array matrix, 652 | const Vector& x, 653 | const Vector& y, 654 | const Vector& z) : 655 | matrix(matrix), x(x), y(y), z(z) {} 656 | }; 657 | } 658 | -------------------------------------------------------------------------------- /include/Covariance/Matrix.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // STL 4 | #include 5 | 6 | namespace Covariance { 7 | 8 | /* Recursively compute the determinant of the NxN matrix A 9 | */ 10 | template T recurse_determinant(T* A, unsigned int N) { 11 | if(N == 1) { 12 | return A[0]; 13 | } else if(N == 2) { 14 | return A[0]*A[3]-A[1]*A[2]; 15 | } else { 16 | // Size of a submatrix 17 | unsigned int NN = (N-1)*(N-1); 18 | 19 | // Calculate the cofactor for the first line 20 | T det = 0.0f; 21 | T* B = new T[NN]; 22 | for(unsigned int i=0; i T cofactor(T* A, int n, int i, int j) { 46 | // Create the cofactor 47 | T* C = new T[(n-1)*(n-1)]; 48 | int I = 0; 49 | int J = 0; 50 | for(int ii=0; ii bool Inverse(T* A, int size) { 75 | T* B = new T[size*size]; 76 | T det = recurse_determinant(A, size); 77 | 78 | if(det < T(0.0)) { 79 | delete[] B; 80 | return false; 81 | } 82 | 83 | for(int i=0; i(A, size, j, i) / det); 86 | } 87 | 88 | for(int i=0; i 102 | T Determinant(T* A, int size) { 103 | return recurse_determinant(A, size); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /tests/Covariance4D.cpp: -------------------------------------------------------------------------------- 1 | // STL includes 2 | #include 3 | #include 4 | #include 5 | 6 | // Covariance includes 7 | #include 8 | using namespace Covariance; 9 | 10 | struct Vector { 11 | double x, y, z; 12 | Vector() {} 13 | Vector(double x, double y, double z) : x(x), y(y), z(z) {} 14 | static double Dot(const Vector& w1, const Vector& w2) { 15 | return w1.x*w2.x + w1.y*w2.y + w1.z*w2.z; 16 | } 17 | static Vector Cross(const Vector& u, const Vector& v) { 18 | Vector r; 19 | r.x = u.y*v.z - u.z*v.y; 20 | r.y = u.z*v.x - u.x*v.z; 21 | r.z = u.x*v.y - u.y*v.x; 22 | return r; 23 | } 24 | void Normalize() { 25 | double norm = sqrt(Dot(*this, *this)); 26 | x /= norm; 27 | y /= norm; 28 | z /= norm; 29 | } 30 | friend Vector operator*(double a, const Vector& w) { 31 | Vector v; 32 | v.x = a*w.x; 33 | v.y = a*w.y; 34 | v.z = a*w.z; 35 | return v; 36 | } 37 | friend Vector operator+(const Vector& a, const Vector& w) { 38 | Vector v; 39 | v.x = a.x+w.x; 40 | v.y = a.y+w.y; 41 | v.z = a.z+w.z; 42 | return v; 43 | } 44 | friend Vector operator-(const Vector& a, const Vector& w) { 45 | Vector v; 46 | v.x = a.x-w.x; 47 | v.y = a.y-w.y; 48 | v.z = a.z-w.z; 49 | return v; 50 | } 51 | friend Vector operator-(const Vector& w) { 52 | Vector v; 53 | v.x = w.x; 54 | v.y = w.y; 55 | v.z = w.z; 56 | return v; 57 | } 58 | friend std::ostream& operator<<(std::ostream& out, const Vector& w) { 59 | out << "[" << w.x << ", " << w.y << ", " << w.z << "]"; 60 | return out; 61 | } 62 | }; 63 | 64 | using Cov = Covariance4D; 65 | 66 | bool IsApprox(const Cov& A, const Cov& B, double Eps=1.0E-3) { 67 | bool IsApprox = true; 68 | for(int i=0; i<10; ++i) { 69 | IsApprox &= std::abs(A.matrix[i] - B.matrix[i]) < Eps; 70 | } 71 | return IsApprox; 72 | } 73 | 74 | bool IsApprox(double a, double b, double Eps=1.0E-3) { 75 | return std::abs(a - b) < Eps; 76 | } 77 | 78 | std::ostream& operator<<(std::ostream& out, const Cov& A) { 79 | for(int i=0; i<10; ++i) { 80 | out << A.matrix[i] << ", "; 81 | } 82 | return out; 83 | } 84 | 85 | int TestRotation() { 86 | int nb_fails = 0; 87 | 88 | Cov A, B; 89 | 90 | A = B = Cov(1.0, 0.0, 1.0, 0.0); 91 | A.Rotate(2.0*M_PI); 92 | if(!IsApprox(A, B)) { 93 | std::cerr << "Error: 2π rotation is not indempotent" << std::endl; 94 | std::cerr << A << std::endl; 95 | std::cerr << B << std::endl; 96 | ++nb_fails; 97 | } 98 | 99 | A = B; 100 | A.Rotate( 0.567); 101 | A.Rotate(-0.567); 102 | if(!IsApprox(A, B)) { 103 | std::cerr << "Error: Rotation and its inverse is not indempotent" << std::endl; 104 | std::cerr << " cos = " << cos(0.567) << std::endl; 105 | std::cerr << " sin = " << sin(0.567) << std::endl; 106 | A = B; 107 | std::cerr << A << std::endl; 108 | A.Rotate( 0.567); 109 | std::cerr << A << std::endl; 110 | A.Rotate(-0.567); 111 | std::cerr << A << std::endl; 112 | ++nb_fails; 113 | } 114 | 115 | return nb_fails; 116 | } 117 | 118 | int TestShear() { 119 | int nb_fails = 0; 120 | 121 | Cov A, B; 122 | double c = 123.456; 123 | double d = 765.432; 124 | 125 | A = B = Cov(1.0, 2.0, 3.0, 4.0); 126 | A.ShearAngleSpace( c, d); 127 | A.ShearAngleSpace(-c, -d); 128 | 129 | if(!IsApprox(A, B)) { 130 | std::cerr << "Error: Shear Angle -> Space is not indempotent" << std::endl; 131 | std::cerr << A << std::endl; 132 | std::cerr << B << std::endl; 133 | ++nb_fails; 134 | } 135 | 136 | A = B = Cov(1.0, 2.0, 3.0, 4.0); 137 | A.ShearSpaceAngle( c, d); 138 | A.ShearSpaceAngle(-c, -d); 139 | 140 | if(!IsApprox(A, B)) { 141 | std::cerr << "Error: Shear Space -> Angle is not indempotent" << std::endl; 142 | std::cerr << A << std::endl; 143 | std::cerr << B << std::endl; 144 | ++nb_fails; 145 | } 146 | 147 | A = Cov(1.0, 2.0, 0.0, 0.0); 148 | B = Cov(0.0, 0.0, 1.0, 2.0); 149 | A.Travel(1); 150 | A.Curvature(-1, -1); 151 | if(!IsApprox(A, B)) { 152 | std::cerr << "Error: Curvature + Travel incorrect" << std::endl; 153 | std::cerr << A << std::endl; 154 | std::cerr << B << std::endl; 155 | ++nb_fails; 156 | } 157 | 158 | // Mimicking a lens looking at the plane in focus 159 | A = Cov(1.0, 1.0, 0.0, 0.0); 160 | B = A; 161 | A.Travel(1); 162 | A.Curvature(-2, -2); 163 | A.Travel(1); 164 | if(!IsApprox(A, B)) { 165 | std::cerr << "Error: Lens operator incorrect" << std::endl; 166 | std::cerr << A << std::endl; 167 | std::cerr << B << std::endl; 168 | ++nb_fails; 169 | } 170 | 171 | return nb_fails; 172 | } 173 | 174 | int TestProjection() { 175 | std::array matrix = {1.0, 176 | 0.1, 1.0, 177 | 0.0, 0.0, 1.0, 178 | 0.0, 0.0, 0.1, 1.0}; 179 | int nb_fails = 0; 180 | 181 | Vector x(1,0,0), y(0,1,0), z(0,0,1), n(0,0,1); 182 | /* 183 | std::cout << Vector::Cross(x, y) << " : " << z << std::endl; 184 | std::cout << Vector::Cross(z, x) << " : " << y << std::endl; 185 | std::cout << Vector::Cross(y, z) << " : " << x << std::endl; 186 | */ 187 | 188 | Cov A(matrix, x, y, z), B(matrix, x, y, z); 189 | 190 | A.Projection(z); 191 | if(!IsApprox(A, B)) { 192 | std::cerr << "Error: Project with n == z is not identity" << std::endl; 193 | std::cerr << A << std::endl; 194 | std::cerr << B << std::endl; 195 | ++nb_fails; 196 | } 197 | 198 | /* Perform a specular reflection on the identity covariance */ 199 | { 200 | const double rho = std::numeric_limits::max(); 201 | const double k = 0.0; 202 | x = Vector(0.0, 1.0, 0.0); 203 | y = Vector(0.5, 0.0, 0.5); y.Normalize(); 204 | z = Vector(0.5, 0.0, -0.5); z.Normalize(); 205 | Vector o = y; 206 | A = Cov(matrix, x, y, z); 207 | A.Travel(1); 208 | B = A; B.Symmetry(); // Reflection mirror along Y 209 | //std::cout << "A: " << A << std::endl; 210 | A.Projection(n); 211 | //std::cout << "After proj: " << A << std::endl; 212 | A.Curvature(k, k); 213 | //std::cout << "After curv: " << A << std::endl; 214 | A.Symmetry(); 215 | //std::cout << "After symm: " << A << std::endl; 216 | A.Reflection(rho, rho); 217 | //std::cout << "After refl: " << A << std::endl; 218 | A.Curvature(-k, -k); 219 | //std::cout << "After icur: " << A << std::endl; 220 | A.InverseProjection(o); 221 | //std::cout << "After ipro: " << A << std::endl; 222 | if(!IsApprox(A, B)) { 223 | std::cerr << "Error: Specular reflection preserve content but negate Y-correlations" << std::endl; 224 | std::cerr << A << std::endl; 225 | std::cerr << B << std::endl; 226 | ++nb_fails; 227 | } 228 | } 229 | 230 | { 231 | const double rho = std::numeric_limits::max(); 232 | const double k = 1.0; 233 | matrix[0] = 0.0; 234 | matrix[1] = 0.0; 235 | matrix[2] = 0.0; 236 | matrix[5] = 1.0; 237 | matrix[8] = 0.0; 238 | matrix[9] = 1.0; 239 | x = Vector( 1, 0, 0); 240 | y = Vector( 0,-1, 0); 241 | z = Vector( 0, 0,-1); 242 | n = Vector( 0, 0, 1); 243 | A = Cov(matrix, x, y, z); 244 | B = A; 245 | //std::cout << "A: " << A << std::endl; 246 | A.Projection(n); 247 | //std::cout << "After proj: " << A << std::endl; 248 | A.Curvature(k, k); 249 | //std::cout << "After curv: " << A << std::endl; 250 | A.Symmetry(); 251 | //std::cout << "After symm: " << A << std::endl; 252 | A.Reflection(rho, rho); 253 | //std::cout << "After refl: " << A << std::endl; 254 | A.Curvature(-k, -k); 255 | //std::cout << "After icur: " << A << std::endl; 256 | A.InverseProjection(n); 257 | //std::cout << "After ipro: " << A << std::endl; 258 | if(A.matrix[5] != B.matrix[5] && A.matrix[9] != B.matrix[9] && 259 | A.matrix[8] != B.matrix[8] && A.matrix[0] != 2.0*B.matrix[0] && 260 | A.matrix[2] != 2*B.matrix[2] && A.matrix[1] != 2*B.matrix[1]) { 261 | std::cerr << "Error: Specular curved reflection preserve angular content" << std::endl; 262 | std::cerr << " but increase spatial content." << std::endl; 263 | std::cerr << A << std::endl; 264 | std::cerr << B << std::endl; 265 | ++nb_fails; 266 | }} 267 | 268 | return nb_fails; 269 | } 270 | 271 | 272 | int TestReflection() { 273 | int nb_fails = 0; 274 | 275 | Cov A(0.0f, 0.0f, 1.0f, 1.0f); 276 | Cov B = A; 277 | Cov Z(0.0f, 0.0f, 0.0f, 0.0f); 278 | 279 | A.Reflection(0.0f, 0.0f); 280 | if(!IsApprox(A, Z)) { 281 | std::cerr << "Error: Diffuse reflection does not kill angular freqs" << std::endl; 282 | std::cerr << A << std::endl; 283 | std::cerr << Z << std::endl; 284 | ++nb_fails; 285 | } 286 | 287 | A = B; 288 | double rho = std::numeric_limits::max(); 289 | A.Reflection(rho, rho); 290 | if(!IsApprox(A, B)) { 291 | std::cerr << "Error: Specular reflection reduces angular freqs" << std::endl; 292 | std::cerr << A << std::endl; 293 | std::cerr << B << std::endl; 294 | ++nb_fails; 295 | } 296 | 297 | return nb_fails; 298 | } 299 | 300 | int TestOrientation() { 301 | int nb_fails = 0; 302 | 303 | std::array matrix; 304 | double r, k; 305 | Vector x, y, z, n, o; 306 | Cov A, B; 307 | 308 | { 309 | matrix = {1.0, 310 | 0.0, 3.0, 311 | 0.0, 0.0, 5.0, 312 | 0.0, 0.0, 0.0, 7.0}; 313 | r = std::numeric_limits::max(); 314 | k = 0.0; 315 | x = Vector( 0, 1, 0); 316 | y = Vector( 1, 0, 1); y.Normalize(); 317 | z = Vector( 1, 0,-1); z.Normalize(); 318 | n = Vector( 0, 0, 1); 319 | o = Vector( 0, 1, 1); o.Normalize(); 320 | A = Cov(matrix, x, y, z); 321 | B = A; 322 | //std::cout << "A: " << A << std::endl; 323 | A.Projection(n); 324 | //std::cout << "After proj: " << A << std::endl; 325 | A.Curvature(k, k); 326 | //std::cout << "After curv: " << A << std::endl; 327 | A.Symmetry(); 328 | //std::cout << "After symm: " << A << std::endl; 329 | A.Reflection(r, r); 330 | //std::cout << "After refl: " << A << std::endl; 331 | A.Curvature(-k, -k); 332 | //std::cout << "After icur: " << A << std::endl; 333 | A.InverseProjection(o); 334 | //std::cout << "After ipro: " << A << std::endl; 335 | if(A.matrix[5] != B.matrix[9] && A.matrix[9] != B.matrix[5] && 336 | A.matrix[8] != B.matrix[8]) { 337 | std::cerr << "Error: Specular curved reflection preserve angular content" << std::endl; 338 | std::cerr << " but increase spatial content." << std::endl; 339 | std::cerr << A << std::endl; 340 | std::cerr << B << std::endl; 341 | ++nb_fails; 342 | }} 343 | 344 | return nb_fails; 345 | } 346 | 347 | int TestVolume() { 348 | int nb_fails = 0; 349 | 350 | std::array matrix; 351 | Vector x, y, z; 352 | double t, k, vol; 353 | Cov A; 354 | 355 | x = Vector( 1, 0, 0); 356 | y = Vector( 0, 1, 0); 357 | z = Vector( 0, 0, 1); 358 | 359 | matrix = {1.0, 360 | 0.0, 1.0, 361 | 0.0, 0.0, 1.0, 362 | 0.0, 0.0, 0.0, 1.0}; 363 | t = 1.0/3.0; 364 | k = sqrt(2.0); 365 | A = Cov(matrix, x, y, z); 366 | 367 | std::cerr.precision(10); 368 | A.Travel(t); 369 | vol = A.Volume(); 370 | if(!IsApprox(vol, 1.0f)) { 371 | std::cerr << "Error: the travel operator does not conserve volume: " << vol << " ≠ 1" << std::endl; 372 | ++nb_fails; 373 | } 374 | 375 | A.Curvature(k, k); 376 | vol = A.Volume(); 377 | if(!IsApprox(vol, 1.0f)) { 378 | std::cerr << "Error: the curvature operator does not conserve volume: " << vol << " ≠ 1" << std::endl; 379 | ++nb_fails; 380 | } 381 | 382 | A.Symmetry(); 383 | vol = A.Volume(); 384 | if(!IsApprox(vol, 1.0f)) { 385 | std::cerr << "Error: the symmetry operator does not conserve volume: " << vol << " ≠ 1" << std::endl; 386 | ++nb_fails; 387 | } 388 | 389 | A.Curvature(-k, -k); 390 | vol = A.Volume(); 391 | if(!IsApprox(vol, 1.0f)) { 392 | std::cerr << "Error: the inv. curvature operator does not conserve volume: " << vol << " ≠ 1" << std::endl; 393 | ++nb_fails; 394 | } 395 | 396 | A.Rotate(0.2*M_PI); 397 | vol = A.Volume(); 398 | if(!IsApprox(vol, 1.0f)) { 399 | std::cerr << "Error: the rotation operator does not conserve volume: " << vol << " ≠ 1" << std::endl; 400 | ++nb_fails; 401 | } 402 | 403 | return nb_fails; 404 | } 405 | 406 | 407 | int main(int argc, char** argv) { 408 | int nb_fails = 0; 409 | std::cout << std::fixed << std::showpos << std::setprecision(2); 410 | 411 | nb_fails += TestRotation(); 412 | nb_fails += TestShear(); 413 | nb_fails += TestProjection(); 414 | nb_fails += TestReflection(); 415 | nb_fails += TestOrientation(); 416 | nb_fails += TestVolume(); 417 | 418 | if(nb_fails > 0) { 419 | return EXIT_FAILURE; 420 | } else { 421 | return EXIT_SUCCESS; 422 | } 423 | } -------------------------------------------------------------------------------- /tests/InvCovariance4D.cpp: -------------------------------------------------------------------------------- 1 | // STL includes 2 | #include 3 | #include 4 | #include 5 | 6 | // Covariance includes 7 | #include 8 | #include 9 | using namespace Covariance; 10 | 11 | struct Vector { 12 | double x, y, z; 13 | Vector() {} 14 | Vector(double x, double y, double z) : x(x), y(y), z(z) {} 15 | static double Dot(const Vector& w1, const Vector& w2) { 16 | return w1.x*w2.x + w1.y*w2.y + w1.z*w2.z; 17 | } 18 | static Vector Cross(const Vector& u, const Vector& v) { 19 | Vector r; 20 | r.x = u.y*v.z - u.z*v.y; 21 | r.y = u.z*v.x - u.x*v.z; 22 | r.z = u.x*v.y - u.y*v.x; 23 | return r; 24 | } 25 | void Normalize() { 26 | double norm = sqrt(Dot(*this, *this)); 27 | x /= norm; 28 | y /= norm; 29 | z /= norm; 30 | } 31 | friend Vector operator*(double a, const Vector& w) { 32 | Vector v; 33 | v.x = a*w.x; 34 | v.y = a*w.y; 35 | v.z = a*w.z; 36 | return v; 37 | } 38 | friend Vector operator+(const Vector& a, const Vector& w) { 39 | Vector v; 40 | v.x = a.x+w.x; 41 | v.y = a.y+w.y; 42 | v.z = a.z+w.z; 43 | return v; 44 | } 45 | friend Vector operator-(const Vector& a, const Vector& w) { 46 | Vector v; 47 | v.x = a.x-w.x; 48 | v.y = a.y-w.y; 49 | v.z = a.z-w.z; 50 | return v; 51 | } 52 | friend Vector operator-(const Vector& w) { 53 | Vector v; 54 | v.x = w.x; 55 | v.y = w.y; 56 | v.z = w.z; 57 | return v; 58 | } 59 | friend std::ostream& operator<<(std::ostream& out, const Vector& w) { 60 | out << "[" << w.x << ", " << w.y << ", " << w.z << "]"; 61 | return out; 62 | } 63 | }; 64 | 65 | using Cov = InvCovariance4D; 66 | 67 | bool IsApprox(double a, double b, double Eps=1.0E-3) { 68 | return std::abs(a - b) < Eps*std::max(0.5*(a+b), 1.0); 69 | } 70 | 71 | bool IsApprox(const Cov& A, const Cov& B, double Eps=1.0E-3) { 72 | bool isApprox = true; 73 | for(int i=0; i<10; ++i) { 74 | isApprox &= IsApprox(A.matrix[i], B.matrix[i], Eps); 75 | } 76 | return isApprox; 77 | } 78 | 79 | std::ostream& operator<<(std::ostream& out, const Cov& A) { 80 | for(int i=0; i<10; ++i) { 81 | out << A.matrix[i] << ", "; 82 | } 83 | return out; 84 | } 85 | 86 | int TestRotation() { 87 | int nb_fails = 0; 88 | 89 | Cov A, B; 90 | 91 | A = B = Cov(1.0, 0.0, 1.0, 0.0); 92 | A.Rotate(2.0*M_PI); 93 | if(!IsApprox(A, B)) { 94 | std::cerr << "Error: 2π rotation is not indempotent" << std::endl; 95 | std::cerr << A << std::endl; 96 | std::cerr << B << std::endl; 97 | ++nb_fails; 98 | } 99 | 100 | A = B; 101 | A.Rotate( 0.567); 102 | A.Rotate(-0.567); 103 | if(!IsApprox(A, B)) { 104 | std::cerr << "Error: Rotation and its inverse is not indempotent" << std::endl; 105 | std::cerr << " cos = " << cos(0.567) << std::endl; 106 | std::cerr << " sin = " << sin(0.567) << std::endl; 107 | A = B; 108 | std::cerr << A << std::endl; 109 | A.Rotate( 0.567); 110 | std::cerr << A << std::endl; 111 | A.Rotate(-0.567); 112 | std::cerr << A << std::endl; 113 | ++nb_fails; 114 | } 115 | 116 | return nb_fails; 117 | } 118 | 119 | int TestShear() { 120 | int nb_fails = 0; 121 | 122 | Cov A, B; 123 | double c = 123.456; 124 | double d = 765.432; 125 | 126 | A = B = Cov(1.0, 2.0, 3.0, 4.0); 127 | A.ShearAngleSpace( c, d); 128 | A.ShearAngleSpace(-c, -d); 129 | 130 | if(!IsApprox(A, B)) { 131 | std::cerr << "Error: Shear Angle -> Space is not indempotent" << std::endl; 132 | std::cerr << A << std::endl; 133 | std::cerr << B << std::endl; 134 | ++nb_fails; 135 | } 136 | 137 | A = B = Cov(1.0, 2.0, 3.0, 4.0); 138 | A.ShearSpaceAngle( c, d); 139 | A.ShearSpaceAngle(-c, -d); 140 | 141 | if(!IsApprox(A, B)) { 142 | std::cerr << "Error: Shear Space -> Angle is not indempotent" << std::endl; 143 | std::cerr << A << std::endl; 144 | std::cerr << B << std::endl; 145 | ++nb_fails; 146 | } 147 | 148 | A = Cov(1.0, 2.0, 0.0, 0.0); 149 | B = Cov(0.0, 0.0, 1.0, 2.0); 150 | A.Travel(1); 151 | A.Curvature(-1, -1); 152 | if(!IsApprox(A, B)) { 153 | std::cerr << "Error: Curvature + Travel incorrect" << std::endl; 154 | std::cerr << A << std::endl; 155 | std::cerr << B << std::endl; 156 | ++nb_fails; 157 | } 158 | 159 | // Mimicking a lens looking at the plane in focus 160 | A = Cov(1.0, 1.0, 0.0, 0.0); 161 | B = A; 162 | A.Travel(1); 163 | A.Curvature(-2, -2); 164 | A.Travel(1); 165 | if(!IsApprox(A, B)) { 166 | std::cerr << "Error: Lens operator incorrect" << std::endl; 167 | std::cerr << A << std::endl; 168 | std::cerr << B << std::endl; 169 | ++nb_fails; 170 | } 171 | 172 | return nb_fails; 173 | } 174 | 175 | int TestProjection() { 176 | std::array matrix = {1.0, 177 | 0.1, 1.0, 178 | 0.0, 0.0, 1.0, 179 | 0.0, 0.0, 0.1, 1.0}; 180 | int nb_fails = 0; 181 | 182 | Vector x(1,0,0), y(0,1,0), z(0,0,1), n(0,0,1); 183 | /* 184 | std::cout << Vector::Cross(x, y) << " : " << z << std::endl; 185 | std::cout << Vector::Cross(z, x) << " : " << y << std::endl; 186 | std::cout << Vector::Cross(y, z) << " : " << x << std::endl; 187 | */ 188 | 189 | Cov A(matrix, x, y, z), B(matrix, x, y, z); 190 | 191 | A.Projection(z); 192 | if(!IsApprox(A, B)) { 193 | std::cerr << "Error: Project with n == z is not identity" << std::endl; 194 | std::cerr << A << std::endl; 195 | std::cerr << B << std::endl; 196 | ++nb_fails; 197 | } 198 | 199 | /* Perform a specular reflection on the identity covariance */ 200 | { 201 | const double rho = std::numeric_limits::max(); 202 | const double k = 0.0; 203 | x = Vector(0.0, 1.0, 0.0); 204 | y = Vector(0.5, 0.0, 0.5); y.Normalize(); 205 | z = Vector(0.5, 0.0, -0.5); z.Normalize(); 206 | Vector o = y; 207 | A = Cov(matrix, x, y, z); 208 | A.Travel(1); 209 | B = A; B.Symmetry(); // Reflection mirror along Y 210 | //std::cout << "A: " << A << std::endl; 211 | A.Projection(n); 212 | //std::cout << "After proj: " << A << std::endl; 213 | A.Curvature(k, k); 214 | //std::cout << "After curv: " << A << std::endl; 215 | A.Symmetry(); 216 | //std::cout << "After symm: " << A << std::endl; 217 | A.Reflection(rho, rho); 218 | //std::cout << "After refl: " << A << std::endl; 219 | A.Curvature(-k, -k); 220 | //std::cout << "After icur: " << A << std::endl; 221 | A.InverseProjection(o); 222 | //std::cout << "After ipro: " << A << std::endl; 223 | if(!IsApprox(A, B)) { 224 | std::cerr << "Error: Specular reflection preserve content but negate Y-correlations" << std::endl; 225 | std::cerr << A << std::endl; 226 | std::cerr << B << std::endl; 227 | ++nb_fails; 228 | } 229 | } 230 | 231 | { 232 | const double rho = std::numeric_limits::max(); 233 | const double k = 1.0; 234 | matrix[0] = 0.0; 235 | matrix[1] = 0.0; 236 | matrix[2] = 0.0; 237 | matrix[5] = 1.0; 238 | matrix[8] = 0.0; 239 | matrix[9] = 1.0; 240 | x = Vector( 1, 0, 0); 241 | y = Vector( 0,-1, 0); 242 | z = Vector( 0, 0,-1); 243 | n = Vector( 0, 0, 1); 244 | A = Cov(matrix, x, y, z); 245 | B = A; 246 | //std::cout << "A: " << A << std::endl; 247 | A.Projection(n); 248 | //std::cout << "After proj: " << A << std::endl; 249 | A.Curvature(k, k); 250 | //std::cout << "After curv: " << A << std::endl; 251 | A.Symmetry(); 252 | //std::cout << "After symm: " << A << std::endl; 253 | A.Reflection(rho, rho); 254 | //std::cout << "After refl: " << A << std::endl; 255 | A.Curvature(-k, -k); 256 | //std::cout << "After icur: " << A << std::endl; 257 | A.InverseProjection(n); 258 | //std::cout << "After ipro: " << A << std::endl; 259 | if(A.matrix[5] != B.matrix[5] && A.matrix[9] != B.matrix[9] && 260 | A.matrix[8] != B.matrix[8] && A.matrix[0] != 2.0*B.matrix[0] && 261 | A.matrix[2] != 2*B.matrix[2] && A.matrix[1] != 2*B.matrix[1]) { 262 | std::cerr << "Error: Specular curved reflection preserve angular content" << std::endl; 263 | std::cerr << " but increase spatial content." << std::endl; 264 | std::cerr << A << std::endl; 265 | std::cerr << B << std::endl; 266 | ++nb_fails; 267 | }} 268 | 269 | return nb_fails; 270 | } 271 | 272 | 273 | int TestReflection() { 274 | int nb_fails = 0; 275 | 276 | Cov A(0.0f, 0.0f, 1.0f, 1.0f); 277 | Cov B = A; 278 | Cov Z(0.0f, 0.0f, 0.0f, 0.0f); 279 | 280 | A.Reflection(0.0f, 0.0f); 281 | if(!IsApprox(A, Z)) { 282 | std::cerr << "Error: Diffuse reflection does not kill angular freqs" << std::endl; 283 | std::cerr << A << std::endl; 284 | std::cerr << Z << std::endl; 285 | ++nb_fails; 286 | } 287 | 288 | A = B; 289 | double rho = std::numeric_limits::max(); 290 | A.Reflection(rho, rho); 291 | if(!IsApprox(A, B)) { 292 | std::cerr << "Error: Specular reflection reduces angular freqs" << std::endl; 293 | std::cerr << A << std::endl; 294 | std::cerr << B << std::endl; 295 | ++nb_fails; 296 | } 297 | 298 | return nb_fails; 299 | } 300 | 301 | int TestOrientation() { 302 | int nb_fails = 0; 303 | 304 | std::array matrix; 305 | double r, k; 306 | Vector x, y, z, n, o; 307 | Cov A, B; 308 | 309 | { 310 | matrix = {1.0, 311 | 0.0, 3.0, 312 | 0.0, 0.0, 5.0, 313 | 0.0, 0.0, 0.0, 7.0}; 314 | r = std::numeric_limits::max(); 315 | k = 0.0; 316 | x = Vector( 0, 1, 0); 317 | y = Vector( 1, 0, 1); y.Normalize(); 318 | z = Vector( 1, 0,-1); z.Normalize(); 319 | n = Vector( 0, 0, 1); 320 | o = Vector( 0, 1, 1); o.Normalize(); 321 | A = Cov(matrix, x, y, z); 322 | B = A; 323 | //std::cout << "A: " << A << std::endl; 324 | A.Projection(n); 325 | //std::cout << "After proj: " << A << std::endl; 326 | A.Curvature(k, k); 327 | //std::cout << "After curv: " << A << std::endl; 328 | A.Symmetry(); 329 | //std::cout << "After symm: " << A << std::endl; 330 | A.Reflection(r, r); 331 | //std::cout << "After refl: " << A << std::endl; 332 | A.Curvature(-k, -k); 333 | //std::cout << "After icur: " << A << std::endl; 334 | A.InverseProjection(o); 335 | //std::cout << "After ipro: " << A << std::endl; 336 | if(A.matrix[5] != B.matrix[9] && A.matrix[9] != B.matrix[5] && 337 | A.matrix[8] != B.matrix[8]) { 338 | std::cerr << "Error: Specular curved reflection preserve angular content" << std::endl; 339 | std::cerr << " but increase spatial content." << std::endl; 340 | std::cerr << A << std::endl; 341 | std::cerr << B << std::endl; 342 | ++nb_fails; 343 | }} 344 | 345 | return nb_fails; 346 | } 347 | 348 | int TestVolume() { 349 | int nb_fails = 0; 350 | 351 | std::array matrix; 352 | Vector x, y, z; 353 | double t, k, vol; 354 | Cov A; 355 | 356 | x = Vector( 1, 0, 0); 357 | y = Vector( 0, 1, 0); 358 | z = Vector( 0, 0, 1); 359 | 360 | matrix = {1.0, 361 | 0.0, 1.0, 362 | 0.0, 0.0, 1.0, 363 | 0.0, 0.0, 0.0, 1.0}; 364 | t = 1.0/3.0; 365 | k = sqrt(2.0); 366 | A = Cov(matrix, x, y, z); 367 | 368 | std::cerr.precision(10); 369 | A.Travel(t); 370 | vol = A.Volume(); 371 | if(!IsApprox(vol, 1.0)) { 372 | std::cerr << "Error: the travel operator does not conserve volume: " << vol << " ≠ 1" << std::endl; 373 | ++nb_fails; 374 | } 375 | 376 | A.Curvature(k, k); 377 | vol = A.Volume(); 378 | if(!IsApprox(vol, 1.0)) { 379 | std::cerr << "Error: the curvature operator does not conserve volume: " << vol << " ≠ 1" << std::endl; 380 | ++nb_fails; 381 | } 382 | 383 | A.Symmetry(); 384 | vol = A.Volume(); 385 | if(!IsApprox(vol, 1.0)) { 386 | std::cerr << "Error: the symmetry operator does not conserve volume: " << vol << " ≠ 1" << std::endl; 387 | ++nb_fails; 388 | } 389 | 390 | A.Curvature(-k, -k); 391 | vol = A.Volume(); 392 | if(!IsApprox(vol, 1.0)) { 393 | std::cerr << "Error: the inv. curvature operator does not conserve volume: " << vol << " ≠ 1" << std::endl; 394 | ++nb_fails; 395 | } 396 | 397 | A.Rotate(0.2*M_PI); 398 | vol = A.Volume(); 399 | if(!IsApprox(vol, 1.0)) { 400 | std::cerr << "Error: the rotation operator does not conserve volume: " << vol << " ≠ 1" << std::endl; 401 | ++nb_fails; 402 | } 403 | 404 | return nb_fails; 405 | } 406 | 407 | 408 | int main(int argc, char** argv) { 409 | int nb_fails = 0; 410 | std::cout << std::fixed << std::showpos << std::setprecision(2); 411 | 412 | nb_fails += TestRotation(); 413 | nb_fails += TestShear(); 414 | nb_fails += TestProjection(); 415 | nb_fails += TestReflection(); 416 | nb_fails += TestOrientation(); 417 | nb_fails += TestVolume(); 418 | 419 | if(nb_fails > 0) { 420 | return EXIT_FAILURE; 421 | } else { 422 | return EXIT_SUCCESS; 423 | } 424 | } -------------------------------------------------------------------------------- /tutorials/common.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Include STL 4 | #define _USE_MATH_DEFINES 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | double Clamp(double x) { 13 | return x<0 ? 0 : x>1 ? 1 : x; 14 | } 15 | 16 | struct Vector { 17 | double x, y, z; 18 | Vector(double x=0, double y=0, double z=0) : x(x), y(y), z(z) {} 19 | static double Dot(const Vector& w1, const Vector& w2) { 20 | return w1.x*w2.x + w1.y*w2.y + w1.z*w2.z; 21 | } 22 | static Vector Cross(const Vector& u, const Vector& v) { 23 | Vector r; 24 | r.x = u.y*v.z - u.z*v.y; 25 | r.y = u.z*v.x - u.x*v.z; 26 | r.z = u.x*v.y - u.y*v.x; 27 | return r; 28 | } 29 | Vector Normalize() { 30 | double norm = sqrt(Dot(*this, *this)); 31 | x /= norm; 32 | y /= norm; 33 | z /= norm; 34 | return *this; 35 | } 36 | static double Norm(const Vector& a) { 37 | return sqrt(Vector::Dot(a, a)); 38 | } 39 | bool IsNull() const { 40 | return x==0.0f && y==0.0f && z==0.0f; 41 | } 42 | Vector Reflect(const Vector& w, const Vector& n) { 43 | Vector r; 44 | const double dot = Dot(w, n); 45 | r = 2*dot*n - w; 46 | return r; 47 | } 48 | Vector Multiply(const Vector& m) { 49 | Vector r; 50 | r.x = this->x * m.x; 51 | r.y = this->y * m.y; 52 | r.z = this->z * m.z; 53 | return r; 54 | } 55 | friend Vector operator*(double a, const Vector& w) { 56 | Vector v; 57 | v.x = a*w.x; 58 | v.y = a*w.y; 59 | v.z = a*w.z; 60 | return v; 61 | } 62 | friend Vector operator*(const Vector& w, double a) { 63 | Vector v; 64 | v.x = a*w.x; 65 | v.y = a*w.y; 66 | v.z = a*w.z; 67 | return v; 68 | } 69 | friend Vector operator+(const Vector& a, const Vector& w) { 70 | Vector v; 71 | v.x = a.x+w.x; 72 | v.y = a.y+w.y; 73 | v.z = a.z+w.z; 74 | return v; 75 | } 76 | friend Vector operator-(const Vector& a, const Vector& w) { 77 | Vector v; 78 | v.x = a.x-w.x; 79 | v.y = a.y-w.y; 80 | v.z = a.z-w.z; 81 | return v; 82 | } 83 | friend Vector operator-(const Vector& w) { 84 | Vector v; 85 | v.x = -w.x; 86 | v.y = -w.y; 87 | v.z = -w.z; 88 | return v; 89 | } 90 | friend std::ostream& operator<<(std::ostream& out, const Vector& w) { 91 | out << "[" << w.x << ", " << w.y << ", " << w.z << "]"; 92 | return out; 93 | } 94 | static void Frame(const Vector& z, Vector& x, Vector& y) { 95 | if(std::fabs(z.x) > 0.1f) { 96 | x = Vector::Cross(Vector(0,1,0), z).Normalize(); 97 | } else { 98 | x = Vector::Cross(Vector(1,0,0), z).Normalize(); 99 | } 100 | y = Vector::Cross(z, x); 101 | } 102 | }; 103 | 104 | struct Ray { 105 | Vector o, d; 106 | Ray(const Vector& o, const Vector& d) : o(o), d(d) {} 107 | }; 108 | 109 | struct Camera { 110 | Vector o, d; 111 | Vector cx, cy; 112 | double fx, fy; 113 | 114 | Camera(const Vector& o, const Vector& d) : o(o), d(d), fx(1.0), fy(1.0) { 115 | Vector::Frame(d, cx, cy); 116 | } 117 | 118 | Vector PixelToDirection(const Vector uv) const { 119 | return (d + (uv.x-.5)*fx*cx + (uv.y-0.5)*fy*cy).Normalize(); 120 | } 121 | 122 | #ifdef TODO 123 | Vector DirectionToPixel(const Vector dir) const { 124 | double u,v; 125 | u = Vector::Dot(dir, cx); 126 | v = Vector::Dot(dir, cy); 127 | } 128 | #endif 129 | }; 130 | 131 | struct Material { 132 | // Diffuse and specular values 133 | Vector ke, kd, ks; 134 | double exponent; 135 | 136 | // Constructors 137 | Material(const Vector& ke, const Vector& kd, const Vector& ks, double exponent) 138 | : ke(ke), kd(kd), ks(ks), exponent(exponent) {} 139 | 140 | Material(const Vector& ke, const Vector& kd) 141 | : ke(ke), kd(kd), ks(), exponent(0.f) {} 142 | 143 | // Evaluate 144 | Vector Emission() const { 145 | return ke; 146 | } 147 | inline double PhongLobe(const Vector& wr, const Vector& wo) const { 148 | const auto dot = Vector::Dot(wr, wo); 149 | const auto f = ((exponent+1.f)/(2.f*M_PI))*pow(dot, exponent); 150 | return f; 151 | } 152 | Vector Reflectance(const Vector& wi, const Vector& wo, const Vector& n) const { 153 | if(Vector::Dot(wi, n) <= 0.0f || Vector::Dot(wo, n) <= 0.0f) { 154 | return Vector(); 155 | } 156 | const auto wr = 2.f*Vector::Dot(wi, n)*n - wi; 157 | const auto fs = PhongLobe(wr, wo); 158 | const auto fd = 1.f / (2.f * M_PI); 159 | return fd*kd + fs*ks; 160 | } 161 | 162 | // Sample phong 163 | Vector Sample(const Vector& wi, const Vector& n, 164 | const Vector& e, double& pdf) const { 165 | Vector u, v; 166 | if(Vector::Dot(wi, n) <= 0.0) { 167 | pdf = 0.0f; 168 | return Vector(); 169 | } 170 | 171 | // TODO here, should scale by the selection ratio 172 | if(e.z * Vector::Norm(kd+ks) <= Vector::Norm(ks)) { 173 | const auto cosT = pow(e.x, 1.f/(exponent+1.f)); 174 | const auto sinT = sqrt(1.f - cosT*cosT); 175 | const auto phi = 2*M_PI*e.y; 176 | const auto wr = 2.f*Vector::Dot(wi, n)*n - wi; 177 | Vector::Frame(wr, u, v); 178 | const auto wo = cosT*wr + sinT*(cos(phi)*u + sin(phi)*v); 179 | pdf = PhongLobe(wr, wo); 180 | return wo; 181 | } else { 182 | Vector::Frame(n, u, v); 183 | const auto cosT = e.x; 184 | const auto sinT = sqrt(1.f - cosT*cosT); 185 | const auto phi = 2*M_PI*e.y; 186 | const auto wo = cosT*n + sinT*(cos(phi)*u + sin(phi)*v); 187 | pdf = 1.f / (2.f * M_PI); 188 | return wo; 189 | } 190 | } 191 | }; 192 | 193 | struct Sphere { 194 | // Geometric information for the sphere 195 | Vector c; 196 | double r; 197 | 198 | // Emission and reflectance color 199 | Material mat; 200 | 201 | Sphere(const Vector& c, double r, const Material& mat) 202 | : c(c), r(r), mat(mat) {} 203 | Sphere(const Vector& c, double r, const Vector& ke, const Vector& kd) 204 | : c(c), r(r), mat(ke, kd) {} 205 | 206 | // returns distance, -1 if nohit 207 | double Intersect(const Ray &ray) const { 208 | Vector op = c-ray.o; // Solve t^2*d.d + 2*t*(o-c).d + (o-c).(o-c)-R^2 = 0 209 | double t, eps = 1e-4; 210 | double b = Vector::Dot(op, ray.d); 211 | double det = b*b - Vector::Dot(op,op) + r*r; 212 | if (det<0) { return 0; } else { det=sqrt(det); } 213 | return (t=b-det)>eps ? t : ((t=b+det)>eps ? t : 0); 214 | } 215 | }; 216 | 217 | /* Random number generator using the STL random and unfiorm distribution 218 | */ 219 | struct Random { 220 | 221 | Random(unsigned int seed) : _gen(seed), _dist(0.0, 1.0) {} 222 | Random() : _gen(clock()), _dist(0.0, 1.0) {} 223 | 224 | double operator()() { return _dist(_gen); } 225 | 226 | std::default_random_engine _gen; 227 | std::uniform_real_distribution _dist; 228 | }; 229 | 230 | /* 'Intersect' routine return the intersection of a ray with a vector of spheres. 231 | * this method return true if a sphere is intersected and false if nothing is 232 | * hit. If a hit is found, 't' contain the distance to the hit point and 'id' the 233 | * index of the hit sphere. 234 | */ 235 | inline bool Intersect(const std::vector& spheres, const Ray &r, double &t, int &id){ 236 | double d, inf=t=1e20; 237 | for(int i=spheres.size(); i--;) if((d=spheres[i].Intersect(r)) && d& spheres, 247 | const Ray &r, 248 | Random& rng, 249 | int depth, 250 | int maxdepth=1) { 251 | double t; // distance to intersection 252 | int id=0; // id of intersected object 253 | if (!Intersect(spheres, r, t, id)) return Vector(); // if miss, return black 254 | const Sphere& obj = spheres[id]; // the hit object 255 | const Material& mat = obj.mat; // Its material 256 | 257 | Vector x = r.o+r.d*t; 258 | Vector n = (x-obj.c).Normalize(); 259 | Vector nl = (Vector::Dot(n, r.d) < 0.f) ? n : (-1.f)*n; 260 | 261 | // If the object is a source return radiance 262 | if(!mat.ke.IsNull()) { 263 | return mat.ke; 264 | 265 | // Terminate the recursion after a finite number of call 266 | } else if(depth > maxdepth) { 267 | return Vector(); 268 | 269 | // Ray shooting 270 | } else { 271 | double pdf = 0.f; 272 | const auto e = Vector(rng(), rng(), rng()); 273 | const auto wo = -r.d; 274 | const auto wi = mat.Sample(wo, nl, e, pdf); 275 | if(Vector::Dot(wo, nl) <= 0.f || pdf <= 0.f) { 276 | return Vector(); 277 | } 278 | auto f = Vector::Dot(wi, nl)*mat.Reflectance(wi, wo, nl); 279 | const Vector rad = Radiance(spheres, Ray(x, wi), rng, depth+1, maxdepth); 280 | 281 | return (1.f/pdf) * f.Multiply(rad); 282 | } 283 | } 284 | 285 | // Covariance Tracing includes 286 | //* 287 | #include 288 | using Cov4D = Covariance::Covariance4D; 289 | /*/ 290 | #include 291 | using Cov4D = Covariance::InvCovariance4D; 292 | //*/ 293 | using PosCov = std::pair; 294 | 295 | std::ostream& operator<<(std::ostream& out, const Cov4D& cov) { 296 | out << std::scientific << std::showpos; 297 | out.precision(3); 298 | out << "[" << cov.matrix[0] << ",\t" << cov.matrix[1] << ",\t" << cov.matrix[3] << ",\t" << cov.matrix[6] << ";" << std::endl; 299 | out << " " << cov.matrix[1] << ",\t" << cov.matrix[2] << ",\t" << cov.matrix[4] << ",\t" << cov.matrix[7] << ";" << std::endl; 300 | out << " " << cov.matrix[3] << ",\t" << cov.matrix[4] << ",\t" << cov.matrix[5] << ",\t" << cov.matrix[8] << ";" << std::endl; 301 | out << " " << cov.matrix[6] << ",\t" << cov.matrix[7] << ",\t" << cov.matrix[8] << ",\t" << cov.matrix[9] << "]"; 302 | return out; 303 | } 304 | 305 | PosCov CovarianceFilter(const std::vector& spheres, const Ray &r, const Cov4D& cov, int depth, int maxdepth=2) { 306 | double t; // distance to Intersection 307 | int id=0; // id of Intersected object 308 | if (!Intersect(spheres, r, t, id)) return PosCov(Vector(), Cov4D()); // if miss, return black 309 | const Sphere& obj = spheres[id]; // the hit object 310 | const Material& mat = obj.mat; // Its material 311 | Vector x = r.o+r.d*t, 312 | n = (x-obj.c).Normalize(), 313 | nl = Vector::Dot(n,r.d) < 0 ? n:n*-1; 314 | const double k = 1.f/spheres[id].r; 315 | 316 | // Update the covariance with travel and project it onto the tangent plane 317 | // of the hit object. 318 | Cov4D cov2 = cov; 319 | cov2.Travel(t); 320 | cov2.Projection(n); 321 | 322 | // if the max depth is reached 323 | if(depth >= maxdepth) { 324 | cov2.matrix[1] = - cov2.matrix[1]; 325 | return PosCov(x, cov2); 326 | } else { 327 | 328 | // Sample a new direction 329 | auto wi = -r.d; 330 | auto wr = 2*Vector::Dot(wi, nl)*nl - wi; 331 | auto r2 = Ray(x, wr); 332 | 333 | cov2.Curvature(k, k); 334 | cov2.Cosine(1.0f); 335 | cov2.Symmetry(); 336 | const double rho = mat.exponent / (4*M_PI*M_PI); 337 | cov2.Reflection(rho, rho); 338 | cov2.Curvature(-k, -k); 339 | cov2.InverseProjection(wr); 340 | return CovarianceFilter(spheres, r2, cov2, depth+1, maxdepth); 341 | } 342 | } 343 | 344 | // TinyEXR includes 345 | #define TINYEXR_IMPLEMENTATION 346 | #include 347 | 348 | int SaveEXR(const Vector* img, int w, int h, const std::string& filename) { 349 | EXRImage image; 350 | InitEXRImage(&image); 351 | image.num_channels = 3; 352 | const char* names[] = {"B", "G", "R"}; 353 | 354 | std::vector images[3]; 355 | images[0].resize(w * h); 356 | images[1].resize(w * h); 357 | images[2].resize(w * h); 358 | 359 | for (int i = 0; i < w * h; i++) { 360 | images[0][i] = img[i].x; 361 | images[1][i] = img[i].y; 362 | images[2][i] = img[i].z; 363 | } 364 | 365 | float* image_ptr[3]; 366 | image_ptr[0] = &(images[2].at(0)); // B 367 | image_ptr[1] = &(images[1].at(0)); // G 368 | image_ptr[2] = &(images[0].at(0)); // R 369 | 370 | image.channel_names = names; 371 | image.images = (unsigned char**)image_ptr; 372 | image.width = w; 373 | image.height = h; 374 | image.compression = TINYEXR_COMPRESSIONTYPE_ZIP; 375 | 376 | image.pixel_types = (int *)malloc(sizeof(int) * image.num_channels); 377 | image.requested_pixel_types = (int *)malloc(sizeof(int) * image.num_channels); 378 | for (int i = 0; i < image.num_channels; i++) { 379 | image.pixel_types[i] = TINYEXR_PIXELTYPE_FLOAT; // pixel type of input image 380 | image.requested_pixel_types[i] = TINYEXR_PIXELTYPE_HALF; // pixel type of output image to be stored in .EXR 381 | } 382 | 383 | const char* err; 384 | int ret = SaveMultiChannelEXRToFile(&image, filename.c_str(), &err); 385 | if (ret != 0) { 386 | fprintf(stderr, "Save EXR err: %s\n", err); 387 | } 388 | 389 | free(image.pixel_types); 390 | free(image.requested_pixel_types); 391 | return ret; 392 | } 393 | -------------------------------------------------------------------------------- /tutorials/opengl.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // STL includes 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #ifdef __APPLE__ 10 | #include 11 | #include 12 | #else 13 | #include 14 | #include 15 | #include 16 | #endif 17 | 18 | 19 | /*** 20 | author : r3dux 21 | version : 0.3 - 15/01/2014 22 | description: Gets GLSL source code either provided as strings or can load from filenames, 23 | compiles the shaders, creates a shader program which the shaders are linked 24 | to, then the program is validated and is ready for use via myProgram.use(), 25 | then calling myProgram.disable(); 26 | 27 | Attributes and uniforms are stored in maps and can be added 28 | via calls to addAttribute() and then the attribute 29 | index can be obtained via myProgram.attribute() - Uniforms 30 | work in the exact same way. 31 | ***/ 32 | 33 | class ShaderProgram 34 | { 35 | private: 36 | // static DEBUG flag - if set to false then, errors aside, we'll run completely silent 37 | bool DEBUG = true; 38 | 39 | // We'll use an enum to differentiate between shaders and shader programs when querying the info log 40 | enum class ObjectType 41 | { 42 | SHADER, PROGRAM 43 | }; 44 | 45 | // Shader program and individual shader Ids 46 | GLuint programId; 47 | GLuint vertexShaderId; 48 | GLuint fragmentShaderId; 49 | 50 | // How many shaders are attached to the shader program 51 | GLuint shaderCount; 52 | 53 | // Map of attributes and their binding locations 54 | std::map attributeMap; 55 | 56 | // Map of uniforms and their binding locations 57 | std::map uniformMap; 58 | 59 | // Has this shader program been initialised? 60 | bool initialised; 61 | 62 | // ---------- PRIVATE METHODS ---------- 63 | 64 | // Private method to compile a shader of a given type 65 | GLuint compileShader(std::string shaderSource, GLenum shaderType) 66 | { 67 | std::string shaderTypeString; 68 | switch (shaderType) 69 | { 70 | case GL_VERTEX_SHADER: 71 | shaderTypeString = "GL_VERTEX_SHADER"; 72 | break; 73 | case GL_FRAGMENT_SHADER: 74 | shaderTypeString = "GL_FRAGMENT_SHADER"; 75 | break; 76 | #ifdef GL_GEOMETRY_SHADER 77 | case GL_GEOMETRY_SHADER: 78 | throw std::runtime_error("Geometry shaders are unsupported at this time."); 79 | break; 80 | #endif 81 | default: 82 | throw std::runtime_error("Bad shader type enum in compileShader."); 83 | break; 84 | } 85 | 86 | // Generate a shader id 87 | // Note: Shader id will be non-zero if successfully created. 88 | GLuint shaderId = glCreateShader(shaderType); 89 | if (shaderId == 0) 90 | { 91 | // Display the shader log via a runtime_error 92 | throw std::runtime_error("Could not create shader of type " + shaderTypeString + ": " + getInfoLog(ObjectType::SHADER, shaderId) ); 93 | } 94 | 95 | // Get the source string as a pointer to an array of characters 96 | const char *shaderSourceChars = shaderSource.c_str(); 97 | 98 | // Attach the GLSL source code to the shader 99 | // Params: GLuint shader, GLsizei count, const GLchar **string, const GLint *length 100 | // Note: The pointer to an array of source chars will be null terminated, so we don't need to specify the length and can instead use NULL. 101 | glShaderSource(shaderId, 1, &shaderSourceChars, NULL); 102 | 103 | // Compile the shader 104 | glCompileShader(shaderId); 105 | 106 | // Check the compilation status and throw a runtime_error if shader compilation failed 107 | GLint shaderStatus; 108 | glGetShaderiv(shaderId, GL_COMPILE_STATUS, &shaderStatus); 109 | if (shaderStatus == GL_FALSE) 110 | { 111 | throw std::runtime_error(shaderTypeString + " compilation failed: " + getInfoLog(ObjectType::SHADER, shaderId) ); 112 | } 113 | else 114 | { 115 | if (DEBUG) 116 | { 117 | std::cout << shaderTypeString << " shader compilation successful." << std::endl; 118 | } 119 | } 120 | 121 | // If everything went well, return the shader id 122 | return shaderId; 123 | } 124 | 125 | // Private method to compile/attach/link/verify the shaders. 126 | // Note: Rather than returning a boolean as a success/fail status we'll just consider 127 | // a failure here to be an unrecoverable error and throw a runtime_error. 128 | void initialise(std::string vertexShaderSource, std::string fragmentShaderSource) 129 | { 130 | // Compile the shaders and return their id values 131 | vertexShaderId = compileShader(vertexShaderSource, GL_VERTEX_SHADER); 132 | fragmentShaderId = compileShader(fragmentShaderSource, GL_FRAGMENT_SHADER); 133 | 134 | // Attach the compiled shaders to the shader program 135 | glAttachShader(programId, vertexShaderId); 136 | glAttachShader(programId, fragmentShaderId); 137 | 138 | // Link the shader program - details are placed in the program info log 139 | glLinkProgram(programId); 140 | 141 | // Once the shader program has the shaders attached and linked, the shaders are no longer required. 142 | // If the linking failed, then we're going to abort anyway so we still detach the shaders. 143 | glDetachShader(programId, vertexShaderId); 144 | glDetachShader(programId, fragmentShaderId); 145 | 146 | // Check the program link status and throw a runtime_error if program linkage failed. 147 | GLint programLinkSuccess = GL_FALSE; 148 | glGetProgramiv(programId, GL_LINK_STATUS, &programLinkSuccess); 149 | if (programLinkSuccess == GL_TRUE) 150 | { 151 | if (DEBUG) 152 | { 153 | std::cout << "Shader program link successful." << std::endl; 154 | } 155 | } 156 | else 157 | { 158 | throw std::runtime_error("Shader program link failed: " + getInfoLog(ObjectType::PROGRAM, programId) ); 159 | } 160 | 161 | // Validate the shader program 162 | glValidateProgram(programId); 163 | 164 | // Check the validation status and throw a runtime_error if program validation failed 165 | GLint programValidatationStatus; 166 | glGetProgramiv(programId, GL_VALIDATE_STATUS, &programValidatationStatus); 167 | if (programValidatationStatus == GL_TRUE) 168 | { 169 | if (DEBUG) 170 | { 171 | std::cout << "Shader program validation successful." << std::endl; 172 | } 173 | } 174 | else 175 | { 176 | throw std::runtime_error("Shader program validation failed: " + getInfoLog(ObjectType::PROGRAM, programId) ); 177 | } 178 | 179 | // Finally, the shader program is initialised 180 | initialised = true; 181 | } 182 | 183 | // Private method to load the shader source code from a file 184 | std::string loadShaderFromFile(const std::string filename) 185 | { 186 | // Create an input filestream and attempt to open the specified file 187 | std::ifstream file( filename.c_str() ); 188 | 189 | // If we couldn't open the file we'll bail out 190 | if ( !file.good() ) 191 | { 192 | throw std::runtime_error("Failed to open file: " + filename); 193 | } 194 | 195 | // Otherwise, create a string stream... 196 | std::stringstream stream; 197 | 198 | // ...and dump the contents of the file into it. 199 | stream << file.rdbuf(); 200 | 201 | // Now that we've read the file we can close it 202 | file.close(); 203 | 204 | // Finally, convert the stringstream into a string and return it 205 | return stream.str(); 206 | } 207 | 208 | // Private method to return the current shader program info log as a string 209 | std::string getInfoLog(ObjectType type, int id) 210 | { 211 | GLint infoLogLength; 212 | if (type == ObjectType::SHADER) 213 | { 214 | glGetShaderiv(id, GL_INFO_LOG_LENGTH, &infoLogLength); 215 | } 216 | else // type must be ObjectType::PROGRAM 217 | { 218 | glGetProgramiv(id, GL_INFO_LOG_LENGTH, &infoLogLength); 219 | } 220 | 221 | GLchar *infoLog = new GLchar[infoLogLength + 1]; 222 | if (type == ObjectType::SHADER) 223 | { 224 | glGetShaderInfoLog(id, infoLogLength, NULL, infoLog); 225 | } 226 | else // type must be ObjectType::PROGRAM 227 | { 228 | glGetProgramInfoLog(id, infoLogLength, NULL, infoLog); 229 | } 230 | 231 | // Convert the info log to a string 232 | std::string infoLogString(infoLog); 233 | 234 | // Delete the char array version of the log 235 | delete[] infoLog; 236 | 237 | // Finally, return the string version of the info log 238 | return infoLogString; 239 | } 240 | 241 | public: 242 | // Constructor 243 | ShaderProgram(bool DEBUG_=true) : DEBUG(DEBUG_) 244 | { 245 | // We start in a non-initialised state - calling initFromFiles() or initFromStrings() will 246 | // initialise us. 247 | initialised = false; 248 | 249 | // Generate a unique Id / handle for the shader program 250 | // Note: We MUST have a valid rendering context before generating the programId or we'll segfault! 251 | programId = glCreateProgram(); 252 | glUseProgram(programId); 253 | 254 | // Initially, we have zero shaders attached to the program 255 | shaderCount = 0; 256 | } 257 | 258 | // Destructor 259 | ~ShaderProgram() 260 | { 261 | // Delete the shader program from the graphics card memory to 262 | // free all the resources it's been using 263 | glDeleteProgram(programId); 264 | } 265 | 266 | // Method to initialise a shader program from shaders provided as files 267 | void initFromFiles(std::string vertexShaderFilename, std::string fragmentShaderFilename) 268 | { 269 | // Get the shader file contents as strings 270 | std::string vertexShaderSource = loadShaderFromFile(vertexShaderFilename); 271 | std::string fragmentShaderSource = loadShaderFromFile(fragmentShaderFilename); 272 | 273 | initialise(vertexShaderSource, fragmentShaderSource); 274 | } 275 | 276 | // Method to initialise a shader program from shaders provided as strings 277 | void initFromStrings(std::string vertexShaderSource, std::string fragmentShaderSource) 278 | { 279 | initialise(vertexShaderSource, fragmentShaderSource); 280 | } 281 | 282 | // Method to enable the shader program - we'll suggest this for inlining 283 | inline void use() 284 | { 285 | // Santity check that we're initialised and ready to go... 286 | if (initialised) 287 | { 288 | glUseProgram(programId); 289 | } 290 | else 291 | { 292 | std::string msg = "Shader program " + programId; 293 | msg += " not initialised - aborting."; 294 | throw std::runtime_error(msg); 295 | } 296 | } 297 | 298 | // Method to disable the shader - we'll also suggest this for inlining 299 | inline void disable() 300 | { 301 | glUseProgram(0); 302 | } 303 | 304 | // Method to return the bound location of a named attribute, or -1 if the attribute was not found 305 | GLuint attribute(const std::string attributeName) 306 | { 307 | // You could do this method with the single line: 308 | // 309 | // return attributeMap[attribute]; 310 | // 311 | // BUT, if you did, and you asked it for a named attribute which didn't exist 312 | // like: attributeMap["FakeAttrib"] then the method would return an invalid 313 | // value which will likely cause the program to segfault. So we're making sure 314 | // the attribute asked for exists, and if it doesn't then we alert the user & bail. 315 | 316 | // Create an iterator to look through our attribute map (only create iterator on first run - 317 | // reuse it for all further calls). 318 | static std::map::const_iterator attributeIter; 319 | 320 | // Try to find the named attribute 321 | attributeIter = attributeMap.find(attributeName); 322 | 323 | // Not found? Bail. 324 | if ( attributeIter == attributeMap.end() ) 325 | { 326 | throw std::runtime_error("Could not find attribute in shader program: " + attributeName); 327 | } 328 | 329 | // Otherwise return the attribute location from the attribute map 330 | return attributeMap[attributeName]; 331 | } 332 | 333 | // Method to returns the bound location of a named uniform 334 | GLuint uniform(const std::string uniformName) 335 | { 336 | // Note: You could do this method with the single line: 337 | // 338 | // return uniformLocList[uniform]; 339 | // 340 | // But we're not doing that. Explanation in the attribute() method above. 341 | 342 | // Create an iterator to look through our uniform map (only create iterator on first run - 343 | // reuse it for all further calls). 344 | static std::map::const_iterator uniformIter; 345 | 346 | // Try to find the named uniform 347 | uniformIter = uniformMap.find(uniformName); 348 | 349 | // Found it? Great - pass it back! Didn't find it? Alert user and halt. 350 | if ( uniformIter == uniformMap.end() ) 351 | { 352 | throw std::runtime_error("Could not find uniform in shader program: " + uniformName); 353 | } 354 | 355 | // Otherwise return the attribute location from the uniform map 356 | return uniformMap[uniformName]; 357 | } 358 | 359 | // Method to add an attribute to the shader and return the bound location 360 | int addAttribute(const std::string attributeName) 361 | { 362 | // Add the attribute location value for the attributeName key 363 | attributeMap[attributeName] = glGetAttribLocation( programId, attributeName.c_str() ); 364 | 365 | // Check to ensure that the shader contains an attribute with this name 366 | if (attributeMap[attributeName] == -1) 367 | { 368 | throw std::runtime_error("Could not add attribute: " + attributeName + " - location returned -1."); 369 | } 370 | else // Valid attribute location? Inform user if we're in debug mode. 371 | { 372 | if (DEBUG) 373 | { 374 | std::cout << "Attribute " << attributeName << " bound to location: " << attributeMap[attributeName] << std::endl; 375 | } 376 | } 377 | 378 | // Return the attribute location 379 | return attributeMap[attributeName]; 380 | } 381 | 382 | // Method to add a uniform to the shader and return the bound location 383 | int addUniform(const std::string uniformName) 384 | { 385 | // Add the uniform location value for the uniformName key 386 | uniformMap[uniformName] = glGetUniformLocation( programId, uniformName.c_str() ); 387 | 388 | // Check to ensure that the shader contains a uniform with this name 389 | if (uniformMap[uniformName] == -1) 390 | { 391 | throw std::runtime_error("Could not add uniform: " + uniformName + " - location returned -1."); 392 | } 393 | else // Valid uniform location? Inform user if we're in debug mode. 394 | { 395 | if (DEBUG) 396 | { 397 | std::cout << "Uniform " << uniformName << " bound to location: " << uniformMap[uniformName] << std::endl; 398 | } 399 | } 400 | 401 | // Return the uniform location 402 | return uniformMap[uniformName]; 403 | } 404 | 405 | }; // End of class 406 | 407 | 408 | // Handle the mouse 409 | struct CMouse 410 | { 411 | double X ,Y, // Normalized mouse position 412 | X0,Y0, // Previous position 413 | Dx,Dy; // Displacement 414 | int Left, Middle, Right; // Mouse buttons state 415 | int Shift, Ctrl, Alt; // Modifiers state 416 | int State; // Global state (see following flags) 417 | } mouse; 418 | 419 | /* Events (mouse + modifiers) */ 420 | #define MOUSE_SHIFT 0x0001 421 | #define MOUSE_CTRL 0x0002 422 | #define MOUSE_ALT 0x0004 423 | #define MOUSE_LEFT 0x0010 424 | #define MOUSE_MIDDLE 0x0020 425 | #define MOUSE_RIGHT 0x0040 426 | 427 | void MouseClicked(int Button, int State, int x, int y) { 428 | int Modifiers; 429 | switch(Button) { 430 | case GLUT_LEFT_BUTTON: 431 | mouse.Left = ( State == GLUT_DOWN ); break; 432 | case GLUT_MIDDLE_BUTTON: 433 | mouse.Middle = ( State == GLUT_DOWN ); break; 434 | case GLUT_RIGHT_BUTTON: 435 | mouse.Right = ( State == GLUT_DOWN ); 436 | break; 437 | } 438 | 439 | Modifiers = glutGetModifiers(); 440 | mouse.Shift = ( Modifiers & GLUT_ACTIVE_SHIFT ) ?1:0; 441 | mouse.Ctrl = ( Modifiers & GLUT_ACTIVE_CTRL ) ?1:0; 442 | mouse.Alt = ( Modifiers & GLUT_ACTIVE_ALT ) ?1:0; 443 | 444 | mouse.State = MOUSE_SHIFT*mouse.Shift | 445 | MOUSE_CTRL*mouse.Ctrl | 446 | MOUSE_ALT*mouse.Alt | 447 | MOUSE_LEFT*mouse.Left | 448 | MOUSE_MIDDLE*mouse.Middle | 449 | MOUSE_RIGHT*mouse.Right; 450 | 451 | 452 | double width = glutGet(GLUT_WINDOW_WIDTH); 453 | double height = glutGet(GLUT_WINDOW_HEIGHT); 454 | mouse.X0 = mouse.X; 455 | mouse.X = x /(double)width; 456 | mouse.Dx = mouse.X0 - mouse.X; 457 | mouse.Y0 = mouse.Y; 458 | mouse.Y = (height-y)/(double)height; 459 | mouse.Dy = mouse.Y0 - mouse.Y; 460 | 461 | glutPostRedisplay(); 462 | } 463 | 464 | void MouseMoved(int x, int y) { 465 | double width = glutGet(GLUT_WINDOW_WIDTH); 466 | double height = glutGet(GLUT_WINDOW_HEIGHT); 467 | mouse.X0 = mouse.X; 468 | mouse.X = x /(double)width; 469 | mouse.Dx = mouse.X0 - mouse.X; 470 | mouse.Y0 = mouse.Y; 471 | mouse.Y = (height-y)/(double)height; 472 | mouse.Dy = mouse.Y0 - mouse.Y; 473 | 474 | glutPostRedisplay(); 475 | } 476 | -------------------------------------------------------------------------------- /tutorials/tutorial1.cpp: -------------------------------------------------------------------------------- 1 | // STL includes 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | std::default_random_engine gen; 8 | std::uniform_real_distribution dist(0,1); 9 | 10 | // Local includes 11 | #include "common.hpp" 12 | 13 | // Covariance Tracing includes 14 | #include 15 | using namespace Covariance; 16 | using Cov = Covariance4D; 17 | using RadCov = std::pair; 18 | 19 | 20 | Material phong(Vector(), Vector(0,0,0), Vector(1,1,1)*.999, 100.0); 21 | 22 | std::vector spheres = { 23 | Sphere(Vector( 1e5+1,40.8,81.6), 1e5, Vector(),Vector(.75,.25,.25)),//Left 24 | Sphere(Vector(-1e5+99,40.8,81.6), 1e5, Vector(),Vector(.25,.25,.75)),//Rght 25 | Sphere(Vector(50,40.8, 1e5), 1e5, Vector(),Vector(.75,.75,.75)),//Back 26 | Sphere(Vector(50,40.8,-1e5+170), 1e5, Vector(),Vector() ),//Frnt 27 | Sphere(Vector(50, 1e5, 81.6), 1e5, Vector(),Vector(.75,.75,.75)),//Botm 28 | Sphere(Vector(50,-1e5+81.6,81.6), 1e5, Vector(),Vector(.75,.75,.75)),//Top 29 | Sphere(Vector(27,16.5,47), 16.5, phong),//Mirr 30 | Sphere(Vector(73,16.5,78), 16.5, Vector(),Vector(1,1,1)*.999),//Glas 31 | Sphere(Vector(50,681.6-.27,81.6), 600, Vector(12,12,12), Vector()) //Lite 32 | }; 33 | 34 | RadCov radiance(const Ray &r, int depth, int maxdepth=1){ 35 | double t; // distance to intersection 36 | int id=0; // id of intersected object 37 | if (!Intersect(spheres, r, t, id)) return RadCov(Vector(), Cov()); // if miss, return black 38 | const Sphere& obj = spheres[id]; // the hit object 39 | const Material& mat = obj.mat; // Its material 40 | 41 | Vector x = r.o+r.d*t; 42 | Vector n = (x-obj.c).Normalize(); 43 | Vector nl = (Vector::Dot(n, r.d) < 0.f) ? n : (-1.f)*n; 44 | 45 | const double k = 1.f/spheres[id].r; 46 | 47 | /* Local Frame at the surface of the object */ 48 | Vector w = nl; 49 | Vector u = Vector::Cross((fabs(w.x) > .1 ? Vector(0,1,0) : Vector(1,0,0)), w).Normalize(); 50 | Vector v = Vector::Cross(w, u); 51 | 52 | // If the object is a source, return the its covariance. A source has a 53 | // constant angular emission but has bounded spatial extent. Since there 54 | // is no way here to infer the extent of the source, its frequency is 55 | // defined as 1.0E5. 56 | if(!mat.ke.IsNull()) { 57 | Cov cov({ 1.0E2, 0.0, 1.0E2, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }, u, v, w); 58 | cov.InverseProjection(-r.d); 59 | cov.Travel(t); 60 | return RadCov(mat.ke, cov) ; 61 | 62 | // Terminate the recursion after a finite number of call. Since this 63 | // implementation is recursive and passing covariance objects, the 64 | // number of max bounces is deterministic. 65 | } else if(depth > maxdepth) { 66 | Cov cov({ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }, u, v, w); 67 | cov.InverseProjection(-r.d); 68 | return RadCov(Vector(), cov) ; 69 | 70 | // Main covariance computation. First this code generate a new direction 71 | // and query the covariance+radiance in that direction. Then, it computes 72 | // the covariance after the reflection/refraction. 73 | } else { 74 | /* Sampling a new direction + recursive call */ 75 | double pdf = 0.f; 76 | const auto e = Vector(dist(gen), dist(gen), dist(gen)); 77 | const auto wo = -r.d; 78 | const auto wi = mat.Sample(wo, nl, e, pdf); 79 | if(Vector::Dot(wo, nl) <= 0.f || pdf <= 0.f) { 80 | Cov cov({ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }, u, v, w); 81 | cov.InverseProjection(wo); 82 | return RadCov(Vector((pdf <= 0.f) ? 1.0 : 0.0,0.0,0.0), cov) ; 83 | } 84 | auto f = Vector::Dot(wi, nl)*mat.Reflectance(wi, wo, nl); 85 | const RadCov radcov = radiance(Ray(x, wi), depth+1); 86 | 87 | /* Covariance computation */ 88 | Cov cov = radcov.second; 89 | cov.Projection(nl); 90 | cov.Curvature(k, k); 91 | cov.Cosine(1.0f); 92 | cov.Symmetry(); 93 | const double rho = mat.exponent / (4*M_PI*M_PI); 94 | cov.Reflection(rho, rho); // TODO correct the formula 95 | cov.Curvature(-k, -k); 96 | cov.InverseProjection(-r.d); 97 | cov.Travel(t); 98 | return RadCov((1.f/pdf) * f.Multiply(radcov.first), cov); 99 | } 100 | } 101 | 102 | #include 103 | 104 | int main(int argc, char** argv){ 105 | int w=512, h=512, samps = argc==2 ? atoi(argv[1])/4 : 1; // # samples 106 | Ray cam(Vector(50,52,295.6), Vector(0,-0.042612,-1).Normalize()); // cam pos, dir 107 | cam.o = cam.o + 140.0*cam.d; 108 | double fovx = 1.2; // 0.5135; 109 | double fovy = 1.2; // 0.5135; 110 | Vector cx = Vector(fovx); 111 | Vector cy = Vector::Cross(cx, cam.d).Normalize()*fovy; 112 | Vector ncx = cx; ncx.Normalize(); 113 | Vector ncy = cy; ncy.Normalize(); 114 | Vector* img = new Vector[w*h]; 115 | 116 | _MM_SET_EXCEPTION_MASK(_MM_GET_EXCEPTION_MASK() & ~_MM_MASK_INVALID); 117 | 118 | // Loop over the rows and columns of the image and evaluate radiance and 119 | // covariance per pixel using Monte-Carlo. 120 | #pragma omp parallel for schedule(dynamic, 1) private(gen) 121 | for (int y=0; y 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | // Local includes 11 | #include "common.hpp" 12 | #include "tutorial2.hpp" 13 | 14 | std::stringstream sout; 15 | 16 | PosCov CovarianceFilter(const std::vector& spheres, const Ray &r, 17 | const Cov4D& cov, int depth, int maxdepth, 18 | std::stringstream& out) { 19 | double t; 20 | int id=0; 21 | if (!Intersect(spheres, r, t, id)) { 22 | return PosCov(Vector(), Cov4D()); 23 | } 24 | const Sphere& obj = spheres[id]; 25 | const Material& mat = obj.mat; 26 | Vector x = r.o+r.d*t, 27 | n = (x-obj.c).Normalize(), 28 | nl = Vector::Dot(n,r.d) < 0 ? n:n*-1; 29 | const double k = 1.f/spheres[id].r; 30 | 31 | // Update the covariance with travel and project it onto the tangent plane 32 | // of the hit object. 33 | Cov4D cov2 = cov; 34 | cov2.Travel(t); 35 | out << "After travel of " << t << " meters" << std::endl; 36 | out << cov2 << std::endl; 37 | out << "Volume = " << cov2.Volume() << std::endl; 38 | out << std::endl; 39 | 40 | out << "After projection, cos=" << Vector::Dot(n, cov2.z) << std::endl; 41 | cov2.Projection(n); 42 | out << cov2 << std::endl; 43 | out << "Volume = " << cov2.Volume() << std::endl; 44 | out << std::endl; 45 | 46 | // if the max depth is reached 47 | if(depth >= maxdepth) { 48 | return PosCov(x, cov2); 49 | } else { 50 | // Sample a new direction 51 | auto wi = -r.d; 52 | auto wr = 2*Vector::Dot(wi, nl)*nl - wi; 53 | auto r2 = Ray(x, wr); 54 | 55 | cov2.Curvature(k, k); 56 | out << "After curvature" << std::endl; 57 | out << cov2 << std::endl; 58 | out << "Volume = " << cov2.Volume() << std::endl; 59 | out << std::endl; 60 | 61 | cov2.Cosine(1.0f); 62 | out << "After cosine multiplication" << std::endl; 63 | out << cov2 << std::endl; 64 | 65 | cov2.Symmetry(); 66 | out << "After symmetry" << std::endl; 67 | out << cov2 << std::endl; 68 | out << "Volume = " << cov2.Volume() << std::endl; 69 | out << std::endl; 70 | 71 | const double rho = mat.exponent / (4*M_PI*M_PI); 72 | cov2.Reflection(rho, rho); 73 | out << "After BRDF convolution, sigma=" << rho << std::endl; 74 | out << cov2 << std::endl; 75 | out << "Volume = " << cov2.Volume() << std::endl; 76 | out << std::endl; 77 | 78 | cov2.Curvature(-k, -k); 79 | out << "After inverse curvature, k=" << -k << std::endl; 80 | out << cov2 << std::endl; 81 | out << "Volume = " << cov2.Volume() << std::endl; 82 | out << std::endl; 83 | 84 | cov2.InverseProjection(wr); 85 | out << "After inverse projection, cos=" << Vector::Dot(n, wr) << std::endl; 86 | out << cov2 << std::endl; 87 | out << "Volume = " << cov2.Volume() << std::endl; 88 | out << std::endl; 89 | 90 | return CovarianceFilter(spheres, r2, cov2, depth+1, maxdepth, out); 91 | } 92 | } 93 | 94 | // Should the covariance image display the Gaussian filter or the equivalent 95 | // ray differential's footprint? 96 | bool useCovFilter = true; 97 | 98 | void CovarianceTexture(int x, int y) { 99 | // Generate a covariance matrix at the sampling position 100 | const auto t = (cx*((x+0.5)/double(width) - .5) + cy*((y+0.5)/double(height) - .5) + cam.d).Normalize(); 101 | //*/ 102 | const auto pixelCov = Cov4D({ 1.0E+5, 0.0, 1.0E+5, 0.0, 0.0, 1.0E+5, 0.0, 0.0, 0.0, 1.0E+5 }, t); 103 | /*/ 104 | const auto pixelCov = Cov4D({ 1.0E-5, 0.0, 1.0E-5, 0.0, 0.0, 1.0E-5, 0.0, 0.0, 0.0, 1.0E-5 }, t); 105 | //*/ 106 | sout.str(""); 107 | const auto surfCov = CovarianceFilter(spheres, Ray(cam.o, t), pixelCov, 0, 1, sout); 108 | sout << surfCov.second << std::endl; 109 | sout << "Volume = " << surfCov.second.Volume() << std::endl; 110 | sout << std::endl; 111 | 112 | double sxx = 0, syy = 0, sxy = 0; 113 | Vector Dx, Dy; 114 | try { 115 | surfCov.second.SpatialFilter(sxx, sxy, syy); 116 | surfCov.second.SpatialExtent(Dx, Dy); 117 | sout << "Spatial filter = [" << sxx << "," << sxy << "; " << sxy << ", " << syy << "]"<< std::endl; 118 | sout << "Extent = " << Dx << ", " << Dy << std::endl; 119 | sout << "|Dx| = " << Vector::Norm(Dx) << ", |Dy| = " << Vector::Norm(Dy) << std::endl; 120 | } catch (...) { 121 | std::cout << "Error: incorrect spatial filter" << std::endl; 122 | sout << surfCov.second << std::endl; 123 | return; 124 | } 125 | 126 | // Loop over the rows and columns of the image and evaluate radiance and 127 | // covariance per pixel using Monte-Carlo. 128 | #pragma omp parallel for schedule(dynamic, 1) 129 | for (int y=0; y { argv, argv + argc }) { 178 | if(str.compare("generateRef") == 0) { 179 | generateReference = true; 180 | } 181 | 182 | if(str.compare("hideBackground") == 0) { 183 | generateBackground = false; 184 | } 185 | } 186 | 187 | // Display the covariance filter 188 | if(generateCovariance) { 189 | CovarianceTexture(x, y); 190 | } 191 | 192 | // Progressively display the background image and the brute force evaluation 193 | // of the indirect pixel filter. 194 | for(int i=0; i spheres = { 27 | Sphere(Vector(27,16.5,47), 16.5, phongH),//RightSp 28 | Sphere(Vector(73,16.5,78), 16.5, phongL),//LeftSp 29 | Sphere(Vector( 1e5+1,40.8,81.6), 1e5, Vector(),Vector(.75,.25,.25)),//Left 30 | Sphere(Vector(-1e5+99,40.8,81.6), 1e5, Vector(),Vector(.25,.25,.75)),//Rght 31 | Sphere(Vector(50,40.8, 1e5), 1e5, Vector(),Vector(.75,.75,.75)),//Back 32 | Sphere(Vector(50,40.8,-1e5+170), 1e5, Vector(),Vector() ),//Frnt 33 | Sphere(Vector(50, 1e5, 81.6), 1e5, Vector(),Vector(.75,.75,.75)),//Botm 34 | Sphere(Vector(50,-1e5+81.6,81.6), 1e5, Vector(),Vector(.75,.75,.75)),//Top 35 | Sphere(Vector(50,681.6-.27,81.6), 600, Vector(12,12,12), Vector()) //Lite 36 | }; 37 | 38 | int width = 512, height = 512; 39 | 40 | Ray cam(Vector(50.0f, 46.0f, 155.8f), Vector(0,0,-1)); 41 | double fov = 1.2; 42 | Vector cx = Vector(width*fov/height); 43 | Vector cy = Vector::Cross(cx, cam.d).Normalize()*fov; 44 | Vector ncx = 1.0/Vector::Norm(cx) * cx; 45 | Vector ncy = 1.0/Vector::Norm(cy) * cy; 46 | 47 | 48 | 49 | /*****************************************************************************\ 50 | 51 | Predefined variables: 52 | 53 | + '*_img' are the buffers corresponding to the background image, the 54 | covariance filter image and the brute force pixel filter image. Each 55 | buffer is associated with a scaling factor to normalize the outputed 56 | image. 57 | 58 | + 'nPasses' is the number of passes used to render the background image 59 | using progressive rendering. 60 | 61 | + 'nPassesFilter' is the number of passes used to render the indirect pixel 62 | filter image using progressive rendering and 'filterRadius' is the radius 63 | used for density estimation. 64 | 65 | \*****************************************************************************/ 66 | 67 | float* bcg_img = new float[width*height]; float bcg_scale = 1.0f; 68 | float* cov_img = new float[width*height]; float cov_scale = 1.0f; 69 | float* ref_img = new float[width*height]; float ref_scale = 1.0f; 70 | 71 | int nPasses = 0; 72 | int nPassesFilter = 0; 73 | float filterRadius = 1.0f; 74 | 75 | std::default_random_engine gen; 76 | std::uniform_real_distribution dist(0,1); 77 | 78 | bool displayBackground = true; 79 | bool generateBackground = true; 80 | bool generateCovariance = true; 81 | bool generateReference = false; 82 | 83 | 84 | 85 | /*****************************************************************************\ 86 | 87 | Render the indirect pixel filter after a specified number of bounces. To 88 | render the indirect pixel filter, I use density estimation. 89 | 90 | First the method 'indirect_filter' enables to compute the position in world 91 | space of a path tracing sample after 'maxdepth' bounces. Its return a pair 92 | of World space coordinate and RGB value corresponding to the importance of 93 | generate this point. 94 | 95 | Then, using the 'BruteForceTexture', a list of world space samples are used 96 | to perform density estimation for a complete frame. This method performs 97 | progressive density estimation to converge towards the correct filter image. 98 | 99 | \*****************************************************************************/ 100 | 101 | using PosFilter = std::pair; 102 | 103 | PosFilter indirect_filter(const Ray &r, Random& rng, int depth, int maxdepth=2){ 104 | double t; // distance to Intersection 105 | int id=0; // id of Intersected object 106 | if (!Intersect(spheres, r, t, id)) return PosFilter(Vector(), Vector()); // if miss, return black 107 | const Sphere& obj = spheres[id]; // the hit object 108 | const Material& mat = obj.mat; // Its material 109 | Vector x = r.o+r.d*t, 110 | n = (x-obj.c).Normalize(), 111 | nl = Vector::Dot(n,r.d) < 0 ? n:n*-1; 112 | 113 | // Once you reach the max depth, return the hit position and the filter's 114 | // value using the recursive form. 115 | if(depth >= maxdepth) { 116 | return PosFilter(x, Vector(1.0f, 1.0f, 1.0f)); 117 | 118 | // Main covariance computation. First this code generate a new direction 119 | // and query the covariance+radiance in that direction. Then, it computes 120 | // the covariance after the reflection/refraction. 121 | } else { 122 | /* Sampling a new direction + recursive call */ 123 | double pdf; 124 | const auto e = Vector(rng(), rng(), rng()); 125 | const auto wo = -r.d; 126 | const auto wi = mat.Sample(wo, nl, e, pdf); 127 | auto f = Vector::Dot(wi, nl)*mat.Reflectance(wi, wo, nl); 128 | const auto res = indirect_filter(Ray(x, wi), rng, depth+1, maxdepth); 129 | return PosFilter(res.first, (1.f/pdf) * f.Multiply(res.second)); 130 | } 131 | } 132 | 133 | void BruteForceTexture(int x, int y, int samps = 1000) { 134 | 135 | std::vector _filter_elems; 136 | 137 | // Sub pixel sampling 138 | const int nthread = std::thread::hardware_concurrency(); 139 | #pragma omp parallel for schedule(dynamic, 1) 140 | for(int t=0; t 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | // Local includes 12 | #include "common.hpp" 13 | #include "opengl.hpp" 14 | #include "tutorial2.hpp" 15 | 16 | std::stringstream sout; 17 | 18 | PosCov CovarianceFilter(const std::vector& spheres, const Ray &r, 19 | const Cov4D& cov, int depth, int maxdepth, 20 | std::stringstream& out) { 21 | double t; 22 | int id=0; 23 | if (!Intersect(spheres, r, t, id)) { 24 | return PosCov(Vector(), Cov4D()); 25 | } 26 | const Sphere& obj = spheres[id]; 27 | const Material& mat = obj.mat; 28 | Vector x = r.o+r.d*t, 29 | n = (x-obj.c).Normalize(), 30 | nl = Vector::Dot(n,r.d) < 0 ? n:n*-1; 31 | const double k = 1.f/spheres[id].r; 32 | 33 | // Update the covariance with travel and project it onto the tangent plane 34 | // of the hit object. 35 | Cov4D cov2 = cov; 36 | cov2.Travel(t); 37 | out << "After travel of " << t << " meters" << std::endl; 38 | out << cov2 << std::endl; 39 | out << "Volume = " << cov2.Volume() << std::endl; 40 | out << std::endl; 41 | 42 | out << "After projection, cos=" << Vector::Dot(n, cov2.z) << std::endl; 43 | cov2.Projection(n); 44 | out << cov2 << std::endl; 45 | out << "Volume = " << cov2.Volume() << std::endl; 46 | out << std::endl; 47 | 48 | // if the max depth is reached 49 | if(depth >= maxdepth) { 50 | return PosCov(x, cov2); 51 | } else { 52 | // Sample a new direction 53 | auto wi = -r.d; 54 | auto wr = 2*Vector::Dot(wi, nl)*nl - wi; 55 | auto r2 = Ray(x, wr); 56 | 57 | cov2.Curvature(k, k); 58 | out << "After curvature" << std::endl; 59 | out << cov2 << std::endl; 60 | out << "Volume = " << cov2.Volume() << std::endl; 61 | out << std::endl; 62 | 63 | cov2.Cosine(1.0f); 64 | out << "After cosine multiplication" << std::endl; 65 | out << cov2 << std::endl; 66 | 67 | cov2.Symmetry(); 68 | out << "After symmetry" << std::endl; 69 | out << cov2 << std::endl; 70 | out << "Volume = " << cov2.Volume() << std::endl; 71 | out << std::endl; 72 | 73 | const double rho = mat.exponent / (4*M_PI*M_PI); 74 | cov2.Reflection(rho, rho); 75 | out << "After BRDF convolution, sigma=" << rho << std::endl; 76 | out << cov2 << std::endl; 77 | out << "Volume = " << cov2.Volume() << std::endl; 78 | out << std::endl; 79 | 80 | cov2.Curvature(-k, -k); 81 | out << "After inverse curvature, k=" << -k << std::endl; 82 | out << cov2 << std::endl; 83 | out << "Volume = " << cov2.Volume() << std::endl; 84 | out << std::endl; 85 | 86 | cov2.InverseProjection(wr); 87 | out << "After inverse projection, cos=" << Vector::Dot(n, wr) << std::endl; 88 | out << cov2 << std::endl; 89 | out << "Volume = " << cov2.Volume() << std::endl; 90 | out << std::endl; 91 | 92 | return CovarianceFilter(spheres, r2, cov2, depth+1, maxdepth, out); 93 | } 94 | } 95 | 96 | // Should the covariance image display the Gaussian filter or the equivalent 97 | // ray differential's footprint? 98 | bool useCovFilter = true; 99 | 100 | void CovarianceTexture() { 101 | // Generate a covariance matrix at the sampling position 102 | int x = width*mouse.X, y = height*mouse.Y; 103 | const auto t = (cx*((x+0.5)/double(width) - .5) + cy*((y+0.5)/double(height) - .5) + cam.d).Normalize(); 104 | //*/ 105 | const auto pixelCov = Cov4D({ 1.0E+5, 0.0, 1.0E+5, 0.0, 0.0, 1.0E+5, 0.0, 0.0, 0.0, 1.0E+5 }, t); 106 | /*/ 107 | const auto pixelCov = Cov4D({ 1.0E-5, 0.0, 1.0E-5, 0.0, 0.0, 1.0E-5, 0.0, 0.0, 0.0, 1.0E-5 }, t); 108 | //*/ 109 | sout.str(""); 110 | const auto surfCov = CovarianceFilter(spheres, Ray(cam.o, t), pixelCov, 0, 1, sout); 111 | sout << surfCov.second << std::endl; 112 | sout << "Volume = " << surfCov.second.Volume() << std::endl; 113 | sout << std::endl; 114 | 115 | double sxx = 0, syy = 0, sxy = 0; 116 | Vector Dx, Dy; 117 | try { 118 | surfCov.second.SpatialFilter(sxx, sxy, syy); 119 | surfCov.second.SpatialExtent(Dx, Dy); 120 | sout << "Spatial filter = [" << sxx << "," << sxy << "; " << sxy << ", " << syy << "]"<< std::endl; 121 | sout << "Extent = " << Dx << ", " << Dy << std::endl; 122 | sout << "|Dx| = " << Vector::Norm(Dx) << ", |Dy| = " << Vector::Norm(Dy) << std::endl; 123 | } catch (...) { 124 | std::cout << "Error: incorrect spatial filter" << std::endl; 125 | sout << surfCov.second << std::endl; 126 | return; 127 | } 128 | 129 | // Loop over the rows and columns of the image and evaluate radiance and 130 | // covariance per pixel using Monte-Carlo. 131 | #pragma omp parallel for schedule(dynamic, 1) 132 | for (int y=0; y 0.0f || fabs(mouse.Dy) > 0.0f) { 221 | nPassesFilter = 0; 222 | filterRadius = 1.0f; 223 | } 224 | BruteForceTexture(width*mouse.X, height*mouse.Y); 225 | 226 | glActiveTexture(GL_TEXTURE2); 227 | glBindTexture(GL_TEXTURE_2D, texs_id[2]); 228 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 229 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_LUMINANCE, GL_FLOAT, ref_img); 230 | } 231 | 232 | program->use(); 233 | 234 | glActiveTexture(GL_TEXTURE0); 235 | glBindTexture(GL_TEXTURE_2D, texs_id[0]); 236 | 237 | glActiveTexture(GL_TEXTURE1); 238 | glBindTexture(GL_TEXTURE_2D, texs_id[1]); 239 | 240 | glActiveTexture(GL_TEXTURE2); 241 | glBindTexture(GL_TEXTURE_2D, texs_id[2]); 242 | 243 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); 244 | auto uniLocation = program->uniform("pointer"); 245 | glUniform2f(uniLocation, mouse.Y, 1.0-mouse.X); 246 | 247 | // Update the scaling 248 | glUniform1f(program->uniform("tex0scale"), displayBackground ? bcg_scale : 0.0f); 249 | glUniform1f(program->uniform("tex1scale"), generateCovariance ? cov_scale : 0.0f); 250 | glUniform1f(program->uniform("tex2scale"), generateReference ? ref_scale : 0.0f); 251 | 252 | glBegin(GL_QUADS); 253 | glVertex3f(-1.0f,-1.0f, 0.0f); glTexCoord2f(0, 0); 254 | glVertex3f( 1.0f,-1.0f, 0.0f); glTexCoord2f(1, 0); 255 | glVertex3f( 1.0f, 1.0f, 0.0f); glTexCoord2f(1, 1); 256 | glVertex3f(-1.0f, 1.0f, 0.0f); glTexCoord2f(0, 1); 257 | glEnd(); 258 | program->disable(); 259 | glutSwapBuffers(); 260 | 261 | if(generateReference || generateBackground || generateCovariance) { 262 | glutPostRedisplay(); 263 | } 264 | } 265 | 266 | // Create geometry and textures 267 | void Init() { 268 | // Background color 269 | glClearColor(0.0f, 0.0f, 0.0f, 2.0f); 270 | 271 | // Create the shader programs 272 | program = new ShaderProgram(false); 273 | std::string vertShader = 274 | "void main(void) {" 275 | " gl_TexCoord[0] = gl_MultiTexCoord0;" 276 | " gl_Position = vec4(gl_Vertex);" 277 | "}"; 278 | std::string fragShader = 279 | "uniform sampler2D tex0; uniform float tex0scale;" 280 | "uniform sampler2D tex1; uniform float tex1scale;" 281 | "uniform sampler2D tex2; uniform float tex2scale;" 282 | "uniform vec2 pointer;" 283 | "uniform float width;" 284 | "uniform float height;" 285 | "void main(void) {" 286 | " float fact = exp(- width*height * pow(length(gl_TexCoord[0].xy - pointer.xy), 2.0));" 287 | " gl_FragColor = vec4(0,0,1,1)*fact + tex0scale*vec4(1,1,1,1)*texture2D(tex0, gl_TexCoord[0].st) + tex1scale*vec4(1,0,0,1)*texture2D(tex1, gl_TexCoord[0].st) + tex2scale*vec4(0,1,0,1)*texture2D(tex2, gl_TexCoord[0].st);" 288 | "}"; 289 | program->initFromStrings(vertShader, fragShader); 290 | 291 | // Reserve textures on the GPU 292 | glGenTextures(3, texs_id); 293 | 294 | // Define the different uniform locations in the shader 295 | program->use(); 296 | 297 | const auto t1Location = program->addUniform("tex0"); 298 | glUniform1i(t1Location, 0); 299 | const auto t2Location = program->addUniform("tex1"); 300 | glUniform1i(t2Location, 1); 301 | const auto t3Location = program->addUniform("tex2"); 302 | glUniform1i(t3Location, 2); 303 | 304 | const auto t1sLocation = program->addUniform("tex0scale"); 305 | glUniform1f(t1sLocation, bcg_scale); 306 | const auto t2sLocation = program->addUniform("tex1scale"); 307 | glUniform1f(t2sLocation, cov_scale); 308 | const auto t3sLocation = program->addUniform("tex2scale"); 309 | glUniform1f(t3sLocation, ref_scale); 310 | 311 | const auto uniWidth = program->addUniform("width"); 312 | glUniform1f(uniWidth, float(width)); 313 | const auto uniHeight = program->addUniform("height"); 314 | glUniform1f(uniHeight, float(height)); 315 | 316 | const auto uniLocation = program->addUniform("pointer"); 317 | glUniform2f(uniLocation, width*mouse.X, height*mouse.Y); 318 | 319 | program->disable(); 320 | 321 | // Clean buffer memory 322 | memset(ref_img, 0.0, width*height); 323 | memset(cov_img, 0.0, width*height); 324 | } 325 | 326 | void PrintHelp() { 327 | std::cout << "Covariance Tracing tutorial 2" << std::endl; 328 | std::cout << "----------------------------" << std::endl; 329 | std::cout << std::endl; 330 | std::cout << "This tutorial display the indirect pixel filter after one bounce for" << std::endl; 331 | std::cout << "non-diffuse surfaces. To display the filter, click on one of the two" << std::endl; 332 | std::cout << "shiny spheres." << std::endl; 333 | std::cout << std::endl; 334 | std::cout << " + 'b' stop/resume rendering the background image" << std::endl; 335 | std::cout << " + 'h' hide/show the background image" << std::endl; 336 | std::cout << " + 'c' stop/resume rendering the covariance filter" << std::endl; 337 | std::cout << " + 'B' brute-force indirect pixel filter (SLOW)" << std::endl; 338 | std::cout << " + 'p' output image to EXR file" << std::endl; 339 | std::cout << " + 'd' print Covariance Tracing step by step" << std::endl; 340 | std::cout << " + 'f' switch between displaying the Gaussian filter or the polygonal footprint" << std::endl; 341 | std::cout << " + '+' increase the BRDF exponent for the right sphere" << std::endl; 342 | std::cout << " + '-' decrease the BRDF exponent for the right sphere" << std::endl; 343 | std::cout << std::endl; 344 | } 345 | 346 | int main(int argc, char** argv) { 347 | 348 | mouse.X = 0.5; 349 | mouse.Y = 0.5; 350 | 351 | PrintHelp(); 352 | 353 | glutInit(&argc, argv); 354 | glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH); 355 | 356 | glutInitWindowSize(width, height); 357 | glutCreateWindow("Covariance Tracing tutorial 2"); 358 | 359 | Init(); 360 | 361 | glutDisplayFunc(Draw); 362 | glutMouseFunc(MouseClicked); 363 | glutMotionFunc(MouseMoved); 364 | glutKeyboardFunc(KeyboardKeys); 365 | glutMainLoop(); 366 | 367 | if(bcg_img) { delete[] bcg_img; } 368 | if(cov_img) { delete[] cov_img; } 369 | if(ref_img) { delete[] ref_img; } 370 | return EXIT_SUCCESS; 371 | } 372 | --------------------------------------------------------------------------------