Hello, World!

Yeah this is quite a thicc first commit but there's no way I'm
untangling this mess just for a nice set of starting commits :^)
This commit is contained in:
2022-12-19 22:41:02 +01:00
parent af8a36563a
commit e7fe9370d3
36 changed files with 4223 additions and 0 deletions

13
Dockerfile Normal file
View File

@@ -0,0 +1,13 @@
FROM weckyy702/raychel_ci:latest
#Copy source files and set the working director
COPY . /usr/src/Raychel
WORKDIR /usr/src/Raychel
ARG COMPILER
RUN cmake -DCMAKE_CXX_COMPILER=${COMPILER} -DRAYCHEL_BUILD_TESTS=ON .
RUN cmake --build . --target all test
LABEL Name=raychel Version=0.0.1

View File

@@ -0,0 +1,166 @@
/**
* \file Deserializer.h
* \author Weckyy702 (weckyy702@gmail.com)
* \brief Header file for Deserializer class
* \date 2022-05-19
*
* MIT License
* Copyright (c) [2022] [Weckyy702 (weckyy702@gmail.com | https://github.com/Weckyy702)]
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
#ifndef RAYCHEL_DESERIALIZER_H
#define RAYCHEL_DESERIALIZER_H
#include <istream>
#include <optional>
#include <string_view>
#include "SDFTransforms.h"
#include "Scene.h"
namespace Raychel {
namespace details {
template <typename T>
concept DeserializableWithoutTarget = requires()
{
// clang-format off
requires !has_target_v<T>;
{ do_deserialize(std::declval<std::istream&>(), DeserializationTag<T>{}) } -> std::same_as<std::optional<T>>;
// clang-format on
};
template <typename T, typename Container>
concept DeserializableWithTarget = requires()
{
// clang-format off
requires has_target_v<T>;
{ do_deserialize(std::declval<std::istream&>(), std::declval<Container>(), DeserializationTag<T>{}) } -> std::same_as<std::optional<T>>;
// clang-format on
};
template <typename T, typename Container>
concept Deserializable = DeserializableWithoutTarget<T> || DeserializableWithTarget<T, Container>;
template <typename Container>
struct IDeserializer
{
IDeserializer() = default;
RAYCHEL_MAKE_NONCOPY_NONMOVE(IDeserializer)
virtual std::optional<Container>
deserialize(std::istream& is, std::optional<Container>&& maybe_target) const noexcept = 0;
virtual SerializableObjectData<Container> get_serializer() const noexcept = 0;
virtual std::string_view contained_type_name() const noexcept = 0;
virtual ~IDeserializer() = default;
};
template <typename Container, Deserializable<Container> Object>
class Deserializer final : public IDeserializer<Container>
{
public:
Deserializer() = default;
std::optional<Container> deserialize(std::istream& is, std::optional<Container>&& maybe_target) const noexcept final
{
if constexpr (has_target_v<Object>) {
return _deserialize_with_target(is, std::move(maybe_target));
} else {
if (maybe_target.has_value()) {
Logger::warn("Type ", Logger::details::type_name<Object>(), " did not expect to have a target!\n");
return std::nullopt;
}
return _deserialize_without_target(is);
}
}
SerializableObjectData<Container> get_serializer() const noexcept final
{
return SerializableObjectData<Container>{SerializableObjectDescriptor<Object>{}};
}
std::string_view contained_type_name() const noexcept final
{
return serializable_type_name_for<Object>();
}
virtual ~Deserializer() = default;
private:
static std::optional<Container> _deserialize_without_target(std::istream& is) noexcept
{
auto maybe_object = do_deserialize(is, DeserializationTag<Object>{});
if (!maybe_object.has_value())
return std::nullopt;
return Container{std::move(maybe_object).value()};
}
static std::optional<Container>
_deserialize_with_target(std::istream& is, std::optional<Container> maybe_target) noexcept
{
if (!maybe_target.has_value()) {
Logger::warn("Type ", Logger::details::type_name<Object>(), " expectec to have a target!\n");
return std::nullopt;
}
auto maybe_object = do_deserialize(is, std::move(maybe_target).value(), DeserializationTag<Object>{});
if (!maybe_object.has_value())
return std::nullopt;
return Container{std::move(maybe_object).value()};
}
};
template <typename Container>
using DeserializerPtr = std::unique_ptr<IDeserializer<Container>>;
template <typename Container, typename... Args>
auto object_deserializers()
{
std::vector<DeserializerPtr<Container>> res{};
((res.emplace_back(std::make_unique<Deserializer<Container, Args>>())), ...);
return res;
}
} // namespace details
template <typename... Objects>
auto object_deserializers()
{
return details::object_deserializers<SDFContainer, Objects...>();
}
template <typename... Materials>
auto material_deserializers()
{
return details::object_deserializers<MaterialContainer, Materials...>();
}
[[nodiscard]] Scene deserialize_scene(
std::istream& is, const std::vector<details::DeserializerPtr<SDFContainer>>& object_deserializers,
const std::vector<details::DeserializerPtr<MaterialContainer>>& material_deserializers) noexcept;
} //namespace Raychel
#endif //!RAYCHEL_DESERIALIZER_H

View File

@@ -0,0 +1,66 @@
/**
* \file Raymarch.h
* \author Weckyy702 (weckyy702@gmail.com)
* \brief Header file for Raymarch class
* \date 2022-04-11
*
* MIT License
* Copyright (c) [2022] [Weckyy702 (weckyy702@gmail.com | https://github.com/Weckyy702)]
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
#ifndef RAYCHEL_RAYMARCH_H
#define RAYCHEL_RAYMARCH_H
#include "Types.h"
#include <limits>
#include <utility>
#include <vector>
namespace Raychel {
constexpr static auto no_hit = std::numeric_limits<std::size_t>::max();
struct RaymarchResult
{
vec3 point{};
double ray_depth{};
std::size_t ray_steps{};
std::size_t hit_index{no_hit};
};
struct RaymarchOptions
{
std::size_t max_ray_steps{1'000};
double max_ray_depth{100};
double surface_epsilon{1e-3};
};
[[nodiscard]] std::pair<double, std::size_t>
evaluate_distance_field(const std::vector<SDFContainer>& surfaces, const vec3& point) noexcept;
[[nodiscard]] RaymarchResult raymarch(
vec3 current_point, const vec3& direction, const std::vector<SDFContainer>& surfaces, RaymarchOptions options) noexcept;
[[nodiscard]] vec3 get_normal(const vec3& point, const SDFContainer& surface, double normal_offset = 1e-6) noexcept;
} // namespace Raychel
#endif //!RAYCHEL_RAYMARCH_H

View File

@@ -0,0 +1,87 @@
/**
* \file SDFBooleans.h
* \author Weckyy702 (weckyy702@gmail.com)
* \brief Header file for boolean operations on SDFs
* \date 2022-05-27
*
* MIT License
* Copyright (c) [2022] [Weckyy702 (weckyy702@gmail.com | https://github.com/Weckyy702)]
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
#ifndef RAYCHEL_SDF_BOOLEANS_H
#define RAYCHEL_SDF_BOOLEANS_H
#include "Types.h"
#include <cmath>
namespace Raychel {
template <typename Target1, typename Target2>
struct Union
{
Target1 target1;
Target2 target2;
};
template <typename T1, typename T2>
Union(T1, T2) -> Union<T1, T2>;
template <typename T1, typename T2>
double evaluate_sdf(const Union<T1, T2>& object, const vec3& p) noexcept
{
return std::min(evaluate_sdf(object.target1, p), evaluate_sdf(object.target2, p));
}
template <typename Target1, typename Target2>
struct Difference
{
Target1 target1;
Target2 target2;
};
template <typename T1, typename T2>
Difference(T1, T2) -> Difference<T1, T2>;
template <typename T1, typename T2>
double evaluate_sdf(const Difference<T1, T2>& object, const vec3& p) noexcept
{
return std::max(-evaluate_sdf(object.target1, p), evaluate_sdf(object.target2, p));
}
template <typename Target1, typename Target2>
struct Intersection
{
Target1 target1;
Target2 target2;
};
template <typename T1, typename T2>
Intersection(T1, T2) -> Intersection<T1, T2>;
template <typename T1, typename T2>
double evaluate_sdf(const Intersection<T1, T2>& object, const vec3& p) noexcept
{
return std::max(evaluate_sdf(object.target1, p), evaluate_sdf(object.target2, p));
}
} // namespace Raychel
#endif //!RAYCHEL_SDF_BOOLEANS_H

View File

@@ -0,0 +1,213 @@
/**
* \file SDFContainer.h
* \author Weckyy702 (weckyy702@gmail.com)
* \brief Header file for SDFContainer class
* \date 2022-04-09
*
* MIT License
* Copyright (c) [2022] [Weckyy702 (weckyy702@gmail.com | https://github.com/Weckyy702)]
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
#ifndef RAYCHEL_SDF_CONTAINER_H
#define RAYCHEL_SDF_CONTAINER_H
#include "Raychel/Core/SDFPrimitives.h"
#include "Types.h"
#include "RaychelCore/Badge.h"
#include "RaychelCore/ClassMacros.h"
#include <RaychelCore/Raychel_assert.h>
#include <cstdint>
#include <memory>
namespace Raychel {
template <typename T>
constexpr bool has_custom_normal_v = requires(T t)
{
{
evaluate_normal(t, vec3{})
} -> std::same_as<vec3>;
};
namespace details {
template <typename T>
class TypeId
{
static char _;
public:
static std::uintptr_t id()
{
return reinterpret_cast<std::uintptr_t>(&_);
}
};
template <typename T>
char TypeId<T>::_{};
struct ISDFContainerImpl
{
ISDFContainerImpl() = default;
RAYCHEL_MAKE_NONCOPY_NONMOVE(ISDFContainerImpl)
[[nodiscard]] virtual std::uintptr_t type_id() const noexcept = 0;
virtual void debug_log() const noexcept = 0;
virtual ~ISDFContainerImpl() = default;
};
template <typename T>
class SDFContainerImpl final : public ISDFContainerImpl
{
public:
explicit SDFContainerImpl(T&& object) noexcept(std::is_nothrow_move_constructible_v<T>)
: object_{std::forward<T>(object)}
{}
void debug_log() const noexcept override
{
std::cout << "SDFContainer with object type " << Logger::details::type_name<T>() << " (type id " << type_id()
<< ")";
if constexpr (has_target_v<T>) {
std::cout << " and target ";
if constexpr (std::is_same_v<decltype(T::target), SDFContainer>) {
object_.target.unsafe_impl()->debug_log();
} else {
std::cout << Logger::details::type_name<decltype(T::target)>() << '\n';
}
} else {
std::cout << '\n';
}
}
[[nodiscard]] std::uintptr_t type_id() const noexcept override
{
return TypeId<T>::id();
}
[[nodiscard]] T& object() noexcept
{
return object_;
}
[[nodiscard]] const T& object() const noexcept
{
return object_;
}
~SDFContainerImpl() override = default;
private:
T object_;
};
template <typename T>
struct Eval
{
static double eval(ISDFContainerImpl* ptr, const vec3& p)
{
auto& obj = get_ref(ptr);
return evaluate_sdf(obj, p);
}
static vec3 get_normal(ISDFContainerImpl* ptr, const vec3& p)
{
if constexpr (has_custom_normal_v<T>) {
auto& obj = get_ref(ptr);
return evaluate_normal(obj, p);
}
RAYCHEL_ASSERT_NOT_REACHED;
}
static T& get_ref(ISDFContainerImpl* ptr)
{
return reinterpret_cast<SDFContainerImpl<T>*>(ptr)->object();
}
};
} // namespace details
class SDFContainer
{
using EvalFunction = double (*)(details::ISDFContainerImpl*, const vec3&);
using NormalFunction = vec3 (*)(details::ISDFContainerImpl*, const vec3&);
public:
template <typename T>
using Impl = details::SDFContainerImpl<T>;
template <typename T>
requires(!std::is_same_v<std::remove_all_extents_t<T>, SDFContainer>) //otherwise the move constructor would be hidden
explicit SDFContainer(T&& object) noexcept(std::is_nothrow_move_constructible_v<T>)
: impl_{std::make_unique<Impl<T>>(std::forward<T>(object))},
eval_{details::Eval<T>::eval},
get_normal_(details::Eval<T>::get_normal),
has_custom_normal_{has_custom_normal_v<T>}
{}
RAYCHEL_MAKE_NONCOPY(SDFContainer)
RAYCHEL_MAKE_DEFAULT_MOVE(SDFContainer)
[[nodiscard]] double evaluate(const vec3& p) const noexcept
{
return eval_(impl_.get(), p);
}
[[nodiscard]] bool has_custom_normal() const noexcept
{
return has_custom_normal_;
}
[[nodiscard]] vec3 get_normal(const vec3& p) const noexcept
{
return get_normal_(impl_.get(), p);
}
[[nodiscard]] auto type_id() const noexcept
{
return impl_->type_id();
}
[[nodiscard]] auto* unsafe_impl() const noexcept
{
return impl_.get();
}
~SDFContainer() = default;
private:
std::unique_ptr<details::ISDFContainerImpl> impl_{};
EvalFunction eval_;
NormalFunction get_normal_;
bool has_custom_normal_ : 1 {};
};
inline double evaluate_sdf(const SDFContainer& obj, const vec3& p)
{
return obj.evaluate(p);
}
} // namespace Raychel
#endif //! RAYCHEL_SDF_CONTAINER_H

View File

@@ -0,0 +1,84 @@
/**
* \file SDFModifiers.h
* \author Weckyy702 (weckyy702@gmail.com)
* \brief Header file for modifiers on SDFs
* \date 2022-05-27
*
* MIT License
* Copyright (c) [2022] [Weckyy702 (weckyy702@gmail.com | https://github.com/Weckyy702)]
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
#ifndef RAYCHEL_SDF_MODIFIERS_H
#define RAYCHEL_SDF_MODIFIERS_H
#include "Raychel/Core/Types.h"
namespace Raychel {
template <typename Target>
struct Hollow
{
Target target;
};
template <typename T>
Hollow(T) -> Hollow<T>;
template <typename T>
double evaluate_sdf(const Hollow<T>& object, const vec3& p) noexcept
{
return std::abs(evaluate_sdf(object.target, p));
}
template <typename Target>
struct Rounded
{
Target target;
double radius;
};
template <typename T>
Rounded(T, double) -> Rounded<T>;
template <typename T>
double evaluate_sdf(const Rounded<T>& object, const vec3& p) noexcept
{
return evaluate_sdf(object.target, p) - object.radius;
}
template <typename Target>
struct Onion
{
Target target;
double thickness;
};
template <typename T>
Onion(T, double) -> Onion<T>;
template <typename T>
double evaluate_sdf(const Onion<T>& object, const vec3& p) noexcept
{
return std::abs(evaluate_sdf(object.target, p)) - object.thickness;
}
} // namespace Raychel
#endif //!RAYCHEL_SDF_MODIFIERS_H

View File

@@ -0,0 +1,105 @@
/**
* \file SDFPrimitives.h
* \author Weckyy702 (weckyy702@gmail.com)
* \brief Header file for SDF primitives
* \date 2022-05-27
*
* MIT License
* Copyright (c) [2022] [Weckyy702 (weckyy702@gmail.com | https://github.com/Weckyy702)]
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
#ifndef RAYCHEL_SDF_PRIMITIVES_H
#define RAYCHEL_SDF_PRIMITIVES_H
#include "Types.h"
#include <cmath>
#include <iostream>
#include <optional>
namespace Raychel {
struct Sphere
{
double radius{1.0};
};
inline double evaluate_sdf(const Sphere& object, const vec3& p) noexcept
{
return mag(p) - object.radius;
}
//optimized normal calculation for spheres
inline vec3 evaluate_normal(const Sphere& /*unused*/, const vec3& p) noexcept
{
return normalize(p);
}
bool do_serialize(std::ostream& os, const Sphere& object) noexcept;
std::optional<Sphere> do_deserialize(std::istream& is, DeserializationTag<Sphere>) noexcept;
struct Box
{
vec3 size{1, 1, 1};
};
inline double evaluate_sdf(const Box& box, const vec3& p) noexcept
{
using std::abs, std::max, std::min;
const auto q = vec3{abs(p.x()), abs(p.y()), abs(p.z())} - box.size;
return mag(vec3{max(q.x(), 0.0), max(q.y(), 0.0), max(q.z(), 0.0)}) + min(max(q.x(), max(q.y(), q.z())), 0.0);
}
bool do_serialize(std::ostream& os, const Box& object) noexcept;
std::optional<Box> do_deserialize(std::istream& is, DeserializationTag<Box>) noexcept;
struct Plane
{
vec3 normal{0, 1, 0};
};
inline double evaluate_sdf(const Plane& object, const vec3& p) noexcept
{
return std::abs(dot(object.normal, p));
}
inline vec3 evaluate_normal(const Plane& object, const vec3&) noexcept
{
return object.normal;
}
bool do_serialize(std::ostream& os, const Plane& object) noexcept;
std::optional<Plane> do_deserialize(std::istream& is, DeserializationTag<Plane>) noexcept;
struct DeserializationErrorPlaceHolder
{};
inline double evaluate_sdf(const DeserializationErrorPlaceHolder /*unused*/, const vec3& /*unused*/) noexcept
{
return 1e9;
}
} // namespace Raychel
#endif //!RAYCHEL_SDF_PRIMITIVES_H

