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_ra * M_PI ),
436 h * sphericalTheta( d ) / M_PI };
439 int s = std::clamp(
int( st.x() ), 0, w - 1 );
440 int t = std::clamp(
int( st.y() ), 0, h - 1 );
442 std::clamp(
int( ( u / 2_ra + 0.5_ra ) * textureSize ), 0, textureSize - 1 );
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 );
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;
456 for (
int imgIdx = 0; imgIdx < 6; ++imgIdx ) {
457 flip_horizontally( m_skyData[imgIdx].get(), textureSize, textureSize, 4 );
461 m_width = m_height = textureSize;
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;
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 ) {
479 float* thePixel = getPixel( x, y, z );
480 updateCoeffs( thePixel, -x, y, z,
std::sin( theta ) * dtheta * dphi );
486void EnvironmentTexture::updateCoeffs(
float* hdr,
float x,
float y,
float z,
float domega ) {
492 for (
int col = 0; col < 3; col++ ) {
497 m_shcoefs[0][col] += hdr[col] * c * domega;
501 m_shcoefs[1][col] += hdr[col] * ( c * y ) * domega;
502 m_shcoefs[2][col] += hdr[col] * ( c * z ) * domega;
503 m_shcoefs[3][col] += hdr[col] * ( c * x ) * domega;
509 m_shcoefs[4][col] += hdr[col] * ( c * x * y ) * domega;
510 m_shcoefs[5][col] += hdr[col] * ( c * y * z ) * domega;
511 m_shcoefs[7][col] += hdr[col] * ( c * x * z ) * domega;
515 m_shcoefs[6][col] += hdr[col] * ( c * ( 3 * z * z - 1 ) ) * domega;
519 m_shcoefs[8][col] += hdr[col] * ( c * ( x * x - y * y ) ) * domega;
523void EnvironmentTexture::tomatrix(
void ) {
526 constexpr float c1 = 0.429043f, c2 = 0.511664f, c3 = 0.743125f, c4 = 0.886227f, c5 = 0.247708f;
528 for ( col = 0; col < 3; col++ ) {
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],
534 c1 * m_shcoefs[4][col],
535 -c1 * m_shcoefs[8][col],
536 c1 * m_shcoefs[5][col],
537 c2 * m_shcoefs[1][col],
539 c1 * m_shcoefs[7][col],
540 c1 * m_shcoefs[5][col],
541 c3 * m_shcoefs[6][col],
542 c2 * m_shcoefs[2][col],
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] )
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;
557 if ( std::abs( y ) > ma ) {
559 axis = 2 + ( y < 0 );
561 sc = ( axis == 2 ) ? -z : z;
563 if ( std::abs( z ) > ma ) {
565 axis = 4 + ( z < 0 );
566 tc = ( axis == 4 ) ? -x : x;
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 )] );
577 if ( m_shtexture !=
nullptr ) {
return m_shtexture.get(); }
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++ ) {
589 v = ( ambientWidth / 2.0_ra - j ) /
590 ( ambientWidth / 2.0_ra );
591 u = ( ambientWidth / 2.0_ra - i ) /
592 ( ambientWidth / 2.0_ra );
593 r =
sqrt( u * u + v * v );
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;
602 Scalar theta = M_PI * r;
603 Scalar phi =
atan2( v, u );
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 ),
618 auto clpfnct = []( Scalar value ) {
return std::clamp( value, 0_ra, 1_ra ); };
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;
632 { GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, GL_LINEAR, GL_LINEAR },
642 m_shtexture = std::make_unique<Ra::Engine::Data::Texture>( params );
643 return m_shtexture.get();
646void EnvironmentTexture::saveShProjection(
const std::string& filename ) {
647 auto tex = getSHImage();
649 stbi_write_png( flnm.c_str(), tex->getWidth(), tex->getHeight(), 4, tex->getTexels(), 0 );
646void EnvironmentTexture::saveShProjection(
const std::string& filename ) {
…}
652const Ra::Core::Matrix4& EnvironmentTexture::getShMatrix(
int channel ) {
653 return m_shMatrices[channel];
652const Ra::Core::Matrix4& EnvironmentTexture::getShMatrix(
int channel ) {
…}
656void EnvironmentTexture::updateGL() {
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"
666 " mat4 mvp = transform.proj * transform.view;\n"
667 " gl_Position = mvp*vec4(in_position, 1.0);\n"
668 " incidentDirection = in_position;\n"
671 "layout (location = 0) out vec4 outColor;\n"
672 "in vec3 incidentDirection;\n"
673 "uniform samplerCube skyTexture;\n"
674 "uniform float strength;\n"
677 " vec3 envColor = texture(skyTexture, normalize(incidentDirection)).rgb;\n"
678 " outColor =vec4(strength*envColor, 1);\n"
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(); }
691 m_displayMesh->updateGL();
692 m_skyTexture->initializeNow();
693 glEnable( GL_TEXTURE_CUBE_MAP_SEAMLESS );
656void EnvironmentTexture::updateGL() {
…}
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;
707 Ra::Engine::RadiumEngine::getInstance()->getSystem(
"DefaultCameraManager" ) );
710 std::clamp( cam->getZoomFactor() * cam->getFOV(), 0.001_ra, Math::Pi - 0.1_ra );
711 skyparams.projMatrix =
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 );
727void EnvironmentTexture::setStrength(
float s ) {
728 m_environmentStrength = s;
727void EnvironmentTexture::setStrength(
float s ) {
…}
731float EnvironmentTexture::getStrength()
const {
732 return m_environmentStrength;
731float EnvironmentTexture::getStrength()
const {
…}
736 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