Radium Engine  1.5.20
Loading...
Searching...
No Matches
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 * M_PI ), h * sphericalTheta( d ) / M_PI };
436 // TODO : use st to access and filter the original envmap
437 // for now, no filtering is done. (eq to GL_NEAREST)
438 int s = std::clamp( int( st.x() ), 0, w - 1 );
439 int t = std::clamp( int( st.y() ), 0, h - 1 );
440 int cu = std::clamp( int( ( u / 2 + 0.5 ) * textureSize ), 0, textureSize - 1 );
441 int cv = std::clamp( int( ( v / 2 + 0.5 ) * textureSize ), 0, textureSize - 1 );
442 int skyIndex = 4 * ( cv * textureSize + cu );
443 int latlonIndex = 4 * ( t * w + s );
444
445 m_skyData[imgIdx][skyIndex + 0] = latlonPix[latlonIndex + 0];
446 m_skyData[imgIdx][skyIndex + 1] = latlonPix[latlonIndex + 1];
447 m_skyData[imgIdx][skyIndex + 2] = latlonPix[latlonIndex + 2];
448 m_skyData[imgIdx][skyIndex + 3] = 1;
449 }
450 }
451 }
452
453 for ( int imgIdx = 0; imgIdx < 6; ++imgIdx ) {
454 flip_horizontally( m_skyData[imgIdx].get(), textureSize, textureSize, 4 );
455 }
456
457 free( latlonPix );
458 m_width = m_height = textureSize;
459}
460
461void EnvironmentTexture::computeSHMatrices() {
462 for ( int i = 0; i < 9; i++ ) {
463 for ( int j = 0; j < 3; j++ ) {
464 m_shcoefs[i][j] = 0.f;
465 }
466 }
469 constexpr Scalar dtheta = 0.005_ra;
470 constexpr Scalar dphi = 0.005_ra;
471 for ( Scalar theta = 0_ra; theta < M_PI; theta += dtheta ) {
472 for ( Scalar phi = 0_ra; phi < 2_ra * M_PI; phi += dphi ) {
473 Scalar x = std::sin( theta ) * std::cos( phi );
474 Scalar y = std::sin( theta ) * std::sin( phi );
475 Scalar z = std::cos( theta );
476 float* thePixel = getPixel( x, y, z );
477 updateCoeffs( thePixel, -x, y, z, std::sin( theta ) * dtheta * dphi );
478 }
479 }
480 tomatrix();
481}
482
483void EnvironmentTexture::updateCoeffs( float* hdr, float x, float y, float z, float domega ) {
484 /******************************************************************
485 Notes: Of course, there are better numerical methods to do
486 integration, but this naive approach is sufficient for our
487 purpose.
488 *********************************************************************/
489 for ( int col = 0; col < 3; col++ ) {
490 float c; /* A different constant for each coefficient */
491
492 /* L_{00}. Note that Y_{00} = 0.282095 */
493 c = 0.282095f;
494 m_shcoefs[0][col] += hdr[col] * c * domega;
495
496 /* L_{1m}. -1 <= m <= 1. The linear terms */
497 c = 0.488603f;
498 m_shcoefs[1][col] += hdr[col] * ( c * y ) * domega; /* Y_{1-1} = 0.488603 y */
499 m_shcoefs[2][col] += hdr[col] * ( c * z ) * domega; /* Y_{10} = 0.488603 z */
500 m_shcoefs[3][col] += hdr[col] * ( c * x ) * domega; /* Y_{11} = 0.488603 x */
501
502 /* The Quadratic terms, L_{2m} -2 <= m <= 2 */
503
504 /* First, L_{2-2}, L_{2-1}, L_{21} corresponding to xy,yz,xz */
505 c = 1.092548f;
506 m_shcoefs[4][col] += hdr[col] * ( c * x * y ) * domega; /* Y_{2-2} = 1.092548 xy */
507 m_shcoefs[5][col] += hdr[col] * ( c * y * z ) * domega; /* Y_{2-1} = 1.092548 yz */
508 m_shcoefs[7][col] += hdr[col] * ( c * x * z ) * domega; /* Y_{21} = 1.092548 xz */
509
510 /* L_{20}. Note that Y_{20} = 0.315392 (3z^2 - 1) */
511 c = 0.315392f;
512 m_shcoefs[6][col] += hdr[col] * ( c * ( 3 * z * z - 1 ) ) * domega;
513
514 /* L_{22}. Note that Y_{22} = 0.546274 (x^2 - y^2) */
515 c = 0.546274f;
516 m_shcoefs[8][col] += hdr[col] * ( c * ( x * x - y * y ) ) * domega;
517 }
518}
519
520void EnvironmentTexture::tomatrix( void ) {
521 /* Form the quadratic form matrix (see equations 11 and 12 in paper) */
522 int col;
523 constexpr float c1 = 0.429043f, c2 = 0.511664f, c3 = 0.743125f, c4 = 0.886227f, c5 = 0.247708f;
524
525 for ( col = 0; col < 3; col++ ) { /* Equation 12 */
526 m_shMatrices[col] = ( Ra::Core::Matrix4() << c1 * m_shcoefs[8][col],
527 c1 * m_shcoefs[4][col],
528 c1 * m_shcoefs[7][col],
529 c2 * m_shcoefs[3][col],
530
531 c1 * m_shcoefs[4][col],
532 -c1 * m_shcoefs[8][col],
533 c1 * m_shcoefs[5][col],
534 c2 * m_shcoefs[1][col],
535
536 c1 * m_shcoefs[7][col],
537 c1 * m_shcoefs[5][col],
538 c3 * m_shcoefs[6][col],
539 c2 * m_shcoefs[2][col],
540
541 c2 * m_shcoefs[3][col],
542 c2 * m_shcoefs[1][col],
543 c2 * m_shcoefs[2][col],
544 c4 * m_shcoefs[0][col] - c5 * m_shcoefs[6][col] )
545 .finished();
546 }
547}
548
549float* EnvironmentTexture::getPixel( float x, float y, float z ) {
550 auto ma = std::abs( x );
551 int axis = ( x > 0 );
552 auto tc = axis ? z : -z;
553 auto sc = y;
554 if ( std::abs( y ) > ma ) {
555 ma = std::abs( y );
556 axis = 2 + ( y < 0 );
557 tc = -x;
558 sc = ( axis == 2 ) ? -z : z;
559 }
560 if ( std::abs( z ) > ma ) {
561 ma = std::abs( z );
562 axis = 4 + ( z < 0 );
563 tc = ( axis == 4 ) ? -x : x;
564 sc = y;
565 }
566 auto s = 0.5_ra * ( 1_ra + sc / ma );
567 auto t = 0.5_ra * ( 1_ra + tc / ma );
568 int is = int( s * m_width );
569 int it = int( t * m_height );
570 return &( m_skyData[axis][4 * ( ( m_height - 1 - is ) * m_width + it )] );
571}
572
573Ra::Engine::Data::Texture* EnvironmentTexture::getSHImage() {
574 if ( m_shtexture != nullptr ) { return m_shtexture.get(); }
575
576 size_t ambientWidth = 1024;
578 new unsigned char[4 * ambientWidth * ambientWidth] );
579#pragma omp parallel for
580 for ( int i = 0; i < int( ambientWidth ); i++ ) {
581 for ( int j = 0; j < int( ambientWidth ); j++ ) {
582
583 /* We now find the cartesian components for the point (i,j) */
584 Scalar u, v, r;
585
586 v = ( ambientWidth / 2.0_ra - j ) /
587 ( ambientWidth / 2.0_ra ); /* v ranges from -1 to 1 */
588 u = ( ambientWidth / 2.0_ra - i ) /
589 ( ambientWidth / 2.0_ra ); /* u ranges from -1 to 1 */
590 r = sqrt( u * u + v * v ); /* The "radius" */
591 if ( r > 1.0_ra ) {
592 thepixels[4 * ( j * ambientWidth + i ) + 0] = 0;
593 thepixels[4 * ( j * ambientWidth + i ) + 1] = 0;
594 thepixels[4 * ( j * ambientWidth + i ) + 2] = 0;
595 thepixels[4 * ( j * ambientWidth + i ) + 3] = 255;
596 continue; /* Consider only circle with r<1 */
597 }
598
599 Scalar theta = M_PI * r; /* theta parameter of (i,j) */
600 Scalar phi = atan2( v, u ); /* phi parameter */
601
602 Scalar x = std::sin( theta ) * std::cos( phi ); /* Cartesian components */
603 Scalar y = std::sin( theta ) * std::sin( phi );
604 Scalar z = std::cos( theta );
605
606 // color = NtMN
607 Ra::Core::Vector4 normal( x, y, z, 1_ra );
608 const Ra::Core::Vector4 lcolor { normal.dot( m_shMatrices[0] * normal ),
609 normal.dot( m_shMatrices[1] * normal ),
610 normal.dot( m_shMatrices[2] * normal ),
611 1_ra };
613 Ra::Core::Utils::Color::linearRGBTosRGB( lcolor * 0.05_ra ) };
614
615 auto clpfnct = []( Scalar value ) { return std::clamp( value, 0_ra, 1_ra ); };
616
617 color.unaryExpr( clpfnct );
618 thepixels[4 * ( j * ambientWidth + i ) + 0] =
619 static_cast<unsigned char>( color[0] * 255 );
620 thepixels[4 * ( j * ambientWidth + i ) + 1] =
621 static_cast<unsigned char>( color[1] * 255 );
622 thepixels[4 * ( j * ambientWidth + i ) + 2] =
623 static_cast<unsigned char>( color[2] * 255 );
624 thepixels[4 * ( j * ambientWidth + i ) + 3] = 255;
625 }
626 }
628 "shImage",
629 { GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, GL_LINEAR, GL_LINEAR },
630 { GL_TEXTURE_2D,
631 ambientWidth,
632 ambientWidth,
633 1,
634 GL_RGBA,
635 GL_RGBA,
636 GL_UNSIGNED_BYTE,
637 false,
638 thepixels } };
639 m_shtexture = std::make_unique<Ra::Engine::Data::Texture>( params );
640 return m_shtexture.get();
641}
642
643void EnvironmentTexture::saveShProjection( const std::string& filename ) {
644 auto tex = getSHImage();
645 auto flnm = std::string( "../" ) + filename;
646 stbi_write_png( flnm.c_str(), tex->getWidth(), tex->getHeight(), 4, tex->getTexels(), 0 );
647}
648
649const Ra::Core::Matrix4& EnvironmentTexture::getShMatrix( int channel ) {
650 return m_shMatrices[channel];
651}
652
653void EnvironmentTexture::updateGL() {
654 if ( !m_glReady ) {
655 // load the skybox shader
656 auto shaderMngr = Ra::Engine::RadiumEngine::getInstance()->getShaderProgramManager();
657 const std::string vertexShaderSource { "#include \"TransformStructs.glsl\"\n"
658 "layout (location = 0) in vec3 in_position;\n"
659 "out vec3 incidentDirection;\n"
660 "uniform Transform transform;\n"
661 "void main(void)\n"
662 "{\n"
663 " mat4 mvp = transform.proj * transform.view;\n"
664 " gl_Position = mvp*vec4(in_position, 1.0);\n"
665 " incidentDirection = in_position;\n"
666 "}\n" };
667 const std::string fragmentShadersource {
668 "layout (location = 0) out vec4 outColor;\n"
669 "in vec3 incidentDirection;\n"
670 "uniform samplerCube skyTexture;\n"
671 "uniform float strength;\n"
672 "void main(void)\n"
673 "{\n"
674 " vec3 envColor = texture(skyTexture, normalize(incidentDirection)).rgb;\n"
675 " outColor =vec4(strength*envColor, 1);\n"
676 "}\n" };
677 Ra::Engine::Data::ShaderConfiguration config { "EnvironmentTexture::Builtin SkyBox" };
678 config.addShaderSource( Ra::Engine::Data::ShaderType::ShaderType_VERTEX,
679 vertexShaderSource );
680 config.addShaderSource( Ra::Engine::Data::ShaderType::ShaderType_FRAGMENT,
681 fragmentShadersource );
682 auto added = shaderMngr->addShaderProgram( config );
683 if ( added ) { m_skyShader = added.value(); }
684 else {
685 // Unable to load the shader ... deactivate skybox
686 m_isSkyBox = false;
687 }
688 m_displayMesh->updateGL();
689 m_skyTexture->initializeNow();
690 glEnable( GL_TEXTURE_CUBE_MAP_SEAMLESS );
691 m_glReady = true;
692 }
693}
694
695void EnvironmentTexture::render( const Ra::Engine::Data::ViewingParameters& viewParams ) {
696 if ( m_isSkyBox ) {
697 // put this in a initializeGL method ?
698 if ( !m_glReady ) { updateGL(); }
699 auto skyparams = viewParams;
700 Ra::Core::Matrix3 t = Ra::Core::Transform { skyparams.viewMatrix }.linear();
701 skyparams.viewMatrix.setIdentity();
702 skyparams.viewMatrix.topLeftCorner<3, 3>() = t;
703 auto cameraManager = static_cast<Ra::Engine::Scene::CameraManager*>(
704 Ra::Engine::RadiumEngine::getInstance()->getSystem( "DefaultCameraManager" ) );
705 auto cam = cameraManager->getActiveCamera();
706 Scalar fov =
707 std::clamp( cam->getZoomFactor() * cam->getFOV(), 0.001_ra, Math::Pi - 0.1_ra );
708 skyparams.projMatrix =
709 Ra::Core::Asset::Camera::perspective( cam->getAspect(), fov, 0.1_ra, 3._ra );
710 m_skyShader->bind();
711 m_skyShader->setUniform( "transform.proj", skyparams.projMatrix );
712 m_skyShader->setUniform( "transform.view", skyparams.viewMatrix );
713 m_skyTexture->bind( 0 );
714 m_skyShader->setUniform( "skytexture", 0 );
715 m_skyShader->setUniform( "strength", m_environmentStrength );
716 GLboolean depthEnabled;
717 glGetBooleanv( GL_DEPTH_WRITEMASK, &depthEnabled );
718 glDepthMask( GL_FALSE );
719 m_displayMesh->render( m_skyShader );
720 glDepthMask( depthEnabled );
721 }
722}
723
724void EnvironmentTexture::setStrength( float s ) {
725 m_environmentStrength = s;
726}
727
728float EnvironmentTexture::getStrength() const {
729 return m_environmentStrength;
730}
731
732Ra::Engine::Data::Texture* EnvironmentTexture::getEnvironmentTexture() {
733 return m_skyTexture.get();
734}
735
736} // namespace Data
737} // namespace Engine
738} // 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:4
hepler function to manage enum as underlying types in VariableSet
Definition Cage.cpp:3
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)