View File

@@ -0,0 +1,115 @@
/**
* \file SDFTransforms.h
* \author Weckyy702 (weckyy702@gmail.com)
* \brief Header file for SDFTransforms class
* \date 2022-05-27
*
* MIT License
* Copyright (c) [2022] [Weckyy702 (weckyy702@gmail.com | https://github.com/Weckyy702)]
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
#ifndef RAYCHEL_SDF_TRANSFORM_H
#define RAYCHEL_SDF_TRANSFORM_H
#include "Types.h"
#include "SDFContainer.h"
#include <iostream>
#include <optional>
namespace Raychel {
template <typename Target = SDFContainer>
struct Translate
{
Target target;
vec3 translation{};
};
template <typename T>
struct has_target<Translate<T>> : std::true_type
{};
template <typename T>
Translate(T, vec3) -> Translate<T>;
template <typename T>
double evaluate_sdf(const Translate<T>& object, const vec3& p) noexcept
{
return evaluate_sdf(object.target, p - object.translation);
}
template <typename T>
bool do_serialize(std::ostream& os, const Translate<T>& object) noexcept
{
os << object.translation << '\n';
return os.good();
}
template <typename T>
std::optional<Translate<T>> do_deserialize(std::istream& is, SDFContainer target, DeserializationTag<Translate<T>>) noexcept
{
vec3 translation{};
if (!(is >> translation))
return std::nullopt;
return Translate<T>{std::move(target), translation};
}
template <typename Target = SDFContainer>
struct Rotate
{
Target target;
Quaternion rotation{};
};
template <typename T>
struct has_target<Rotate<T>> : std::true_type
{};
template <typename T>
Rotate(T, Quaternion) -> Rotate<T>;
template <typename T>
double evaluate_sdf(const Rotate<T>& object, const vec3& p) noexcept
{
return evaluate_sdf(object.target, p * inverse(object.rotation));
}
template <typename T>
bool do_serialize(std::ostream& os, const Rotate<T>& object) noexcept
{
os << object.rotation << '\n';
return os.good();
}
template <typename T>
std::optional<Rotate<T>> do_deserialize(std::istream& is, SDFContainer target, DeserializationTag<Rotate<T>>) noexcept
{
Quaternion rotation{};
if (!(is >> rotation))
return std::nullopt;
return Rotate{std::move(target), rotation};
}
} // namespace Raychel
#endif //!RAYCHEL_SDF_TRANSFORM_H

View File

@@ -0,0 +1,36 @@
/**
* \file SDFs.h
* \author Weckyy702 (weckyy702@gmail.com)
* \brief Header file for predefined signed distance functions
* \date 2022-04-12
*
* MIT License
* Copyright (c) [2022] [Weckyy702 (weckyy702@gmail.com | https://github.com/Weckyy702)]
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
#ifndef RAYCHEL_PREDEFINED_SIGNED_DISTANCE_FUNCTIONS_H
#define RAYCHEL_PREDEFINED_SIGNED_DISTANCE_FUNCTIONS_H
#include "SDFBooleans.h"
#include "SDFModifiers.h"
#include "SDFPrimitives.h"
#include "SDFTransforms.h"
#endif //!RAYCHEL_PREDEFINED_SIGNED_DISTANCE_FUNCTIONS_H

View File

@@ -0,0 +1,141 @@
/**
* \file Scene.h
* \author Weckyy702 (weckyy702@gmail.com)
* \brief Header file for Scene class
* \date 2022-04-09
*
* MIT License
* Copyright (c) [2022] [Weckyy702 (weckyy702@gmail.com | https://github.com/Weckyy702)]
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
#ifndef RAYCHEL_SCENE_H
#define RAYCHEL_SCENE_H
#include "Raychel/Render/MaterialContainer.h"
#include "SDFContainer.h"
#include "Serialize.h"
#include <algorithm>
#include <iterator>
#include <vector>
namespace Raychel {
template <typename Object, typename Material>
struct RaymarchableObject
{
std::size_t index_in_scene;
Object& object;
Material& material;
};
//Clang needs this deduction guide
template <typename Object, typename Material>
RaymarchableObject(std::size_t, Object&, Material&) -> RaymarchableObject<Object, Material>;
class Scene
{
public:
Scene() = default;
static Scene unsafe_from_data(
std::vector<SDFContainer> objects, std::vector<SerializableObjectData<SDFContainer>> object_serializers,
std::vector<MaterialContainer> materials,
std::vector<SerializableObjectData<MaterialContainer>> material_serializers) noexcept;
template <typename Object, typename Material>
auto add_object(Object&& object, Material&& material) noexcept
{
const auto where = std::lower_bound(
objects_.begin(), objects_.end(), details::TypeId<Object>::id(), [](const auto& cont, const auto& id) {
return cont.type_id() < id;
});
const auto obj = objects_.emplace(where, std::forward<Object>(object));
const auto index = std::distance(objects_.begin(), obj);
const auto mat = materials_.emplace(materials_.begin() + index, std::forward<Material>(material));
object_serializers_.emplace(object_serializers_.begin() + index, details::SerializableObjectDescriptor<Object>{});
material_serializers_.emplace(
material_serializers_.begin() + index, details::SerializableObjectDescriptor<Material>{});
return RaymarchableObject{
objects_.size() - 1U,
details::get_container_content<Object>(*obj),
details::get_container_content<Material>(*mat)};
}
void remove_object(std::size_t index) noexcept;
template <std::invocable<const RenderData&> F>
requires(std::is_same_v<std::invoke_result_t<F, const RenderData&>, color>) void set_background_function(F&& f) noexcept
{
background_function_ = f;
}
[[nodiscard]] const auto& objects() const noexcept
{
return objects_;
}
[[nodiscard]] const auto& materials() const noexcept
{
return materials_;
}
[[nodiscard]] const auto& background_function() const noexcept
{
return background_function_;
}
[[nodiscard]] const auto& object_serializers() const noexcept
{
return object_serializers_;
}
[[nodiscard]] const auto& material_serializers() const noexcept
{
return material_serializers_;
}
private:
Scene(
std::vector<SDFContainer> objects, std::vector<SerializableObjectData<SDFContainer>> object_serializers,
std::vector<MaterialContainer> materials, std::vector<SerializableObjectData<MaterialContainer>> material_serializers)
: object_serializers_{std::move(object_serializers)},
material_serializers_{std::move(material_serializers)},
objects_{std::move(objects)},
materials_{std::move(materials)}
{}
std::vector<SerializableObjectData<SDFContainer>> object_serializers_{};
std::vector<SerializableObjectData<MaterialContainer>> material_serializers_{};
std::vector<SDFContainer> objects_{};
std::vector<MaterialContainer> materials_{};
BackgroundFunction background_function_{};
};
bool serialize_scene(const Scene& scene, std::ostream& os) noexcept;
} // namespace Raychel
#endif //! RAYCHEL_SCENE_H

View File

@@ -0,0 +1,163 @@
/**
* \file SerializableObjectData.h
* \author Weckyy702 (weckyy702@gmail.com)
* \brief Header file for SerializableObjectData class
* \date 2022-05-18
*
* MIT License
* Copyright (c) [2022] [Weckyy702 (weckyy702@gmail.com | https://github.com/Weckyy702)]
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
#ifndef RAYCHEL_SERIALIZABLE_OBJECT_DATA_H
#define RAYCHEL_SERIALIZABLE_OBJECT_DATA_H
#include "Types.h"
#include "RaychelCore/ClassMacros.h"
#include "RaychelCore/Raychel_assert.h"
#include "RaychelLogger/Logger.h"
#include <concepts>
#include <functional>
#include <ostream>
namespace Raychel {
namespace details {
template <typename Contained, typename Container>
auto& get_container_content(Container& container) noexcept
{
using Impl = typename Container::template Impl<Contained>;
auto* impl_interface = container.unsafe_impl();
auto* impl = dynamic_cast<Impl*>(impl_interface);
RAYCHEL_ASSERT(impl != nullptr);
return impl->object();
}
template <typename Contained, typename Container>
const auto& get_container_content(const Container& container) noexcept
{
using Impl = typename Container::template Impl<Contained>;
auto* impl_interface = container.unsafe_impl();
auto* impl = dynamic_cast<Impl*>(impl_interface);
RAYCHEL_ASSERT(impl != nullptr);
return impl->object();
}
template <typename T>
requires(has_target_v<T>) constexpr std::string_view serializable_type_name_for()
{
constexpr auto full_type_name = Logger::details::type_name<T>();
constexpr auto bracket_index = full_type_name.find('<');
static_assert(bracket_index != std::string_view::npos);
constexpr std::string_view base_type_name{full_type_name.begin(), full_type_name.begin() + bracket_index};
return base_type_name;
}
template <typename T>
constexpr std::string_view serializable_type_name_for()
{
return Logger::details::type_name<T>();
}
// clang-format off
template <typename Object>
concept Serializable = requires(const Object& obj)
{
{ do_serialize(std::declval<std::ostream&>(), obj) } -> std::same_as<bool>;
};
// clang-format on
template <typename Object>
struct SerializableObjectDescriptor
{};
template <Serializable T>
bool serialize_internal(std::ostream&, const T&, std::size_t = 0) noexcept;
template <typename T>
bool serialize_internal(std::ostream&, const T&, std::size_t = 0) noexcept;
template <Serializable Object>
requires(has_target_v<Object>) bool serialize_with_target(
std::ostream& os, const Object& object, std::size_t recursion_depth) noexcept
{
os << serializable_type_name_for<Object>() << "<> with ";
return os.good() && do_serialize(os, object) && serialize_internal(os, object.target, recursion_depth + 1);
}
template <Serializable Object>
bool serialize_with_target(std::ostream& os, const Object& object, std::size_t /*unused*/) noexcept
{
os << serializable_type_name_for<Object>() << " with ";
return os.good() && do_serialize(os, object);
}
template <Serializable Object>
bool serialize_internal(std::ostream& os, const Object& object, std::size_t recursion_depth) noexcept
{
for (std::size_t i{}; i != recursion_depth; ++i)
os << " ";
return serialize_with_target(os, object, recursion_depth);
}
template <typename Object>
bool serialize_internal(std::ostream& os, const Object& /*unused*/, std::size_t /*unused*/) noexcept
{
constexpr auto type_name = Logger::details::type_name<Object>();
Logger::warn(
"Object of type '",
type_name,
"' cannot be serialized. Make sure the function bool do_serialize(std::ostream&, const ",
type_name,
"&) exists and can be located by ADL\n");
os << "__NONSERIALIZABLE__\n";
return os.good();
}
} // namespace details
template <typename Container>
class SerializableObjectData
{
public:
template <typename Contained>
explicit SerializableObjectData(details::SerializableObjectDescriptor<Contained>&& /*unused*/)
: serialize_{[](std::ostream& os, const Container& container) -> bool {
return details::serialize_internal(os, details::get_container_content<Contained>(container));
}}
{}
bool serialize(const Container& container, std::ostream& os) const noexcept
{
return serialize_(os, container);
}
private:
std::function<bool(std::ostream&, const Container&)> serialize_;
};
} // namespace Raychel
#endif //!RAYCHEL_SERIALIZABLE_OBJECT_DATA_H

View File

