8#include <Engine/Data/EnvironmentTexture.hpp>
10#include <stb/stb_image.h>
11#include <stb/stb_image_write.h>
13#include <Core/Asset/Camera.hpp>
14#include <Core/Geometry/MeshPrimitives.hpp>
15#include <Core/Math/Math.hpp>
16#include <Core/Resources/Resources.hpp>
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>
26#include <tinyEXR/tinyexr.h>
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 ) {
68 if (
nullptr == fptr ) {
72 int width, height, color,
swap;
73 if ( !readHeader( fptr, width, height, color, swap ) ) {
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 ) ) {
85 if ( !readFloat( fptr, ptr + 1, swap ) ) {
88 if ( !readFloat( fptr, ptr + 2, swap ) ) {
93 HDRIMAGE* ret =
new HDRIMAGE;
100 static bool readFloat( FILE* fptr,
float* n,
int swap ) {
102 if (
fread( n, 4, 1, fptr ) != 1 )
return false;
104 unsigned char* cptr = (
unsigned char*)n;
112 static bool readHeader( FILE* fptr,
int& width,
int& height,
int& color,
int& swap ) {
115 if ( NULL ==
fgets( buf, 32, fptr ) )
return false;
117 if ( 0 ==
strncmp(
"PF", buf, 2 ) )
119 else if ( 0 ==
strncmp(
"Pf", buf, 2 ) )
124 if ( NULL ==
fgets( buf, 32, fptr ) )
return false;
127 width =
strtol( buf, &endptr, 10 );
128 if ( endptr == buf )
return false;
129 height =
strtol( endptr, (
char**)NULL, 10 );
131 if ( NULL ==
fgets( buf, 32, fptr ) )
return false;
132 if ( endptr == buf )
return false;
133 float aspect =
strtof( buf, &endptr );
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; }
159void EnvironmentTexture::initializeTexture() {
161 case EnvMapType::ENVMAP_PFM:
162 setupTexturesFromPfm();
164 case EnvMapType::ENVMAP_CUBE:
165 setupTexturesFromCube();
167 case EnvMapType::ENVMAP_LATLON:
168 setupTexturesFromSphericalEquiRectangular();
171 LOG( logERROR ) <<
"EnvironmentTexture::initializeTexture(): unkown EnvMapType";
178 { m_skyData[0], m_skyData[1], m_skyData[2], m_skyData[3], m_skyData[4], m_skyData[5] } };
184 GL_LINEAR_MIPMAP_LINEAR,
187 { GL_TEXTURE_CUBE_MAP,
196 m_skyTexture = std::make_unique<Ra::Engine::Data::Texture>( params );
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 ) );
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;
212 for (
int imgIdx = 0; imgIdx < 6; ++imgIdx ) {
216#pragma omp parallel for
217 for (
int imgIdx = 0; imgIdx < 6; ++imgIdx ) {
222 xOffset = 1 * m_width;
223 yOffset = 0 * m_height;
226 xOffset = 1 * m_width;
227 yOffset = 2 * m_height;
230 xOffset = 1 * m_width;
231 yOffset = 3 * m_height;
234 xOffset = 1 * m_width;
235 yOffset = 1 * m_height;
238 xOffset = 0 * m_width;
239 yOffset = 2 * m_height;
242 xOffset = 2 * m_width;
243 yOffset = 2 * m_height;
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;
264 [4 * ( ( m_width - 1 - j ) * m_height + m_height - 1 - i ) + 0] =
265 img->pixels[( i + yOffset ) * img->width + j + xOffset].r;
267 [4 * ( ( m_width - 1 - j ) * m_height + m_height - 1 - i ) + 1] =
268 img->pixels[( i + yOffset ) * img->width + j + xOffset].g;
270 [4 * ( ( m_width - 1 - j ) * m_height + m_height - 1 - i ) + 2] =
271 img->pixels[( i + yOffset ) * img->width + j + xOffset].b;
273 [4 * ( ( m_width - 1 - j ) * m_height + m_height - 1 - i ) + 3] =
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;
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;
302 for (
int imgIdx = 0; imgIdx < 6; ++imgIdx ) {
303 flip_horizontally( m_skyData[imgIdx].get(), m_width, m_height, 4 );
306 delete[] img->pixels;
310void EnvironmentTexture::setupTexturesFromCube() {
313 while (
getline( imgs, imgname,
';' ) ) {
315 bool flipV {
false };
317 if ( ( imgname.
find(
"posx" ) != imgname.npos ) ||
318 ( imgname.
find(
"-X-plux" ) != imgname.npos ) ) {
322 if ( ( imgname.
find(
"negx" ) != imgname.npos ) ||
323 ( imgname.
find(
"-X-minux" ) != imgname.npos ) ) {
327 if ( ( imgname.
find(
"posy" ) != imgname.npos ) ||
328 ( imgname.
find(
"-Y-plux" ) != imgname.npos ) ) {
333 if ( ( imgname.
find(
"negy" ) != imgname.npos ) ||
334 ( imgname.
find(
"-Y-minux" ) != imgname.npos ) ) {
339 if ( ( imgname.
find(
"posz" ) != imgname.npos ) ||
340 ( imgname.
find(
"-Z-plux" ) != imgname.npos ) ) {
344 if ( ( imgname.
find(
"negz" ) != imgname.npos ) ||
345 ( imgname.
find(
"-Z-minux" ) != imgname.npos ) ) {
352 stbi_set_flip_vertically_on_load( flipV );
354 auto loaded = stbi_loadf( imgname.
c_str(), &w, &h, &n, 0 );
356 LOG( logERROR ) <<
"EnvironmentTexture::setupTexturesFromCube : unable to load "
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;
373 stbi_image_free( loaded );
377void EnvironmentTexture::setupTexturesFromSphericalEquiRectangular() {
378 auto ext = m_name.
substr( m_name.size() - 3 );
379 float* latlonPix {
nullptr };
381 if ( ext ==
"exr" ) {
382 const char* err {
nullptr };
384 int ret = LoadEXR( &latlonPix, &w, &h, m_name.c_str(), &err );
386 if ( ret != TINYEXR_SUCCESS ) {
388 std::cerr <<
"EnvironmentTexture -- Error reading " << m_name <<
" : " << err
390 FreeEXRErrorMessage( err );
395 stbi_set_flip_vertically_on_load(
false );
397 latlonPix = stbi_loadf( m_name.c_str(), &w, &h, &n, 4 );
400 while ( textureSize < h ) {
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 ) {
415 return ( p < 0 ) ? ( p + 2 * M_PI ) : p;
417 auto sphericalTheta = [](
const Vector3& d ) {
return std::acos( d.z() ); };
419 for (
int imgIdx = 0; imgIdx < 6; ++imgIdx ) {
424 Scalar duv = 2_ra / textureSize;
426#pragma omp parallel for firstprivate( duv )
427 for (
int imgIdx = 0; imgIdx < 6; ++imgIdx ) {
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];
435 Vector2 st { w * sphericalPhi( d ) / ( 2 * M_PI ), h * sphericalTheta( d ) / M_PI };
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 );
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;
453 for (
int imgIdx = 0; imgIdx < 6; ++imgIdx ) {
454 flip_horizontally( m_skyData[imgIdx].get(), textureSize, textureSize, 4 );
458 m_width = m_height = textureSize;
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;
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 ) {
476 float* thePixel = getPixel( x, y, z );
477 updateCoeffs( thePixel, -x, y, z,
std::sin( theta ) * dtheta * dphi );
483void EnvironmentTexture::updateCoeffs(
float* hdr,
float x,
float y,
float z,
float domega ) {
489 for (
int col = 0; col < 3; col++ ) {
494 m_shcoefs[0][col] += hdr[col] * c * domega;
498 m_shcoefs[1][col] += hdr[col] * ( c * y ) * domega;
499 m_shcoefs[2][col] += hdr[col] * ( c * z ) * domega;
500 m_shcoefs[3][col] += hdr[col] * ( c * x ) * domega;
506 m_shcoefs[4][col] += hdr[col] * ( c * x * y ) * domega;
507 m_shcoefs[5][col] += hdr[col] * ( c * y * z ) * domega;
508 m_shcoefs[7][col] += hdr[col] * ( c * x * z ) * domega;
512 m_shcoefs[6][col] += hdr[col] * ( c * ( 3 * z * z - 1 ) ) * domega;
516 m_shcoefs[8][col] += hdr[col] * ( c * ( x * x - y * y ) ) * domega;
520void EnvironmentTexture::tomatrix(
void ) {
523 constexpr float c1 = 0.429043f, c2 = 0.511664f, c3 = 0.743125f, c4 = 0.886227f, c5 = 0.247708f;
525 for ( col = 0; col < 3; col++ ) {
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],
531 c1 * m_shcoefs[4][col],
532 -c1 * m_shcoefs[8][col],
533 c1 * m_shcoefs[5][col],
534 c2 * m_shcoefs[1][col],
536 c1 * m_shcoefs[7][col],
537 c1 * m_shcoefs[5][col],
538 c3 * m_shcoefs[6][col],
539 c2 * m_shcoefs[2][col],
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] )
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;
554 if ( std::abs( y ) > ma ) {
556 axis = 2 + ( y < 0 );
558 sc = ( axis == 2 ) ? -z : z;
560 if ( std::abs( z ) > ma ) {
562 axis = 4 + ( z < 0 );
563 tc = ( axis == 4 ) ? -x : x;
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 )] );
574 if ( m_shtexture !=
nullptr ) {
return m_shtexture.get(); }
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++ ) {
586 v = ( ambientWidth / 2.0_ra - j ) /
587 ( ambientWidth / 2.0_ra );
588 u = ( ambientWidth / 2.0_ra - i ) /
589 ( ambientWidth / 2.0_ra );
590 r =
sqrt( u * u + v * v );
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;
599 Scalar theta = M_PI * r;
600 Scalar phi =
atan2( v, u );
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 ),
615 auto clpfnct = []( Scalar value ) {
return std::clamp( value, 0_ra, 1_ra ); };
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;
629 { GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, GL_LINEAR, GL_LINEAR },
639 m_shtexture = std::make_unique<Ra::Engine::Data::Texture>( params );
640 return m_shtexture.get();
643void EnvironmentTexture::saveShProjection(
const std::string& filename ) {
644 auto tex = getSHImage();
646 stbi_write_png( flnm.c_str(), tex->getWidth(), tex->getHeight(), 4, tex->getTexels(), 0 );
649const Ra::Core::Matrix4& EnvironmentTexture::getShMatrix(
int channel ) {
650 return m_shMatrices[channel];
653void EnvironmentTexture::updateGL() {
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"
663 " mat4 mvp = transform.proj * transform.view;\n"
664 " gl_Position = mvp*vec4(in_position, 1.0);\n"
665 " incidentDirection = in_position;\n"
668 "layout (location = 0) out vec4 outColor;\n"
669 "in vec3 incidentDirection;\n"
670 "uniform samplerCube skyTexture;\n"
671 "uniform float strength;\n"
674 " vec3 envColor = texture(skyTexture, normalize(incidentDirection)).rgb;\n"
675 " outColor =vec4(strength*envColor, 1);\n"
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(); }
688 m_displayMesh->updateGL();
689 m_skyTexture->initializeNow();
690 glEnable( GL_TEXTURE_CUBE_MAP_SEAMLESS );
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;
704 Ra::Engine::RadiumEngine::getInstance()->getSystem(
"DefaultCameraManager" ) );
707 std::clamp( cam->getZoomFactor() * cam->getFOV(), 0.001_ra, Math::Pi - 0.1_ra );
708 skyparams.projMatrix =
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 );
724void EnvironmentTexture::setStrength(
float s ) {
725 m_environmentStrength = s;
728float EnvironmentTexture::getStrength()
const {
729 return m_environmentStrength;
733 return m_skyTexture.get();
static Core::Matrix4 perspective(Scalar a, Scalar y, Scalar n, Scalar f)
static ColorBase linearRGBTosRGB(const ColorBase &lrgb)
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.
Ra::Core::Asset::Camera * getActiveCamera()
Get the pointer on the active camera data.
This namespace contains everything "low level", related to data, datastuctures, and computation.
hepler function to manage enum as underlying types in VariableSet
Describes the sampler and image of a texture.
the set of viewing parameters extracted from the camera and given to the renderer