#pragma once #include "BaseMaterial.h" #include "../../inc/glm/glm.hpp" #include "../../core/Hashers.h" #include "../../core/Comparers.h" #include "../../core/CollectionHelper.h" #include "../../core/Serializer.h" #include "../../core/BitHelper.h" // 8 bit normal representation using octahedron normal vectors // https://knarkowicz.wordpress.com/2014/04/16/octahedron-normal-vector-encoding/ // Technique proposed in: // Meyer, Q., Süßmuth, J., Sußner, G., Stamminger, M., & Greiner, G. (2010, June). // On Floating Point Normal Vectors. // In Computer Graphics Forum (Vol. 29, No. 4, pp. 1405-1409). Blackwell Publishing Ltd. class SmallNormal { public: static const unsigned8 BITS = 12; static const unsigned8 BYTES = BITS / 8 + (((BITS % 8) == 0) ? 0 : 1); static const unsigned8 CHANNELSPERPIXEL = BYTES; static const unsigned32 SINGLECOORDMASK = ((1 << (BITS / 2)) - 1); static const unsigned32 MAXCOORDVALUE = SINGLECOORDMASK; private: unsigned8 mData[BYTES]; inline unsigned32 normal() const { unsigned32 res = 0; BitHelper::JoinBytes(mData, res, 0, BYTES); return res; } inline void normal(unsigned32 value) { BitHelper::SplitInBytesAndMove(value, mData, 0, BYTES); } inline static float DiscrMult() { return (float)MAXCOORDVALUE; } // Wraps a vector between [-1, 1] glm::vec2 Wrap(glm::vec2 v) const { return (1.0f - glm::abs(glm::vec2(v.y, v.x))) * (glm::vec2(v.x >= 0 ? 1 : -1, v.y >= 0 ? 1 : -1)); } public: SmallNormal() { normal(0); } SmallNormal(glm::vec3 normal) { Set(normal); } // Returns the x component of the octahedral normal vector as an unsigned integer between 0 and MAXCOORDVALUE unsigned32 GetXComponent() const { return normal() >> (BITS >> 1); } // Returns the y component of the octahedral normal vector as an unsigned integer between 0 and MAXCOORDVALUE unsigned32 GetYComponent() const { return normal() & SINGLECOORDMASK; } // Sets the x component of the octahedral normal vector as an unsigned integer. Should be between 0 and MAXCOORDVALUE void SetXComponent(unsigned32 value) { assert(value <= MAXCOORDVALUE); unsigned32 mNormal = normal(); mNormal &= SINGLECOORDMASK; mNormal |= value << (BITS / 2); normal(mNormal); } // Sets the y component of the octahedral normal vector as an unsigned integer. Should be between 0 and MAXCOORDVALUE void SetYComponent(unsigned32 value) { assert(value <= MAXCOORDVALUE); unsigned32 mNormal = normal(); mNormal &= ~SINGLECOORDMASK; mNormal |= value & SINGLECOORDMASK; normal(mNormal); } void Set(glm::vec3 n) { n /= (abs(n.x) + abs(n.y) + abs(n.z)); glm::vec2 res(n.x, n.y); if (n.z < 0) res = Wrap(res); res = res * 0.5f + 0.5f; SetXComponent((unsigned32)(res.x * DiscrMult())); SetYComponent((unsigned32)(res.y * DiscrMult())); //if (n.x == 0 && n.y == 0 && n.z == 0) mNormal = 0; //else //{ // n = glm::normalize(n); // // Calculate spherical coordinates, normalized between 0 and 1 // glm::vec2 sphericalCoords = ((glm::vec2(atan2(n.y, n.x) / mPi, n.z) + 1.0f) * 0.5f); // // Store them using 4 bits per channel // SetXComponent((unsigned8)(sphericalCoords.x * 16.0f)); // SetYComponent((unsigned8)(sphericalCoords.y * 16.0f)); //} } glm::vec3 Get() const { glm::vec2 enc(((float)GetXComponent()) / DiscrMult(), ((float)GetYComponent()) / DiscrMult()); enc = enc * 2.0f - 1.0f; glm::vec3 n(enc.x, enc.y, 1.0 - abs(enc.x) - abs(enc.y)); if (n.z < 0) { glm::vec2 wrapped = Wrap(enc); n.x = wrapped.x; n.y = wrapped.y; } return glm::normalize(n); //// Extract the spherical coordinates //glm::vec2 sphericalCoords(((float)GetXComponent()) / 16.0f, ((float)GetYComponent()) / 16.0f); //// Normalize back //sphericalCoords = (sphericalCoords * 2.0f) - 1.0f; //glm::vec2 scth(sin(sphericalCoords.x), cos(sphericalCoords.y)); //glm::vec2 scphi(sqrt(1.0 - sphericalCoords.y*sphericalCoords.y), sphericalCoords.y); //return glm::normalize(glm::vec3(scth.y * scphi.x, scth.x * scphi.x, scphi.y)); } unsigned32 GetValue() const { return normal(); } void SetValue(unsigned32 value) { normal(value); } glm::u16vec3 GetProperties() const { return glm::u16vec3(0, 0, normal()); } void SetProperties(glm::u16vec3 props) { normal(props.z); } static SmallNormal Average(const std::vector& normals) { glm::vec3 sum(0); for (SmallNormal n : normals) sum += n.Get(); if (sum.x == 0 && sum.y == 0 && sum.z == 0) return SmallNormal(); return SmallNormal(glm::normalize(sum / (float)normals.size())); } static SmallNormal WeightedAverage(const std::vector& normals, const std::vector& weights) { glm::vec3 sum(0); for (size_t i = 0; i < normals.size(); i++) sum += normals[i].Get() * weights[i]; if (sum.x == 0 && sum.y == 0 && sum.z == 0) return SmallNormal(); return SmallNormal(glm::normalize(sum / CollectionHelper::Sum(weights))); } static float Distance(const SmallNormal a, const SmallNormal b) { return glm::distance(a.Get(), b.Get()) * 0.5f; } std::string GetTypeSuffix() const { return "n"; } std::vector Serialize() const { std::vector res(BYTES); for (unsigned8 i = 0; i < BYTES; i++) res[i] = mData[i]; return res; } void Deserialize(const std::vector& value) { assert(value.size() == BYTES); for (unsigned8 i = 0; i < BYTES; i++) mData[i] = value[i]; } void Serialize(std::ostream& stream) const { Serializer::Serialize(&mData[0], BYTES, stream); } void Deserialize(std::istream& stream) { Serializer::Deserialize(&mData[0], BYTES, stream); } bool operator==(const SmallNormal& n) const { return n.normal() == normal(); } bool operator!=(const SmallNormal& n) const { return !(n == *this); } // Assignment operator, used for easy access to different kinds of materials SmallNormal& operator=(const unsigned& source) { normal(source); } operator unsigned() const { return normal(); } static std::vector GetAll() { std::vector all(((size_t)1) << BITS); for (unsigned32 i = 0; i < (unsigned32)all.size(); i++) { SmallNormal n; n.SetValue(i); all[i] = n; } return all; } }; namespace std { template<> struct hash { size_t operator()(const SmallNormal &value) const { return value.GetValue(); } }; } struct NormalCompare { bool operator()(const SmallNormal& n1, const SmallNormal& n2) const { return n1.GetValue() < n2.GetValue(); } };