@@ -0,0 +1,70 @@
/**
* \file Types.h
* \author Weckyy702 (weckyy702@gmail.com)
* \brief Header file for Types class
* \date 2022-04-10
*
* MIT License
* Copyright (c) [2022] [Weckyy702 (weckyy702@gmail.com | https://github.com/Weckyy702)]
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
#ifndef RAYCHEL_TYPES_H
#define RAYCHEL_TYPES_H
#include "RaychelMath/Transform.h"
#include "RaychelMath/color.h"
#include "RaychelMath/vec2.h"
#include "RaychelMath/vec3.h"
#include <functional>
namespace Raychel {
using vec3 = basic_vec3<double>;
using color = basic_color<double>;
using Quaternion = basic_quaternion<double>;
using Transform = basic_transform<double>;
using Size2D = basic_vec2<std::size_t>;
class SDFContainer;
class Scene;
struct RenderData;
using BackgroundFunction = std::function<color(const RenderData&)>;
template <typename T>
struct DeserializationTag
{};
template <typename T>
struct has_target : std::false_type
{};
template <typename T>
constexpr bool has_target_v = has_target<T>::value && requires(T t){
t.target;
};
} // namespace Raychel
#endif //!RAYCHEL_TYPES_H

View File

@@ -0,0 +1,153 @@
/**
* \file Xoroshiro128+.h
* \author Weckyy702 (weckyy702@gmail.com)
* \brief Header file for Xoroshiro128+ class
* \date 2022-04-24
*
* MIT License
* Copyright (c) [2022] [Weckyy702 (weckyy702@gmail.com | https://github.com/Weckyy702)]
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
#ifndef RAYCHEL_XOROSHIRO128_H
#define RAYCHEL_XOROSHIRO128_H
#include <bit>
#include <concepts>
#include <cstdint>
#include <limits>
namespace Raychel {
//This is an implementation of the xoroshiro128+ algorithm as described at https://prng.di.unimi.it/xoroshiro128plus.c ported to C++20
// template<int A=24, int B=16, int C=37>
//TODO: template this once we figure out how jump() and long_jump() can be generalized
class Xoroshiro128
{
struct TwoUint64s
{
std::uint64_t first, second;
};
public:
static constexpr std::int32_t A = 24, B = 16, C = 37;
constexpr Xoroshiro128() = default;
constexpr Xoroshiro128(std::uint64_t seed) : state_{seed, 0}
{
jump(); //jump through the domain a bit so we don't get weird results on the first call to next()
}
constexpr Xoroshiro128(std::uint64_t s0, std::uint64_t s1) : state_{s0, s1}
{}
static constexpr auto min() noexcept
{
return std::numeric_limits<std::uint64_t>::min();
}
static constexpr auto max() noexcept
{
return std::numeric_limits<std::uint64_t>::max();
}
template <std::integral Result = std::uint64_t>
[[nodiscard]] constexpr Result operator()() noexcept
{
return next<Result>();
}
template <std::integral Result = std::uint64_t>
constexpr Result next() noexcept
{
static_assert(sizeof(Result) <= sizeof(std::uint64_t), "Xoroshiro128+ cannot produce more than 64 random bits!");
auto [s0, s1] = state_;
const auto result = static_cast<Result>(s0 + s1);
s1 ^= s0;
state_.first = std::rotl(s0, A) ^ s1 ^ (s1 << B);
state_.second = std::rotl(s1, C);
return result;
}
constexpr void jump() noexcept
{
uint64_t s0{};
uint64_t s1{};
for (std::size_t b{}; b != 64U; ++b) {
if (short_jump_.first & std::uint64_t{1} << b) {
s0 ^= state_.first;
s1 ^= state_.second;
}
next();
}
for (std::size_t b{}; b != 64U; ++b) {
if (short_jump_.second & std::uint64_t{1} << b) {
s0 ^= state_.first;
s1 ^= state_.second;
}
next();
}
state_.first = s0;
state_.second = s1;
}
constexpr void long_jump() noexcept
{
uint64_t s0{};
uint64_t s1{};
for (std::size_t b{}; b != 64; ++b) {
if (long_jump_.first & std::uint64_t{1} << b) {
s0 ^= state_.first;
s1 ^= state_.second;
}
next();
}
for (std::size_t b{}; b != 64; ++b) {
if (long_jump_.second & std::uint64_t{1} << b) {
s0 ^= state_.first;
s1 ^= state_.second;
}
next();
}
state_.first = s0;
state_.second = s1;
}
private:
TwoUint64s state_{123456789, 987654321};
static constexpr TwoUint64s short_jump_{0xdf900294d8f554a5, 0x170865df4b3201fc};
static constexpr TwoUint64s long_jump_{0xd2a98b26625eee7b, 0xdddf9b1090aa7ac1};
};
} // namespace Raychel
#endif //!RAYCHEL_XOROSHIRO128_H

View File

@@ -0,0 +1,39 @@
/**
* \file ZigguratNormal.h
* \author Weckyy702 (weckyy702@gmail.com)
* \brief Header file for ZigguratNormal class
* \date 2022-04-24
*
* MIT License
* Copyright (c) [2022] [Weckyy702 (weckyy702@gmail.com | https://github.com/Weckyy702)]
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
#ifndef RAYCHEL_ZIGGURAT_NORMAL_H
#define RAYCHEL_ZIGGURAT_NORMAL_H
namespace Raychel {
[[nodiscard]] double uniform_random() noexcept;
[[nodiscard]] double ziggurat_normal() noexcept;
} // namespace Raychel
#endif //!RAYCHEL_ZIGGURAT_NORMAL_H

View File

@@ -0,0 +1,43 @@
/**
* \file Camera.h
* \author Weckyy702 (weckyy702@gmail.com)
* \brief Header file for Camera class
* \date 2022-04-11
*
* MIT License
* Copyright (c) [2022] [Weckyy702 (weckyy702@gmail.com | https://github.com/Weckyy702)]
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
#ifndef RAYCHEL_CAMERA_H
#define RAYCHEL_CAMERA_H
#include "Raychel/Core/Types.h"
namespace Raychel {
struct Camera
{
Transform transform;
double zoom{1.0};
};
} // namespace Raychel
#endif //!RAYCHEL_CAMERA_H

View File

@@ -0,0 +1,50 @@
/**
* \file Denoise.h
* \author Weckyy702 (weckyy702@gmail.com)
* \brief Header file for Denoise class
* \date 2022-05-06
*
* MIT License
* Copyright (c) [2022] [Weckyy702 (weckyy702@gmail.com | https://github.com/Weckyy702)]
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
#ifndef RAYCHEL_DENOISE_H
#define RAYCHEL_DENOISE_H
#include "FatPixel.h"
#include "Framebuffer.h"
namespace Raychel {
struct DenoisingOptions
{
std::size_t half_patch_size{1};
std::size_t half_search_window_size{6};
double distance_threshold{1.};
std::size_t num_scales{3};
};
Framebuffer denoise_single_scale(const FatFramebuffer& input_pixels, DenoisingOptions options = {}) noexcept;
Framebuffer denoise_multiscale(const FatFramebuffer& input_pixels, DenoisingOptions options = {}) noexcept;
} // namespace Raychel
#endif //!RAYCHEL_DENOISE_H

View File

@@ -0,0 +1,64 @@
/**
* \file FatPixel.h
* \author Weckyy702 (weckyy702@gmail.com)
* \brief Header file for FatPixel class
* \date 2022-05-01
*
* MIT License
* Copyright (c) [2022] [Weckyy702 (weckyy702@gmail.com | https://github.com/Weckyy702)]
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
#ifndef RAYCHEL_FAT_PIXEL_H
#define RAYCHEL_FAT_PIXEL_H
#include "RayHistogram.h"
#include <vector>
namespace Raychel {
namespace details {
template <std::size_t NumBins>
struct FatPixel
{
using Histogram = RayHistogram<NumBins>;
FatPixel operator+(const FatPixel& other) const noexcept
{
return {noisy_color + other.noisy_color, histogram + other.histogram};
}
template <std::convertible_to<double> T>
FatPixel operator/(T s) const noexcept
{
return {noisy_color / s, histogram / s};
}
color noisy_color{};
Histogram histogram{};
};
} // namespace details
using FatPixel = details::FatPixel<30U>;
} // namespace Raychel
#endif //!RAYCHEL_FAT_PIXEL_H

View File

@@ -0,0 +1,77 @@
/**
* \file Framebuffer.h
* \author Weckyy702 (weckyy702@gmail.com)
* \brief Header file for Framebuffer class
* \date 2022-04-11
*
* MIT License
* Copyright (c) [2022] [Weckyy702 (weckyy702@gmail.com | https://github.com/Weckyy702)]
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
#ifndef RAYCHEL_FRAMEBUFFER_H
#define RAYCHEL_FRAMEBUFFER_H
#include "FatPixel.h"
#include "Raychel/Core/Types.h"
#include <vector>
namespace Raychel {
namespace details {
template <typename Pixel>
struct BasicFramebuffer
{
constexpr Pixel& at(std::size_t x, std::size_t y)
{
return const_cast<Pixel&>(const_cast<const BasicFramebuffer*>(this)->at(x, y));
}
constexpr const Pixel& at(std::size_t x, std::size_t y) const
{
RAYCHEL_ASSERT((x < size.x()) && (y < size.y()));
return pixel_data.at(x + (y * size.x()));
}
const Size2D size{};
std::vector<Pixel> pixel_data{};
};
template <typename T>
constexpr auto begin(const BasicFramebuffer<T>& obj)
{
return obj.pixel_data.begin();
}
template <typename T>
constexpr auto end(const BasicFramebuffer<T>& obj)
{
return obj.pixel_data.end();
}
} // namespace details
using Framebuffer = details::BasicFramebuffer<color>;
using FatFramebuffer = details::BasicFramebuffer<FatPixel>;
} // namespace Raychel
#endif //!RAYCHEL_FRAMEBUFFER_H

View File

@@ -0,0 +1,130 @@
/**
* \file MaterialContainer.h
* \author Weckyy702 (weckyy702@gmail.com)
* \brief Header file for MaterialContainer class
* \date 2022-04-10
*
* MIT License
* Copyright (c) [2022] [Weckyy702 (weckyy702@gmail.com | https://github.com/Weckyy702)]
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
#ifndef RAYCHEL_MATERIAL_CONTAINER_H
#define RAYCHEL_MATERIAL_CONTAINER_H
#include "Materials.h"
#include "Raychel/Core/Types.h"
#include "RaychelCore/Badge.h"
#include "RaychelCore/ClassMacros.h"
#include <memory>
namespace Raychel {
namespace details {
struct IMaterialContainerImpl
{
IMaterialContainerImpl() = default;
RAYCHEL_MAKE_NONCOPY_NONMOVE(IMaterialContainerImpl)
[[nodiscard]] virtual color get_surface_color_internal(const ShadingData& data) const noexcept = 0;
[[nodiscard]] virtual double get_material_ior_internal() const noexcept = 0;
virtual ~IMaterialContainerImpl() = default;
};
template <typename T>
class MaterialContainerImpl final : public IMaterialContainerImpl
{
public:
explicit MaterialContainerImpl(T&& object) noexcept(std::is_nothrow_move_constructible_v<T>)
: object_{std::forward<T>(object)}
{}
[[nodiscard]] color get_surface_color_internal(const ShadingData& data) const noexcept override
{
return get_surface_color(object_, data);
}
[[nodiscard]] double get_material_ior_internal() const noexcept override
{
if constexpr (is_transparent_material_v<T>)
return get_material_ior(object_);
return 1.0;
}
[[nodiscard]] T& object() noexcept
{
return object_;
}
[[nodiscard]] const T& object() const noexcept
{
return object_;
}
private:
T object_;
};
} // namespace details
class MaterialContainer
{
public:
template <typename T>
using Impl = details::MaterialContainerImpl<T>;
template <typename T>
requires(!std::is_same_v<T, MaterialContainer>) explicit MaterialContainer(T&& object) noexcept(
std::is_nothrow_move_constructible_v<T>)
: impl_{std::make_unique<details::MaterialContainerImpl<T>>(std::forward<T>(object))}
{}
RAYCHEL_MAKE_NONCOPY(MaterialContainer)
RAYCHEL_MAKE_DEFAULT_MOVE(MaterialContainer)
[[nodiscard]] color get_surface_color(const ShadingData& data) const noexcept
{
return impl_->get_surface_color_internal(data);
}
[[nodiscard]] double get_material_ior() const noexcept
{
return impl_->get_material_ior_internal();
}
[[nodiscard]] auto* unsafe_impl() const noexcept
{
return impl_.get();
}
~MaterialContainer() = default;
private:
std::unique_ptr<details::IMaterialContainerImpl> impl_{};
};
} //namespace Raychel
#endif //!RAYCHEL_MATERIAL_CONTAINER_H

View File

@@ -0,0 +1,66 @@
/**
* \file Materials.h
* \author Weckyy702 (weckyy702@gmail.com)
* \brief Header file for Materials class
* \date 2022-05-20
*
* MIT License
* Copyright (c) [2022] [Weckyy702 (weckyy702@gmail.com | https://github.com/Weckyy702)]
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
#ifndef RAYCHEL_MATERIALS_H
#define RAYCHEL_MATERIALS_H
#include "Raychel/Core/Types.h"
#include <cmath>
namespace Raychel {
struct RenderState;
struct ShadingData
{
vec3 position;
vec3 normal;
vec3 incoming_direction;
const RenderState& state;
std::size_t recursion_depth;
};
template <typename T>
struct is_transparent_material : std::false_type
{};
template <typename T>
constexpr bool is_transparent_material_v = is_transparent_material<T>::value;
struct DeserializationErrorMaterial
{};
constexpr color get_surface_color(DeserializationErrorMaterial /*unused*/, const ShadingData& /*unused*/) noexcept
{
return color{1, 0, 1};
}
} // namespace Raychel
#endif //!RAYCHEL_MATERIALS_H

View File

