Loading [MathJax]/jax/output/HTML-CSS/config.js
Radium Engine  1.5.28
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
EnvironmentTexture.cpp
1#include <algorithm>
2#include <cmath>
3#include <filesystem>
4#include <iostream>
5
6#include <cstring>
7
8#include <Engine/Data/EnvironmentTexture.hpp>
9
10#include <stb/stb_image.h>
11#include <stb/stb_image_write.h>
12
13#include <Core/Asset/Camera.hpp>
14#include <Core/Geometry/MeshPrimitives.hpp>
15#include <Core/Math/Math.hpp>
16#include <Core/Resources/Resources.hpp>
17
18#include <Engine/Data/Mesh.hpp>
19#include <Engine/Data/ShaderProgram.hpp>
20#include <Engine/Data/ShaderProgramManager.hpp>
21#include <Engine/Data/Texture.hpp>
22#include <Engine/Data/ViewingParameters.hpp>
23#include <Engine/RadiumEngine.hpp>
24#include <Engine/Scene/CameraManager.hpp>
25
26#include <tinyEXR/tinyexr.h>
27
28namespace Ra {
29namespace Engine {
30namespace Data {
31using namespace gl;
32
33// Flip horizontally an image of w x h pixels with c commponents
34template <typename T>
35void flip_horizontally( T* img, size_t w, size_t h, size_t c ) {
36#pragma omp parallel for firstprivate( img )
37 for ( int r = 0; r < int( h ); ++r ) {
38 auto limg = img + r * ( w * c );
39 for ( int l = 0; l < int( w ) / 2; ++l ) {
40 T* from = limg + ( l * c );
41 T* to = limg + ( w - ( l + 1 ) ) * c;
42 for ( int e = 0; e < int( c ); ++e ) {
43 std::swap( *( from + e ), *( to + e ) );
44 }
45 }
46 }
47}
48
49// -------------------------------------------------------------------
50class PfmReader
51{
52 public:
53 typedef struct {
54 float r, g, b;
55 } HDRPIXEL;
56
57 typedef struct {
58 int width;
59 int height;
60 HDRPIXEL* pixels;
61 } HDRIMAGE;
62 /*
63 Read a possibly byte swapped floating point number
64 Assume IEEE format
65 */
66 static HDRIMAGE* open( std::string fileName ) {
67 FILE* fptr = fopen( fileName.c_str(), "rb" );
68 if ( nullptr == fptr ) {
69 std::cerr << "could not open " << fileName << std::endl;
70 return nullptr;
71 }
72 int width, height, color, swap;
73 if ( !readHeader( fptr, width, height, color, swap ) ) {
74 std::cerr << "bad header " << fileName << std::endl;
75 return nullptr;
76 }
77
78 HDRPIXEL* pixels = new HDRPIXEL[width * height];
79 for ( int i = 0; i < width * height; ++i ) {
80 float* ptr = &( pixels[i].r );
81 if ( !readFloat( fptr, ptr, swap ) ) {
82 std::cerr << "could not read pixel " << i << std::endl;
83 }
84 if ( color == 3 ) {
85 if ( !readFloat( fptr, ptr + 1, swap ) ) {
86 std::cerr << "could not read pixel " << i << std::endl;
87 }
88 if ( !readFloat( fptr, ptr + 2, swap ) ) {
89 std::cerr << "could not read pixel " << i << std::endl;
90 }
91 }
92 }
93 HDRIMAGE* ret = new HDRIMAGE;
94 ret->width = width;
95 ret->height = height;
96 ret->pixels = pixels;
97 return ret;
98 }
99
100 static bool readFloat( FILE* fptr, float* n, int swap ) {
101
102 if ( fread( n, 4, 1, fptr ) != 1 ) return false;
103 if ( swap ) {
104 unsigned char* cptr = (unsigned char*)n;
105 std::swap( cptr[0], cptr[3] );
106 std::swap( cptr[1], cptr[2] );
107 }
108
109 return true;
110 }
111
112 static bool readHeader( FILE* fptr, int& width, int& height, int& color, int& swap ) {
113 char buf[32];
114 // read first line
115 if ( NULL == fgets( buf, 32, fptr ) ) return false;
116 // determine gray or color : PF -> color, Pf -> gray
117 if ( 0 == strncmp( "PF", buf, 2 ) )
118 color = 3;
119 else if ( 0 == strncmp( "Pf", buf, 2 ) )
120 color = 1;
121 else
122 return false;
123 // read second line
124 if ( NULL == fgets( buf, 32, fptr ) ) return false;
125 // determine image size
126 char* endptr = NULL;
127 width = strtol( buf, &endptr, 10 );
128 if ( endptr == buf ) return false;
129 height = strtol( endptr, (char**)NULL, 10 );
130 // read third line
131 if ( NULL == fgets( buf, 32, fptr ) ) return false;
132 if ( endptr == buf ) return false;
133 float aspect = strtof( buf, &endptr );
134 if ( aspect > 0 )
135 swap = 1;
136 else
137 swap = 0;
138 return true;
139 }
140};
141
142// -------------------------------------------------------------------
143using namespace Ra::Core;
144
145// -------------------------------------------------------------------
146EnvironmentTexture::EnvironmentTexture( const std::string& mapName, bool isSkybox ) :
147 m_name( mapName ),
148 m_skyData { { nullptr, nullptr, nullptr, nullptr, nullptr, nullptr } },
149 m_isSkyBox( isSkybox ) {
150 m_type = ( mapName.find( ';' ) != mapName.npos ) ? EnvironmentTexture::EnvMapType::ENVMAP_CUBE
151 : EnvironmentTexture::EnvMapType::ENVMAP_PFM;
152 if ( m_type == EnvironmentTexture::EnvMapType::ENVMAP_PFM ) {
153 auto ext = mapName.substr( mapName.size() - 3 );
154 if ( ext != "pfm" ) { m_type = EnvironmentTexture::EnvMapType::ENVMAP_LATLON; }
155 }
156 initializeTexture();
157}
158
159void EnvironmentTexture::initializeTexture() {
160 switch ( m_type ) {
161 case EnvMapType::ENVMAP_PFM:
162 setupTexturesFromPfm();
163 break;
164 case EnvMapType::ENVMAP_CUBE:
165 setupTexturesFromCube();
166 break;
167 case EnvMapType::ENVMAP_LATLON:
168 setupTexturesFromSphericalEquiRectangular();
169 break;
170 default:
171 LOG( logERROR ) << "EnvironmentTexture::initializeTexture(): unkown EnvMapType";
172 }
173 computeSHMatrices();
174 // make the envmap cube texture
175
176 // proper convert shared ptr float[] to void
178 { m_skyData[0], m_skyData[1], m_skyData[2], m_skyData[3], m_skyData[4], m_skyData[5] } };
180 {
181 GL_CLAMP_TO_EDGE,
182 GL_CLAMP_TO_EDGE,
183 GL_CLAMP_TO_EDGE,
184 GL_LINEAR_MIPMAP_LINEAR,
185 GL_LINEAR,
186 },
187 { GL_TEXTURE_CUBE_MAP,
188 m_width,
189 m_height,
190 1,
191 GL_RGBA,
192 GL_RGBA,
193 GL_FLOAT,
194 false,
195 std::move( cubeMap ) } };
196 m_skyTexture = std::make_unique<Ra::Engine::Data::Texture>( params );
197
198 if ( m_isSkyBox ) {
199 // make the skybox geometry
200 Aabb aabb( Vector3 { -1_ra, -1_ra, -1_ra }, Vector3 { 1_ra, 1_ra, 1_ra } );
201 Geometry::TriangleMesh skyMesh = Geometry::makeSharpBox( aabb );
202 m_displayMesh = std::make_unique<Ra::Engine::Data::Mesh>( "skyBox" );
203 m_displayMesh->loadGeometry( std::move( skyMesh ) );
204 }
205}
206
207void EnvironmentTexture::setupTexturesFromPfm() {
208 PfmReader::HDRIMAGE* img = PfmReader::open( m_name.c_str() );
209 m_width = img->width / 3;
210 m_height = img->height / 4;
211
212 for ( int imgIdx = 0; imgIdx < 6; ++imgIdx ) {
213 m_skyData[imgIdx] = std::shared_ptr<float[]>( new float[m_width * m_height * 4] );
214 }
215
216#pragma omp parallel for
217 for ( int imgIdx = 0; imgIdx < 6; ++imgIdx ) {
218 int xOffset = 0;
219 int yOffset = 0;
220 switch ( imgIdx ) {
221 case 0: // X- side
222 xOffset = 1 * m_width;
223 yOffset = 0 * m_height;
224 break;
225 case 1: // X+ side
226 xOffset = 1 * m_width;
227 yOffset = 2 * m_height;
228 break;
229 case 2: // Y+ side
230 xOffset = 1 * m_width;
231 yOffset = 3 * m_height;
232 break;
233 case 3: // Y- side
234 xOffset = 1 * m_width;
235 yOffset = 1 * m_height;
236 break;
237 case 4: // Z+ side
238 xOffset = 0 * m_width;
239 yOffset = 2 * m_height;
240 break;
241 case 5: // Z- side
242 xOffset = 2 * m_width;
243 yOffset = 2 * m_height;
244 break;
245 default:
246 xOffset = 0;
247 yOffset = 0;
248 break;
249 }
250 for ( size_t i = 0; i < m_height; ++i ) {
251 for ( size_t j = 0; j < m_width; ++j ) {
252 if ( imgIdx == 1 || imgIdx == 4 || imgIdx == 5 ) {
253 m_skyData[imgIdx][4 * ( ( m_height - 1 - i ) * m_width + j ) + 0] =
254 img->pixels[( i + yOffset ) * img->width + j + xOffset].r;
255 m_skyData[imgIdx][4 * ( ( m_height - 1 - i ) * m_width + j ) + 1] =
256 img->pixels[( i + yOffset ) * img->width + j + xOffset].g;
257 m_skyData[imgIdx][4 * ( ( m_height - 1 - i ) * m_width + j ) + 2] =
258 img->pixels[( i + yOffset ) * img->width + j + xOffset].b;
259 m_skyData[imgIdx][4 * ( ( m_height - 1 - i ) * m_width + j ) + 3] = 1.f;
260 }
261 else {
262 if ( imgIdx == 2 ) {
263 m_skyData[imgIdx]
264 [4 * ( ( m_width - 1 - j ) * m_height + m_height - 1 - i ) + 0] =
265 img->pixels[( i + yOffset ) * img->width + j + xOffset].r;
266 m_skyData[imgIdx]
267 [4 * ( ( m_width - 1 - j ) * m_height + m_height - 1 - i ) + 1] =
268 img->pixels[( i + yOffset ) * img->width + j + xOffset].g;
269 m_skyData[imgIdx]
270 [4 * ( ( m_width - 1 - j ) * m_height + m_height - 1 - i ) + 2] =
271 img->pixels[( i + yOffset ) * img->width + j + xOffset].b;
272 m_skyData[imgIdx]
273 [4 * ( ( m_width - 1 - j ) * m_height + m_height - 1 - i ) + 3] =
274 1.f;
275 }
276 else {
277 if ( imgIdx == 0 ) {
278 m_skyData[imgIdx][4 * ( i * m_width + m_width - 1 - j ) + 0] =
279 img->pixels[( i + yOffset ) * img->width + j + xOffset].r;
280 m_skyData[imgIdx][4 * ( i * m_width + m_width - 1 - j ) + 1] =
281 img->pixels[( i + yOffset ) * img->width + j + xOffset].g;
282 m_skyData[imgIdx][4 * ( i * m_width + m_width - 1 - j ) + 2] =
283 img->pixels[( i + yOffset ) * img->width + j + xOffset].b;
284 m_skyData[imgIdx][4 * ( i * m_width + m_width - 1 - j ) + 3] = 1.f;
285 }
286 else {
287 // imgIdx == 3
288 m_skyData[imgIdx][4 * ( j * m_width + i ) + 0] =
289 img->pixels[( i + yOffset ) * img->width + j + xOffset].r;
290 m_skyData[imgIdx][4 * ( j * m_width + i ) + 1] =
291 img->pixels[( i + yOffset ) * img->width + j + xOffset].g;
292 m_skyData[imgIdx][4 * ( j * m_width + i ) + 2] =
293 img->pixels[( i + yOffset ) * img->width + j + xOffset].b;
294 m_skyData[imgIdx][4 * ( j * m_width + i ) + 3] = 1.f;
295 }
296 }
297 }
298 }
299 }
300 }
301
302 for ( int imgIdx = 0; imgIdx < 6; ++imgIdx ) {
303 flip_horizontally( m_skyData[imgIdx].get(), m_width, m_height, 4 );
304 }
305
306 delete[] img->pixels;
307 delete img;
308}
309
310void EnvironmentTexture::setupTexturesFromCube() {
311 std::stringstream imgs( m_name );
312 std::string imgname;
313 while ( getline( imgs, imgname, ';' ) ) {
314 int imgIdx { -1 };
315 bool flipV { false };
316 // is it a +X face ?
317 if ( ( imgname.find( "posx" ) != imgname.npos ) ||
318 ( imgname.find( "-X-plux" ) != imgname.npos ) ) {
319 imgIdx = 0;
320 }
321 // is it a -X face ?
322 if ( ( imgname.find( "negx" ) != imgname.npos ) ||
323 ( imgname.find( "-X-minux" ) != imgname.npos ) ) {
324 imgIdx = 1;
325 }
326 // is it a +Y face ?
327 if ( ( imgname.find( "posy" ) != imgname.npos ) ||
328 ( imgname.find( "-Y-plux" ) != imgname.npos ) ) {
329 imgIdx = 2;
330 flipV = true;
331 }
332 // is it a -Y face ?
333 if ( ( imgname.find( "negy" ) != imgname.npos ) ||
334 ( imgname.find( "-Y-minux" ) != imgname.npos ) ) {
335 imgIdx = 3;
336 flipV = true;
337 }
338 // is it a +Z face ? --> goes to the -Z of cubemap, need to flip horizontally
339 if ( ( imgname.find( "posz" ) != imgname.npos ) ||
340 ( imgname.find( "-Z-plux" ) != imgname.npos ) ) {
341 imgIdx = 5;
342 }
343 // is it a -Z face ? --> goes to the +Z of cubemap, need to flip horizontally
344 if ( ( imgname.find( "negz" ) != imgname.npos ) ||
345 ( imgname.find( "-Z-minux" ) != imgname.npos ) ) {
346 imgIdx = 4;
347 }
348
349 int n;
350 int w;
351 int h;
352 stbi_set_flip_vertically_on_load( flipV );
353 // Assume input images are in sRGB color space. stbi_loadf will convert to linear space.
354 auto loaded = stbi_loadf( imgname.c_str(), &w, &h, &n, 0 );
355 if ( !loaded ) {
356 LOG( logERROR ) << "EnvironmentTexture::setupTexturesFromCube : unable to load "
357 << imgname.c_str();
358 }
359 m_width = w;
360 m_height = h;
361 m_skyData[imgIdx] = std::shared_ptr<float[]>( new float[m_width * m_height * 4] );
362
363 for ( int l = 0; l < int( m_height ); ++l ) {
364 for ( int c = 0; c < int( m_width ); ++c ) {
365 auto is = ( l * m_width + c );
366 auto id = flipV ? is : ( l * m_width + ( m_width - c - 1 ) );
367 m_skyData[imgIdx][id * 4 + 0] = loaded[is * n + 0];
368 m_skyData[imgIdx][id * 4 + 1] = loaded[is * n + 1];
369 m_skyData[imgIdx][id * 4 + 2] = loaded[is * n + 2];
370 m_skyData[imgIdx][id * 4 + 3] = 0;
371 }
372 }
373 stbi_image_free( loaded );
374 }
375}
376
377void EnvironmentTexture::setupTexturesFromSphericalEquiRectangular() {
378 auto ext = m_name.substr( m_name.size() - 3 );
379 float* latlonPix { nullptr };
380 int n, w, h;
381 if ( ext == "exr" ) {
382 const char* err { nullptr };
383 // EXR image are always in linear RGB color space
384 int ret = LoadEXR( &latlonPix, &w, &h, m_name.c_str(), &err );
385 n = 4;
386 if ( ret != TINYEXR_SUCCESS ) {
387 if ( err ) {
388 std::cerr << "EnvironmentTexture -- Error reading " << m_name << " : " << err
389 << std::endl;
390 FreeEXRErrorMessage( err ); // release memory of error message.
391 }
392 }
393 }
394 else {
395 stbi_set_flip_vertically_on_load( false );
396 // Assume input images are in sRGB color space. stbi_loadf will convert to linear space.
397 latlonPix = stbi_loadf( m_name.c_str(), &w, &h, &n, 4 );
398 }
399 int textureSize = 1;
400 while ( textureSize < h ) {
401 textureSize <<= 1;
402 }
403 textureSize >>= 1;
404 // Bases to use to convert sphericalequirectangular images to cube faces
405 // These bases allow to convert (u, v) cordinates of each faces to (x, y, z) in the frame of
406 // the equirectangular map. : (x, y, z) = u*A[0] + v*A[1] + A[2]
407 Vector3 bases[6][3] = { { { -1, 0, 0 }, { 0, 1, 0 }, { 0, 0, -1 } },
408 { { 1, 0, 0 }, { 0, -1, 0 }, { 0, 0, -1 } },
409 { { 0, 0, 1 }, { 1, 0, 0 }, { 0, 1, 0 } },
410 { { 0, 0, -1 }, { 1, 0, 0 }, { 0, -1, 0 } },
411 { { 0, 1, 0 }, { 1, 0, 0 }, { 0, 0, -1 } },
412 { { 0, -1, 0 }, { -1, 0, 0 }, { 0, 0, -1 } } };
413 auto sphericalPhi = []( const Vector3& d ) {
414 Scalar p = std::atan2( d.x(), d.y() );
415 return ( p < 0 ) ? ( p + 2 * M_PI ) : p;
416 };
417 auto sphericalTheta = []( const Vector3& d ) { return std::acos( d.z() ); };
418
419 for ( int imgIdx = 0; imgIdx < 6; ++imgIdx ) {
420 // Alllocate the images
421 m_skyData[imgIdx] = std::shared_ptr<float[]>( new float[textureSize * textureSize * 4] );
422 }
423
424 Scalar duv = 2_ra / textureSize;
425
426#pragma omp parallel for firstprivate( duv )
427 for ( int imgIdx = 0; imgIdx < 6; ++imgIdx ) {
428 // Fill in pixels
429 for ( int i = 0; i < textureSize; i++ ) {
430 Scalar u = -1 + i * duv;
431 for ( int j = 0; j < textureSize; j++ ) {
432 Scalar v = -1 + j * duv;
433 Vector3 d = bases[imgIdx][0] + u * bases[imgIdx][1] + v * bases[imgIdx][2];
434 d = d.normalized();
435 Vector2 st { w * sphericalPhi( d ) / ( 2_ra * M_PI ),
436 h * sphericalTheta( d ) / M_PI };
437 // TODO : use st to access and filter the original envmap
438 // for now, no filtering is done. (eq to GL_NEAREST)
439 int s = std::clamp( int( st.x() ), 0, w - 1 );
440 int t = std::clamp( int( st.y() ), 0, h - 1 );
441 int cu =
442 std::clamp( int( ( u / 2_ra + 0.5_ra ) * textureSize ), 0, textureSize - 1 );
443 int cv =
444 std::clamp( int( ( v / 2_ra + 0.5_ra ) * textureSize ), 0, textureSize - 1 );
445 int skyIndex = 4 * ( cv * textureSize + cu );
446 int latlonIndex = 4 * ( t * w + s );
447
448 m_skyData[imgIdx][skyIndex + 0] = latlonPix[latlonIndex + 0];
449 m_skyData[imgIdx][skyIndex + 1] = latlonPix[latlonIndex + 1];
450 m_skyData[imgIdx][skyIndex + 2] = latlonPix[latlonIndex + 2];
451 m_skyData[imgIdx][skyIndex + 3] = 1;
452 }
453 }
454 }
455
456 for ( int imgIdx = 0; imgIdx < 6; ++imgIdx ) {
457 flip_horizontally( m_skyData[imgIdx].get(), textureSize, textureSize, 4 );
458 }
459
460 free( latlonPix );
461 m_width = m_height = textureSize;
462}
463
464void EnvironmentTexture::computeSHMatrices() {
465 for ( int i = 0; i < 9; i++ ) {
466 for ( int j = 0; j < 3; j++ ) {
467 m_shcoefs[i][j] = 0.f;
468 }
469 }
472 constexpr Scalar dtheta = 0.005_ra;
473 constexpr Scalar dphi = 0.005_ra;
474 for ( Scalar theta = 0_ra; theta < M_PI; theta += dtheta ) {
475 for ( Scalar phi = 0_ra; phi < 2_ra * M_PI; phi += dphi ) {
476 Scalar x = std::sin( theta ) * std::cos( phi );
477 Scalar y = std::sin( theta ) * std::sin( phi );
478 Scalar z = std::cos( theta );
479 float* thePixel = getPixel( x, y, z );
480 updateCoeffs( thePixel, -x, y, z, std::sin( theta ) * dtheta * dphi );
481 }
482 }
483 tomatrix();
484}
485
486void EnvironmentTexture::updateCoeffs( float* hdr, float x, float y, float z, float domega ) {
487 /******************************************************************
488 Notes: Of course, there are better numerical methods to do
489 integration, but this naive approach is sufficient for our
490 purpose.
491 *********************************************************************/
492 for ( int col = 0; col < 3; col++ ) {
493 float c; /* A different constant for each coefficient */
494
495 /* L_{00}. Note that Y_{00} = 0.282095 */
496 c = 0.282095f;
497 m_shcoefs[0][col] += hdr[col] * c * domega;
498
499 /* L_{1m}. -1 <= m <= 1. The linear terms */
500 c = 0.488603f;
501 m_shcoefs[1][col] += hdr[col] * ( c * y ) * domega; /* Y_{1-1} = 0.488603 y */
502 m_shcoefs[2][col] += hdr[col] * ( c * z ) * domega; /* Y_{10} = 0.488603 z */
503 m_shcoefs[3][col] += hdr[col] * ( c * x ) * domega; /* Y_{11} = 0.488603 x */
504
505 /* The Quadratic terms, L_{2m} -2 <= m <= 2 */
506
507 /* First, L_{2-2}, L_{2-1}, L_{21} corresponding to xy,yz,xz */
508 c = 1.092548f;
509 m_shcoefs[4][col] += hdr[col] * ( c * x * y ) * domega; /* Y_{2-2} = 1.092548 xy */
510 m_shcoefs[5][col] += hdr[col] * ( c * y * z ) * domega; /* Y_{2-1} = 1.092548 yz */
511 m_shcoefs[7][col] += hdr[col] * ( c * x * z ) * domega; /* Y_{21} = 1.092548 xz */
512
513 /* L_{20}. Note that Y_{20} = 0.315392 (3z^2 - 1) */
514 c = 0.315392f;
515 m_shcoefs[6][col] += hdr[col] * ( c * ( 3 * z * z - 1 ) ) * domega;
516
517 /* L_{22}. Note that Y_{22} = 0.546274 (x^2 - y^2) */
518 c = 0.546274f;
519 m_shcoefs[8][col] += hdr[col] * ( c * ( x * x - y * y ) ) * domega;
520 }
521}
522
523void EnvironmentTexture::tomatrix( void ) {
524 /* Form the quadratic form matrix (see equations 11 and 12 in paper) */
525 int col;
526 constexpr float c1 = 0.429043f, c2 = 0.511664f, c3 = 0.743125f, c4 = 0.886227f, c5 = 0.247708f;
527
528 for ( col = 0; col < 3; col++ ) { /* Equation 12 */
529 m_shMatrices[col] = ( Ra::Core::Matrix4() << c1 * m_shcoefs[8][col],
530 c1 * m_shcoefs[4][col],
531 c1 * m_shcoefs[7][col],
532 c2 * m_shcoefs[3][col],
533
534 c1 * m_shcoefs[4][col],
535 -c1 * m_shcoefs[8][col],
536 c1 * m_shcoefs[5][col],
537 c2 * m_shcoefs[1][col],
538
539 c1 * m_shcoefs[7][col],
540 c1 * m_shcoefs[5][col],
541 c3 * m_shcoefs[6][col],
542 c2 * m_shcoefs[2][col],
543
544 c2 * m_shcoefs[3][col],
545 c2 * m_shcoefs[1][col],
546 c2 * m_shcoefs[2][col],
547 c4 * m_shcoefs[0][col] - c5 * m_shcoefs[6][col] )
548 .finished();
549 }
550}
551
552float* EnvironmentTexture::getPixel( float x, float y, float z ) {
553 auto ma = std::abs( x );
554 int axis = ( x > 0 );
555 auto tc = axis ? z : -z;
556 auto sc = y;
557 if ( std::abs( y ) > ma ) {
558 ma = std::abs( y );
559 axis = 2 + ( y < 0 );
560 tc = -x;
561 sc = ( axis == 2 ) ? -z : z;
562 }
563 if ( std::abs( z ) > ma ) {
564 ma = std::abs( z );
565 axis = 4 + ( z < 0 );
566 tc = ( axis == 4 ) ? -x : x;
567 sc = y;
568 }
569 auto s = 0.5_ra * ( 1_ra + sc / ma );
570 auto t = 0.5_ra * ( 1_ra + tc / ma );
571 int is = int( s * m_width );
572 int it = int( t * m_height );
573 return &( m_skyData[axis][4 * ( ( m_height - 1 - is ) * m_width + it )] );
574}
575
576Ra::Engine::Data::Texture* EnvironmentTexture::getSHImage() {
577 if ( m_shtexture != nullptr ) { return m_shtexture.get(); }
578
579 size_t ambientWidth = 1024;
581 new unsigned char[4 * ambientWidth * ambientWidth] );
582#pragma omp parallel for
583 for ( int i = 0; i < int( ambientWidth ); i++ ) {
584 for ( int j = 0; j < int( ambientWidth ); j++ ) {
585
586 /* We now find the cartesian components for the point (i,j) */
587 Scalar u, v, r;
588
589 v = ( ambientWidth / 2.0_ra - j ) /
590 ( ambientWidth / 2.0_ra ); /* v ranges from -1 to 1 */
591 u = ( ambientWidth / 2.0_ra - i ) /
592 ( ambientWidth / 2.0_ra ); /* u ranges from -1 to 1 */
593 r = sqrt( u * u + v * v ); /* The "radius" */
594 if ( r > 1.0_ra ) {
595 thepixels[4 * ( j * ambientWidth + i ) + 0] = 0;
596 thepixels[4 * ( j * ambientWidth + i ) + 1] = 0;
597 thepixels[4 * ( j * ambientWidth + i ) + 2] = 0;
598 thepixels[4 * ( j * ambientWidth + i ) + 3] = 255;
599 continue; /* Consider only circle with r<1 */
600 }
601
602 Scalar theta = M_PI * r; /* theta parameter of (i,j) */
603 Scalar phi = atan2( v, u ); /* phi parameter */
604
605 Scalar x = std::sin( theta ) * std::cos( phi ); /* Cartesian components */
606 Scalar y = std::sin( theta ) * std::sin( phi );
607 Scalar z = std::cos( theta );
608
609 // color = NtMN
610 Ra::Core::Vector4 normal( x, y, z, 1_ra );
611 const Ra::Core::Vector4 lcolor { normal.dot( m_shMatrices[0] * normal ),
612 normal.dot( m_shMatrices[1] * normal ),
613 normal.dot( m_shMatrices[2] * normal ),
614 1_ra };
616 Ra::Core::Utils::Color::linearRGBTosRGB( lcolor * 0.05_ra ) };
617
618 auto clpfnct = []( Scalar value ) { return std::clamp( value, 0_ra, 1_ra ); };
619
620 color.unaryExpr( clpfnct );
621 thepixels[4 * ( j * ambientWidth + i ) + 0] =
622 static_cast<unsigned char>( color[0] * 255 );
623 thepixels[4 * ( j * ambientWidth + i ) + 1] =
624 static_cast<unsigned char>( color[1] * 255 );
625 thepixels[4 * ( j * ambientWidth + i ) + 2] =
626 static_cast<unsigned char>( color[2] * 255 );
627 thepixels[4 * ( j * ambientWidth + i ) + 3] = 255;
628 }
629 }
631 "shImage",
632 { GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, GL_LINEAR, GL_LINEAR },
633 { GL_TEXTURE_2D,
634 ambientWidth,
635 ambientWidth,
636 1,
637 GL_RGBA,
638 GL_RGBA,
639 GL_UNSIGNED_BYTE,
640 false,
641 thepixels } };
642 m_shtexture = std::make_unique<Ra::Engine::Data::Texture>( params );
643 return m_shtexture.get();
644}
645
646void EnvironmentTexture::saveShProjection( const std::string& filename ) {
647 auto tex = getSHImage();
648 auto flnm = std::string( "../" ) + filename;
649 stbi_write_png( flnm.c_str(), tex->getWidth(), tex->getHeight(), 4, tex->getTexels(), 0 );
650}
651
652const Ra::Core::Matrix4& EnvironmentTexture::getShMatrix( int channel ) {
653 return m_shMatrices[channel];
654}
655
656void EnvironmentTexture::updateGL() {
657 if ( !m_glReady ) {
658 // load the skybox shader
659 auto shaderMngr = Ra::Engine::RadiumEngine::getInstance()->getShaderProgramManager();
660 const std::string vertexShaderSource { "#include \"TransformStructs.glsl\"\n"
661 "layout (location = 0) in vec3 in_position;\n"
662 "out vec3 incidentDirection;\n"
663 "uniform Transform transform;\n"
664 "void main(void)\n"
665 "{\n"
666 " mat4 mvp = transform.proj * transform.view;\n"
667 " gl_Position = mvp*vec4(in_position, 1.0);\n"
668 " incidentDirection = in_position;\n"
669 "}\n" };
670 const std::string fragmentShadersource {
671 "layout (location = 0) out vec4 outColor;\n"
672 "in vec3 incidentDirection;\n"
673 "uniform samplerCube skyTexture;\n"
674 "uniform float strength;\n"
675 "void main(void)\n"
676 "{\n"
677 " vec3 envColor = texture(skyTexture, normalize(incidentDirection)).rgb;\n"
678 " outColor =vec4(strength*envColor, 1);\n"
679 "}\n" };
680 Ra::Engine::Data::ShaderConfiguration config { "EnvironmentTexture::Builtin SkyBox" };
681 config.addShaderSource( Ra::Engine::Data::ShaderType::ShaderType_VERTEX,
682 vertexShaderSource );
683 config.addShaderSource( Ra::Engine::Data::ShaderType::ShaderType_FRAGMENT,
684 fragmentShadersource );
685 auto added = shaderMngr->addShaderProgram( config );
686 if ( added ) { m_skyShader = added.value(); }
687 else {
688 // Unable to load the shader ... deactivate skybox
689 m_isSkyBox = false;
690 }
691 m_displayMesh->updateGL();
692 m_skyTexture->initializeNow();
693 glEnable( GL_TEXTURE_CUBE_MAP_SEAMLESS );
694 m_glReady = true;
695 }
696}
697
698void EnvironmentTexture::render( const Ra::Engine::Data::ViewingParameters& viewParams ) {
699 if ( m_isSkyBox ) {
700 // put this in a initializeGL method ?
701 if ( !m_glReady ) { updateGL(); }
702 auto skyparams = viewParams;
703 Ra::Core::Matrix3 t = Ra::Core::Transform { skyparams.viewMatrix }.linear();
704 skyparams.viewMatrix.setIdentity();
705 skyparams.viewMatrix.topLeftCorner<3, 3>() = t;
706 auto cameraManager = static_cast<Ra::Engine::Scene::CameraManager*>(
707 Ra::Engine::RadiumEngine::getInstance()->getSystem( "DefaultCameraManager" ) );
708 auto cam = cameraManager->getActiveCamera();
709 Scalar fov =
710 std::clamp( cam->getZoomFactor() * cam->getFOV(), 0.001_ra, Math::Pi - 0.1_ra );
711 skyparams.projMatrix =
712 Ra::Core::Asset::Camera::perspective( cam->getAspect(), fov, 0.1_ra, 3._ra );
713 m_skyShader->bind();
714 m_skyShader->setUniform( "transform.proj", skyparams.projMatrix );
715 m_skyShader->setUniform( "transform.view", skyparams.viewMatrix );
716 m_skyTexture->bind( 0 );
717 m_skyShader->setUniform( "skytexture", 0 );
718 m_skyShader->setUniform( "strength", m_environmentStrength );
719 GLboolean depthEnabled;
720 glGetBooleanv( GL_DEPTH_WRITEMASK, &depthEnabled );
721 glDepthMask( GL_FALSE );
722 m_displayMesh->render( m_skyShader );
723 glDepthMask( depthEnabled );
724 }
725}
726
727void EnvironmentTexture::setStrength( float s ) {
728 m_environmentStrength = s;
729}
730
731float EnvironmentTexture::getStrength() const {
732 return m_environmentStrength;
733}
734
735Ra::Engine::Data::Texture* EnvironmentTexture::getEnvironmentTexture() {
736 return m_skyTexture.get();
737}
738
739} // namespace Data
740} // namespace Engine
741} // namespace Ra
T acos(T... args)
T atan2(T... args)
T c_str(T... args)
static Core::Matrix4 perspective(Scalar a, Scalar y, Scalar n, Scalar f)
Definition Camera.cpp:84
static ColorBase linearRGBTosRGB(const ColorBase &lrgb)
Definition Color.hpp:72
EnvironmentTexture(const std::string &mapName, bool isSkybox=false)
Construct an envmap from a file. Supported image component of the envmap are the following.
void addShaderSource(ShaderType type, const std::string &source)
Represent a Texture of the engine.
Definition Texture.hpp:120
Ra::Core::Asset::Camera * getActiveCamera()
Get the pointer on the active camera data.
T cos(T... args)
T endl(T... args)
T fgets(T... args)
T find(T... args)
T fopen(T... args)
T fread(T... args)
T free(T... args)
T getline(T... args)
T move(T... args)
This namespace contains everything "low level", related to data, datastuctures, and computation.
Definition Cage.cpp:5
hepler function to manage enum as underlying types in VariableSet
Definition Cage.cpp:4
T sin(T... args)
T sqrt(T... args)
T strncmp(T... args)
T strtof(T... args)
T strtol(T... args)
Describes the sampler and image of a texture.
Definition Texture.hpp:99
the set of viewing parameters extracted from the camera and given to the renderer
T substr(T... args)
T swap(T... args)