Files
test2/source/blender/blenlib/BLI_bounds.hh
Clément Foucault 4a7e98be40 EEVEE-Next: Volume: Fragment shader voxelization
This replaces the compute shader pass for volume material properties
voxelization by a fragment shader that is run only once per pixel.
The fragment shader then execute the nodetree in a loop for each
individual froxel.

The motivations are:
- faster evaluation of homogenous materials: can evaluate nodetree
  once and fast write the properties for all froxel in a loop.
  This matches cycles homogenous material optimization (except that
  it only considers the first hit).
- no invocations for empty froxels: not restricted to box dispach.
- support for more than one material: invocations are per pixel.
- cleaner implementation (no compute shader specific paths).

Implementation wise, this is done by adding a stencil texture when
rendering volumetric objects. It is populated during the occupancy
phase but it is not directly used (the stencil test is enabled but
since we use `imageAtomic` to set the occupancy bits, the fragment
shader is forced to be run). The early depth-test is then turned
on for the material properties pass, allowing only one fragment to
be invoked.
This fragment runs the nodetree at the desired frequency: once per
direction (homogenous), or once per froxel (heterogenous).

Note that I tried to use the frontmost fragment using a depth equal
test but it was failing for some reason on Apple silicon producing
flickering artifacts. We might reconsider this frontmost fragment
approach later since the result is now face order dependant when
an object has multiple materials.

Pull Request: https://projects.blender.org/blender/blender/pulls/119439
2024-04-05 16:33:58 +02:00

222 lines
5.9 KiB
C++

/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
*
* Generic algorithms for finding the largest and smallest elements in a span.
*/
#include <optional>
#include "BLI_bounds_types.hh"
#include "BLI_index_mask.hh"
#include "BLI_math_vector.hh"
#include "BLI_task.hh"
namespace blender {
namespace bounds {
template<typename T> [[nodiscard]] inline Bounds<T> merge(const Bounds<T> &a, const Bounds<T> &b)
{
return {math::min(a.min, b.min), math::max(a.max, b.max)};
}
template<typename T>
[[nodiscard]] inline std::optional<Bounds<T>> merge(const std::optional<Bounds<T>> &a,
const std::optional<Bounds<T>> &b)
{
if (a.has_value() && b.has_value()) {
return merge(*a, *b);
}
if (a.has_value()) {
return a;
}
if (b.has_value()) {
return b;
}
return std::nullopt;
}
template<typename T>
[[nodiscard]] inline std::optional<Bounds<T>> min_max(const std::optional<Bounds<T>> &a,
const T &b)
{
if (a.has_value()) {
return merge(*a, {b, b});
}
return Bounds<T>{b, b};
}
/**
* Find the smallest and largest values element-wise in the span.
*/
template<typename T> [[nodiscard]] inline std::optional<Bounds<T>> min_max(const Span<T> values)
{
if (values.is_empty()) {
return std::nullopt;
}
const Bounds<T> init{values.first(), values.first()};
return threading::parallel_reduce(
values.index_range(),
1024,
init,
[&](const IndexRange range, const Bounds<T> &init) {
Bounds<T> result = init;
for (const int i : range) {
math::min_max(values[i], result.min, result.max);
}
return result;
},
[](const Bounds<T> &a, const Bounds<T> &b) { return merge(a, b); });
}
template<typename T>
[[nodiscard]] inline std::optional<Bounds<T>> min_max(const IndexMask &mask, const Span<T> values)
{
if (values.is_empty() || mask.is_empty()) {
return std::nullopt;
}
if (mask.size() == values.size()) {
/* To avoid mask slice/lookup. */
return min_max(values);
}
const Bounds<T> init{values.first(), values.first()};
return threading::parallel_reduce(
mask.index_range(),
1024,
init,
[&](const IndexRange range, const Bounds<T> &init) {
Bounds<T> result = init;
mask.slice(range).foreach_index_optimized<int64_t>(
[&](const int i) { math::min_max(values[i], result.min, result.max); });
return result;
},
[](const Bounds<T> &a, const Bounds<T> &b) { return merge(a, b); });
}
/**
* Find the smallest and largest values element-wise in the span, adding the radius to each element
* first. The template type T is expected to have an addition operator implemented with RadiusT.
*/
template<typename T, typename RadiusT>
[[nodiscard]] inline std::optional<Bounds<T>> min_max_with_radii(const Span<T> values,
const Span<RadiusT> radii)
{
BLI_assert(values.size() == radii.size());
if (values.is_empty()) {
return std::nullopt;
}
const Bounds<T> init{values.first(), values.first()};
return threading::parallel_reduce(
values.index_range(),
1024,
init,
[&](const IndexRange range, const Bounds<T> &init) {
Bounds<T> result = init;
for (const int i : range) {
result.min = math::min(values[i] - radii[i], result.min);
result.max = math::max(values[i] + radii[i], result.max);
}
return result;
},
[](const Bounds<T> &a, const Bounds<T> &b) { return merge(a, b); });
}
/**
* Returns a new bound that contains the intersection of the two given bound.
* Returns no box if there are no overlap.
*/
template<typename T>
[[nodiscard]] inline std::optional<Bounds<T>> intersect(const std::optional<Bounds<T>> &a,
const std::optional<Bounds<T>> &b)
{
if (!a.has_value() || !b.has_value()) {
return std::nullopt;
}
const Bounds<T> result{math::max(a.value().min, b.value().min),
math::min(a.value().max, b.value().max)};
if (result.is_empty()) {
return std::nullopt;
}
return result;
}
} // namespace bounds
namespace detail {
template<typename T, int Size>
[[nodiscard]] inline bool any_less_or_equal_than(const VecBase<T, Size> &a,
const VecBase<T, Size> &b)
{
for (int i = 0; i < Size; i++) {
if (a[i] <= b[i]) {
return true;
}
}
return false;
}
} // namespace detail
template<typename T> inline bool Bounds<T>::is_empty() const
{
if constexpr (std::is_integral<T>::value || std::is_floating_point<T>::value) {
return this->max <= this->min;
}
else {
return detail::any_less_or_equal_than(this->max, this->min);
}
}
template<typename T> inline T Bounds<T>::center() const
{
return math::midpoint(this->min, this->max);
}
template<typename T> inline T Bounds<T>::size() const
{
return math::abs(max - min);
}
template<typename T> inline void Bounds<T>::translate(const T &offset)
{
this->min += offset;
this->max += offset;
}
template<typename T> inline void Bounds<T>::scale_from_center(const T &scale)
{
const T center = this->center();
const T new_half_size = this->size() / T(2) * scale;
this->min = center - new_half_size;
this->max = center + new_half_size;
}
template<typename T> inline void Bounds<T>::resize(const T &new_size)
{
this->min = this->center() - (new_size / T(2));
this->max = this->min + new_size;
}
template<typename T> inline void Bounds<T>::recenter(const T &new_center)
{
const T offset = new_center - this->center();
this->translate(offset);
}
template<typename T>
template<typename PaddingT>
inline void Bounds<T>::pad(const PaddingT &padding)
{
this->min = this->min - padding;
this->max = this->max + padding;
}
} // namespace blender