@@ -0,0 +1,153 @@
/**
* \file RayHistogram.h
* \author Weckyy702 (weckyy702@gmail.com)
* \brief Header file for RayHistogram class
* \date 2022-04-30
*
* MIT License
* Copyright (c) [2022] [Weckyy702 (weckyy702@gmail.com | https://github.com/Weckyy702)]
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
#ifndef RAYCHEL_RAY_HISTOGRAM_H
#define RAYCHEL_RAY_HISTOGRAM_H
#include "Raychel/Core/Types.h"
#include <array>
#include <cstdint>
namespace Raychel {
namespace details {
struct BinData
{
std::size_t low_bin_index{};
double low_bin_weight{};
std::size_t high_bin_index{};
double high_bin_weight{};
};
}; // namespace details
template <std::size_t NumBins>
requires(NumBins > 2U) class RayHistogram
{
using BinnedChannel = std::array<double, NumBins>;
public:
RayHistogram() = default;
void add_sample(color c) noexcept
{
_add_channel(red_, c.r());
_add_channel(green_, c.g());
_add_channel(blue_, c.b());
}
const auto& red_channel() const noexcept
{
return red_;
}
const auto& green_channel() const noexcept
{
return green_;
}
const auto& blue_channel() const noexcept
{
return blue_;
}
RayHistogram operator+(const RayHistogram& other) const noexcept
{
BinnedChannel union_red{}, union_green{}, union_blue{};
for (std::size_t i{}; i != NumBins; ++i) {
union_red[i] = red_[i] + other.red_[i];
union_green[i] = green_[i] + other.green_[i];
union_blue[i] = blue_[i] + other.blue_[i];
}
return {std::move(union_red), std::move(union_green), std::move(union_blue)};
}
template <std::convertible_to<double> T>
RayHistogram operator/(T _s) const noexcept
{
const auto s = static_cast<double>(_s);
BinnedChannel new_red{}, new_green{}, new_blue{};
for (std::size_t i{}; i != NumBins; ++i) {
new_red[i] = red_[i] / s;
new_green[i] = green_[i] / s;
new_blue[i] = blue_[i] / s;
}
return {std::move(new_red), std::move(new_green), std::move(new_blue)};
}
private:
RayHistogram(BinnedChannel red, BinnedChannel green, BinnedChannel blue)
: red_{std::move(red)}, green_{std::move(green)}, blue_{std::move(blue)}
{}
static void _add_channel(BinnedChannel& channel, double value) noexcept
{
const auto [low_bin, low_weight, high_bin, high_weight] = _get_bin_data(value);
channel.at(low_bin) += low_weight;
channel.at(high_bin) += high_weight;
}
static details::BinData _get_bin_data(double value) noexcept
{
constexpr auto max_value = 7.5;
constexpr auto saturated_value = 2.5;
constexpr auto fbin_factor = static_cast<double>(NumBins - 2U);
auto v = std::max(value, 0.0);
v = std::pow(v, 1.0 / 2.2);
v /= max_value;
v = std::min(saturated_value, v);
const auto fbin = v * fbin_factor;
const auto bin_low = static_cast<std::size_t>(fbin);
if (bin_low < (NumBins - 2U)) {
const auto high_weight = std::fmod(fbin, 1.0);
const auto low_weight = 1.0 - high_weight;
return {bin_low, low_weight, bin_low + 1U, high_weight};
}
const auto high_weight = (v - 1) / (saturated_value - 1);
const auto low_weight = 1.0 - high_weight;
return {NumBins - 2U, low_weight, NumBins - 1U, high_weight};
}
BinnedChannel red_{};
BinnedChannel green_{};
BinnedChannel blue_{};
};
} // namespace Raychel
#endif //!RAYCHEL_RAY_HISTOGRAM_H

View File

@@ -0,0 +1,55 @@
/**
* \file RenderHelper.h
* \author Weckyy702 (weckyy702@gmail.com)
* \brief Header file for RenderHelper class
* \date 2022-04-12
*
* MIT License
* Copyright (c) [2022] [Weckyy702 (weckyy702@gmail.com | https://github.com/Weckyy702)]
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
#ifndef RAYCHEL_RENDER_UTILS_H
#define RAYCHEL_RENDER_UTILS_H
#include "Renderer.h"
namespace Raychel {
struct RefractionData
{
vec3 surface_point;
vec3 incoming_direction;
vec3 normal;
double material_ior;
double ior_variation;
const RenderState& state;
std::size_t recursion_depth;
};
[[nodiscard]] color get_shaded_color(const RenderData& data) noexcept;
[[nodiscard]] color get_diffuse_lighting(const ShadingData& data) noexcept;
[[nodiscard]] color get_refraction(const RefractionData& data) noexcept;
} // namespace Raychel
#endif //!RAYCHEL_RENDER_UTILS_H

View File

@@ -0,0 +1,95 @@
/**
* \file Renderer.h
* \author Weckyy702 (weckyy702@gmail.com)
* \brief Header file for Renderer class
* \date 2022-04-11
*
* MIT License
* Copyright (c) [2022] [Weckyy702 (weckyy702@gmail.com | https://github.com/Weckyy702)]
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
#ifndef RAYCHEL_RENDERER_H
#define RAYCHEL_RENDERER_H
#include "Camera.h"
#include "Framebuffer.h"
#include "MaterialContainer.h"
#include "Raychel/Core/SDFContainer.h"
#include <functional>
#include <vector>
namespace Raychel {
struct RenderOptions
{
//Size of the output image
Size2D output_size{1280, 720};
//Maximum number of steps until raymarching terminates
std::size_t max_ray_steps{1'024};
//Maximum depth for recursive algorithms
std::size_t max_recursion_depth{6};
//Maximum number of light bounces for indirect lighting
std::size_t max_lighting_bounces{2};
//Number of samples per pixel for rendering. Dramatically increases render times!
std::size_t samples_per_pixel{128};
//If antialiasing is used. (Low performance impact)
bool do_aa{true};
//How many threads are used for rendering. If 0, the library will choose
std::size_t thread_count{0};
//Maximum distance a ray can travel
double max_ray_depth{500};
//Maximum distance between the ray and a surface
double surface_epsilon{1e-6};
//Radius used for normal calculation. Should be smaller than surface_epsilon to avoid weirdness
double normal_epsilon{1e-12};
//Offset along the surface normal to avoid shadow weirdness. Should be larger than surface_epsilon
double shading_epsilon{1e-5};
};
struct RenderState
{
const std::vector<SDFContainer>& surfaces;
const std::vector<MaterialContainer>& materials;
BackgroundFunction get_background{};
RenderOptions options{};
};
struct RenderData
{
vec3 origin, direction;
const RenderState& state;
std::size_t recursion_depth;
};
FatFramebuffer render_scene(const Scene& scene, const Camera& camera, const RenderOptions& options = {}) noexcept;
} // namespace Raychel
#endif //!RAYCHEL_RENDERER_H

View File

@@ -0,0 +1,47 @@
from math import *
C = 128
R = 3.442619855899
V = 9.91256303526217e-3
zig_x = [0 for i in range(C+1)]
zig_r = [0 for i in range(C)]
#C code:
#static void zigNorInit(int iC, double dR, double dV)
#{
# int i; double f;
# f = exp(-0.5 * R * R);
# zig_x[0] = V / f;
# zig_x[1] = R;
# zig_x[iC] = 0;
# for (i = 2; i < C; ++i)
# {
# zig_x[i] = sqrt(-2 * log(V / zig_x[i - 1] + f));
# f = exp(-0.5 * zig_x[i] * zig_x[i]);
# }
# for (i = 0; i < C; ++i)
# zig_r[i] = zig_x[i + 1] / zig_x[i];
#}
def main():
f = exp(-0.5 * R * R)
zig_x[0] = V / f
zig_x[1] = R
zig_x[C] = 0
for i in range(2, C):
zig_x[i] = sqrt(-2 * log(V / zig_x[i - 1] + f))
f = exp(-0.5 * zig_x[i] * zig_x[i])
for i in range(C):
zig_r[i] = zig_x[i + 1] / zig_x[i]
print("X:")
for x in zig_x:
print(f"{x}, ")
print("R: ")
for r in zig_r:
print(f"{r}, ")
if __name__ == "__main__":
main()

267
src/Core/Deserialize.cpp Normal file
View File

@@ -0,0 +1,267 @@
/**
* \file Deserialize.cpp
* \author Weckyy702 (weckyy702@gmail.com)
* \brief Implementation file for Deserialize class
* \date 2022-05-19
*
* MIT License
* Copyright (c) [2022] [Weckyy702 (weckyy702@gmail.com | https://github.com/Weckyy702)]
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
#include "Raychel/Core/Deserialize.h"
#include "Raychel/Core/SDFPrimitives.h"
namespace Raychel {
struct DeserializerState
{
std::istream& input_stream;
const std::vector<details::DeserializerPtr<SDFContainer>>& object_deserializers;
const std::vector<details::DeserializerPtr<MaterialContainer>>& material_deserializers;
std::vector<SDFContainer> objects{};
std::vector<SerializableObjectData<SDFContainer>> object_serializers{};
std::vector<MaterialContainer> materials{};
std::vector<SerializableObjectData<MaterialContainer>> material_serializers{};
bool is_in_object_section{true};
};
template <typename Container>
struct ContainerAndSerializer
{
Container container;
SerializableObjectData<Container> serializer;
};
template <typename T>
ContainerAndSerializer(T, SerializableObjectData<T>) -> ContainerAndSerializer<T>;
enum class ParseObjectFromLineResult {
ok = 0,
entered_material_section,
no_type_name_separator,
empty_line,
};
static bool check_object_header(std::istream& is) noexcept
{
std::string line{};
std::getline(is, line);
if (line == "--BEGIN SURFACES--")
return true;
Logger::warn("Incorrect surface section header '", line, "'\n");
return false;
}
static std::string get_shortened_line(std::istream& is) noexcept
{
std::string line{};
if (!std::getline(is, line))
return {};
//skip whitespace characters
auto current_char = line.begin();
while (std::isspace(*current_char) != 0) {
current_char++;
}
return {current_char, line.end()};
}
template <typename Container>
static details::IDeserializer<Container>* find_deserializer_for(
std::string_view type_name, const std::vector<details::DeserializerPtr<Container>>& deserializers) noexcept
{
for (const auto& deserializer : deserializers) {
if (deserializer->contained_type_name() == type_name) {
return deserializer.get();
}
}
Logger::warn("Could not find deserializer for type name '", type_name, "'\n");
return nullptr;
}
template <typename Container>
static std::optional<ContainerAndSerializer<Container>> parse_object(
std::string_view type_name, std::string_view rest_of_line,
const std::vector<details::DeserializerPtr<Container>>& deserializers) noexcept
{
const auto maybe_deserializer = find_deserializer_for(type_name, deserializers);
if (maybe_deserializer == nullptr)
return std::nullopt;
std::stringstream line_stream{std::string{rest_of_line}}; //Hmmmmmmmmmmmmmmmmmmm
auto maybe_object = maybe_deserializer->deserialize(line_stream, std::nullopt);
if (!maybe_object.has_value()) {
Logger::warn("Could not deserialize object of type '", type_name, " with data '", rest_of_line, "'\n");
return std::nullopt;
}
return ContainerAndSerializer{std::move(maybe_object).value(), maybe_deserializer->get_serializer()};
}
template <typename Container>
static std::optional<ContainerAndSerializer<Container>> parse_targeted(
DeserializerState& state, std::string_view type_name, std::string_view rest_of_line,
const std::vector<details::DeserializerPtr<Container>>& deserializers) noexcept
{
const auto maybe_deserializer = find_deserializer_for(type_name, deserializers);
if (maybe_deserializer == nullptr)
return std::nullopt;
auto [maybe_target_and_serializer, result] = parse_object_from_line(state, deserializers);
if (result != ParseObjectFromLineResult::ok)
return std::nullopt;
if (!maybe_target_and_serializer.has_value())
return std::nullopt;
Container target = std::move(maybe_target_and_serializer).value().container;
std::stringstream line_stream{std::string{rest_of_line}};
auto maybe_object = maybe_deserializer->deserialize(line_stream, std::move(target));
if (!maybe_object.has_value()) {
Logger::warn("Could not deserialize object of type '", type_name, " with data '", rest_of_line, "'\n");
return std::nullopt;
}
return ContainerAndSerializer{std::move(maybe_object).value(), maybe_deserializer->get_serializer()};
}
template <typename Container>
static std::optional<ContainerAndSerializer<Container>> parse_type(
DeserializerState& state, std::string_view type_name, std::string_view rest_of_line,
const std::vector<details::DeserializerPtr<Container>>& deserializers) noexcept
{
//If the type is has a target, recursively resolve that target
if (type_name.ends_with("<>")) {
type_name.remove_suffix(2U);
return parse_targeted(state, type_name, rest_of_line, deserializers);
}
return parse_object(type_name, rest_of_line, deserializers);
}
template <typename Container>
static bool add_object(
std::optional<ContainerAndSerializer<Container>> maybe_object, std::vector<Container>& containers,
std::vector<SerializableObjectData<Container>>& serializers) noexcept
{
if (!maybe_object.has_value())
return false;
auto [container, serializer] = std::move(maybe_object).value();
containers.emplace_back(std::move(container));
serializers.emplace_back(std::move(serializer));
return true;
}
template <typename Container>
static std::pair<std::optional<ContainerAndSerializer<Container>>, ParseObjectFromLineResult> parse_object_from_line(
DeserializerState& state, const std::vector<details::DeserializerPtr<Container>>& deserializers) noexcept
{
const auto line = get_shortened_line(state.input_stream);
if (line.empty())
return {std::nullopt, ParseObjectFromLineResult::empty_line};
if (line == "--BEGIN MATERIALS--") {
return {std::nullopt, ParseObjectFromLineResult::entered_material_section};
}
const auto end_of_type_name_index = line.find(" with ");
if (end_of_type_name_index == std::string::npos) {
Logger::warn("Incorrect type name separator!\n");
return {std::nullopt, ParseObjectFromLineResult::no_type_name_separator};
}
const auto end_of_type_name = line.begin() + static_cast<std::ptrdiff_t>(end_of_type_name_index);
std::string_view type_name{line.begin(), end_of_type_name};
std::string_view rest_of_line{end_of_type_name + 6, line.end()};
return std::make_pair(parse_type(state, type_name, rest_of_line, deserializers), ParseObjectFromLineResult::ok);
}
static bool parse_line(DeserializerState& state) noexcept
{
if (state.is_in_object_section) {
auto [maybe_object, result] = parse_object_from_line(state, state.object_deserializers);
if (result == ParseObjectFromLineResult::entered_material_section) {
state.is_in_object_section = false;
return true;
}
if (result == ParseObjectFromLineResult::empty_line)
return true;
if (result != ParseObjectFromLineResult::ok)
return false;
return add_object(std::move(maybe_object), state.objects, state.object_serializers);
}
auto [maybe_material, result] = parse_object_from_line(state, state.material_deserializers);
if (result == ParseObjectFromLineResult::entered_material_section) {
Logger::warn("Entered material section twice!\n");
return false;
}
if (result == ParseObjectFromLineResult::empty_line)
return true;
if (result != ParseObjectFromLineResult::ok) {
return false;
}
return add_object(std::move(maybe_material), state.materials, state.material_serializers);
}
static void place_dummy(DeserializerState& state) noexcept
{
if (state.is_in_object_section) {
state.objects.emplace_back(DeserializationErrorPlaceHolder{});
state.object_serializers.emplace_back(details::SerializableObjectDescriptor<DeserializationErrorPlaceHolder>{});
return;
}
state.materials.emplace_back(DeserializationErrorMaterial{});
state.material_serializers.emplace_back((details::SerializableObjectDescriptor<DeserializationErrorMaterial>{}));
}
Scene deserialize_scene(
std::istream& is, const std::vector<details::DeserializerPtr<SDFContainer>>& object_deserializers,
const std::vector<details::DeserializerPtr<MaterialContainer>>& material_deserializers) noexcept
{
DeserializerState state{is, object_deserializers, material_deserializers};
if (!check_object_header(is))
return {};
while (state.input_stream) {
if (!parse_line(state)) {
Logger::debug("Placing dummy\n");
place_dummy(state);
}
}
if (state.is_in_object_section) {
Logger::warn("Parser did not leave object section! Incorrect material section header?\n");
}
return Scene::unsafe_from_data(
std::move(state.objects),
std::move(state.object_serializers),
std::move(state.materials),
std::move(state.material_serializers));
}
} //namespace Raychel

87
src/Core/Raymarch.cpp Normal file
View File

@@ -0,0 +1,87 @@
/**
* \file Raymarch.cpp
* \author Weckyy702 (weckyy702@gmail.com)
* \brief Implementation file for Raymarch class
* \date 2022-06-11
*
* MIT License
* Copyright (c) [2022] [Weckyy702 (weckyy702@gmail.com | https://github.com/Weckyy702)]
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
#include "Raychel/Core/Raymarch.h"
#include "Raychel/Core/SDFContainer.h"
namespace Raychel {
std::pair<double, std::size_t> evaluate_distance_field(const std::vector<SDFContainer>& surfaces, const vec3& point) noexcept
{
const auto surfaces_size = surfaces.size();
double min_distance{1e9};
auto hit_index = no_hit;
for (std::size_t i{}; i != surfaces_size; ++i) {
const auto surface_distance = std::abs(surfaces[i].evaluate(point));
if (surface_distance < min_distance) {
hit_index = i;
min_distance = surface_distance;
}
}
return {min_distance, hit_index};
}
RaymarchResult raymarch(
vec3 current_point, const vec3& direction, const std::vector<SDFContainer>& surfaces, RaymarchOptions options) noexcept
{
double depth{};
std::size_t step{};
while (step != options.max_ray_steps && depth < options.max_ray_depth) {
const auto [max_distance, hit_index] = evaluate_distance_field(surfaces, current_point);
if (max_distance < options.surface_epsilon) {
return {current_point, depth, step, hit_index};
}
current_point += direction * max_distance;
depth += max_distance;
++step;
}
return {current_point, depth, step, no_hit};
}
vec3 get_normal(const vec3& point, const SDFContainer& surface, double normal_offset) noexcept
{
//This implements the tetrahedon sampling technique found at https://iquilezles.org/articles/normalsSDF/
constexpr vec3 xyy{1, -1, -1}, yyx{-1, -1, 1}, yxy{-1, 1, -1}, xxx{1, 1, 1};
if (surface.has_custom_normal()) {
return surface.get_normal(point);
}
// clang-format off
return normalize(vec3{
xyy*surface.evaluate(point+xyy*normal_offset) +
yyx*surface.evaluate(point+yyx*normal_offset) +
yxy*surface.evaluate(point+yxy*normal_offset) +
xxx*surface.evaluate(point+xxx*normal_offset)
});
// clang-format on
}
} // namespace Raychel

View File

@@ -0,0 +1,78 @@
/**
* \file SDFPrimitives.cpp
* \author Weckyy702 (weckyy702@gmail.com)
* \brief Implementation file for SDFPrimitives class
* \date 2022-05-27
*
* MIT License
* Copyright (c) [2022] [Weckyy702 (weckyy702@gmail.com | https://github.com/Weckyy702)]
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
#include "Raychel/Core/SDFPrimitives.h"
namespace Raychel {
bool do_serialize(std::ostream& os, const Sphere& object) noexcept
{
os << object.radius << '\n';
return os.good();
}
std::optional<Sphere> do_deserialize(std::istream& is, DeserializationTag<Sphere>) noexcept
{
double radius{};
if (!(is >> radius))
return std::nullopt;
return Sphere{radius};
}
bool do_serialize(std::ostream& os, const Box& object) noexcept
{
os << object.size << '\n';
return os.good();
}
std::optional<Box> do_deserialize(std::istream& is, DeserializationTag<Box> /*unused*/) noexcept
{
vec3 size{};
if (!(is >> size))
return std::nullopt;
return Box{size};
}
bool do_serialize(std::ostream& os, const Plane& object) noexcept
{
os << object.normal << '\n';
return os.good();
}
std::optional<Plane> do_deserialize(std::istream& is, DeserializationTag<Plane>) noexcept
{
vec3 normal{};
if (!(is >> normal))
return std::nullopt;
if (normal == vec3{})
return std::nullopt;
return Plane{normalize(normal)};
}
} //namespace Raychel

