diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 6665e71642..37ee116f40 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -885,6 +885,10 @@ void Application::paintGL() { _myCamera.update(1.0f / _fps); } + // Sync up the View Furstum with the camera + // FIXME: it's happening again in the updateSHadow and it shouldn't, this should be the place + loadViewFrustum(_myCamera, _viewFrustum); + if (getShadowsEnabled()) { updateShadowMap(); } diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index d382e078fc..d7b9c56827 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -445,7 +445,7 @@ void EntityTreeRenderer::applyZonePropertiesToScene(const ZoneEntityItem* zone) if (zone->getBackgroundMode() == BACKGROUND_MODE_SKYBOX) { stage->getSkybox()->setColor(zone->getSkyboxProperties().getColorVec3()); if (zone->getSkyboxProperties().getURL().isEmpty()) { - stage->getSkybox()->clearCubemap(); + stage->getSkybox()->setCubemap(gpu::TexturePointer()); } else { // Update the Texture of the Skybox with the one pointed by this zone auto cubeMap = DependencyManager::get()->getTexture(zone->getSkyboxProperties().getURL(), CUBE_TEXTURE); diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 62e61dc039..e076507b76 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -85,8 +85,8 @@ EntityItemID EntityScriptingInterface::addEntity(const EntityItemProperties& pro if (_entityTree) { _entityTree->lockForWrite(); EntityItem* entity = _entityTree->addEntity(id, propertiesWithSimID); - entity->setLastBroadcast(usecTimestampNow()); if (entity) { + entity->setLastBroadcast(usecTimestampNow()); // This Node is creating a new object. If it's in motion, set this Node as the simulator. bidForSimulationOwnership(propertiesWithSimID); } else { diff --git a/libraries/gpu/src/gpu/Texture.cpp b/libraries/gpu/src/gpu/Texture.cpp index 542a47c915..be26133f2a 100755 --- a/libraries/gpu/src/gpu/Texture.cpp +++ b/libraries/gpu/src/gpu/Texture.cpp @@ -11,6 +11,8 @@ #include "Texture.h" #include +#include + #include using namespace gpu; @@ -407,3 +409,362 @@ void Texture::setSampler(const Sampler& sampler) { _sampler = sampler; _samplerStamp++; } + + +bool Texture::generateIrradiance() { + if (getType() != TEX_CUBE) { + return false; + } + if (!isDefined()) { + return false; + } + if (!_irradiance) { + _irradiance.reset(new SphericalHarmonics()); + } + + _irradiance->evalFromTexture(*this); + return true; +} + +void SphericalHarmonics::assignPreset(int p) { + switch (p) { + case OLD_TOWN_SQUARE: { + L00 = glm::vec3( 0.871297f, 0.875222f, 0.864470f); + L1m1 = glm::vec3( 0.175058f, 0.245335f, 0.312891f); + L10 = glm::vec3( 0.034675f, 0.036107f, 0.037362f); + L11 = glm::vec3(-0.004629f,-0.029448f,-0.048028f); + L2m2 = glm::vec3(-0.120535f,-0.121160f,-0.117507f); + L2m1 = glm::vec3( 0.003242f, 0.003624f, 0.007511f); + L20 = glm::vec3(-0.028667f,-0.024926f,-0.020998f); + L21 = glm::vec3(-0.077539f,-0.086325f,-0.091591f); + L22 = glm::vec3(-0.161784f,-0.191783f,-0.219152f); + } + break; + case GRACE_CATHEDRAL: { + L00 = glm::vec3( 0.79f, 0.44f, 0.54f); + L1m1 = glm::vec3( 0.39f, 0.35f, 0.60f); + L10 = glm::vec3(-0.34f, -0.18f, -0.27f); + L11 = glm::vec3(-0.29f, -0.06f, 0.01f); + L2m2 = glm::vec3(-0.11f, -0.05f, -0.12f); + L2m1 = glm::vec3(-0.26f, -0.22f, -0.47f); + L20 = glm::vec3(-0.16f, -0.09f, -0.15f); + L21 = glm::vec3( 0.56f, 0.21f, 0.14f); + L22 = glm::vec3( 0.21f, -0.05f, -0.30f); + } + break; + case EUCALYPTUS_GROVE: { + L00 = glm::vec3( 0.38f, 0.43f, 0.45f); + L1m1 = glm::vec3( 0.29f, 0.36f, 0.41f); + L10 = glm::vec3( 0.04f, 0.03f, 0.01f); + L11 = glm::vec3(-0.10f, -0.10f, -0.09f); + L2m2 = glm::vec3(-0.06f, -0.06f, -0.04f); + L2m1 = glm::vec3( 0.01f, -0.01f, -0.05f); + L20 = glm::vec3(-0.09f, -0.13f, -0.15f); + L21 = glm::vec3(-0.06f, -0.05f, -0.04f); + L22 = glm::vec3( 0.02f, 0.00f, -0.05f); + } + break; + case ST_PETERS_BASILICA: { + L00 = glm::vec3( 0.36f, 0.26f, 0.23f); + L1m1 = glm::vec3( 0.18f, 0.14f, 0.13f); + L10 = glm::vec3(-0.02f, -0.01f, 0.00f); + L11 = glm::vec3( 0.03f, 0.02f, -0.00f); + L2m2 = glm::vec3( 0.02f, 0.01f, -0.00f); + L2m1 = glm::vec3(-0.05f, -0.03f, -0.01f); + L20 = glm::vec3(-0.09f, -0.08f, -0.07f); + L21 = glm::vec3( 0.01f, 0.00f, 0.00f); + L22 = glm::vec3(-0.08f, -0.03f, -0.00f); + } + break; + case UFFIZI_GALLERY: { + L00 = glm::vec3( 0.32f, 0.31f, 0.35f); + L1m1 = glm::vec3( 0.37f, 0.37f, 0.43f); + L10 = glm::vec3( 0.00f, 0.00f, 0.00f); + L11 = glm::vec3(-0.01f, -0.01f, -0.01f); + L2m2 = glm::vec3(-0.02f, -0.02f, -0.03f); + L2m1 = glm::vec3(-0.01f, -0.01f, -0.01f); + L20 = glm::vec3(-0.28f, -0.28f, -0.32f); + L21 = glm::vec3( 0.00f, 0.00f, 0.00f); + L22 = glm::vec3(-0.24f, -0.24f, -0.28f); + } + break; + case GALILEOS_TOMB: { + L00 = glm::vec3( 1.04f, 0.76f, 0.71f); + L1m1 = glm::vec3( 0.44f, 0.34f, 0.34f); + L10 = glm::vec3(-0.22f, -0.18f, -0.17f); + L11 = glm::vec3( 0.71f, 0.54f, 0.56f); + L2m2 = glm::vec3( 0.64f, 0.50f, 0.52f); + L2m1 = glm::vec3(-0.12f, -0.09f, -0.08f); + L20 = glm::vec3(-0.37f, -0.28f, -0.32f); + L21 = glm::vec3(-0.17f, -0.13f, -0.13f); + L22 = glm::vec3( 0.55f, 0.42f, 0.42f); + } + break; + case VINE_STREET_KITCHEN: { + L00 = glm::vec3( 0.64f, 0.67f, 0.73f); + L1m1 = glm::vec3( 0.28f, 0.32f, 0.33f); + L10 = glm::vec3( 0.42f, 0.60f, 0.77f); + L11 = glm::vec3(-0.05f, -0.04f, -0.02f); + L2m2 = glm::vec3(-0.10f, -0.08f, -0.05f); + L2m1 = glm::vec3( 0.25f, 0.39f, 0.53f); + L20 = glm::vec3( 0.38f, 0.54f, 0.71f); + L21 = glm::vec3( 0.06f, 0.01f, -0.02f); + L22 = glm::vec3(-0.03f, -0.02f, -0.03f); + } + break; + case BREEZEWAY: { + L00 = glm::vec3( 0.32f, 0.36f, 0.38f); + L1m1 = glm::vec3( 0.37f, 0.41f, 0.45f); + L10 = glm::vec3(-0.01f, -0.01f, -0.01f); + L11 = glm::vec3(-0.10f, -0.12f, -0.12f); + L2m2 = glm::vec3(-0.13f, -0.15f, -0.17f); + L2m1 = glm::vec3(-0.01f, -0.02f, 0.02f); + L20 = glm::vec3(-0.07f, -0.08f, -0.09f); + L21 = glm::vec3( 0.02f, 0.03f, 0.03f); + L22 = glm::vec3(-0.29f, -0.32f, -0.36f); + } + break; + case CAMPUS_SUNSET: { + L00 = glm::vec3( 0.79f, 0.94f, 0.98f); + L1m1 = glm::vec3( 0.44f, 0.56f, 0.70f); + L10 = glm::vec3(-0.10f, -0.18f, -0.27f); + L11 = glm::vec3( 0.45f, 0.38f, 0.20f); + L2m2 = glm::vec3( 0.18f, 0.14f, 0.05f); + L2m1 = glm::vec3(-0.14f, -0.22f, -0.31f); + L20 = glm::vec3(-0.39f, -0.40f, -0.36f); + L21 = glm::vec3( 0.09f, 0.07f, 0.04f); + L22 = glm::vec3( 0.67f, 0.67f, 0.52f); + } + break; + case FUNSTON_BEACH_SUNSET: { + L00 = glm::vec3( 0.68f, 0.69f, 0.70f); + L1m1 = glm::vec3( 0.32f, 0.37f, 0.44f); + L10 = glm::vec3(-0.17f, -0.17f, -0.17f); + L11 = glm::vec3(-0.45f, -0.42f, -0.34f); + L2m2 = glm::vec3(-0.17f, -0.17f, -0.15f); + L2m1 = glm::vec3(-0.08f, -0.09f, -0.10f); + L20 = glm::vec3(-0.03f, -0.02f, -0.01f); + L21 = glm::vec3( 0.16f, 0.14f, 0.10f); + L22 = glm::vec3( 0.37f, 0.31f, 0.20f); + } + break; + } +} + + + +glm::vec3 sRGBToLinear(glm::vec3& color) { + const float GAMMA_CORRECTION = 2.2f; + return glm::pow(color, glm::vec3(GAMMA_CORRECTION)); +} + +glm::vec3 linearTosRGB(glm::vec3& color) { + const float GAMMA_CORRECTION_INV = 1.0f / 2.2f; + return glm::pow(color, glm::vec3(GAMMA_CORRECTION_INV)); +} + +// Originial code for the Spherical Harmonics taken from "Sun and Black Cat- Igor Dykhta (igor dykhta email) © 2007-2014 " +void sphericalHarmonicsAdd(float * result, int order, const float * inputA, const float * inputB) { + const int numCoeff = order * order; + for(int i=0; i < numCoeff; i++) { + result[i] = inputA[i] + inputB[i]; + } +} + +void sphericalHarmonicsScale(float * result, int order, const float * input, float scale) { + const int numCoeff = order * order; + for(int i=0; i < numCoeff; i++) { + result[i] = input[i] * scale; + } +} + +void sphericalHarmonicsEvaluateDirection(float * result, int order, const glm::vec3 & dir) { + // calculate coefficients for first 3 bands of spherical harmonics + double P_0_0 = 0.282094791773878140; + double P_1_0 = 0.488602511902919920 * dir.z; + double P_1_1 = -0.488602511902919920; + double P_2_0 = 0.946174695757560080 * dir.z * dir.z - 0.315391565252520050; + double P_2_1 = -1.092548430592079200 * dir.z; + double P_2_2 = 0.546274215296039590; + result[0] = P_0_0; + result[1] = P_1_1 * dir.y; + result[2] = P_1_0; + result[3] = P_1_1 * dir.x; + result[4] = P_2_2 * (dir.x * dir.y + dir.y * dir.x); + result[5] = P_2_1 * dir.y; + result[6] = P_2_0; + result[7] = P_2_1 * dir.x; + result[8] = P_2_2 * (dir.x * dir.x - dir.y * dir.y); +} + +bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector & output, const uint order) { + const uint sqOrder = order*order; + + // allocate memory for calculations + output.resize(sqOrder); + std::vector resultR(sqOrder); + std::vector resultG(sqOrder); + std::vector resultB(sqOrder); + + int width, height; + + // initialize values + float fWt = 0.0f; + for(uint i=0; i < sqOrder; i++) { + output[i] = glm::vec3(0.0f); + resultR[i] = 0.0f; + resultG[i] = 0; + resultB[i] = 0; + } + std::vector shBuff(sqOrder); + std::vector shBuffB(sqOrder); + // get width and height + width = height = cubeTexture.getWidth(); + if(width != height) { + return false; + } + + const float UCHAR_TO_FLOAT = 1.0f / float(std::numeric_limits::max()); + + // for each face of cube texture + for(int face=0; face < gpu::Texture::NUM_CUBE_FACES; face++) { + + auto numComponents = cubeTexture.accessStoredMipFace(0,face)->_format.getDimensionCount(); + auto data = cubeTexture.accessStoredMipFace(0,face)->_sysmem.readData(); + if (data == nullptr) { + continue; + } + + // step between two texels for range [0, 1] + float invWidth = 1.0f / float(width); + // initial negative bound for range [-1, 1] + float negativeBound = -1.0f + invWidth; + // step between two texels for range [-1, 1] + float invWidthBy2 = 2.0f / float(width); + + for(int y=0; y < width; y++) { + // texture coordinate V in range [-1 to 1] + const float fV = negativeBound + float(y) * invWidthBy2; + + for(int x=0; x < width; x++) { + // texture coordinate U in range [-1 to 1] + const float fU = negativeBound + float(x) * invWidthBy2; + + // determine direction from center of cube texture to current texel + glm::vec3 dir; + switch(face) { + case gpu::Texture::CUBE_FACE_RIGHT_POS_X: { + dir.x = 1.0f; + dir.y = 1.0f - (invWidthBy2 * float(y) + invWidth); + dir.z = 1.0f - (invWidthBy2 * float(x) + invWidth); + dir = -dir; + break; + } + case gpu::Texture::CUBE_FACE_LEFT_NEG_X: { + dir.x = -1.0f; + dir.y = 1.0f - (invWidthBy2 * float(y) + invWidth); + dir.z = -1.0f + (invWidthBy2 * float(x) + invWidth); + dir = -dir; + break; + } + case gpu::Texture::CUBE_FACE_TOP_POS_Y: { + dir.x = - 1.0f + (invWidthBy2 * float(x) + invWidth); + dir.y = 1.0f; + dir.z = - 1.0f + (invWidthBy2 * float(y) + invWidth); + dir = -dir; + break; + } + case gpu::Texture::CUBE_FACE_BOTTOM_NEG_Y: { + dir.x = - 1.0f + (invWidthBy2 * float(x) + invWidth); + dir.y = - 1.0f; + dir.z = 1.0f - (invWidthBy2 * float(y) + invWidth); + dir = -dir; + break; + } + case gpu::Texture::CUBE_FACE_BACK_POS_Z: { + dir.x = - 1.0f + (invWidthBy2 * float(x) + invWidth); + dir.y = 1.0f - (invWidthBy2 * float(y) + invWidth); + dir.z = 1.0f; + break; + } + case gpu::Texture::CUBE_FACE_FRONT_NEG_Z: { + dir.x = 1.0f - (invWidthBy2 * float(x) + invWidth); + dir.y = 1.0f - (invWidthBy2 * float(y) + invWidth); + dir.z = - 1.0f; + break; + } + default: + return false; + } + + // normalize direction + dir = glm::normalize(dir); + + // scale factor depending on distance from center of the face + const float fDiffSolid = 4.0f / ((1.0f + fU*fU + fV*fV) * + sqrtf(1.0f + fU*fU + fV*fV)); + fWt += fDiffSolid; + + // calculate coefficients of spherical harmonics for current direction + sphericalHarmonicsEvaluateDirection(shBuff.data(), order, dir); + + // index of texel in texture + uint pixOffsetIndex = (x + y * width) * numComponents; + + // get color from texture and map to range [0, 1] + glm::vec3 clr(float(data[pixOffsetIndex]) * UCHAR_TO_FLOAT, + float(data[pixOffsetIndex+1]) * UCHAR_TO_FLOAT, + float(data[pixOffsetIndex+2]) * UCHAR_TO_FLOAT); + + // Gamma correct + clr = sRGBToLinear(clr); + + // scale color and add to previously accumulated coefficients + sphericalHarmonicsScale(shBuffB.data(), order, + shBuff.data(), clr.r * fDiffSolid); + sphericalHarmonicsAdd(resultR.data(), order, + resultR.data(), shBuffB.data()); + sphericalHarmonicsScale(shBuffB.data(), order, + shBuff.data(), clr.g * fDiffSolid); + sphericalHarmonicsAdd(resultG.data(), order, + resultG.data(), shBuffB.data()); + sphericalHarmonicsScale(shBuffB.data(), order, + shBuff.data(), clr.b * fDiffSolid); + sphericalHarmonicsAdd(resultB.data(), order, + resultB.data(), shBuffB.data()); + } + } + } + + // final scale for coefficients + const float fNormProj = (4.0f * glm::pi()) / fWt; + sphericalHarmonicsScale(resultR.data(), order, resultR.data(), fNormProj); + sphericalHarmonicsScale(resultG.data(), order, resultG.data(), fNormProj); + sphericalHarmonicsScale(resultB.data(), order, resultB.data(), fNormProj); + + // save result + for(uint i=0; i < sqOrder; i++) { + // gamma Correct + // output[i] = linearTosRGB(glm::vec3(resultR[i], resultG[i], resultB[i])); + output[i] = glm::vec3(resultR[i], resultG[i], resultB[i]); + } + + return true; +} + +void SphericalHarmonics::evalFromTexture(const Texture& texture) { + if (texture.isDefined()) { + std::vector< glm::vec3 > coefs; + sphericalHarmonicsFromTexture(texture, coefs, 3); + + L00 = coefs[0]; + L1m1 = coefs[1]; + L10 = coefs[2]; + L11 = coefs[3]; + L2m2 = coefs[4]; + L2m1 = coefs[5]; + L20 = coefs[6]; + L21 = coefs[7]; + L22 = coefs[8]; + } +} \ No newline at end of file diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h index 5d094ddbb4..0d9664f1ab 100755 --- a/libraries/gpu/src/gpu/Texture.h +++ b/libraries/gpu/src/gpu/Texture.h @@ -17,6 +17,44 @@ namespace gpu { +// THe spherical harmonics is a nice tool for cubemap, so if required, the irradiance SH can be automatically generated +// with the cube texture +class Texture; +class SphericalHarmonics { +public: + glm::vec3 L00 ; float spare0; + glm::vec3 L1m1 ; float spare1; + glm::vec3 L10 ; float spare2; + glm::vec3 L11 ; float spare3; + glm::vec3 L2m2 ; float spare4; + glm::vec3 L2m1 ; float spare5; + glm::vec3 L20 ; float spare6; + glm::vec3 L21 ; float spare7; + glm::vec3 L22 ; float spare8; + + static const int NUM_COEFFICIENTS = 9; + + enum Preset { + OLD_TOWN_SQUARE = 0, + GRACE_CATHEDRAL, + EUCALYPTUS_GROVE, + ST_PETERS_BASILICA, + UFFIZI_GALLERY, + GALILEOS_TOMB, + VINE_STREET_KITCHEN, + BREEZEWAY, + CAMPUS_SUNSET, + FUNSTON_BEACH_SUNSET, + + NUM_PRESET, + }; + + void assignPreset(int p); + + void evalFromTexture(const Texture& texture); +}; +typedef std::shared_ptr< SphericalHarmonics > SHPointer; + class Sampler { public: @@ -173,6 +211,7 @@ public: static Texture* createFromStorage(Storage* storage); + Texture(); Texture(const Texture& buf); // deep copy of the sysmem texture Texture& operator=(const Texture& buf); // deep copy of the sysmem texture ~Texture(); @@ -302,6 +341,10 @@ public: bool isDefined() const { return _defined; } + // For Cube Texture, it's possible to generate the irradiance spherical harmonics and make them availalbe with the texture + bool generateIrradiance(); + const SHPointer& getIrradiance(uint16 slice = 0) const { return _irradiance; } + bool isIrradianceValid() const { return _isIrradianceValid; } // Own sampler void setSampler(const Sampler& sampler); @@ -332,11 +375,14 @@ protected: uint16 _maxMip = 0; Type _type = TEX_1D; + + SHPointer _irradiance; bool _autoGenerateMips = false; + bool _isIrradianceValid = false; bool _defined = false; + static Texture* create(Type type, const Element& texelFormat, uint16 width, uint16 height, uint16 depth, uint16 numSamples, uint16 numSlices, const Sampler& sampler); - Texture(); Size resize(Type type, const Element& texelFormat, uint16 width, uint16 height, uint16 depth, uint16 numSamples, uint16 numSlices); @@ -392,7 +438,7 @@ public: explicit operator bool() const { return bool(_texture); } bool operator !() const { return (!_texture); } }; -typedef std::vector TextureViews; +typedef std::vector TextureViews; }; diff --git a/libraries/model/src/model/Light.h b/libraries/model/src/model/Light.h index abcb06181f..920549d0f9 100755 --- a/libraries/model/src/model/Light.h +++ b/libraries/model/src/model/Light.h @@ -26,162 +26,6 @@ typedef glm::vec3 Vec3; typedef glm::vec4 Vec4; typedef glm::quat Quat; -class SphericalHarmonics { -public: - glm::vec3 L00 ; float spare0; - glm::vec3 L1m1 ; float spare1; - glm::vec3 L10 ; float spare2; - glm::vec3 L11 ; float spare3; - glm::vec3 L2m2 ; float spare4; - glm::vec3 L2m1 ; float spare5; - glm::vec3 L20 ; float spare6; - glm::vec3 L21 ; float spare7; - glm::vec3 L22 ; float spare8; - - static const int NUM_COEFFICIENTS = 9; - - enum Preset { - OLD_TOWN_SQUARE = 0, - GRACE_CATHEDRAL, - EUCALYPTUS_GROVE, - ST_PETERS_BASILICA, - UFFIZI_GALLERY, - GALILEOS_TOMB, - VINE_STREET_KITCHEN, - BREEZEWAY, - CAMPUS_SUNSET, - FUNSTON_BEACH_SUNSET, - - NUM_PRESET, - }; - - void assignPreset(int p) { - switch (p) { - case OLD_TOWN_SQUARE: { - L00 = glm::vec3( 0.871297f, 0.875222f, 0.864470f); - L1m1 = glm::vec3( 0.175058f, 0.245335f, 0.312891f); - L10 = glm::vec3( 0.034675f, 0.036107f, 0.037362f); - L11 = glm::vec3(-0.004629f,-0.029448f,-0.048028f); - L2m2 = glm::vec3(-0.120535f,-0.121160f,-0.117507f); - L2m1 = glm::vec3( 0.003242f, 0.003624f, 0.007511f); - L20 = glm::vec3(-0.028667f,-0.024926f,-0.020998f); - L21 = glm::vec3(-0.077539f,-0.086325f,-0.091591f); - L22 = glm::vec3(-0.161784f,-0.191783f,-0.219152f); - } - break; - case GRACE_CATHEDRAL: { - L00 = glm::vec3( 0.79f, 0.44f, 0.54f); - L1m1 = glm::vec3( 0.39f, 0.35f, 0.60f); - L10 = glm::vec3(-0.34f, -0.18f, -0.27f); - L11 = glm::vec3(-0.29f, -0.06f, 0.01f); - L2m2 = glm::vec3(-0.11f, -0.05f, -0.12f); - L2m1 = glm::vec3(-0.26f, -0.22f, -0.47f); - L20 = glm::vec3(-0.16f, -0.09f, -0.15f); - L21 = glm::vec3( 0.56f, 0.21f, 0.14f); - L22 = glm::vec3( 0.21f, -0.05f, -0.30f); - } - break; - case EUCALYPTUS_GROVE: { - L00 = glm::vec3( 0.38f, 0.43f, 0.45f); - L1m1 = glm::vec3( 0.29f, 0.36f, 0.41f); - L10 = glm::vec3( 0.04f, 0.03f, 0.01f); - L11 = glm::vec3(-0.10f, -0.10f, -0.09f); - L2m2 = glm::vec3(-0.06f, -0.06f, -0.04f); - L2m1 = glm::vec3( 0.01f, -0.01f, -0.05f); - L20 = glm::vec3(-0.09f, -0.13f, -0.15f); - L21 = glm::vec3(-0.06f, -0.05f, -0.04f); - L22 = glm::vec3( 0.02f, 0.00f, -0.05f); - } - break; - case ST_PETERS_BASILICA: { - L00 = glm::vec3( 0.36f, 0.26f, 0.23f); - L1m1 = glm::vec3( 0.18f, 0.14f, 0.13f); - L10 = glm::vec3(-0.02f, -0.01f, 0.00f); - L11 = glm::vec3( 0.03f, 0.02f, -0.00f); - L2m2 = glm::vec3( 0.02f, 0.01f, -0.00f); - L2m1 = glm::vec3(-0.05f, -0.03f, -0.01f); - L20 = glm::vec3(-0.09f, -0.08f, -0.07f); - L21 = glm::vec3( 0.01f, 0.00f, 0.00f); - L22 = glm::vec3(-0.08f, -0.03f, -0.00f); - } - break; - case UFFIZI_GALLERY: { - L00 = glm::vec3( 0.32f, 0.31f, 0.35f); - L1m1 = glm::vec3( 0.37f, 0.37f, 0.43f); - L10 = glm::vec3( 0.00f, 0.00f, 0.00f); - L11 = glm::vec3(-0.01f, -0.01f, -0.01f); - L2m2 = glm::vec3(-0.02f, -0.02f, -0.03f); - L2m1 = glm::vec3(-0.01f, -0.01f, -0.01f); - L20 = glm::vec3(-0.28f, -0.28f, -0.32f); - L21 = glm::vec3( 0.00f, 0.00f, 0.00f); - L22 = glm::vec3(-0.24f, -0.24f, -0.28f); - } - break; - case GALILEOS_TOMB: { - L00 = glm::vec3( 1.04f, 0.76f, 0.71f); - L1m1 = glm::vec3( 0.44f, 0.34f, 0.34f); - L10 = glm::vec3(-0.22f, -0.18f, -0.17f); - L11 = glm::vec3( 0.71f, 0.54f, 0.56f); - L2m2 = glm::vec3( 0.64f, 0.50f, 0.52f); - L2m1 = glm::vec3(-0.12f, -0.09f, -0.08f); - L20 = glm::vec3(-0.37f, -0.28f, -0.32f); - L21 = glm::vec3(-0.17f, -0.13f, -0.13f); - L22 = glm::vec3( 0.55f, 0.42f, 0.42f); - } - break; - case VINE_STREET_KITCHEN: { - L00 = glm::vec3( 0.64f, 0.67f, 0.73f); - L1m1 = glm::vec3( 0.28f, 0.32f, 0.33f); - L10 = glm::vec3( 0.42f, 0.60f, 0.77f); - L11 = glm::vec3(-0.05f, -0.04f, -0.02f); - L2m2 = glm::vec3(-0.10f, -0.08f, -0.05f); - L2m1 = glm::vec3( 0.25f, 0.39f, 0.53f); - L20 = glm::vec3( 0.38f, 0.54f, 0.71f); - L21 = glm::vec3( 0.06f, 0.01f, -0.02f); - L22 = glm::vec3(-0.03f, -0.02f, -0.03f); - } - break; - case BREEZEWAY: { - L00 = glm::vec3( 0.32f, 0.36f, 0.38f); - L1m1 = glm::vec3( 0.37f, 0.41f, 0.45f); - L10 = glm::vec3(-0.01f, -0.01f, -0.01f); - L11 = glm::vec3(-0.10f, -0.12f, -0.12f); - L2m2 = glm::vec3(-0.13f, -0.15f, -0.17f); - L2m1 = glm::vec3(-0.01f, -0.02f, 0.02f); - L20 = glm::vec3(-0.07f, -0.08f, -0.09f); - L21 = glm::vec3( 0.02f, 0.03f, 0.03f); - L22 = glm::vec3(-0.29f, -0.32f, -0.36f); - } - break; - case CAMPUS_SUNSET: { - L00 = glm::vec3( 0.79f, 0.94f, 0.98f); - L1m1 = glm::vec3( 0.44f, 0.56f, 0.70f); - L10 = glm::vec3(-0.10f, -0.18f, -0.27f); - L11 = glm::vec3( 0.45f, 0.38f, 0.20f); - L2m2 = glm::vec3( 0.18f, 0.14f, 0.05f); - L2m1 = glm::vec3(-0.14f, -0.22f, -0.31f); - L20 = glm::vec3(-0.39f, -0.40f, -0.36f); - L21 = glm::vec3( 0.09f, 0.07f, 0.04f); - L22 = glm::vec3( 0.67f, 0.67f, 0.52f); - } - break; - case FUNSTON_BEACH_SUNSET: { - L00 = glm::vec3( 0.68f, 0.69f, 0.70f); - L1m1 = glm::vec3( 0.32f, 0.37f, 0.44f); - L10 = glm::vec3(-0.17f, -0.17f, -0.17f); - L11 = glm::vec3(-0.45f, -0.42f, -0.34f); - L2m2 = glm::vec3(-0.17f, -0.17f, -0.15f); - L2m1 = glm::vec3(-0.08f, -0.09f, -0.10f); - L20 = glm::vec3(-0.03f, -0.02f, -0.01f); - L21 = glm::vec3( 0.16f, 0.14f, 0.10f); - L22 = glm::vec3( 0.37f, 0.31f, 0.20f); - } - break; - } - } -}; -typedef std::shared_ptr< SphericalHarmonics > SHPointer; - class Light { public: enum Type { @@ -250,9 +94,9 @@ public: float getAmbientIntensity() const { return getSchema()._ambientIntensity; } // Spherical Harmonics storing the Ambien lighting approximation used for the Sun typed light - void setAmbientSphere(const SphericalHarmonics& sphere) { _ambientSphere = sphere; } - const SphericalHarmonics& getAmbientSphere() const { return _ambientSphere; } - void setAmbientSpherePreset(SphericalHarmonics::Preset preset) { _ambientSphere.assignPreset(preset); } + void setAmbientSphere(const gpu::SphericalHarmonics& sphere) { _ambientSphere = sphere; } + const gpu::SphericalHarmonics& getAmbientSphere() const { return _ambientSphere; } + void setAmbientSpherePreset(gpu::SphericalHarmonics::Preset preset) { _ambientSphere.assignPreset(preset); } // Schema to access the attribute values of the light class Schema { @@ -278,7 +122,7 @@ protected: Flags _flags; UniformBufferView _schemaBuffer; Transform _transform; - SphericalHarmonics _ambientSphere; + gpu::SphericalHarmonics _ambientSphere; const Schema& getSchema() const { return _schemaBuffer.get(); } Schema& editSchema() { return _schemaBuffer.edit(); } diff --git a/libraries/model/src/model/Skybox.cpp b/libraries/model/src/model/Skybox.cpp index ab11966188..a3f4220362 100755 --- a/libraries/model/src/model/Skybox.cpp +++ b/libraries/model/src/model/Skybox.cpp @@ -39,22 +39,13 @@ void Skybox::setColor(const Color& color) { } void Skybox::setCubemap(const gpu::TexturePointer& cubemap) { - if (_isSHValid && (cubemap != _cubemap)) { - _isSHValid = false; - } _cubemap = cubemap; } -void Skybox::clearCubemap() { - _cubemap.reset(); -} - void Skybox::render(gpu::Batch& batch, const ViewFrustum& viewFrustum, const Skybox& skybox) { if (skybox.getCubemap() && skybox.getCubemap()->isDefined()) { - skybox.getIrradianceSH(); - static gpu::PipelinePointer thePipeline; static gpu::BufferPointer theBuffer; static gpu::Stream::FormatPointer theFormat; @@ -120,222 +111,3 @@ void Skybox::render(gpu::Batch& batch, const ViewFrustum& viewFrustum, const Sky } } - -glm::vec3 sRGBToLinear(glm::vec3& color) { - const float GAMMA_CORRECTION = 2.2f; - return glm::pow(color, glm::vec3(GAMMA_CORRECTION)); -} - -glm::vec3 linearTosRGB(glm::vec3& color) { - const float GAMMA_CORRECTION_INV = 1.0f / 2.2f; - return glm::pow(color, glm::vec3(GAMMA_CORRECTION_INV)); -} - -// Originial code for the Spherical Harmonics taken from "Sun and Black Cat- Igor Dykhta (igor dykhta email) © 2007-2014 " -void sphericalHarmonicsAdd(float * result, int order, const float * inputA, const float * inputB) { - const int numCoeff = order * order; - for(int i=0; i < numCoeff; i++) { - result[i] = inputA[i] + inputB[i]; - } -} - -void sphericalHarmonicsScale(float * result, int order, const float * input, float scale) { - const int numCoeff = order * order; - for(int i=0; i < numCoeff; i++) { - result[i] = input[i] * scale; - } -} - -void sphericalHarmonicsEvaluateDirection(float * result, int order, const glm::vec3 & dir) { - // calculate coefficients for first 3 bands of spherical harmonics - double P_0_0 = 0.282094791773878140; - double P_1_0 = 0.488602511902919920 * dir.z; - double P_1_1 = -0.488602511902919920; - double P_2_0 = 0.946174695757560080 * dir.z * dir.z - 0.315391565252520050; - double P_2_1 = -1.092548430592079200 * dir.z; - double P_2_2 = 0.546274215296039590; - result[0] = P_0_0; - result[1] = P_1_1 * dir.y; - result[2] = P_1_0; - result[3] = P_1_1 * dir.x; - result[4] = P_2_2 * (dir.x * dir.y + dir.y * dir.x); - result[5] = P_2_1 * dir.y; - result[6] = P_2_0; - result[7] = P_2_1 * dir.x; - result[8] = P_2_2 * (dir.x * dir.x - dir.y * dir.y); -} - -void sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector & output, const uint order) { - const uint sqOrder = order*order; - - // allocate memory for calculations - output.resize(sqOrder); - std::vector resultR(sqOrder); - std::vector resultG(sqOrder); - std::vector resultB(sqOrder); - - int width, height; - - // initialize values - float fWt = 0.0f; - for(uint i=0; i < sqOrder; i++) { - output[i] = glm::vec3(0.0f); - resultR[i] = 0.0f; - resultG[i] = 0; - resultB[i] = 0; - } - std::vector shBuff(sqOrder); - std::vector shBuffB(sqOrder); - // get width and height - width = height = cubeTexture.getWidth(); - if(width != height) { - return; - } - - const float UCHAR_TO_FLOAT = 1.0f / float(std::numeric_limits::max()); - - // for each face of cube texture - for(int face=0; face < gpu::Texture::NUM_CUBE_FACES; face++) { - - auto numComponents = cubeTexture.accessStoredMipFace(0,face)->_format.getDimensionCount(); - auto data = cubeTexture.accessStoredMipFace(0,face)->_sysmem.readData(); - if (data == nullptr) { - continue; - } - - // step between two texels for range [0, 1] - float invWidth = 1.0f / float(width); - // initial negative bound for range [-1, 1] - float negativeBound = -1.0f + invWidth; - // step between two texels for range [-1, 1] - float invWidthBy2 = 2.0f / float(width); - - for(int y=0; y < width; y++) { - // texture coordinate V in range [-1 to 1] - const float fV = negativeBound + float(y) * invWidthBy2; - - for(int x=0; x < width; x++) { - // texture coordinate U in range [-1 to 1] - const float fU = negativeBound + float(x) * invWidthBy2; - - // determine direction from center of cube texture to current texel - glm::vec3 dir; - switch(face) { - case gpu::Texture::CUBE_FACE_RIGHT_POS_X: { - dir.x = 1.0f; - dir.y = 1.0f - (invWidthBy2 * float(y) + invWidth); - dir.z = 1.0f - (invWidthBy2 * float(x) + invWidth); - dir = -dir; - break; - } - case gpu::Texture::CUBE_FACE_LEFT_NEG_X: { - dir.x = -1.0f; - dir.y = 1.0f - (invWidthBy2 * float(y) + invWidth); - dir.z = -1.0f + (invWidthBy2 * float(x) + invWidth); - dir = -dir; - break; - } - case gpu::Texture::CUBE_FACE_TOP_POS_Y: { - dir.x = - 1.0f + (invWidthBy2 * float(x) + invWidth); - dir.y = 1.0f; - dir.z = - 1.0f + (invWidthBy2 * float(y) + invWidth); - dir = -dir; - break; - } - case gpu::Texture::CUBE_FACE_BOTTOM_NEG_Y: { - dir.x = - 1.0f + (invWidthBy2 * float(x) + invWidth); - dir.y = - 1.0f; - dir.z = 1.0f - (invWidthBy2 * float(y) + invWidth); - dir = -dir; - break; - } - case gpu::Texture::CUBE_FACE_BACK_POS_Z: { - dir.x = - 1.0f + (invWidthBy2 * float(x) + invWidth); - dir.y = 1.0f - (invWidthBy2 * float(y) + invWidth); - dir.z = 1.0f; - break; - } - case gpu::Texture::CUBE_FACE_FRONT_NEG_Z: { - dir.x = 1.0f - (invWidthBy2 * float(x) + invWidth); - dir.y = 1.0f - (invWidthBy2 * float(y) + invWidth); - dir.z = - 1.0f; - break; - } - default: - return; - } - - // normalize direction - dir = glm::normalize(dir); - - // scale factor depending on distance from center of the face - const float fDiffSolid = 4.0f / ((1.0f + fU*fU + fV*fV) * - sqrtf(1.0f + fU*fU + fV*fV)); - fWt += fDiffSolid; - - // calculate coefficients of spherical harmonics for current direction - sphericalHarmonicsEvaluateDirection(shBuff.data(), order, dir); - - // index of texel in texture - uint pixOffsetIndex = (x + y * width) * numComponents; - - // get color from texture and map to range [0, 1] - glm::vec3 clr(float(data[pixOffsetIndex]) * UCHAR_TO_FLOAT, - float(data[pixOffsetIndex+1]) * UCHAR_TO_FLOAT, - float(data[pixOffsetIndex+2]) * UCHAR_TO_FLOAT); - - // Gamma correct - clr = sRGBToLinear(clr); - - // scale color and add to previously accumulated coefficients - sphericalHarmonicsScale(shBuffB.data(), order, - shBuff.data(), clr.r * fDiffSolid); - sphericalHarmonicsAdd(resultR.data(), order, - resultR.data(), shBuffB.data()); - sphericalHarmonicsScale(shBuffB.data(), order, - shBuff.data(), clr.g * fDiffSolid); - sphericalHarmonicsAdd(resultG.data(), order, - resultG.data(), shBuffB.data()); - sphericalHarmonicsScale(shBuffB.data(), order, - shBuff.data(), clr.b * fDiffSolid); - sphericalHarmonicsAdd(resultB.data(), order, - resultB.data(), shBuffB.data()); - } - } - } - - // final scale for coefficients - const float fNormProj = (4.0f * glm::pi()) / fWt; - sphericalHarmonicsScale(resultR.data(), order, resultR.data(), fNormProj); - sphericalHarmonicsScale(resultG.data(), order, resultG.data(), fNormProj); - sphericalHarmonicsScale(resultB.data(), order, resultB.data(), fNormProj); - - // save result - for(uint i=0; i < sqOrder; i++) { - // gamma Correct - // output[i] = linearTosRGB(glm::vec3(resultR[i], resultG[i], resultB[i])); - output[i] = glm::vec3(resultR[i], resultG[i], resultB[i]); - } -} - -const SphericalHarmonics& Skybox::getIrradianceSH() const { - if (!_isSHValid) { - if (_cubemap && _cubemap->isDefined()) { - std::vector< glm::vec3 > coefs; - sphericalHarmonicsFromTexture(*_cubemap, coefs, 3); - - _irradianceSH.L00 = coefs[0]; - _irradianceSH.L1m1 = coefs[1]; - _irradianceSH.L10 = coefs[2]; - _irradianceSH.L11 = coefs[3]; - _irradianceSH.L2m2 = coefs[4]; - _irradianceSH.L2m1 = coefs[5]; - _irradianceSH.L20 = coefs[6]; - _irradianceSH.L21 = coefs[7]; - _irradianceSH.L22 = coefs[8]; - - _isSHValid = true; - } - } - return _irradianceSH; -} diff --git a/libraries/model/src/model/Skybox.h b/libraries/model/src/model/Skybox.h index 9c413405f1..7a4550d75a 100755 --- a/libraries/model/src/model/Skybox.h +++ b/libraries/model/src/model/Skybox.h @@ -34,17 +34,11 @@ public: void setCubemap(const gpu::TexturePointer& cubemap); const gpu::TexturePointer& getCubemap() const { return _cubemap; } - void clearCubemap(); - - const SphericalHarmonics& getIrradianceSH() const; static void render(gpu::Batch& batch, const ViewFrustum& frustum, const Skybox& skybox); protected: gpu::TexturePointer _cubemap; - - mutable SphericalHarmonics _irradianceSH; - mutable bool _isSHValid = false; Color _color{1.0f, 1.0f, 1.0f}; }; diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index b12fcc03a6..463dbe809f 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -89,7 +89,7 @@ void DeferredLightingEffect::init(AbstractViewStateInterface* viewState) { lp->setColor(glm::vec3(1.0f)); lp->setIntensity(1.0f); lp->setType(model::Light::SUN); - lp->setAmbientSpherePreset(model::SphericalHarmonics::Preset(_ambientLightMode % model::SphericalHarmonics::NUM_PRESET)); + lp->setAmbientSpherePreset(gpu::SphericalHarmonics::Preset(_ambientLightMode % gpu::SphericalHarmonics::NUM_PRESET)); } void DeferredLightingEffect::bindSimpleProgram() { @@ -335,13 +335,11 @@ void DeferredLightingEffect::render() { auto globalLight = _allocatedLights[_globalLights.front()]; if (locations->ambientSphere >= 0) { - model::SphericalHarmonics sh; - if (useSkyboxCubemap) { - sh = _skybox->getIrradianceSH(); - } else { - sh = globalLight->getAmbientSphere(); + gpu::SphericalHarmonics sh = globalLight->getAmbientSphere(); + if (useSkyboxCubemap && _skybox->getCubemap()->getIrradiance()) { + sh = (*_skybox->getCubemap()->getIrradiance()); } - for (int i =0; i setUniformValue(locations->ambientSphere + i, *(((QVector4D*) &sh) + i)); } } @@ -634,10 +632,10 @@ void DeferredLightingEffect::loadLightProgram(const char* fragSource, bool limit } void DeferredLightingEffect::setAmbientLightMode(int preset) { - if ((preset >= 0) && (preset < model::SphericalHarmonics::NUM_PRESET)) { + if ((preset >= 0) && (preset < gpu::SphericalHarmonics::NUM_PRESET)) { _ambientLightMode = preset; auto light = _allocatedLights.front(); - light->setAmbientSpherePreset(model::SphericalHarmonics::Preset(preset % model::SphericalHarmonics::NUM_PRESET)); + light->setAmbientSpherePreset(gpu::SphericalHarmonics::Preset(preset % gpu::SphericalHarmonics::NUM_PRESET)); } else { // force to preset 0 setAmbientLightMode(0); diff --git a/libraries/render-utils/src/TextureCache.cpp b/libraries/render-utils/src/TextureCache.cpp index 3b27ca7c0d..dcd4481227 100644 --- a/libraries/render-utils/src/TextureCache.cpp +++ b/libraries/render-utils/src/TextureCache.cpp @@ -354,7 +354,7 @@ NetworkTexture::NetworkTexture(const QUrl& url, TextureType type, const QByteArr class ImageReader : public QRunnable { public: - ImageReader(const QWeakPointer& texture, QNetworkReply* reply, const QUrl& url = QUrl(), + ImageReader(const QWeakPointer& texture, TextureType type, QNetworkReply* reply, const QUrl& url = QUrl(), const QByteArray& content = QByteArray()); virtual void run(); @@ -362,14 +362,25 @@ public: private: QWeakPointer _texture; + TextureType _type; QNetworkReply* _reply; QUrl _url; QByteArray _content; }; -ImageReader::ImageReader(const QWeakPointer& texture, QNetworkReply* reply, +void NetworkTexture::downloadFinished(QNetworkReply* reply) { + // send the reader off to the thread pool + QThreadPool::globalInstance()->start(new ImageReader(_self, _type, reply)); +} + +void NetworkTexture::loadContent(const QByteArray& content) { + QThreadPool::globalInstance()->start(new ImageReader(_self, _type, NULL, _url, content)); +} + +ImageReader::ImageReader(const QWeakPointer& texture, TextureType type, QNetworkReply* reply, const QUrl& url, const QByteArray& content) : _texture(texture), + _type(type), _reply(reply), _url(url), _content(content) { @@ -387,6 +398,41 @@ void listSupportedImageFormats() { }); } + +class CubeLayout { +public: + int _widthRatio = 1; + int _heightRatio = 1; + + class Face { + public: + int _x = 0; + int _y = 0; + bool _horizontalMirror = false; + bool _verticalMirror = false; + + Face() {} + Face(int x, int y, bool horizontalMirror, bool verticalMirror) : _x(x), _y(y), _horizontalMirror(horizontalMirror), _verticalMirror(verticalMirror) {} + }; + + Face _faceXPos; + Face _faceXNeg; + Face _faceYPos; + Face _faceYNeg; + Face _faceZPos; + Face _faceZNeg; + + CubeLayout(int wr, int hr, Face fXP, Face fXN, Face fYP, Face fYN, Face fZP, Face fZN) : + _widthRatio(wr), + _heightRatio(hr), + _faceXPos(fXP), + _faceXNeg(fXN), + _faceYPos(fYP), + _faceYNeg(fYN), + _faceZPos(fZP), + _faceZNeg(fZN) {} +}; + void ImageReader::run() { QSharedPointer texture = _texture.toStrongRef(); if (texture.isNull()) { @@ -442,12 +488,18 @@ void ImageReader::run() { } } + int opaquePixels = 0; + int translucentPixels = 0; + bool isTransparent = false; + int redTotal = 0, greenTotal = 0, blueTotal = 0, alphaTotal = 0; const int EIGHT_BIT_MAXIMUM = 255; + QColor averageColor(EIGHT_BIT_MAXIMUM, EIGHT_BIT_MAXIMUM, EIGHT_BIT_MAXIMUM); + if (!image.hasAlphaChannel()) { if (image.format() != QImage::Format_RGB888) { image = image.convertToFormat(QImage::Format_RGB888); } - int redTotal = 0, greenTotal = 0, blueTotal = 0; + // int redTotal = 0, greenTotal = 0, blueTotal = 0; for (int y = 0; y < image.height(); y++) { for (int x = 0; x < image.width(); x++) { QRgb rgb = image.pixel(x, y); @@ -456,69 +508,46 @@ void ImageReader::run() { blueTotal += qBlue(rgb); } } - QColor averageColor(EIGHT_BIT_MAXIMUM, EIGHT_BIT_MAXIMUM, EIGHT_BIT_MAXIMUM); if (imageArea > 0) { averageColor.setRgb(redTotal / imageArea, greenTotal / imageArea, blueTotal / imageArea); } - QMetaObject::invokeMethod(texture.data(), "setImage", Q_ARG(const QImage&, image), Q_ARG(bool, false), - Q_ARG(const QColor&, averageColor), Q_ARG(int, originalWidth), Q_ARG(int, originalHeight)); - return; - } - if (image.format() != QImage::Format_ARGB32) { - image = image.convertToFormat(QImage::Format_ARGB32); - } + } else { + if (image.format() != QImage::Format_ARGB32) { + image = image.convertToFormat(QImage::Format_ARGB32); + } - // check for translucency/false transparency - int opaquePixels = 0; - int translucentPixels = 0; - int redTotal = 0, greenTotal = 0, blueTotal = 0, alphaTotal = 0; - for (int y = 0; y < image.height(); y++) { - for (int x = 0; x < image.width(); x++) { - QRgb rgb = image.pixel(x, y); - redTotal += qRed(rgb); - greenTotal += qGreen(rgb); - blueTotal += qBlue(rgb); - int alpha = qAlpha(rgb); - alphaTotal += alpha; - if (alpha == EIGHT_BIT_MAXIMUM) { - opaquePixels++; - } else if (alpha != 0) { - translucentPixels++; + // check for translucency/false transparency + // int opaquePixels = 0; + // int translucentPixels = 0; + // int redTotal = 0, greenTotal = 0, blueTotal = 0, alphaTotal = 0; + for (int y = 0; y < image.height(); y++) { + for (int x = 0; x < image.width(); x++) { + QRgb rgb = image.pixel(x, y); + redTotal += qRed(rgb); + greenTotal += qGreen(rgb); + blueTotal += qBlue(rgb); + int alpha = qAlpha(rgb); + alphaTotal += alpha; + if (alpha == EIGHT_BIT_MAXIMUM) { + opaquePixels++; + } else if (alpha != 0) { + translucentPixels++; + } } } + if (opaquePixels == imageArea) { + qCDebug(renderutils) << "Image with alpha channel is completely opaque:" << _url; + image = image.convertToFormat(QImage::Format_RGB888); + } + + averageColor = QColor(redTotal / imageArea, + greenTotal / imageArea, blueTotal / imageArea, alphaTotal / imageArea); + + isTransparent = (translucentPixels >= imageArea / 2); } - if (opaquePixels == imageArea) { - qCDebug(renderutils) << "Image with alpha channel is completely opaque:" << _url; - image = image.convertToFormat(QImage::Format_RGB888); - } - QMetaObject::invokeMethod(texture.data(), "setImage", Q_ARG(const QImage&, image), - Q_ARG(bool, translucentPixels >= imageArea / 2), Q_ARG(const QColor&, QColor(redTotal / imageArea, - greenTotal / imageArea, blueTotal / imageArea, alphaTotal / imageArea)), - Q_ARG(int, originalWidth), Q_ARG(int, originalHeight)); -} -void NetworkTexture::downloadFinished(QNetworkReply* reply) { - // send the reader off to the thread pool - QThreadPool::globalInstance()->start(new ImageReader(_self, reply)); -} - -void NetworkTexture::loadContent(const QByteArray& content) { - QThreadPool::globalInstance()->start(new ImageReader(_self, NULL, _url, content)); -} - -void NetworkTexture::setImage(const QImage& image, bool translucent, const QColor& averageColor, int originalWidth, - int originalHeight) { - _translucent = translucent; - _averageColor = averageColor; - _originalWidth = originalWidth; - _originalHeight = originalHeight; - _width = image.width(); - _height = image.height(); - - finishedLoading(true); - imageLoaded(image); - - if ((_width > 0) && (_height > 0)) { + gpu::Texture* theTexture = nullptr; + if ((image.width() > 0) && (image.height() > 0)) { // bool isLinearRGB = true; //(_type == NORMAL_TEXTURE) || (_type == EMISSIVE_TEXTURE); bool isLinearRGB = !(_type == CUBE_TEXTURE); //(_type == NORMAL_TEXTURE) || (_type == EMISSIVE_TEXTURE); @@ -531,11 +560,8 @@ void NetworkTexture::setImage(const QImage& image, bool translucent, const QColo } if (_type == CUBE_TEXTURE) { - - std::vector faces; - if (_height == (6 * _width)) { - int faceWidth = _width; + const CubeLayout CUBEMAP_LAYOUTS[] = { // Here is the expected layout for the faces in an image with the 1/6 aspect ratio: // // WIDTH @@ -567,16 +593,14 @@ void NetworkTexture::setImage(const QImage& image, bool translucent, const QColo // V +------+ // // FaceWidth = width = height / 6 - - faces.push_back(image.copy(QRect(0, 0 * faceWidth, faceWidth, faceWidth)).mirrored(true, false)); - faces.push_back(image.copy(QRect(0, 1 * faceWidth, faceWidth, faceWidth)).mirrored(true, false)); - faces.push_back(image.copy(QRect(0, 2 * faceWidth, faceWidth, faceWidth)).mirrored(false, true)); - faces.push_back(image.copy(QRect(0, 3 * faceWidth, faceWidth, faceWidth)).mirrored(false, true)); - faces.push_back(image.copy(QRect(0, 4 * faceWidth, faceWidth, faceWidth)).mirrored(true, false)); - faces.push_back(image.copy(QRect(0, 5 * faceWidth, faceWidth, faceWidth)).mirrored(true, false)); - - } else if ((_height / 3) == (_width / 4)) { - int faceWidth = _height / 3; + { 1, 6, + {0, 0, true, false}, + {0, 1, true, false}, + {0, 2, false, true}, + {0, 3, false, true}, + {0, 4, true, false}, + {0, 5, true, false} + }, // Here is the expected layout for the faces in an image with the 3/4 aspect ratio: // @@ -596,22 +620,14 @@ void NetworkTexture::setImage(const QImage& image, bool translucent, const QColo // V +------+------+------+------+ // // FaceWidth = width / 4 = height / 3 - - // Right = +X - faces.push_back(image.copy(QRect(2 * faceWidth, faceWidth, faceWidth, faceWidth)).mirrored(true, false)); - // Left = -X - faces.push_back(image.copy(QRect(0 * faceWidth, faceWidth, faceWidth, faceWidth)).mirrored(true, false)); - // Top = +Y - faces.push_back(image.copy(QRect(1 * faceWidth, 0, faceWidth, faceWidth)).mirrored(false, true)); - // Bottom = -Y - faces.push_back(image.copy(QRect(1 * faceWidth, 2 * faceWidth, faceWidth, faceWidth)).mirrored(false, true)); - // Back = +Z - faces.push_back(image.copy(QRect(3 * faceWidth, faceWidth, faceWidth, faceWidth)).mirrored(true, false)); - // Front = -Z - faces.push_back(image.copy(QRect(1 * faceWidth, faceWidth, faceWidth, faceWidth)).mirrored(true, false)); - - } else if ((_height / 4) == (_width / 3)) { - int faceWidth = _height / 4; + { 4, 3, + {2, 1, true, false}, + {0, 1, true, false}, + {1, 0, false, true}, + {1, 2, false, true}, + {3, 1, true, false}, + {1, 1, true, false} + }, // Here is the expected layout for the faces in an image with the 4/3 aspect ratio: // @@ -635,36 +651,95 @@ void NetworkTexture::setImage(const QImage& image, bool translucent, const QColo // V +------+------+------+ // // FaceWidth = width / 3 = height / 4 + { 3, 4, + {2, 1, true, false}, + {0, 1, true, false}, + {1, 0, false, true}, + {1, 2, false, true}, + {1, 3, false, true}, + {1, 1, true, false} + } + }; + const int NUM_CUBEMAP_LAYOUTS = sizeof(CUBEMAP_LAYOUTS) / sizeof(CubeLayout); - // Right = +X - faces.push_back(image.copy(QRect(2 * faceWidth, faceWidth, faceWidth, faceWidth)).mirrored(true, false)); - // Left = -X - faces.push_back(image.copy(QRect(0 * faceWidth, faceWidth, faceWidth, faceWidth)).mirrored(true, false)); - // Top = +Y - faces.push_back(image.copy(QRect(1 * faceWidth, 0, faceWidth, faceWidth)).mirrored(false, true)); - // Bottom = -Y - faces.push_back(image.copy(QRect(1 * faceWidth, 2 * faceWidth, faceWidth, faceWidth)).mirrored(false, true)); - // Back = +Z - faces.push_back(image.copy(QRect(1 * faceWidth, 3 * faceWidth, faceWidth, faceWidth)).mirrored(false, true)); - // Front = -Z - faces.push_back(image.copy(QRect(1 * faceWidth, faceWidth, faceWidth, faceWidth)).mirrored(true, false)); - } - - if (faces.size() == gpu::Texture::NUM_FACES_PER_TYPE[gpu::Texture::TEX_CUBE]) { - _gpuTexture = gpu::TexturePointer(gpu::Texture::createCube(formatGPU, faces[0].width(), gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP))); - _gpuTexture->autoGenerateMips(-1); - int f = 0; - for (auto& face : faces) { - _gpuTexture->assignStoredMipFace(0, formatMip, face.byteCount(), face.constBits(), f); - f++; + // Find the layout of the cubemap in the 2D image + int foundLayout = -1; + for (int i = 0; i < NUM_CUBEMAP_LAYOUTS; i++) { + if ((image.height() * CUBEMAP_LAYOUTS[i]._widthRatio) == (image.width() * CUBEMAP_LAYOUTS[i]._heightRatio)) { + foundLayout = i; + break; } } + + std::vector faces; + // If found, go extract the faces as separate images + if (foundLayout >= 0) { + auto& layout = CUBEMAP_LAYOUTS[foundLayout]; + int faceWidth = image.width() / layout._widthRatio; + + faces.push_back(image.copy(QRect(layout._faceXPos._x * faceWidth, layout._faceXPos._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceXPos._horizontalMirror, layout._faceXPos._verticalMirror)); + faces.push_back(image.copy(QRect(layout._faceXNeg._x * faceWidth, layout._faceXNeg._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceXNeg._horizontalMirror, layout._faceXNeg._verticalMirror)); + faces.push_back(image.copy(QRect(layout._faceYPos._x * faceWidth, layout._faceYPos._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceYPos._horizontalMirror, layout._faceYPos._verticalMirror)); + faces.push_back(image.copy(QRect(layout._faceYNeg._x * faceWidth, layout._faceYNeg._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceYNeg._horizontalMirror, layout._faceYNeg._verticalMirror)); + faces.push_back(image.copy(QRect(layout._faceZPos._x * faceWidth, layout._faceZPos._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceZPos._horizontalMirror, layout._faceZPos._verticalMirror)); + faces.push_back(image.copy(QRect(layout._faceZNeg._x * faceWidth, layout._faceZNeg._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceZNeg._horizontalMirror, layout._faceZNeg._verticalMirror)); + } else { + qCDebug(renderutils) << "Failed to find a known cube map layout from this image:" << _url; + return; + } + + // If the 6 faces have been created go on and define the true Texture + if (faces.size() == gpu::Texture::NUM_FACES_PER_TYPE[gpu::Texture::TEX_CUBE]) { + theTexture = gpu::Texture::createCube(formatGPU, faces[0].width(), gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP)); + theTexture->autoGenerateMips(-1); + int f = 0; + for (auto& face : faces) { + theTexture->assignStoredMipFace(0, formatMip, face.byteCount(), face.constBits(), f); + f++; + } + + // GEnerate irradiance while we are at it + theTexture->generateIrradiance(); + } + } else { - _gpuTexture = gpu::TexturePointer(gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR))); - _gpuTexture->assignStoredMip(0, formatMip, image.byteCount(), image.constBits()); - _gpuTexture->autoGenerateMips(-1); + theTexture = (gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR))); + theTexture->assignStoredMip(0, formatMip, image.byteCount(), image.constBits()); + theTexture->autoGenerateMips(-1); } } + + QMetaObject::invokeMethod(texture.data(), "setImage", + Q_ARG(const QImage&, image), + Q_ARG(void*, theTexture), + Q_ARG(bool, isTransparent), + Q_ARG(const QColor&, averageColor), + Q_ARG(int, originalWidth), Q_ARG(int, originalHeight)); + + +} + +void NetworkTexture::setImage(const QImage& image, void* voidTexture, bool translucent, const QColor& averageColor, int originalWidth, + int originalHeight) { + _translucent = translucent; + _averageColor = averageColor; + _originalWidth = originalWidth; + _originalHeight = originalHeight; + + gpu::Texture* texture = static_cast(voidTexture); + // Passing ownership + _gpuTexture.reset(texture); + + if (_gpuTexture) { + _width = _gpuTexture->getWidth(); + _height = _gpuTexture->getHeight(); + } else { + _width = _height = 0; + } + + finishedLoading(true); + + imageLoaded(image); } void NetworkTexture::imageLoaded(const QImage& image) { diff --git a/libraries/render-utils/src/TextureCache.h b/libraries/render-utils/src/TextureCache.h index 848a0ce4d8..b1a6f904a9 100644 --- a/libraries/render-utils/src/TextureCache.h +++ b/libraries/render-utils/src/TextureCache.h @@ -17,6 +17,8 @@ #include #include +#include + #include #include #include @@ -148,6 +150,7 @@ private: }; /// A texture loaded from the network. + class NetworkTexture : public Resource, public Texture { Q_OBJECT @@ -172,7 +175,8 @@ protected: virtual void downloadFinished(QNetworkReply* reply); Q_INVOKABLE void loadContent(const QByteArray& content); - Q_INVOKABLE void setImage(const QImage& image, bool translucent, const QColor& averageColor, int originalWidth, + // FIXME: This void* should be a gpu::Texture* but i cannot get it to work for now, moving on... + Q_INVOKABLE void setImage(const QImage& image, void* texture, bool translucent, const QColor& averageColor, int originalWidth, int originalHeight); virtual void imageLoaded(const QImage& image);