Files
CDAG/Research/scene/Material/SmallNormal.h

186 lines
6.3 KiB
C++

#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<SmallNormal>& 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<SmallNormal>& normals, const std::vector<float>& 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<unsigned8> Serialize() const
{
std::vector<unsigned8> res(BYTES);
for (unsigned8 i = 0; i < BYTES; i++) res[i] = mData[i];
return res;
}
void Deserialize(const std::vector<unsigned8>& value) {
assert(value.size() == BYTES);
for (unsigned8 i = 0; i < BYTES; i++) mData[i] = value[i];
}
void Serialize(std::ostream& stream) const { Serializer<unsigned8*>::Serialize(&mData[0], BYTES, stream); }
void Deserialize(std::istream& stream) { Serializer<unsigned8*>::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<SmallNormal> GetAll()
{
std::vector<SmallNormal> 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<SmallNormal> {
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();
}
};