75
src/Core/Scene.cpp Normal file
View File

@@ -0,0 +1,75 @@
/**
* \file Scene.cpp
* \author Weckyy702 (weckyy702@gmail.com)
* \brief Implementation file for Scene class
* \date 2022-05-17
*
* MIT License
* Copyright (c) [2022] [Weckyy702 (weckyy702@gmail.com | https://github.com/Weckyy702)]
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
#include "Raychel/Core/Scene.h"
namespace Raychel {
void Scene::remove_object(std::size_t _index) noexcept
{
using Diff = std::iter_difference_t<decltype(objects_.begin())>;
if (_index >= objects_.size()) {
return;
}
RAYCHEL_ASSERT(_index < std::numeric_limits<Diff>::max())
const auto index = static_cast<Diff>(_index);
objects_.erase(objects_.begin() + index);
materials_.erase(materials_.begin() + index);
object_serializers_.erase(object_serializers_.begin() + index);
material_serializers_.erase(material_serializers_.begin() + index);
}
Scene Scene::unsafe_from_data(
std::vector<SDFContainer> objects, std::vector<SerializableObjectData<SDFContainer>> object_serializers,
std::vector<MaterialContainer> materials,
std::vector<SerializableObjectData<MaterialContainer>> material_serializers) noexcept
{
//These all need to be the exact same size
if (!(objects.size() == object_serializers.size() && objects.size() == materials.size() &&
objects.size() == material_serializers.size())) {
Logger::warn(
"Unable to create Scene from invalid data! Data sizes:"
"\n Objects: ",
objects.size(),
"\n Object serializers: ",
object_serializers.size(),
"\n Materials: ",
materials.size(),
"\n Material serializers: ",
material_serializers.size(),
'\n');
return Scene{};
}
return Scene{std::move(objects), std::move(object_serializers), std::move(materials), std::move(material_serializers)};
}
} //namespace Raychel

55
src/Core/Serialize.cpp Normal file
View File

@@ -0,0 +1,55 @@
/**
* \file Serialize.cpp
* \author Weckyy702 (weckyy702@gmail.com)
* \brief Implementation file for Serialize class
* \date 2022-05-19
*
* MIT License
* Copyright (c) [2022] [Weckyy702 (weckyy702@gmail.com | https://github.com/Weckyy702)]
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
#include "Raychel/Core/Serialize.h"
#include "Raychel/Core/Scene.h"
namespace Raychel {
bool serialize_scene(const Scene& scene, std::ostream& os) noexcept
{
std::size_t i{};
os << "--BEGIN SURFACES--\n";
for (const auto& serializer : scene.object_serializers()) {
if (!serializer.serialize(scene.objects().at(i), os))
return false;
++i;
}
i = 0U;
os << "--BEGIN MATERIALS--\n";
for (const auto& serializer : scene.material_serializers()) {
if (!serializer.serialize(scene.materials().at(i), os))
return false;
++i;
}
return true;
}
} //namespace Raychel

212
src/Core/ZigguratNormal.cpp Normal file
View File

@@ -0,0 +1,212 @@
/**
* \file ZigguratNormal.cpp
* \author Weckyy702 (weckyy702@gmail.com)
* \brief Implementation for the Ziggurat algorithm to approximate a normal distribution (taken from https://www.jstatsoft.org/article/download/v005i08/623)
* \date 2022-04-24
*
* MIT License
* Copyright (c) [2022] [Weckyy702 (weckyy702@gmail.com | https://github.com/Weckyy702)]
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
#include "Raychel/Core/ZigguratNormal.h"
#include "Raychel/Core/Xoroshiro128+.h"
#include "RaychelCore/Raychel_assert.h"
#include "RaychelMath/math.h"
#include <array>
#include <cmath>
#include <cstdint>
#include <random>
namespace Raychel {
namespace details {
constexpr auto R = 3.6541528853610088;
constexpr static std::array X_table{
3.7130862467425505, 3.442619855899, 3.2230849845811416,
3.0832288582168683, 2.9786962526477803, 2.894344007021529,
2.8231253505489105, 2.761169372387177, 2.7061135731218195,
2.6564064112613597, 2.6109722484318474, 2.569033625924938,
2.5300096723888275, 2.493454522095372, 2.4590181774118305,
2.42642064553375, 2.3954342780110625, 2.3658713701176386,
2.3375752413392368, 2.310413683698763, 2.2842740596774718,
2.2590595738691985, 2.2346863955909795, 2.2110814088787034,
2.188180432076049, 2.165926793748922, 2.1442701823603953,
2.1231657086739766, 2.1025731351892385, 2.082456237992017,
2.0627822745083084, 2.0435215366550676, 2.0246469733773855,
2.006133869963472, 1.98795957412762, 1.9701032608543265,
1.9525457295535567, 1.9352692282966228, 1.9182573008645099,
1.901494653105151, 1.884967035707759, 1.8686611409944887,
1.8525645117280911, 1.836665460258446, 1.8209529965961255,
1.8054167642192285, 1.7900469825998586, 1.7748343955860695,
1.7597702248995934, 1.7448461281138004, 1.7300541605637305,
1.7153867407136676, 1.7008366185699169, 1.6863968467791681,
1.672060754097601, 1.6578219209540241, 1.6436741568628686,
1.6296114794706347, 1.615628095043161, 1.6017183802213781,
1.5878768648905761, 1.5740982160230008, 1.560377222366169,
1.5467087798599104, 1.5330878776740433, 1.5195095847659401,
1.5059690368632033, 1.492461423781354, 1.4789819769899242,
1.4655259573427108, 1.4520886428892246, 1.4386653166845635,
1.42525125451406, 1.4118417124470577, 1.3984319141310053,
1.3850170377326518, 1.3715922024273426, 1.3581524543301435,
1.344692751753547, 1.3312079496656273, 1.317692783209414,
1.3041418501286168, 1.2905495919261964, 1.2769102735601556,
1.263217961454621, 1.2494664995730682, 1.2356494832633627,
1.2217602305399964, 1.2077917504159497, 1.1937367078331287,
1.1795873846639882, 1.1653356361647524, 1.1509728421488674,
1.1364898520131608, 1.1218769225825422, 1.107123647534036,
1.0922188769072774, 1.0771506248928957, 1.0619059636948243,
1.0464709007640454, 1.0308302360681956, 1.0149673952513305,
0.9988642334929836, 0.982500803515429, 0.9658550794011499,
0.9489026255113064, 0.9316161966151508, 0.9139652510230323,
0.8959153525809377, 0.8774274291129234, 0.8584568431938132,
0.8389522142975774, 0.8188539067003573, 0.7980920606440569,
0.7765839878947599, 0.7542306644540556, 0.7309119106424888,
0.7064796113354365, 0.6807479186691546, 0.6534786387399752,
0.6243585973360507, 0.5929629424714483, 0.5586921784081852,
0.5206560387620606, 0.4774378372966898, 0.4265479863554235,
0.36287143109703196, 0.27232086481396467, 0.0,
};
constexpr static std::array R_table{
0.9271586026096681, 0.9362302895738892, 0.9566079929529229, 0.9660963845448882,
0.971681487982781, 0.9753938521821022, 0.9780541171685178, 0.980060694640489,
0.9816315315239645, 0.9828963811271866, 0.9839375456663325, 0.9848098704733534,
0.9855513792328944, 0.9861893030819736, 0.9867436799867864, 0.9872295978111943,
0.9876586437103296, 0.9880398701570176, 0.9883804563121089, 0.9886861715693078,
0.9889617072428545, 0.9892109183130244, 0.9894370025436909, 0.9896426351781105,
0.9898300715969688, 0.9900012265183524, 0.9901577357834697, 0.9903010050508025,
0.9904322485336944, 0.9905525200843218, 0.9906627383358567, 0.9907637071892196,
0.9908561326209719, 0.9909406365607181, 0.991017768416579, 0.9910880146997187,
0.991151807102165, 0.991209529308185, 0.9912615227624552, 0.9913080915739614,
0.9913495066999154, 0.9913860095266759, 0.9914178149430195, 0.9914451139838447,
0.9914680761085329, 0.9914868511670121, 0.9915015710974835, 0.9915123513923666,
0.9915192923629307, 0.9915224802280646, 0.9915219880484646, 0.9915178765240442,
0.9915101946694387, 0.9914989803800052, 0.9914842608986051, 0.9914660531916395,
0.9914443642412228, 0.9914191912590011, 0.9913905218258715, 0.9913583339607497,
0.9913225961204966, 0.9912832671321499, 0.9912402960576856, 0.991193621990624,
0.991143173782899, 0.991088869699481, 0.9910306169972894, 0.9909683114239041,
0.9909018366304913, 0.9908310634921467, 0.9907558493275227, 0.9906760370080955,
0.9905914539457294, 0.9905019109452362, 0.9904072009063883, 0.990307097357238,
0.990201352797563, 0.9900896968277136, 0.9899718340339569, 0.9898474415964779,
0.9897161665803526, 0.9895776228628198, 0.9894313876418468, 0.9892769974609422,
0.9891139436730952, 0.9889416672520418, 0.9887595528412437, 0.9885669219091597,
0.9883630248526034, 0.9881470318569457, 0.9879180222809051, 0.987674972282531,
0.9874167403388364, 0.9871420502305995, 0.9868494709610887, 0.9865373929461655,
0.986203999644239, 0.9858472335755389, 0.98546475539409, 0.9850538942989907,
0.9846115875710347, 0.9841343063494573, 0.9836179638544746, 0.9830578010168337,
0.9824482427525728, 0.9817827157061126, 0.9810534148544756, 0.9802510014227667,
0.9793642073274506, 0.9783793105963312, 0.9772794298852922, 0.9760435609386315,
0.9746452378300764, 0.9730506368752245, 0.9712158326862985, 0.9690827290502092,
0.9665728537853818, 0.9635775863118795, 0.959942176565901, 0.9554384188286962,
0.9497153478809163, 0.9422042060159378, 0.9319193267489506, 0.9169927970716931,
0.8934105197245976, 0.8507165493794344, 0.7504610213889943, 0.0,
};
//NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
static thread_local std::uint32_t xorshift32_seed{2463534242U};
[[nodiscard]] std::uint32_t random32() noexcept
{
xorshift32_seed ^= (xorshift32_seed << 13U);
xorshift32_seed ^= (xorshift32_seed >> 17U);
xorshift32_seed ^= (xorshift32_seed << 5U);
return xorshift32_seed;
}
//NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
static thread_local std::uint64_t xorshift64_seed{88172645463325252U};
[[nodiscard]] std::uint64_t random64() noexcept
{
xorshift64_seed ^= (xorshift64_seed << 13U);
xorshift64_seed ^= (xorshift64_seed >> 7U);
xorshift64_seed ^= (xorshift64_seed << 17U);
return xorshift64_seed;
}
//Fast way to generate "uniform" reals in the range [0; 1]. Credit to Inigo Quilez (https://iquilezles.org/articles/sfrand/)
[[nodiscard]] double uniform01() noexcept
{
//The idea is to generate 52 random bits for the mantissa and fix the exponent to 1023 to generate a random number between 1 and 2.
//We can then easily map that number into the [0; 1] range by subtracting 1
static_assert(sizeof(std::uint64_t) == sizeof(double));
constexpr std::uint64_t fix_exponent{0x3FF0000000000000};
const auto mantissa = (random64() >> 12U);
return std::bit_cast<double>(mantissa | fix_exponent) - 1.0;
}
[[nodiscard]] static double normal_tail(bool is_negative) noexcept
{
double x{};
double y{};
std::size_t guard{};
do {
x = std::log(uniform01()) / R;
y = std::log(uniform01());
RAYCHEL_ASSERT(++guard != 16U);
} while (-2 * y < sq(x));
if (is_negative) {
return x - R;
}
return R - x;
}
} // namespace details
double uniform_random() noexcept
{
return details::uniform01() * 2 - 1;
}
double ziggurat_normal() noexcept
{
//The guard variable is just to prevent an infinte loop. It's not gonna matter in practice
for (std::size_t guard{}; guard != 16U; ++guard) {
const auto u = 2.0 * details::uniform01() - 1.0;
const auto i = details::random32() & 0x7FU;
/* first try the rectangular boxes */
if (std::abs(u) < details::R_table.at(i)) {
return u * details::X_table.at(i);
}
/* bottom box: sample from the tail */
if (i == 0) {
return details::normal_tail(u < 0);
}
/* is this a sample from the wedges? */
const auto x = u * details::X_table.at(i);
const auto f0 = std::exp(-0.5 * (sq(details::X_table.at(i)) - sq(x)));
const auto f1 = std::exp(-0.5 * (sq(details::X_table.at(i + 1)) - sq(x)));
if (f1 + details::uniform01() * (f0 - f1) < 1.0) {
return x;
}
}
RAYCHEL_ASSERT_NOT_REACHED;
}
} //namespace Raychel

