diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3f5a1d4595..24020210a7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,7 +3,7 @@ _I would really love to write a nice proper CONTRIBUTING.md, so excuse me for wh We would really love for you to tackle one of our burning issues, which you can view by clicking this link: -https://github.com/buildaworldnet/IrrlichtBAW/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc +https://github.com/Devsh-Graphics-Programming/Nabla/issues (sorted by the most recently updated) diff --git a/ci b/ci index 043f8a0aa0..8c3e98c651 160000 --- a/ci +++ b/ci @@ -1 +1 @@ -Subproject commit 043f8a0aa074f134b3230d56f27ed7d0c645f6b3 +Subproject commit 8c3e98c651e55a9e0e9c6121d055d483b2f90618 diff --git a/examples_tests b/examples_tests index 332e8d72cf..cd3efce0ac 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 332e8d72cf776dd78e4cf707d87c25f0aeeb6342 +Subproject commit cd3efce0ac9b498bd8569a68fb9ddef0f6f19332 diff --git a/include/nbl/builtin/glsl/ies/functions.glsl b/include/nbl/builtin/glsl/ies/functions.glsl index 70f5165f98..54c266d114 100644 --- a/include/nbl/builtin/glsl/ies/functions.glsl +++ b/include/nbl/builtin/glsl/ies/functions.glsl @@ -6,25 +6,33 @@ #include -// TODO: implement proper mirroing -// MIRROR_180_BITS = 0b001, Last Angle is 180, so map V slightly differently -// MIRROR_90_BITS = 0b010, Last Angle is 90, so map both U and V slightly differently -// ISOTROPIC_BITS = 0b011, texture to sample is Nx1, pretend v=middle always -// FULL_THETA_BIT = 0b100, handle extended domain and rotate by 45 degrees for anisotropic - -vec2 nbl_glsl_IES_convert_dir_to_uv(vec3 dir) { - float sum = dot(vec3(1.0f), abs(dir)); +// TODO: when rewriting to HLSL this is not IES namespace or folder, this should be octahedral mapping sitting somewhere where the spherical/polar sits +// NOTE: I changed it to return NDC [-1,1]^2 instead of UV coords [0,1]^2 +vec2 nbl_glsl_TODOnamespace_octahedral_mapping(vec3 dir) +{ + float sum = dot(vec3(1.0f), abs(dir)); vec3 s = dir / sum; - if(s.z < 0.0f) { - s.xy = sign(s.xy) * (1.0f - abs(s.yx)); + if(s.z < 0.0f) + { + const uvec2 flipSignMask = floatBitsToUint(s.xy)&0x80000000u; + s.xy = uintBitsToFloat(floatBitsToUint(1.0f - abs(s.yx))^flipSignMask); } - return s.xy * 0.5f + 0.5f; + return s.xy; } -// vec2 nbl_glsl_IES_convert_dir_to_uv(vec3 dir) { -// return vec2((atan(dir.x, dir.y) + nbl_glsl_PI) / (2.0*nbl_glsl_PI), acos(dir.z) / nbl_glsl_PI); -// } +// TODO: implement proper mirroing +// MIRROR_180_BITS = 0b001, Last Angle is 180, so map V with MIRROR and corner sampling off +// MIRROR_90_BITS = 0b010, Last Angle is 90, so map both U and V with MIRROR and corner sampling off +// ISOTROPIC_BITS = 0b011, texture to sample is Nx1, pretend v=middle always , and make u REPEAT or CLAMP_TO_BORDER +// FULL_THETA_BIT = 0b100, handle truncated domain and rotate by 45 degrees for anisotropic +// (certain combos wont work like 90 degree 2 symmetry domain & half theta), it really needs to be an 8 case label thing explicitly enumerated +vec2 nbl_glsl_IES_convert_dir_to_uv(vec3 dir, vec2 halfMinusHalfPixel) +{ + // halfMinusHalfPixel = 0.5-0.5/texSize + // believe it or not, cornerSampled(NDC*0.5+0.5) = NDC*0.5*(1-1/texSize)+0.5 + return nbl_glsl_TODOnamespace_octahedral_mapping(dir)*halfMinusHalfPixel+0.5; +} #endif \ No newline at end of file diff --git a/include/nbl/builtin/glsl/material_compiler/common.glsl b/include/nbl/builtin/glsl/material_compiler/common.glsl index da67b12cbf..f6b7d97c46 100644 --- a/include/nbl/builtin/glsl/material_compiler/common.glsl +++ b/include/nbl/builtin/glsl/material_compiler/common.glsl @@ -601,7 +601,8 @@ vec3 nbl_glsl_MC_oriented_material_t_getEmissive(in nbl_glsl_MC_oriented_materia if ((floatBitsToInt(emitter.orientation[0])&1u) != 1u) { right *= -1; } - return emissive * nbl_glsl_vTextureGrad(emitter.emissionProfile, nbl_glsl_IES_convert_dir_to_uv(mat3(right, up, view)*dir), mat2(0.0)).r; + vec2 halfMinusHalfPixel = vec2(0.5)-vec2(0.5)/vec2(nbl_glsl_unpackSize(emitter.emissionProfile)); + return emissive * nbl_glsl_vTextureGrad(emitter.emissionProfile, nbl_glsl_IES_convert_dir_to_uv(mat3(right, up, view)*dir,halfMinusHalfPixel), mat2(0.0)).r; } #endif return emissive; diff --git a/src/nbl/asset/utils/CIESProfile.cpp b/src/nbl/asset/utils/CIESProfile.cpp index b507ab0d45..431cc14eba 100644 --- a/src/nbl/asset/utils/CIESProfile.cpp +++ b/src/nbl/asset/utils/CIESProfile.cpp @@ -88,8 +88,8 @@ inline core::vectorSIMDf CIESProfile::octahdronUVToDir(const float& u, const flo float abs_x = core::abs(pos.x), abs_y = core::abs(pos.y); pos.z = 1.0 - abs_x - abs_y; if (pos.z < 0.0) { - pos.x = core::sign(pos.x) * (1.0 - abs_y); - pos.y = core::sign(pos.y) * (1.0 - abs_x); + pos.x = (pos.x<0.f ? (-1.f):1.f) * (1.0 - abs_y); + pos.y = (pos.y<0.f ? (-1.f):1.f) * (1.0 - abs_x); } return core::normalize(pos); @@ -116,6 +116,13 @@ core::smart_refctd_ptr CIESProfile::createIESTexture(Execu if (height > CDC_MAX_TEXTURE_HEIGHT) height = CDC_MAX_TEXTURE_HEIGHT; + // TODO: If no symmetry (no folding in half and abuse of mirror sampler) make dimensions odd-sized so middle texel taps the south pole + + // TODO: This is hack because the mitsuba loader and its material compiler use Virtual Texturing, and there's some bug with IES not sampling sub 128x128 mip levels + // don't want to spend time to fix this since we'll be using descriptor indexing for the next iteration + width = core::max(width,128); + height = core::max(height,128); + asset::ICPUImage::SCreationParams imgInfo; imgInfo.type = asset::ICPUImage::ET_2D; imgInfo.extent.width = width; @@ -161,15 +168,19 @@ core::smart_refctd_ptr CIESProfile::createIESTexture(Execu const double maxValue = getMaxCandelaValue(); const double maxValueRecip = 1.0 / maxValue; - const double vertInv = 1.0 / height; - const double horiInv = 1.0 / width; + // There is one huge issue, the IES files love to give us values for degrees 0, 90, 180 an 360 + // So standard octahedral mapping won't work, because for above data points you need corner sampled images. + const float vertInv = 1.0 / (height-1); + const float horiInv = 1.0 / (width-1); const double flattenTarget = getAvgEmmision(fullDomainFlatten); const double domainLo = core::radians(vAngles.front()); const double domainHi = core::radians(vAngles.back()); auto fill = [&](uint32_t blockArrayOffset, core::vectorSIMDu32 position) -> void { - const auto dir = octahdronUVToDir(((float)position.x + 0.5) * vertInv, ((float)position.y + 0.5) * horiInv); + // We don't currently support generating IES images that exploit symmetries or reduced domains, all are full octahederal mappings of a sphere. + // If we did, we'd rely on MIRROR and CLAMP samplers to do some of the work for us while handling the discontinuity due to corner sampling. + const auto dir = octahdronUVToDir(position.x * vertInv, position.y * horiInv); const auto [theta, phi] = sphericalDirToRadians(dir); const auto intensity = sample(theta, phi); diff --git a/src/nbl/asset/utils/CIESProfile.h b/src/nbl/asset/utils/CIESProfile.h index ea3d539613..1da2c60b79 100644 --- a/src/nbl/asset/utils/CIESProfile.h +++ b/src/nbl/asset/utils/CIESProfile.h @@ -72,7 +72,12 @@ namespace nbl inline IES_STORAGE_FORMAT getAvgEmmision(const bool fullDomain=false) const { if (fullDomain) - return totalEmissionIntegral*0.25/core::radians(vAngles.back()-vAngles.front()); + { + const float cosLo = std::cos(core::radians(vAngles.front())); + const float cosHi = std::cos(core::radians(vAngles.back())); + const float dsinTheta = cosLo - cosHi; + return totalEmissionIntegral*(0.5/core::PI())/dsinTheta; + } return avgEmmision; } diff --git a/src/nbl/asset/utils/CIESProfileParser.cpp b/src/nbl/asset/utils/CIESProfileParser.cpp index e0593cd7f2..dd6b321414 100644 --- a/src/nbl/asset/utils/CIESProfileParser.cpp +++ b/src/nbl/asset/utils/CIESProfileParser.cpp @@ -106,12 +106,6 @@ bool CIESProfileParser::parse(CIESProfile& result) if (vSize < 2) return false; - { - const uint32_t maxDimMeasureSize = core::max(hSize, vSize); - result.optimalIESResolution = decltype(result.optimalIESResolution){ maxDimMeasureSize, maxDimMeasureSize }; - result.optimalIESResolution *= 2u; // safe bias for our bilinear interpolation to work nicely and increase resolution of a profile - } - auto& vAngles = result.vAngles; for (int i = 0; i < vSize; i++) { vAngles[i] = getDouble("vertical angle truncated"); @@ -180,15 +174,29 @@ bool CIESProfileParser::parse(CIESProfile& result) float totalEmissionIntegral = 0.0, nonZeroEmissionDomainSize = 0.0; constexpr auto FULL_SOLID_ANGLE = 4.0f * core::PI(); + // TODO: this code could have two separate inner for loops for `result.symmetry != CIESProfile::ISOTROPIC` cases const auto H_ANGLES_I_RANGE = result.symmetry != CIESProfile::ISOTROPIC ? result.hAngles.size() - 1 : 1; const auto V_ANGLES_I_RANGE = result.vAngles.size() - 1; - for (size_t i = 0; i < H_ANGLES_I_RANGE; i++) + float smallestRangeSolidAngle = FULL_SOLID_ANGLE; + for (size_t j = 0; j < V_ANGLES_I_RANGE; j++) { - const float dPhiRad = result.symmetry != CIESProfile::ISOTROPIC ? (hAngles[i + 1] - hAngles[i]) : core::PI() * 2.0f; - - for (size_t j = 0; j < V_ANGLES_I_RANGE; j++) + const float thetaRad = core::radians(result.vAngles[j]); + const float cosLo = std::cos(thetaRad); + const float cosHi = std::cos(core::radians(result.vAngles[j+1])); + const float dsinTheta = cosLo - cosHi; + + float stripIntegral = 0.f; + float nonZeroStripDomain = 0.f; + for (size_t i = 0; i < H_ANGLES_I_RANGE; i++) { + const float dPhiRad = result.symmetry != CIESProfile::ISOTROPIC ? core::radians(hAngles[i + 1] - hAngles[i]) : (core::PI() * 2.0f); + // TODO: in reality one should transform the 4 vertices (or 3) into octahedral map, work out the dUV/dPhi and dUV/dTheta vectors as-if for Anisotropic Filtering + // then choose the minor axis length, and use that as a pixel size (since looking for smallest thing, dont have to worry about handling discont) + const float solidAngle = dsinTheta * dPhiRad; + if (solidAngle(result.vAngles[j]); - const float cosLo = std::cos(core::radians(result.vAngles[j])); - const float cosHi = std::cos(core::radians(result.vAngles[j + 1])); - - const auto differentialSolidAngle = dPhiRad*(cosLo - cosHi); - const auto integralV = candelaAverage * differentialSolidAngle; - - if (integralV > 0.0) - { - totalEmissionIntegral += integralV; - nonZeroEmissionDomainSize += differentialSolidAngle; - } + stripIntegral += candelaAverage*dPhiRad; + if (candelaAverage>0.f) + nonZeroStripDomain += dPhiRad; } + totalEmissionIntegral += stripIntegral*dsinTheta; + nonZeroEmissionDomainSize += nonZeroStripDomain*dsinTheta; + } + + // assuming octahedral map + { + const uint32_t maxDimMeasureSize = core::sqrt(FULL_SOLID_ANGLE/smallestRangeSolidAngle); + result.optimalIESResolution = decltype(result.optimalIESResolution){ maxDimMeasureSize, maxDimMeasureSize }; + result.optimalIESResolution *= 2u; // safe bias for our bilinear interpolation to work nicely and increase resolution of a profile } - nonZeroEmissionDomainSize = std::clamp(nonZeroEmissionDomainSize, 0.0, FULL_SOLID_ANGLE); - if (nonZeroEmissionDomainSize <= 0) // protect us from division by 0 (just in case, we should never hit it) + assert(nonZeroEmissionDomainSize >= 0.f); + //assert(nonZeroEmissionDomainSize*fluxMultiplier =approx= 2.f*(cosBack-cosFront)*PI); + if (nonZeroEmissionDomainSize <= std::numeric_limits::min()) // protect us from division by small numbers (just in case, we should never hit it) return false; result.avgEmmision = totalEmissionIntegral / static_cast(nonZeroEmissionDomainSize);