From a12d91518088c8013367e2564994d4a6100e7225 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Wed, 21 Feb 2024 06:22:37 +0000 Subject: [PATCH] Loads an IES Photometric profile. ADDED: Ability to add IES profile as the cookie texture slot in both point lights and spot lights TODO: Have the IES Profile also drive the settings for the lights. Make it work with Cookie textures. IES profiles are to be another slot in the advanced light section. --- Engine/source/CMakeLists.txt | 2 +- .../source/gfx/bitmap/loaders/bitmapSTB.cpp | 108 +++- .../gfx/bitmap/loaders/ies/ies_loader.cpp | 579 ++++++++++++++++++ .../gfx/bitmap/loaders/ies/ies_loader.h | 116 ++++ .../advanced/advancedLightBinManager.cpp | 1 + .../lighting/advanced/pointLightP.hlsl | 44 +- .../shaders/lighting/advanced/spotLightP.hlsl | 19 +- 7 files changed, 832 insertions(+), 37 deletions(-) create mode 100644 Engine/source/gfx/bitmap/loaders/ies/ies_loader.cpp create mode 100644 Engine/source/gfx/bitmap/loaders/ies/ies_loader.h diff --git a/Engine/source/CMakeLists.txt b/Engine/source/CMakeLists.txt index dea14fe4b..78343a2e6 100644 --- a/Engine/source/CMakeLists.txt +++ b/Engine/source/CMakeLists.txt @@ -79,7 +79,7 @@ if(TORQUE_SFX_OPENAL AND NOT TORQUE_DEDICATED) endif() endif() # Handle GFX -torqueAddSourceDirectories("gfx" "gfx/Null" "gfx/test" "gfx/bitmap" "gfx/bitmap/loaders" +torqueAddSourceDirectories("gfx" "gfx/Null" "gfx/test" "gfx/bitmap" "gfx/bitmap/loaders" "gfx/bitmap/loaders/ies" "gfx/util" "gfx/video" "gfx/sim" ) # add the stb headers diff --git a/Engine/source/gfx/bitmap/loaders/bitmapSTB.cpp b/Engine/source/gfx/bitmap/loaders/bitmapSTB.cpp index fa65219c5..a5a97917a 100644 --- a/Engine/source/gfx/bitmap/loaders/bitmapSTB.cpp +++ b/Engine/source/gfx/bitmap/loaders/bitmapSTB.cpp @@ -28,6 +28,7 @@ #include "core/strings/stringFunctions.h" #include "gfx/bitmap/gBitmap.h" #include "gfx/bitmap/imageUtils.h" +#include "gfx/bitmap/loaders/ies/ies_loader.h" #ifdef __clang__ #define STBIWDEF static inline @@ -69,6 +70,7 @@ static struct _privateRegisterSTB reg.extensions.push_back("psd"); reg.extensions.push_back("hdr"); reg.extensions.push_back("tga"); + reg.extensions.push_back("ies"); reg.readFunc = sReadSTB; reg.readStreamFunc = sReadStreamSTB; @@ -93,6 +95,103 @@ bool sReadSTB(const Torque::Path& path, GBitmap* bitmap) U32 prevWaterMark = FrameAllocator::getWaterMark(); + // if this is an ies profile we need to create a texture for it. + if (ext.equal("ies")) + { + String textureName = path.getFullPath(); + textureName.replace(".ies", ".png"); + x = 256; + y = 1; + n = 4; + channels = 4; + GFXFormat format = GFXFormatR8G8B8A8; + + if (Torque::FS::IsFile(textureName.c_str())) + { + // if the txture already exist, load it. + unsigned char* data = stbi_load(textureName.c_str(), &x, &y, &n, channels); + + // actually allocate the bitmap space... + bitmap->allocateBitmap(x, y, + false, // don't extrude miplevels... + format); // use determined format... + + U8* pBase = (U8*)bitmap->getBits(); + + U32 rowBytes = bitmap->getByteSize(); + + dMemcpy(pBase, data, rowBytes); + + stbi_image_free(data); + + FrameAllocator::setWaterMark(prevWaterMark); + + return true; + } + else + { + FileStream* readIes = new FileStream; + + if (!readIes->open(path.getFullPath(), Torque::FS::File::Read)) + { + Con::printf("Failed to open IES profile:%s", path.getFullFileName().c_str()); + return false; + } + + if (readIes->getStatus() != Stream::Ok) + { + Con::printf("Failed to open IES profile:%s", path.getFullFileName().c_str()); + return false; + } + + U32 buffSize = readIes->getStreamSize(); + char* buffer = new char[buffSize]; + readIes->read(buffSize, buffer); + + + IESFileInfo info; + IESLoadHelper IESLoader; + + if (!IESLoader.load(buffer, buffSize, info)) + { + Con::printf("Failed to load IES profile:%s \n LoaderError: %s", path.getFullFileName().c_str(), info.error().c_str()); + return false; + } + + float* data = new float[x*y*channels]; + + if (!IESLoader.saveAs1D(info, data, x, channels)) + { + Con::printf("Failed to create 2d Texture for IES profile:%s", path.getFullFileName().c_str()); + return false; + } + + // use stb function to convert float data to uchar + unsigned char* dataChar = stbi__hdr_to_ldr(data, x, y, channels); + + bitmap->deleteImage(); + // actually allocate the bitmap space... + bitmap->allocateBitmap(x, y, + false, + format); + + U8* pBase = (U8*)bitmap->getBits(); + + U32 rowBytes = x * y * channels; + + dMemcpy(pBase, dataChar, rowBytes); + + stbi_image_free(dataChar); + + FrameAllocator::setWaterMark(prevWaterMark); + + sWriteSTB(textureName, bitmap, 10); + + return true; + } + + } + if (!stbi_info(path.getFullPath().c_str(), &x, &y, &channels)) { FrameAllocator::setWaterMark(prevWaterMark); @@ -142,21 +241,22 @@ bool sReadSTB(const Torque::Path& path, GBitmap* bitmap) if (ext.equal("hdr")) { // force load to 4 channel. - float* data = stbi_loadf(path.getFullPath().c_str(), &x, &y, &n, 4); + float* data = stbi_loadf(path.getFullPath().c_str(), &x, &y, &n, 0); - unsigned char* dataChar = stbi__hdr_to_ldr(data, x, y, 4); + unsigned char* dataChar = stbi__hdr_to_ldr(data, x, y, n); bitmap->deleteImage(); // actually allocate the bitmap space... bitmap->allocateBitmap(x, y, false, - GFXFormatR8G8B8A8); + GFXFormatR8G8B8); U8* pBase = (U8*)bitmap->getBits(); - U32 rowBytes = x * y * 4; + U32 rowBytes = x * y * n; dMemcpy(pBase, dataChar, rowBytes); + //stbi_image_free(data); stbi_image_free(dataChar); FrameAllocator::setWaterMark(prevWaterMark); diff --git a/Engine/source/gfx/bitmap/loaders/ies/ies_loader.cpp b/Engine/source/gfx/bitmap/loaders/ies/ies_loader.cpp new file mode 100644 index 000000000..48602fc9f --- /dev/null +++ b/Engine/source/gfx/bitmap/loaders/ies/ies_loader.cpp @@ -0,0 +1,579 @@ +// +---------------------------------------------------------------------- +// | Project : ray. +// | All rights reserved. +// +---------------------------------------------------------------------- +// | Copyright (c) 2013-2017. +// +---------------------------------------------------------------------- +// | * Redistribution and use of this software in source and binary forms, +// | with or without modification, are permitted provided that the following +// | conditions are met: +// | +// | * Redistributions of source code must retain the above +// | copyright notice, this list of conditions and the +// | following disclaimer. +// | +// | * Redistributions in binary form must reproduce the above +// | copyright notice, this list of conditions and the +// | following disclaimer in the documentation and/or other +// | materials provided with the distribution. +// | +// | * Neither the name of the ray team, nor the names of its +// | contributors may be used to endorse or promote products +// | derived from this software without specific prior +// | written permission of the ray team. +// | +// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +---------------------------------------------------------------------- +#include "ies_loader.h" +#include +#include +#include + +IESFileInfo::IESFileInfo() + : _cachedIntegral(std::numeric_limits::max()) + , _error("No data loaded") +{ +} + +bool +IESFileInfo::valid() const +{ + return _error.empty(); +} + +const std::string& +IESFileInfo::error() const +{ + return _error; +} + +IESLoadHelper::IESLoadHelper() +{ +} + +IESLoadHelper::~IESLoadHelper() +{ +} + +bool +IESLoadHelper::load(const char* data, std::size_t dataLength, IESFileInfo& info) +{ + assert(!info.valid()); + return this->load(std::string(data, dataLength), info); +} + +bool +IESLoadHelper::load(const std::string& data, IESFileInfo& info) +{ + assert(!info.valid()); + + std::string dataPos; + + std::string version; + this->getLineContent(data, dataPos, version, false, false); + + if (version.empty()) + { + info._error = "Unknown IES version"; + return false; + } + else if (version == "IESNA:LM-63-1995") + info._version = "IESNA:LM-63-1995"; + else if (version == "IESNA91") + info._version = "IESNA91"; + else if (version == "IESNA:LM-63-2002") + info._version = "IESNA:LM-63-2002"; + else + info._version = version; + + while (!dataPos.empty()) + { + std::string line; + this->getLineContent(dataPos, dataPos, line, false, false); + + if (line.compare(0, 9, "TILT=NONE", 9) == 0 || + line.compare(0, 10, "TILT= NONE", 10) == 0 || + line.compare(0, 10, "TILT =NONE", 10) == 0 || + line.compare(0, 11, "TILT = NONE", 11) == 0) + { + break; + } + else if (line.compare(0, 5, "TILT=", 5) == 0 || + line.compare(0, 5, "TILT =", 5) == 0) + { + info._error = "Not supported yet."; + return false; + } + } + + this->getFloat(dataPos, dataPos, info.totalLights); + if (info.totalLights < 0 || info.totalLights > std::numeric_limits::max()) + { + info._error = "Light Count is not valid"; + return false; + } + + this->getFloat(dataPos, dataPos, info.totalLumens); + if (info.totalLumens < 0) + { + info._error = "TotalLumens is not positive number"; + return false; + } + + this->getFloat(dataPos, dataPos, info.candalaMult); + if (info.candalaMult < 0) + { + info._error = "CandalaMult is not positive number"; + return false; + } + + this->getInt(dataPos, dataPos, info.anglesNumV); + if (info.anglesNumV < 0 || info.anglesNumV > std::numeric_limits::max()) + { + info._error = "VAnglesNum is not valid"; + return false; + } + + this->getInt(dataPos, dataPos, info.anglesNumH); + if (info.anglesNumH < 0 || info.anglesNumH > std::numeric_limits::max()) + { + info._error = "HAnglesNum is not valid"; + return false; + } + + this->getInt(dataPos, dataPos, info.typeOfPhotometric); + this->getInt(dataPos, dataPos, info.typeOfUnit); + + this->getFloat(dataPos, dataPos, info.width); + this->getFloat(dataPos, dataPos, info.length); + this->getFloat(dataPos, dataPos, info.height); + + this->getFloat(dataPos, dataPos, info.ballastFactor); + this->getFloat(dataPos, dataPos, info.futureUse); + this->getFloat(dataPos, dataPos, info.inputWatts); + + float minSoFarV = std::numeric_limits::lowest(); + float minSoFarH = std::numeric_limits::lowest(); + + info._anglesV.reserve(info.anglesNumV); + info._anglesH.reserve(info.anglesNumH); + + for (std::int32_t y = 0; y < info.anglesNumV; ++y) + { + float value; + this->getFloat(dataPos, dataPos, value, true, true); + + if (value < minSoFarV) + { + info._error = "V Values is not valid"; + return false; + } + + minSoFarV = value; + info._anglesV.push_back(value); + } + + for (std::int32_t x = 0; x < info.anglesNumH; ++x) + { + float value; + this->getFloat(dataPos, dataPos, value, true, true); + + if (value < minSoFarH) + { + info._error = "H Values is not valid"; + return false; + } + + minSoFarH = value; + info._anglesH.push_back(value); + } + + info._candalaValues.reserve(info.anglesNumH * info.anglesNumV); + + for (std::int32_t y = 0; y < info.anglesNumH; ++y) + { + for (std::int32_t x = 0; x < info.anglesNumV; ++x) + { + float value; + this->getFloat(dataPos, dataPos, value, true, true); + info._candalaValues.push_back(value * info.candalaMult); + } + } + + skipSpaceAndLineEnd(dataPos, dataPos); + + if (!dataPos.empty()) + { + std::string line; + this->getLineContent(dataPos, dataPos, line, true, false); + + if (line == "END") + skipSpaceAndLineEnd(dataPos, dataPos); + + if (!dataPos.empty()) + { + info._error = "Unexpected content after END."; + return false; + } + } + + info._error.clear(); + + return true; +} + +bool +IESLoadHelper::saveAs1D(const IESFileInfo& info, float* data, std::uint32_t width, std::uint8_t channel) noexcept +{ + assert(data); + assert(width > 0); + assert(channel == 1 || channel == 3 || channel == 4); + assert(info.valid()); + + float invW = 1.0f / width; + float invMaxValue = this->computeInvMax(info._candalaValues); + + for (std::uint32_t x = 0; x < width; ++x) + { + float fraction = x * invW; + float value = invMaxValue * interpolate1D(info, fraction * 180.0f); + + switch (channel) + { + case 1: + *data++ = value; + break; + case 3: + *data++ = value; + *data++ = value; + *data++ = value; + break; + case 4: + *data++ = value; + *data++ = value; + *data++ = value; + *data++ = 1.0f; + break; + default: + return false; + } + } + + return true; +} + +bool +IESLoadHelper::saveAs2D(const IESFileInfo& info, float* data, std::uint32_t width, std::uint32_t height, std::uint8_t channel) noexcept +{ + assert(data); + assert(width > 0 && height > 0); + assert(channel == 1 || channel == 3 || channel == 4); + assert(info.valid()); + + float invW = 1.0f / width; + float invH = 1.0f / height; + float invMaxValue = this->computeInvMax(info._candalaValues); + + for (std::uint32_t y = 0; y < height; ++y) + { + for (std::uint32_t x = 0; x < width; ++x) + { + float fractionV = x * invW * 180.0f; + float fractionH = y * invH * 180.0f; + float value = invMaxValue * interpolate2D(info, fractionV, fractionH); + + switch (channel) + { + case 1: + *data++ = value; + break; + case 3: + *data++ = value; + *data++ = value; + *data++ = value; + break; + case 4: + *data++ = value; + *data++ = value; + *data++ = value; + *data++ = 1.0; + break; + default: + return false; + } + } + } + + return true; +} + +bool +IESLoadHelper::saveAsPreview(const IESFileInfo& info, std::uint8_t* data, std::uint32_t width, std::uint32_t height, std::uint8_t channel) noexcept +{ + assert(data); + assert(width > 0 && height > 0); + assert(channel == 1 || channel == 3 || channel == 4); + assert(info.valid()); + + std::vector ies(256); + if (!this->saveAs1D(info, ies.data(), ies.size(), 1)) + return false; + + float maxValue = this->computeInvMax(info._candalaValues); + + auto TonemapHable = [](float x) + { + const float A = 0.22f; + const float B = 0.30f; + const float C = 0.10f; + const float D = 0.20f; + const float E = 0.01f; + const float F = 0.30f; + return ((x*(A*x + C*B) + D*E) / (x*(A*x + B) + D*F)) - E / F; + }; + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + float u = ((float)x / width) * 2.0f - 1.0f; + float v = 1.0f - ((float)y / height) * 2.0f - 1.0f; + + u *= 2.2f; + v *= 2.4f; + + // float3(0.0f, 0.0f, -0.5f) - ray::float3(u, v, 0.0f) + float lx = +0.0f - u; + float ly = +0.0f - v; + float lz = -0.5f - 0.0f; + + // normalize + float length = std::sqrt(lx * lx + ly * ly + lz * lz); + lx /= length; + ly /= length; + lz /= length; + + float angle = 1.0 - std::acos(lx * 0.0 + ly * -1.0 + lz * 0.0f) / 3.141592654; + + float intensity = ies[angle * 255] * maxValue / length; + + std::uint8_t value = std::min(std::max((int)std::floor(TonemapHable(intensity) / TonemapHable(maxValue) * 255.0f), 0), 255); + + switch (channel) + { + case 1: + *data++ = value; + break; + case 3: + *data++ = value; + *data++ = value; + *data++ = value; + break; + case 4: + *data++ = value; + *data++ = value; + *data++ = value; + *data++ = 1.0; + break; + default: + return false; + } + } + } + + return true; +} + +float +IESLoadHelper::computeInvMax(const std::vector& candalaValues) const +{ + assert(candalaValues.size()); + + float candala = *std::max_element(candalaValues.begin(), candalaValues.end()); + return 1.0f / candala; +} + +float +IESLoadHelper::computeFilterPos(float value, const std::vector& angles) const +{ + assert(angles.size()); + + std::size_t start = 0; + std::size_t end = angles.size() - 1; + + if (value < angles[start]) return 0.0f; + if (value > angles[end]) return (float)end; + + while (start < end) + { + std::size_t index = (start + end + 1) / 2; + + float angle = angles[index]; + if (value >= angle) + { + assert(start != index); + start = index; + } + else + { + assert(end != index - 1); + end = index - 1; + } + } + + float leftValue = angles[start]; + float fraction = 0.0f; + + if (start + 1 < (std::uint32_t)angles.size()) + { + float rightValue = angles[start + 1]; + float deltaValue = rightValue - leftValue; + + if (deltaValue > 0.0001f) + { + fraction = (value - leftValue) / deltaValue; + } + } + + return start + fraction; +} + +float +IESLoadHelper::interpolate1D(const IESFileInfo& info, float angle) const +{ + float angleV = this->computeFilterPos(angle, info._anglesV); + float anglesNum = (float)info._anglesH.size(); + float angleTotal = 0.0f; + + for (float x = 0; x < anglesNum; x++) + angleTotal += this->interpolateBilinear(info, x, angleV); + + return angleTotal / anglesNum; +} + +float +IESLoadHelper::interpolate2D(const IESFileInfo& info, float angleV, float angleH) const +{ + float u = this->computeFilterPos(angleH, info._anglesH); + float v = this->computeFilterPos(angleV, info._anglesV); + return this->interpolateBilinear(info, u, v); +} + +float +IESLoadHelper::interpolatePoint(const IESFileInfo& info, std::uint32_t x, std::uint32_t y) const +{ + assert(x >= 0); + assert(y >= 0); + + std::size_t anglesNumH = info._anglesH.size(); + std::size_t anglesNumV = info._anglesV.size(); + + x %= anglesNumH; + y %= anglesNumV; + + assert(x < anglesNumH); + assert(y < anglesNumV); + + return info._candalaValues[y + anglesNumV * x]; +} + +float +IESLoadHelper::interpolateBilinear(const IESFileInfo& info, float x, float y) const +{ + int ix = (int)std::floor(x); + int iy = (int)std::floor(y); + + float fracX = x - ix; + float fracY = y - iy; + + float p00 = this->interpolatePoint(info, ix + 0, iy + 0); + float p10 = this->interpolatePoint(info, ix + 1, iy + 0); + float p01 = this->interpolatePoint(info, ix + 0, iy + 1); + float p11 = this->interpolatePoint(info, ix + 1, iy + 1); + + auto lerp = [](float t1, float t2, float t3) -> float { return t1 + (t2 - t1) * t3; }; + + float p0 = lerp(p00, p01, fracY); + float p1 = lerp(p10, p11, fracY); + + return lerp(p0, p1, fracX); +} + +void +IESLoadHelper::skipSpaceAndLineEnd(const std::string& data, std::string& out, bool stopOnComma) +{ + std::size_t dataBegin = 0; + std::size_t dataEnd = data.size(); + + while (dataBegin < dataEnd) + { + if (data[dataBegin] != '\r' && data[dataBegin] != '\n' && data[dataBegin] > ' ') + break; + dataBegin++; + } + + if (stopOnComma) + { + while (dataBegin < dataEnd) + { + if (data[dataBegin] != ',') + break; + dataBegin++; + } + } + + out = data.substr(dataBegin, data.size() - dataBegin); +} + +void +IESLoadHelper::getLineContent(const std::string& data, std::string& next, std::string& line, bool stopOnWhiteSpace, bool stopOnComma) +{ + skipSpaceAndLineEnd(data, next); + + auto it = data.begin(); + auto end = data.end(); + + for (; it < end; ++it) + { + if ((*it == '\r') || + (*it == '\n') || + (*it <= ' ' && stopOnWhiteSpace) || + (*it == ',' && stopOnComma)) + { + break; + } + } + + line.assign(data, 0, it - data.begin()); + next.assign(data, it - data.begin(), end - it); + + skipSpaceAndLineEnd(next, next, stopOnComma); +} + +void +IESLoadHelper::getFloat(const std::string& data, std::string& next, float& ret, bool stopOnWhiteSpace, bool stopOnComma) +{ + std::string line; + getLineContent(data, next, line, stopOnWhiteSpace, stopOnComma); + assert(!line.empty()); + ret = (float)std::atof(line.c_str()); +} + +void +IESLoadHelper::getInt(const std::string& data, std::string& next, std::int32_t& ret, bool stopOnWhiteSpace, bool stopOnComma) +{ + std::string line; + getLineContent(data, next, line, stopOnWhiteSpace, stopOnComma); + assert(!line.empty()); + ret = std::atoi(line.c_str()); +} \ No newline at end of file diff --git a/Engine/source/gfx/bitmap/loaders/ies/ies_loader.h b/Engine/source/gfx/bitmap/loaders/ies/ies_loader.h new file mode 100644 index 000000000..532b47d32 --- /dev/null +++ b/Engine/source/gfx/bitmap/loaders/ies/ies_loader.h @@ -0,0 +1,116 @@ +// +---------------------------------------------------------------------- +// | Project : ray. +// | All rights reserved. +// +---------------------------------------------------------------------- +// | Copyright (c) 2013-2017. +// +---------------------------------------------------------------------- +// | * Redistribution and use of this software in source and binary forms, +// | with or without modification, are permitted provided that the following +// | conditions are met: +// | +// | * Redistributions of source code must retain the above +// | copyright notice, this list of conditions and the +// | following disclaimer. +// | +// | * Redistributions in binary form must reproduce the above +// | copyright notice, this list of conditions and the +// | following disclaimer in the documentation and/or other +// | materials provided with the distribution. +// | +// | * Neither the name of the ray team, nor the names of its +// | contributors may be used to endorse or promote products +// | derived from this software without specific prior +// | written permission of the ray team. +// | +// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +---------------------------------------------------------------------- +#ifndef _H_IES_LOADER_H_ +#define _H_IES_LOADER_H_ + +#include +#include + +// https://knowledge.autodesk.com/support/3ds-max/learn-explore/caas/CloudHelp/cloudhelp/2016/ENU/3DSMax/files/GUID-EA0E3DE0-275C-42F7-83EC-429A37B2D501-htm.html +class IESFileInfo +{ +public: + IESFileInfo(); + + bool valid() const; + + const std::string& error() const; + +public: + float totalLights; + float totalLumens; + + float candalaMult; + + std::int32_t typeOfPhotometric; + std::int32_t typeOfUnit; + + std::int32_t anglesNumH; + std::int32_t anglesNumV; + + float width; + float length; + float height; + + float ballastFactor; + float futureUse; + float inputWatts; + +private: + friend class IESLoadHelper; + + float _cachedIntegral; + + std::string _error; + std::string _version; + + std::vector _anglesH; + std::vector _anglesV; + std::vector _candalaValues; +}; + +class IESLoadHelper final +{ +public: + IESLoadHelper(); + ~IESLoadHelper(); + + bool load(const std::string& data, IESFileInfo& info); + bool load(const char* data, std::size_t dataLength, IESFileInfo& info); + + bool saveAs1D(const IESFileInfo& info, float* data, std::uint32_t width = 256, std::uint8_t channel = 3) noexcept; + bool saveAs2D(const IESFileInfo& info, float* data, std::uint32_t width = 256, std::uint32_t height = 256, std::uint8_t channel = 3) noexcept; + bool saveAsPreview(const IESFileInfo& info, std::uint8_t* data, std::uint32_t width = 64, std::uint32_t height = 64, std::uint8_t channel = 3) noexcept; + +private: + float computeInvMax(const std::vector& candalaValues) const; + float computeFilterPos(float value, const std::vector& angle) const; + + float interpolate1D(const IESFileInfo& info, float angle) const; + float interpolate2D(const IESFileInfo& info, float angleV, float angleH) const; + float interpolatePoint(const IESFileInfo& info, std::uint32_t x, std::uint32_t y) const; + float interpolateBilinear(const IESFileInfo& info, float x, float y) const; + +private: + static void skipSpaceAndLineEnd(const std::string& data, std::string& out, bool stopOnComma = false); + + static void getLineContent(const std::string& data, std::string& next, std::string& line, bool stopOnWhiteSpace, bool stopOnComma); + static void getFloat(const std::string& data, std::string& next, float& ret, bool stopOnWhiteSpace = true, bool stopOnComma = false); + static void getInt(const std::string& data, std::string& next, std::int32_t& ret, bool stopOnWhiteSpace = true, bool stopOnComma = false); +}; + +#endif \ No newline at end of file diff --git a/Engine/source/lighting/advanced/advancedLightBinManager.cpp b/Engine/source/lighting/advanced/advancedLightBinManager.cpp index c857933dd..998bc2bcf 100644 --- a/Engine/source/lighting/advanced/advancedLightBinManager.cpp +++ b/Engine/source/lighting/advanced/advancedLightBinManager.cpp @@ -830,6 +830,7 @@ void AdvancedLightBinManager::LightMaterialInfo::setLightParameters( const Light const F32 radius = lightInfo->getRange().x; const F32 invSqrRadius = 1.0f / (radius * radius); matParams->setSafe( lightRange, radius); + matParams->setSafe( lightDirection, -lightInfo->getTransform().getUpVector()); matParams->setSafe( lightInvSqrRange, invSqrRadius); luxTargMultiplier =radius; } diff --git a/Templates/BaseGame/game/core/rendering/shaders/lighting/advanced/pointLightP.hlsl b/Templates/BaseGame/game/core/rendering/shaders/lighting/advanced/pointLightP.hlsl index 339bc6619..52931ed15 100644 --- a/Templates/BaseGame/game/core/rendering/shaders/lighting/advanced/pointLightP.hlsl +++ b/Templates/BaseGame/game/core/rendering/shaders/lighting/advanced/pointLightP.hlsl @@ -109,7 +109,7 @@ TORQUE_UNIFORM_SAMPLER2D(colorBuffer, 3); TORQUE_UNIFORM_SAMPLER2D(matInfoBuffer, 4); #ifdef USE_COOKIE_TEX /// The texture for cookie rendering. -TORQUE_UNIFORM_SAMPLERCUBE(cookieMap, 5); +TORQUE_UNIFORM_SAMPLER2D(cookieMap, 5); #endif uniform float4 rtParams0; @@ -117,6 +117,7 @@ uniform float4 lightColor; uniform float lightBrightness; uniform float3 lightPosition; +uniform float3 lightDirection; uniform float4 lightMapParams; uniform float4 vsFarPlane; @@ -163,31 +164,20 @@ float4 main( ConvexConnectP IN ) : SV_TARGET #ifndef NO_SHADOW if (getFlag(surface.matFlag, 0)) //also skip if we don't recieve shadows { - #ifdef SHADOW_CUBE + #ifdef SHADOW_CUBE - // TODO: We need to fix shadow cube to handle soft shadows! - float occ = TORQUE_TEXCUBE( shadowMap, mul( worldToLightProj, -surfaceToLight.L ) ).r; - shadow = saturate( exp( lightParams.y * ( occ - distToLight ) ) ); + // TODO: We need to fix shadow cube to handle soft shadows! + float occ = TORQUE_TEXCUBE( shadowMap, mul( worldToLightProj, -surfaceToLight.L ) ).r; + shadow = saturate( exp( lightParams.y * ( occ - distToLight ) ) ); - #else - float2 shadowCoord = decodeShadowCoord( mul( worldToLightProj, -surfaceToLight.L ) ).xy; - shadow = softShadow_filter(TORQUE_SAMPLER2D_MAKEARG(shadowMap), ssPos.xy, shadowCoord, shadowSoftness, distToLight, surfaceToLight.NdotL, lightParams.y); - #endif + #else + float2 shadowCoord = decodeShadowCoord( mul( worldToLightProj, -surfaceToLight.L ) ).xy; + shadow = softShadow_filter(TORQUE_SAMPLER2D_MAKEARG(shadowMap), ssPos.xy, shadowCoord, shadowSoftness, distToLight, surfaceToLight.NdotL, lightParams.y); + #endif } #endif // !NO_SHADOW float3 lightCol = lightColor.rgb; - #ifdef USE_COOKIE_TEX - // Lookup the cookie sample. - float4 cookie = TORQUE_TEXCUBE(cookieMap, mul(worldToLightProj, -surfaceToLight.L)); - // Multiply the light with the cookie tex. - lightCol *= cookie.rgb; - // Use a maximum channel luminance to attenuate - // the lighting else we get specular in the dark - // regions of the cookie texture. - lightCol *= max(cookie.r, max(cookie.g, cookie.b)); - #endif - #ifdef DIFFUSE_LIGHT_VIZ float attenuation = getDistanceAtt(surfaceToLight.Lu, radius); float3 factor = lightColor * max(surfaceToLight.NdotL, 0) * shadow * lightIntensity * attenuation; @@ -198,7 +188,7 @@ float4 main( ConvexConnectP IN ) : SV_TARGET #endif #ifdef SPECULAR_LIGHT_VIZ - float attenuation = getDistanceAtt(surfaceToLight.Lu, radius); + float attenuation = getDistanceAtt(surfaceToLight.Lu, radius); float3 factor = lightColor * max(surfaceToLight.NdotL, 0) * shadow * lightIntensity * attenuation; float3 diffuse = BRDF_GetDebugSpecular(surface,surfaceToLight) * factor; @@ -219,7 +209,17 @@ float4 main( ConvexConnectP IN ) : SV_TARGET //get punctual light contribution lighting = getPunctualLight(surface, surfaceToLight, lightCol, lightBrightness, lightInvSqrRange, shadow); + + #ifdef USE_COOKIE_TEX + // Lookup the cookie sample.d + float cosTheta = dot(-surfaceToLight.L, lightDirection); + float angle = acos(cosTheta) * ( M_1OVER_PI_F); + float cookie = TORQUE_TEX2D(cookieMap, float2(angle, 0.0)).r; + // Multiply the light with the cookie tex. + lighting *= cookie; + #endif } - + + return float4(lighting, 0); } diff --git a/Templates/BaseGame/game/core/rendering/shaders/lighting/advanced/spotLightP.hlsl b/Templates/BaseGame/game/core/rendering/shaders/lighting/advanced/spotLightP.hlsl index d4ef36a36..22b2275d5 100644 --- a/Templates/BaseGame/game/core/rendering/shaders/lighting/advanced/spotLightP.hlsl +++ b/Templates/BaseGame/game/core/rendering/shaders/lighting/advanced/spotLightP.hlsl @@ -109,16 +109,7 @@ float4 main( ConvexConnectP IN ) : SV_TARGET //distance to light in shadow map space float distToLight = pxlPosLightProj.z / lightRange; shadow = softShadow_filter(TORQUE_SAMPLER2D_MAKEARG(shadowMap), ssPos.xy, shadowCoord, shadowSoftness, distToLight, surfaceToLight.NdotL, lightParams.y); - #ifdef USE_COOKIE_TEX - // Lookup the cookie sample. - float4 cookie = TORQUE_TEX2D(cookieMap, shadowCoord); - // Multiply the light with the cookie tex. - lightCol *= cookie.rgb; - // Use a maximum channel luminance to attenuate - // the lighting else we get specular in the dark - // regions of the cookie texture. - lightCol *= max(cookie.r, max(cookie.g, cookie.b)); - #endif + } #endif @@ -153,6 +144,14 @@ float4 main( ConvexConnectP IN ) : SV_TARGET //get spot light contribution lighting = getSpotlight(surface, surfaceToLight, lightCol, lightBrightness, lightInvSqrRange, lightDirection, lightSpotParams, shadow); + #ifdef USE_COOKIE_TEX + // Lookup the cookie sample.d + float cosTheta = dot(-surfaceToLight.L, lightDirection); + float angle = acos(cosTheta) * ( M_1OVER_PI_F); + float cookie = TORQUE_TEX2D(cookieMap, float2(angle, 0.0)).r; + // Multiply the light with the cookie tex. + lighting *= cookie; + #endif } return float4(lighting, 0);