389
src/Render/Denoise.cpp Normal file
View File

@@ -0,0 +1,389 @@
/**
* \file Denoise.cpp
* \author Weckyy702 (weckyy702@gmail.com)
* \brief Implementation of the Ray Histogram Fusion algorithm by Mauricio Delbracio et al.
* \date 2022-05-02
*
* MIT License
* Copyright (c) [2022] [Weckyy702 (weckyy702@gmail.com | https://github.com/Weckyy702)]
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
#include "Raychel/Render/Denoise.h"
#include <cmath>
#include <functional>
#include <thread>
namespace Raychel {
static std::size_t to_index(std::size_t x, std::size_t y, std::size_t image_width)
{
return x + (y * image_width);
}
static std::size_t safe_sub(std::size_t a, std::size_t b)
{
if (b > a) {
return 0U;
}
return a - b;
}
template <std::size_t NumBins>
static double chi_squared_distance(const std::array<double, NumBins>& a, const std::array<double, NumBins>& b)
{
double sum{};
double num_nonempty_bins{};
for (std::size_t i{}; i != NumBins; ++i) {
const auto divisor = a[i] + b[i];
if (divisor != 0.0) {
sum += sq(a[i] - b[i]) / divisor;
++num_nonempty_bins;
}
}
if (num_nonempty_bins == 0.0) {
return 0.0;
}
return sum / num_nonempty_bins;
}
template <std::size_t NumBins>
static auto chi_squared_distance(const RayHistogram<NumBins>& a, const RayHistogram<NumBins>& b)
{
//TODO: process the three channels in one loop
return Tuple{
chi_squared_distance(a.red_channel(), b.red_channel()),
chi_squared_distance(a.green_channel(), b.green_channel()),
chi_squared_distance(a.blue_channel(), b.blue_channel())};
}
struct SearchWindow
{
[[nodiscard]] std::size_t width() const noexcept
{
return end_x - start_x;
}
[[nodiscard]] std::size_t height() const noexcept
{
return end_y - start_y;
}
[[nodiscard]] std::size_t area() const noexcept
{
return width() * height();
}
std::size_t start_x{}, start_y{}, end_x{}, end_y{};
};
using Patch = SearchWindow;
static SearchWindow
search_window_for_pixel(std::size_t x, std::size_t y, Size2D image_size, std::size_t half_search_window_size) noexcept
{
const auto start_x = safe_sub(x, half_search_window_size);
const auto start_y = safe_sub(y, half_search_window_size);
const auto end_x = std::min(x + half_search_window_size, image_size.x());
const auto end_y = std::min(y + half_search_window_size, image_size.y());
return {start_x, start_y, end_x, end_y};
}
static Patch patch_for_pixel(std::size_t x, std::size_t y, Size2D image_size, std::size_t half_patch_size) noexcept
{
return search_window_for_pixel(x, y, image_size, half_patch_size);
}
static std::vector<color> get_denoised_patch_with_search_window(
const SearchWindow& search_window, const Patch& this_patch, const std::vector<FatPixel>& input_pixels,
const Size2D image_size, const DenoisingOptions& options) noexcept
{
std::vector<Tuple<double, 3>> c{this_patch.area()};
std::vector<color> V(this_patch.area());
for (auto search_y = search_window.start_y; search_y != search_window.end_y; ++search_y) {
for (auto search_x = search_window.start_x; search_x != search_window.end_x; ++search_x) {
const auto& other_patch = patch_for_pixel(search_x, search_y, image_size, options.half_patch_size);
for (auto this_y = this_patch.start_y; this_y != this_patch.end_y; ++this_y) {
for (auto this_x = this_patch.start_x; this_x != this_patch.end_x; ++this_x) {
const auto index_in_image = to_index(this_x, this_y, image_size.x());
const auto& this_pixel = input_pixels.at(index_in_image);
const auto index_in_patch =
to_index(this_x - this_patch.start_x, this_y - this_patch.start_y, this_patch.width());
for (auto other_y = other_patch.start_y; other_y != other_patch.end_y; ++other_y) {
for (auto other_x = other_patch.start_x; other_x != other_patch.end_x; ++other_x) {
const auto& other_pixel = input_pixels.at(to_index(other_x, other_y, image_size.x()));
const auto d = chi_squared_distance(this_pixel.histogram, other_pixel.histogram);
for (std::size_t i{}; i != 3U; ++i) {
if (d[i] < options.distance_threshold) {
V.at(index_in_patch)[i] += other_pixel.noisy_color[i];
++c.at(index_in_patch)[i];
}
}
}
}
}
}
}
}
for (std::size_t i{}; i != V.size(); ++i) {
for (std::size_t j{}; j != 3U; ++j) {
if (c.at(i)[j] != 0) {
V.at(i)[j] /= c.at(i)[j];
}
}
}
return V;
}
static void denoise_part(
std::vector<color>& output, Size2D begin, Size2D end, const std::vector<FatPixel>& input_pixels, Size2D image_size,
DenoisingOptions options) noexcept
{
std::vector<double> N(input_pixels.size());
for (auto y = begin.y(); y != end.y(); ++y) {
for (auto x = begin.x(); x != end.x(); ++x) {
const auto this_patch = patch_for_pixel(x, y, image_size, options.half_patch_size);
const auto search_window = search_window_for_pixel(x, y, image_size, options.half_search_window_size);
const auto V =
get_denoised_patch_with_search_window(search_window, this_patch, input_pixels, image_size, options);
for (auto patch_y = this_patch.start_y; patch_y != this_patch.end_y; ++patch_y) {
for (auto patch_x = this_patch.start_x; patch_x != this_patch.end_x; ++patch_x) {
const auto index_in_patch =
to_index(patch_x - this_patch.start_x, patch_y - this_patch.start_y, this_patch.width());
const auto index_in_image = to_index(patch_x, patch_y, image_size.x());
++N.at(index_in_image);
auto& p = output.at(index_in_image);
p += (V.at(index_in_patch) - p) / N.at(index_in_image);
}
}
}
}
}
template <
typename Pixel, std::invocable<Pixel, Pixel> Add = std::plus<Pixel>,
std::invocable<Pixel, std::size_t> Divide = std::divides<void>>
details::BasicFramebuffer<Pixel> gaussian_subsample(
const details::BasicFramebuffer<Pixel>& input_pixels, std::size_t scale, Add&& adder = {}, Divide&& divider = {})
{
//Because we use operator<<, specifying a scale larger than the number of bits in an unsigned int would result in UB.
RAYCHEL_ASSERT(scale < (sizeof(unsigned int) * 8));
if (scale == 0U) {
return input_pixels;
}
const auto half_sample_window_size = (1U << (scale - 1));
const auto scaling_factor = std::pow(0.5, static_cast<double>(scale));
const auto pixel_step = 1U << scale;
const auto scaled_size = input_pixels.size * scaling_factor;
if (scaled_size == Size2D{}) {
return {};
}
std::vector<Pixel> output_pixels{scaled_size.x() * scaled_size.y(), Pixel{}};
for (std::size_t y{}; y != scaled_size.y(); ++y) {
for (std::size_t x{}; x != scaled_size.x(); ++x) {
const auto sample_patch =
patch_for_pixel(x * pixel_step, y * pixel_step, input_pixels.size, half_sample_window_size);
std::size_t num_samples{};
Pixel output_pixel{};
for (auto patch_y = sample_patch.start_y; patch_y != sample_patch.end_y; ++patch_y) {
for (auto patch_x = sample_patch.start_x; patch_x != sample_patch.end_x; ++patch_x) {
++num_samples;
const auto& sample_pixel = input_pixels.pixel_data.at(to_index(patch_x, patch_y, input_pixels.size.x()));
output_pixel = adder(output_pixel, sample_pixel);
}
}
output_pixel = divider(output_pixel, num_samples);
output_pixels.at(to_index(x, y, scaled_size.x())) = output_pixel;
}
}
return {scaled_size, output_pixels};
}
static void add_scaled_part(
const Raychel::Framebuffer& scaled_input, std::vector<Raychel::color>& output, std::size_t scale, std::size_t num_scales)
{
using namespace Raychel;
RAYCHEL_ASSERT(scale < (sizeof(unsigned int) * 8));
if (scale == 0U) {
std::copy(scaled_input.pixel_data.begin(), scaled_input.pixel_data.end(), output.begin());
return;
}
const auto scaling_factor = 1U << scale;
const auto step_size = std::pow(0.5, static_cast<double>(scale));
const auto correction_factor = 0.5 / static_cast<double>(num_scales);
for (std::size_t y{}; y != scaled_input.size.y(); ++y) {
for (std::size_t x{}; x != scaled_input.size.x(); ++x) {
const auto next_x = std::min(x + 1U, scaled_input.size.x() - 1U);
const auto next_y = std::min(y + 1U, scaled_input.size.y() - 1U);
const auto top_left = scaled_input.at(x, y);
const auto top_right = scaled_input.at(next_x, y);
const auto bottom_left = scaled_input.at(x, next_y);
const auto bottom_right = scaled_input.at(next_x, next_y);
const auto large_x = x * scaling_factor;
const auto large_y = y * scaling_factor;
for (std::size_t part_y{}; part_y != scaling_factor; ++part_y) {
for (std::size_t part_x{}; part_x != scaling_factor; ++part_x) {
const auto relative_x = static_cast<double>(part_x) * step_size;
const auto relative_y = static_cast<double>(part_y) * step_size;
const auto right_weight = std::fmod(relative_x * static_cast<double>(scaling_factor), 1.0);
const auto left_weight = 1.0 - right_weight;
const auto bottom_weight = std::fmod(relative_y * static_cast<double>(scaling_factor), 1.0);
const auto top_weight = 1.0 - bottom_weight;
// clang-format off
output.at(to_index(large_x + part_x, large_y + part_y, scaled_input.size.x() * scaling_factor)) += (
(top_left * (top_weight * left_weight)) +
(top_right * (top_weight * right_weight)) +
(bottom_left * (top_weight * left_weight)) +
(bottom_right * (bottom_weight * right_weight))
) * correction_factor;
// clang-format on
}
}
}
}
}
static void denoise_threaded(
std::vector<color>& output, const FatFramebuffer& input_pixels, unsigned int num_threads,
DenoisingOptions options) noexcept
{
constexpr Size2D patch_size{128, 128};
std::vector<std::jthread> thread_pool{};
thread_pool.reserve(num_threads);
std::atomic_size_t current_patch_index{0U};
const auto num_patches_x =
static_cast<std::size_t>(std::ceil(static_cast<double>(input_pixels.size.x()) / static_cast<double>(patch_size.x())));
const auto num_patches_y =
static_cast<std::size_t>(std::ceil(static_cast<double>(input_pixels.size.y()) / static_cast<double>(patch_size.y())));
const auto max_patch_index = num_patches_x * num_patches_y - 1U;
for (std::size_t i{}; i != num_threads; ++i) {
thread_pool.emplace_back([&, i] {
std::size_t patch_index{};
do {
patch_index = current_patch_index.fetch_add(1U);
const auto x_index = patch_index % num_patches_x;
const auto y_index = patch_index / num_patches_x;
const Size2D patch_begin{
std::min(x_index * patch_size.x(), input_pixels.size.x()),
std::min(y_index * patch_size.y(), input_pixels.size.y())};
const Size2D patch_end{
std::min(patch_begin.x() + patch_size.x(), input_pixels.size.x()),
std::min(patch_begin.y() + patch_size.y(), input_pixels.size.y())};
Logger::debug("Thread ", i, " got patch ", patch_index, " from ", patch_begin, " to ", patch_end, '\n');
denoise_part(output, patch_begin, patch_end, input_pixels.pixel_data, input_pixels.size, options);
} while (patch_index < max_patch_index);
});
}
}
void denoise_internal(std::vector<color>& output, const FatFramebuffer& input_pixels, DenoisingOptions options) noexcept
{
const auto num_threads = std::thread::hardware_concurrency();
if (num_threads == 0U) {
denoise_part(output, Size2D{}, input_pixels.size, input_pixels.pixel_data, input_pixels.size, options);
return;
}
denoise_threaded(output, input_pixels, num_threads, options);
}
Framebuffer denoise_single_scale(const FatFramebuffer& input_pixels, DenoisingOptions options) noexcept
{
std::vector<color> output(input_pixels.pixel_data.size());
denoise_internal(output, input_pixels, options);
return {input_pixels.size, std::move(output)};
}
Framebuffer denoise_multiscale(const FatFramebuffer& input_pixels, DenoisingOptions options) noexcept
{
if (options.num_scales == 1U) {
return denoise_single_scale(input_pixels, options);
}
RAYCHEL_TODO("Multiscale denoising");
auto scale = options.num_scales - 1U;
std::vector<color> u_old{};
while (scale != 0) {
//uS = Ds(u)
const auto scaled_input = gaussian_subsample(input_pixels, scale);
//TODO: verify that the number of samples is still the same
std::vector<color> scaled_output{scaled_input.pixel_data.size(), color{}};
denoise_internal(scaled_output, scaled_input, options);
add_scaled_part({input_pixels.size, scaled_output}, u_old, scale, options.num_scales);
--scale;
}
return {input_pixels.size, std::move(u_old)};
}
} // namespace Raychel

260
src/Render/RenderUtils.cpp Normal file
View File

@@ -0,0 +1,260 @@
/**
* \file RenderUtils.cpp
* \author Weckyy702 (weckyy702@gmail.com)
* \brief Implementation file for RenderUtils class
* \date 2022-04-12
*
* MIT License
* Copyright (c) [2022] [Weckyy702 (weckyy702@gmail.com | https://github.com/Weckyy702)]
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
#include "Raychel/Render/RenderUtils.h"
#include <iterator>
#include "Raychel/Core/Raymarch.h"
#include "Raychel/Core/ZigguratNormal.h"
#include "RaychelMath/vector.h"
namespace Raychel {
color get_shaded_color(const RenderData& data) noexcept
{
const auto get_background_color = [&] {
if (data.state.get_background) {
return data.state.get_background(data);
}
return color{data.direction.x(), data.direction.y(), data.direction.z()};
};
if (data.recursion_depth >= data.state.options.max_recursion_depth) {
return get_background_color();
}
const auto& [surfaces, materials, _, options] = data.state;
const auto result = raymarch(
data.origin, data.direction, surfaces, {options.max_ray_steps, options.max_ray_depth, options.surface_epsilon});
if (result.hit_index == no_hit) {
return get_background_color();
}
const auto surface_normal = get_normal(result.point, surfaces[result.hit_index], options.normal_epsilon);
RAYCHEL_ASSERT(equivalent(mag_sq(surface_normal), 1.0));
return data.state.materials[result.hit_index].get_surface_color(
{.position = result.point + surface_normal * options.shading_epsilon,
.normal = surface_normal,
.incoming_direction = data.direction,
.state = data.state,
.recursion_depth = data.recursion_depth + 1U});
}
static vec3 get_random_direction_on_weighted_hemisphere(const vec3& normal) noexcept
{
vec3 test{};
do {
test = normal + vec3{ziggurat_normal(), ziggurat_normal(), ziggurat_normal()};
} while (test == vec3{});
test = normalize(test);
if (dot(test, normal) < 0.0) {
test *= -1;
}
return test;
}
color get_diffuse_lighting(const ShadingData& data) noexcept
{
const auto direction = get_random_direction_on_weighted_hemisphere(data.normal);
return get_shaded_color(RenderData{
.origin = data.position,
.direction = direction,
.state = data.state,
.recursion_depth = std::max(
data.state.options.max_recursion_depth - data.state.options.max_lighting_bounces, data.recursion_depth)}) *
dot(direction, data.normal);
}
[[nodiscard]] static double fresnel(const vec3& direction, vec3 normal, double interior_ior, double exterior_ior)
{
RAYCHEL_ASSERT(equivalent(mag_sq(direction), 1.0));
RAYCHEL_ASSERT(equivalent(mag_sq(normal), 1.0));
auto cosi = dot(direction, normal);
if (cosi > 0.0) {
std::swap(interior_ior, exterior_ior);
normal *= -1.0;
} else {
cosi *= -1.0;
}
const auto etai = exterior_ior;
const auto etat = interior_ior;
const auto sint = (exterior_ior / interior_ior) * std::sqrt(std::max(0.0, 1.0 - sq(cosi)));
if (sint >= 1.0) {
return 1.0; //TIR
}
const auto cost = std::sqrt(1.0 - sq(sint));
const auto Rs = ((etat * cosi) - (etai * cost)) / ((etat * cosi) + (etai * cost));
const auto Rp = ((etai * cosi) - (etat * cost)) / ((etai * cosi) + (etat * cost));
return std::clamp((sq(Rs) + sq(Rp)) * 0.5, 0.0, 1.0);
}
[[nodiscard]] static vec3 refract(const vec3& direction, vec3 normal, double interior_ior, double exterior_ior)
{
RAYCHEL_ASSERT(equivalent(mag_sq(direction), 1.0));
RAYCHEL_ASSERT(equivalent(mag_sq(normal), 1.0));
auto cosi = dot(direction, normal);
if (cosi > 0.0) {
std::swap(interior_ior, exterior_ior);
normal *= -1.0;
} else {
cosi *= -1.0;
}
const auto eta = exterior_ior / interior_ior;
const auto k = 1.0 - sq(eta) * (1.0 - sq(cosi));
if (k < 0.0) {
return vec3{}; //TIR
}
return normalize((direction * eta) + (normal * (eta * cosi - std::sqrt(k))));
}
static std::size_t get_surrounding_object(const std::vector<SDFContainer>& surfaces, const vec3& point) noexcept
{
std::size_t hit_index{no_hit};
double min_distance{-1e9};
for (std::size_t i{}; i != surfaces.size(); ++i) {
const auto object_distance = surfaces[i].evaluate(point);
if (object_distance < 0.0 && object_distance > min_distance) {
min_distance = object_distance;
hit_index = i;
}
}
return hit_index;
}
static double get_surrounding_ior(
const vec3& surface_point, const std::vector<SDFContainer>& surfaces, const std::vector<MaterialContainer>& materials)
{
const auto closest_object_index = get_surrounding_object(surfaces, surface_point);
if (closest_object_index == no_hit) {
return 1.0;
}
return materials[closest_object_index].get_material_ior();
}
[[nodiscard]] static color get_reflective_component(const RefractionData& data, double reflection_factor) noexcept
{
if (reflection_factor < 0.01) {
return color{};
}
return get_shaded_color(
{.origin = data.surface_point,
.direction = reflect(data.incoming_direction, data.normal),
.state = data.state,
.recursion_depth = data.recursion_depth}) *
reflection_factor;
}
[[nodiscard]] static color get_refractive_component(const RefractionData& data, double ior_factor, double outer_ior) noexcept
{
const auto trace_direction = refract(data.incoming_direction, data.normal, data.material_ior * ior_factor, outer_ior);
const auto trace_origin = data.surface_point - ((2.0 * data.state.options.shading_epsilon) * data.normal);
if (trace_direction == vec3{}) {
Logger::warn("Did not expect to reach ", __FILE__, ':', __LINE__, '\n');
return color{0};
}
const auto& options = data.state.options;
const auto result = raymarch(
trace_origin,
trace_direction,
data.state.surfaces,
{options.max_ray_steps, options.max_ray_depth, options.surface_epsilon});
// RAYCHEL_ASSERT(result.hit_index != no_hit)
if (result.hit_index == no_hit) {
return color{0};
}
auto opposite_normal = get_normal(result.point, data.state.surfaces[result.hit_index], options.normal_epsilon);
const auto opposite_shading_point = result.point + (opposite_normal * options.shading_epsilon);
const auto out_direction = refract(
trace_direction,
opposite_normal,
data.material_ior,
get_surrounding_ior(data.surface_point, data.state.surfaces, data.state.materials));
//Total internal reflection
if (out_direction == vec3{}) {
return get_shaded_color(RenderData{
.origin = opposite_shading_point,
.direction = reflect(trace_direction, opposite_normal),
.state = data.state,
.recursion_depth = data.recursion_depth});
}
return get_shaded_color(RenderData{
.origin = opposite_shading_point,
.direction = out_direction,
.state = data.state,
.recursion_depth = data.recursion_depth});
}
[[nodiscard]] static color get_refractive_component(const RefractionData& data, double refraction_factor) noexcept
{
if (refraction_factor < 0.01) {
return color{};
}
const auto outer_ior = get_surrounding_ior(data.surface_point, data.state.surfaces, data.state.materials);
if (data.ior_variation == 0.0) {
return get_refractive_component(data, 1.0, outer_ior) * refraction_factor;
}
return color{
get_refractive_component(data, 1.0 - data.ior_variation, outer_ior).r(),
get_refractive_component(data, 1, outer_ior).g(),
get_refractive_component(data, 1.0 + data.ior_variation, outer_ior).b(),
} *
refraction_factor;
}
color get_refraction(const RefractionData& data) noexcept
{
const auto outer_ior = get_surrounding_ior(data.surface_point, data.state.surfaces, data.state.materials);
const auto reflection_factor = fresnel(data.incoming_direction, data.normal, data.material_ior, outer_ior);
return get_reflective_component(data, reflection_factor) + get_refractive_component(data, 1.0 - reflection_factor);
}
} // namespace Raychel

234
src/Render/Renderer.cpp Normal file
View File

@@ -0,0 +1,234 @@
/**
* \file Renderer.cpp
* \author Weckyy702 (weckyy702@gmail.com)
* \brief Implementation file for Renderer class
* \date 2022-04-11
*
* MIT License
* Copyright (c) [2022] [Weckyy702 (weckyy702@gmail.com | https://github.com/Weckyy702)]
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
#include "Raychel/Render/Renderer.h"
#include "Raychel/Core/Scene.h"
#include "Raychel/Core/ZigguratNormal.h"
#include "Raychel/Render/FatPixel.h"
#include "Raychel/Render/RenderUtils.h"
#include "RaychelCore/ScopedTimer.h"
#include <algorithm>
#include <atomic>
#include <execution>
#include <fstream>
#include <map>
#include <random>
#include <thread>
namespace Raychel {
struct CacheComparator
{
constexpr bool operator()(const auto& lhs, const auto& rhs) const
{
if (lhs.first < rhs.first) {
return true;
}
return std::lexicographical_compare(lhs.second.begin(), lhs.second.end(), rhs.second.begin(), rhs.second.end());
}
};
struct CacheResult
{
std::vector<vec3>& data;
bool data_was_in_cache{false};
};
static CacheResult get_cached_ray_data(const Camera& camera, const RenderOptions options) noexcept
{
using Key = std::pair<double, Size2D>;
static std::map<Key, std::vector<vec3>, CacheComparator> ray_cache{};
const Key cache_key{camera.zoom, options.output_size};
if (const auto it = ray_cache.find(cache_key); it != ray_cache.end()) {
Logger::debug("Found cached ray data for zoom=", camera.zoom, ", size=", options.output_size, '\n');
return {it->second, true};
}
Logger::debug("Cache not populated. Generating rays for ", options.output_size, " plane\n");
return {ray_cache[cache_key], false};
}
static void generate_rays_internal(std::vector<vec3>& rays, const Camera& camera, const RenderOptions options) noexcept
{
using vec2 = basic_vec2<double>;
constexpr vec3 right{1, 0, 0};
constexpr vec3 up{0, 1, 0};
constexpr vec3 forward{0, 0, 1};
const auto get_relative_coordinates = [&options](const vec2 pixel_coordinate) {
const auto [plane_x, plane_y] = options.output_size;
const auto aspect_ratio = static_cast<double>(plane_x) / static_cast<double>(plane_y);
const auto raw_relative_x = pixel_coordinate.x() / static_cast<double>(plane_x) - 0.5;
const auto raw_relative_y = pixel_coordinate.y() / static_cast<double>(plane_y) - 0.5;
if (aspect_ratio > 1.0) {
return vec2{raw_relative_x * aspect_ratio, raw_relative_y};
}
return vec2{raw_relative_x, raw_relative_y / aspect_ratio};
};
rays.reserve(options.output_size.y() * options.output_size.x());
for (std::size_t y{options.output_size.y()}; y != 0U; --y) {
for (std::size_t x{}; x != options.output_size.x(); ++x) {
const auto [relative_x, relative_y] = get_relative_coordinates(vec2{x, y});
// clang-format off
const auto direction = normalize( (right * relative_x) +
(up * relative_y) +
(forward * camera.zoom));
// clang-format on
rays.emplace_back(direction);
}
}
}
static const std::vector<vec3>& generate_rays(const Camera& camera, const RenderOptions& options) noexcept
{
const auto [rays, was_cached] = get_cached_ray_data(camera, options);
if (was_cached) {
return rays;
}
generate_rays_internal(rays, camera, options);
return rays;
}
[[nodiscard]] static vec3 get_direction_with_aa(const vec3& direction, const Size2D& output_size) noexcept
{
const vec3 jitter{
uniform_random() / static_cast<double>(output_size.x()), uniform_random() / static_cast<double>(output_size.y())};
return normalize(direction + jitter);
}
[[maybe_unused]] static void
write_framebuffer(const std::string& file_name, Size2D size, const std::vector<FatPixel>& pixel_data) noexcept
{
std::ofstream output_image{file_name};
if (!output_image) {
return;
}
output_image << "P6\n" << size.x() << ' ' << size.y() << '\n' << "255\n";
for (const auto& pixel : pixel_data) {
const auto pixel_rgb = convert_color<std::uint8_t>(pixel.noisy_color);
if (!output_image.write(reinterpret_cast<const char*>(&pixel_rgb), sizeof(pixel_rgb)).good()) {
break;
}
}
}
[[nodiscard]] static std::vector<FatPixel>
render_fat_pixels(const Scene& scene, const Camera& camera, const RenderOptions& options) noexcept
{
const auto& rays = generate_rays(camera, options);
std::vector<FatPixel> fat_pixels{rays.size()};
ScopedTimer<std::chrono::milliseconds> timer{"Render time"};
std::atomic_size_t pixels_rendered{};
std::jthread notifier{[&pixels_rendered, pixel_count = rays.size(), &fat_pixels, options] {
using namespace std::chrono_literals;
[[maybe_unused]] std::chrono::high_resolution_clock::time_point last_check_point{};
std::size_t pixels_so_far{};
do {
const auto previous = pixels_so_far;
pixels_so_far = pixels_rendered.load();
const auto percentage = (pixels_so_far * 100) / pixel_count; //beware of overflow *spooky noises*
const auto pixels_per_second = (pixels_so_far - previous) * 1'000 / 30;
Logger::info(
"Rendered ",
pixels_so_far,
"/",
pixel_count,
" pixels (",
percentage,
"%) ~",
pixels_per_second,
" pixels per second \r"); //big brain padding
#if 1
const auto now = std::chrono::high_resolution_clock::now();
if (duration_cast<std::chrono::seconds>(now-last_check_point).count() >= 1) {
last_check_point = now;
write_framebuffer("progress.ppm", options.output_size, fat_pixels);
}
#endif
std::this_thread::sleep_for(30ms);
} while (pixels_so_far != pixel_count);
Logger::log('\n');
}};
const RenderState state{scene.objects(), scene.materials(), scene.background_function(), options};
std::transform(std::execution::par, rays.begin(), rays.end(), fat_pixels.begin(), [&](const vec3& ray_direction) {
const auto get_direction = [&] {
if (options.do_aa) {
return get_direction_with_aa(ray_direction, options.output_size) * camera.transform.rotation;
}
return ray_direction * camera.transform.rotation;
};
FatPixel::Histogram histogram;
color pixel_color{};
for (std::size_t i{}; i != options.samples_per_pixel; ++i) {
const auto sample_direction = get_direction();
const auto sample = get_shaded_color(RenderData{camera.transform.offset, sample_direction, state, 0U});
histogram.add_sample(sample);
pixel_color += (sample / options.samples_per_pixel);
}
++pixels_rendered;
return FatPixel{pixel_color, histogram};
});
return fat_pixels;
}
FatFramebuffer render_scene(const Scene& scene, const Camera& camera, const RenderOptions& options) noexcept
{
return {options.output_size, render_fat_pixels(scene, camera, options)};
}
} //namespace Raychel

23
test/CMakeLists.txt Normal file
View File

@@ -0,0 +1,23 @@
if(NOT RAYCHEL_LOGGER_EXTERNAL)
find_package(RaychelLogger REQUIRED)
endif()
if(NOT RAYCHEL_CORE_EXTERNAL)
find_package(RaychelCore REQUIRED)
endif()
if(NOT RAYCHEL_MATH_EXTERNAL)
find_package(RaychelMath REQUIRED)
endif()
file(GLOB RAYCHEL_TEST_SOURCES "*.test.cpp")
add_executable(Raychel_test
${RAYCHEL_TEST_SOURCES}
)
target_compile_features(Raychel_test PUBLIC cxx_std_20)
target_link_libraries(Raychel_test PUBLIC
Raychel
)

29
test/Ziggurat.test.cpp Normal file
View File

@@ -0,0 +1,29 @@
#include "Raychel/Core/ZigguratNormal.h"
#include <cmath>
#include <iomanip>
#include <iostream>
#include <map>
int zig_main()
{
constexpr auto scale = 25.0;
constexpr std::size_t n{100'000'000};
constexpr auto expected0 = static_cast<std::size_t>(n * 0.04 / 100.0);
std::map<std::int32_t, std::size_t> hist{};
for (std::size_t i{}; i != n; ++i) {
++hist[static_cast<std::int32_t>(std::round(Raychel::ziggurat_normal() * scale))];
}
for (const auto& [value, count] : hist) {
const auto actual_value = static_cast<double>(value) / scale;
if (actual_value >= 0) {
std::cout << ' ';
}
std::cout << std::fixed << std::setprecision(3) << actual_value << ": " << std::string(count / expected0, '*') << ">\n";
}
return 0;
}

283
test/main.test.cpp Normal file
View File

@@ -0,0 +1,283 @@
#include "Raychel/Core/Deserialize.h"
#include "Raychel/Core/Raymarch.h"
#include "Raychel/Core/SDFBooleans.h"
#include "Raychel/Core/SDFModifiers.h"
#include "Raychel/Core/SDFPrimitives.h"
#include "Raychel/Core/SDFTransforms.h"
#include "Raychel/Core/Scene.h"
#include "Raychel/Core/Types.h"
#include "Raychel/Render/RenderUtils.h"
#include "Raychel/Render/Renderer.h"
#include "RaychelMath/vector.h"
#include <RaychelMath/Quaternion.h>
#include <RaychelMath/color.h>
#include <RaychelMath/constants.h>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <sstream>
struct FlatMaterial
{
Raychel::color surface_color{};
};
struct ReflectiveMaterial
{
Raychel::color reflectivity{};
};
struct DiffuseMaterial
{
Raychel::color surface_color{};
};
struct TransparentMaterial
{
Raychel::color transparency{};
double ior{1.0};
double ior_variation{.1};
};
struct DebugMaterial
{};
template <>
struct Raychel::is_transparent_material<TransparentMaterial> : std::true_type
{};
Raychel::color get_surface_color(const FlatMaterial& material, const Raychel::ShadingData& /*unused*/) noexcept
{
return material.surface_color;
}
Raychel::color get_surface_color(const ReflectiveMaterial& material, const Raychel::ShadingData& data) noexcept
{
return Raychel::get_shaded_color(Raychel::RenderData{
.origin = data.position,
.direction = reflect(data.incoming_direction, data.normal),
.state = data.state,
.recursion_depth = data.recursion_depth}) *
material.reflectivity;
}
Raychel::color get_surface_color(const DiffuseMaterial& material, const Raychel::ShadingData& data) noexcept
{
return Raychel::get_diffuse_lighting(data) * material.surface_color;
}
Raychel::color get_surface_color(const TransparentMaterial& material, const Raychel::ShadingData& data) noexcept
{
return Raychel::get_refraction(Raychel::RefractionData{
.surface_point = data.position,
.incoming_direction = data.incoming_direction,
.normal = data.normal,
.material_ior = material.ior,
.ior_variation = material.ior_variation,
.state = data.state,
.recursion_depth = data.recursion_depth}) *
material.transparency;
}
Raychel::color get_surface_color(const DebugMaterial& /*unused*/, const Raychel::ShadingData& data) noexcept
{
const auto [x, y, z] = data.normal;
return Raychel::color{std::abs(x), std::abs(y), std::abs(z)};
}
double get_material_ior(const TransparentMaterial& material) noexcept
{
return material.ior;
}
[[maybe_unused]] static void write_framebuffer(const std::string& file_name, const Raychel::Framebuffer& framebuffer) noexcept
{
//Don't bother writing an empty framebuffer
if (framebuffer.pixel_data.empty()) {
return;
}
const auto label = Logger::startTimer("Write time");
std::ofstream output_image{file_name};
if (!output_image) {
Logger::error("Unable to open output file '", file_name, "'\n");
return;
}
output_image << "P6\n" << framebuffer.size.x() << ' ' << framebuffer.size.y() << '\n' << "255\n";
for (const auto& pixel : framebuffer.pixel_data) {
const auto pixel_rgb = convert_color<std::uint8_t>(pixel);
if (!output_image.write(reinterpret_cast<const char*>(&pixel_rgb), sizeof(pixel_rgb)).good()) {
Logger::error("Error while writing output file!\n");
break;
}
}
Logger::logDuration(label);
}
[[maybe_unused]] static Raychel::Framebuffer fat_pixels_to_regular(const Raychel::FatFramebuffer& input) noexcept
{
std::vector<Raychel::color> output{};
output.reserve(input.size.x() * input.size.y());
for (const auto& pixel : input) {
output.emplace_back(pixel.noisy_color);
}
return {input.size, std::move(output)};
}
[[maybe_unused]] static void build_cornell_box(Raychel::Scene& scene)
{
using namespace Raychel;
constexpr auto room_size = 1.0;
constexpr auto box_size = room_size * 1.1;
constexpr auto slim = 0.1;
//Floor
scene.add_object(Translate{Box{vec3{box_size, slim, box_size}}, vec3{0, -room_size, 0}}, DiffuseMaterial{color{1, 1, 1}});
//Ceiling
scene.add_object(Translate{Box{vec3{box_size, slim, box_size}}, vec3{0, room_size, 0}}, FlatMaterial{color{1, 1, .9} * 2.5});
//Left wall
scene.add_object(
Translate{Box{vec3{slim, box_size, box_size}}, vec3{-room_size * 1.01, 0, 0}}, DiffuseMaterial{color{1, 0, 0}});
//Right wall
scene.add_object(Translate{Box{vec3{slim, box_size, box_size}}, vec3{room_size, 0, 0}}, DiffuseMaterial{color{0, 1, 0}});
//Back wall
scene.add_object(Translate{Box{vec3{box_size, box_size, slim}}, vec3{0, 0, room_size}}, DiffuseMaterial{color{1, 1, 1}});
//Back sphere
scene.add_object(
Translate{Sphere{0.5}, vec3{-room_size + slim + 0.5, -room_size + 1.1 * slim + 0.5, room_size - 2 * slim - 0.5}},
ReflectiveMaterial{color_from_hex<double>(0xFF5733) * 0.95});
//Front box
scene.add_object(
Translate{
Rotate{Sphere{.25}, rotate_around(vec3{0, 1, 0}, 60 * deg_to_rad<double>)},
vec3{room_size - slim - .5625, -room_size + slim + .25, -room_size + .375}},
TransparentMaterial{.transparency = color_from_hex<double>(0xa8ccd7), .ior = 1.5});
}
bool do_serialize(std::ostream& os, const FlatMaterial& material) noexcept
{
os << material.surface_color << '\n';
return os.good();
}
bool do_serialize(std::ostream& os, const ReflectiveMaterial& material) noexcept
{
os << material.reflectivity << '\n';
return os.good();
}
bool do_serialize(std::ostream& os, const DiffuseMaterial& material) noexcept
{
os << material.surface_color << '\n';
return os.good();
}
std::optional<DiffuseMaterial> do_deserialize(std::istream& is, Raychel::DeserializationTag<DiffuseMaterial>) noexcept
{
Raychel::color c{};
if (!(is >> c))
return std::nullopt;
return DiffuseMaterial{c};
}
std::optional<FlatMaterial> do_deserialize(std::istream& is, Raychel::DeserializationTag<FlatMaterial>) noexcept
{
Raychel::color c{};
if (!(is >> c))
return std::nullopt;
return FlatMaterial{c};
}
std::optional<ReflectiveMaterial> do_deserialize(std::istream& is, Raychel::DeserializationTag<ReflectiveMaterial>) noexcept
{
Raychel::color c{};
if (!(is >> c))
return std::nullopt;
return ReflectiveMaterial{c};
}
template <Raychel::Arithmetic T, std::convertible_to<T> T_>
constexpr Raychel::basic_color<T> lerp(const Raychel::basic_color<T>& a, const Raychel::basic_color<T>& b, T_ x)
{
return (b * x) + (a * (1.0 - x));
}
int main()
{
Logger::setMinimumLogLevel(Logger::LogLevel::debug);
using namespace Raychel;
#if 0
Scene scene;
build_cornell_box(scene);
std::ofstream out_stream{"cornell_box.txt"};
serialize_scene(scene, out_stream);
#endif
#if 0
std::ifstream in_file{"cornell_box.txt"};
auto scene = deserialize_scene(
in_file,
object_deserializers<Translate<>, Rotate<>, Sphere, Box>(),
material_deserializers<DiffuseMaterial, FlatMaterial, ReflectiveMaterial>());
#endif
#if 1
Scene scene;
//scene.add_object(Translate{Sphere{.75}, vec3{0, -.25, 0}}, TransparentMaterial{color{1, .65, .45}, 1.5, .05});
//scene.add_object(Translate{Plane{vec3{0, 1, 0}}, vec3{0, -1.01, 0}}, DiffuseMaterial{color{1}});
//scene.add_object(Translate{Box{vec3{.1, 2, 2}}, vec3{-2, 0, 0}}, FlatMaterial{color{10}});
scene.add_object(Translate{Sphere{.5}, vec3{-3.5, 2.5, -1.5}}, FlatMaterial{(color_from_hex<double>(0x2FE3E0)) * 10});
scene.add_object(Translate{Sphere{.5}, vec3{2.5, 2.5, 1.5}}, FlatMaterial{color_from_hex<double>(0xD01C1F) * 10});
scene.add_object(Translate{Sphere{.05}, vec3{0, .8, 0}}, FlatMaterial{color{1, .75, .5625} * 5});
scene.add_object(
Difference{Translate{Sphere{.5}, vec3{0, .85, 0}}, Rounded{Box{vec3{1, 1, 1}}, 0.1}}, DiffuseMaterial{color{1, 1, 1}});
scene.set_background_function([](const RenderData& data) {
/* if (data.direction.z() < -0.99) {
return color_from_hex<double>(0xfdd835) * 15;
}
return lerp(
color_from_hex<double>(0xFFC922),
color_from_hex<double>(0x87CEEB),
std::pow(std::abs(data.direction.y()) + 0.2, 1.0 / 3.0)) *
0.75;*/
(void)data;
return color{};
});
#endif
for (const auto& obj : scene.objects()) {
obj.unsafe_impl()->debug_log();
}
const auto rendered_image = render_scene(
scene,
Camera{
.transform = {.offset = vec3{0, 2.5, -2.5}, .rotation = rotate_around(vec3{1, 0, 0}, quarter_pi<double>)},
.zoom = 1.0},
RenderOptions{
.output_size = Size2D{1920, 1080} / 2U,
.max_ray_steps = 4096,
.max_recursion_depth = 100,
.samples_per_pixel = 1U << 10U,
});
write_framebuffer("out.ppm", fat_pixels_to_regular(rendered_image));
return 0;
}