This implement the design detailed in #135935. A new per object property called `Shadow Terminator Normal Offset` is introduced to shift the shadowed position along the shading normal. The amount of shift is defined in object space on the object datablock. This amount is modulated by the facing ratio to the light. Faces already facing the light will get no offset. This avoids most light leaking artifacts. In case of multiple shading normal, the normal used for the shift is arbitrary. Note that this is the same behavior for other biases. The magnitude of the bias is controlled by `Shadow Terminator Normal Offset`. The amount of faces affected by the bias is controlled using `Shadow Terminator Geometry Offset` just like cycles. Tweaking the `Shadow Terminator Geometry Offset` allows to avoid too much shadow distortion on surfaces with bump mapping. Cycles properties are copied from the Cycles object datablock to the blender datablock. This break the python API for Cycles. The defaults are set to no bias because: - There is no good default. The best value depends on the geometry. - The best value might depend on real-time displacement. - Any bias will introduce light leaking on surfaces that do not need it. - There is an additional cost of enabling it, which is proportional to the amount of pixels on screen using it. Pull Request: https://projects.blender.org/blender/blender/pulls/136935
268 lines
8.7 KiB
C++
268 lines
8.7 KiB
C++
/* SPDX-FileCopyrightText: 2022 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
#pragma once
|
|
|
|
/** \file
|
|
* \ingroup draw
|
|
*
|
|
* Component / Object level resources like object attributes, matrices, visibility etc...
|
|
* Each of them are reference by resource index (#ResourceHandle).
|
|
*/
|
|
|
|
#include "BLI_math_base.h"
|
|
#include "BLI_math_matrix.hh"
|
|
|
|
#include "BKE_curve.hh"
|
|
#include "BKE_duplilist.hh"
|
|
#include "BKE_mesh.h"
|
|
#include "BKE_object.hh"
|
|
#include "BKE_volume.hh"
|
|
#include "BLI_hash.h"
|
|
#include "DNA_curve_types.h"
|
|
#include "DNA_layer_types.h"
|
|
#include "DNA_meta_types.h"
|
|
#include "DNA_object_types.h"
|
|
|
|
#include "DRW_render.hh"
|
|
#include "draw_handle.hh"
|
|
#include "draw_shader_shared.hh"
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name ObjectMatrices
|
|
* \{ */
|
|
|
|
inline void ObjectMatrices::sync(const Object &object)
|
|
{
|
|
model = object.object_to_world();
|
|
model_inverse = object.world_to_object();
|
|
}
|
|
|
|
inline void ObjectMatrices::sync(const float4x4 &model_matrix)
|
|
{
|
|
model = model_matrix;
|
|
model_inverse = blender::math::invert(model_matrix);
|
|
}
|
|
|
|
inline std::ostream &operator<<(std::ostream &stream, const ObjectMatrices &matrices)
|
|
{
|
|
stream << "ObjectMatrices(" << std::endl;
|
|
stream << "model=" << matrices.model << ", " << std::endl;
|
|
stream << "model_inverse=" << matrices.model_inverse << ")" << std::endl;
|
|
return stream;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name ObjectInfos
|
|
* \{ */
|
|
|
|
ENUM_OPERATORS(eObjectInfoFlag, OBJECT_NEGATIVE_SCALE)
|
|
|
|
inline void ObjectInfos::sync()
|
|
{
|
|
object_attrs_len = 0;
|
|
object_attrs_offset = 0;
|
|
|
|
flag = eObjectInfoFlag::OBJECT_NO_INFO;
|
|
}
|
|
|
|
inline void ObjectInfos::sync(const blender::draw::ObjectRef ref, bool is_active_object)
|
|
{
|
|
object_attrs_len = 0;
|
|
object_attrs_offset = 0;
|
|
light_and_shadow_set_membership = 0;
|
|
|
|
LightLinking *light_linking = (ref.dupli_parent) != nullptr ? ref.dupli_parent->light_linking :
|
|
ref.object->light_linking;
|
|
if (light_linking) {
|
|
light_and_shadow_set_membership |= light_linking->runtime.receiver_light_set;
|
|
light_and_shadow_set_membership |= light_linking->runtime.blocker_shadow_set << 8;
|
|
}
|
|
|
|
bool is_holdout = (ref.object->base_flag & BASE_HOLDOUT) ||
|
|
(ref.object->visibility_flag & OB_HOLDOUT);
|
|
|
|
ob_color = ref.object->color;
|
|
index = ref.object->index;
|
|
SET_FLAG_FROM_TEST(flag, is_active_object, eObjectInfoFlag::OBJECT_ACTIVE);
|
|
SET_FLAG_FROM_TEST(
|
|
flag, ref.object->base_flag & BASE_SELECTED, eObjectInfoFlag::OBJECT_SELECTED);
|
|
SET_FLAG_FROM_TEST(
|
|
flag, ref.object->base_flag & BASE_FROM_DUPLI, eObjectInfoFlag::OBJECT_FROM_DUPLI);
|
|
SET_FLAG_FROM_TEST(
|
|
flag, ref.object->base_flag & BASE_FROM_SET, eObjectInfoFlag::OBJECT_FROM_SET);
|
|
SET_FLAG_FROM_TEST(
|
|
flag, ref.object->transflag & OB_NEG_SCALE, eObjectInfoFlag::OBJECT_NEGATIVE_SCALE);
|
|
SET_FLAG_FROM_TEST(flag, is_holdout, eObjectInfoFlag::OBJECT_HOLDOUT);
|
|
|
|
if (ref.object->shadow_terminator_normal_offset > 0.0f) {
|
|
using namespace blender::math;
|
|
shadow_terminator_geometry_offset = ref.object->shadow_terminator_geometry_offset;
|
|
shadow_terminator_normal_offset = ref.object->shadow_terminator_normal_offset *
|
|
reduce_max(to_scale(ref.object->object_to_world()));
|
|
}
|
|
else {
|
|
shadow_terminator_geometry_offset = 0.0f;
|
|
shadow_terminator_normal_offset = 0.0f;
|
|
}
|
|
|
|
if (ref.dupli_object == nullptr) {
|
|
/* TODO(fclem): this is rather costly to do at draw time. Maybe we can
|
|
* put it in ob->runtime and make depsgraph ensure it is up to date. */
|
|
random = BLI_hash_int_2d(BLI_hash_string(ref.object->id.name + 2), 0) *
|
|
(1.0f / (float)0xFFFFFFFF);
|
|
}
|
|
else {
|
|
random = ref.dupli_object->random_id * (1.0f / (float)0xFFFFFFFF);
|
|
}
|
|
|
|
if (ref.object->data == nullptr) {
|
|
orco_add = float3(0.0f);
|
|
orco_mul = float3(1.0f);
|
|
return;
|
|
}
|
|
|
|
switch (GS(reinterpret_cast<ID *>(ref.object->data)->name)) {
|
|
case ID_VO: {
|
|
std::optional<const blender::Bounds<float3>> bounds = BKE_volume_min_max(
|
|
&DRW_object_get_data_for_drawing<const Volume>(*ref.object));
|
|
if (bounds) {
|
|
orco_add = blender::math::midpoint(bounds->min, bounds->max);
|
|
orco_mul = (bounds->max - bounds->min) * 0.5f;
|
|
}
|
|
else {
|
|
orco_add = float3(0.0f);
|
|
orco_mul = float3(1.0f);
|
|
}
|
|
break;
|
|
}
|
|
case ID_ME: {
|
|
BKE_mesh_texspace_get(
|
|
&DRW_object_get_data_for_drawing<Mesh>(*ref.object), orco_add, orco_mul);
|
|
break;
|
|
}
|
|
case ID_CU_LEGACY: {
|
|
Curve &cu = DRW_object_get_data_for_drawing<Curve>(*ref.object);
|
|
BKE_curve_texspace_ensure(&cu);
|
|
orco_add = cu.texspace_location;
|
|
orco_mul = cu.texspace_size;
|
|
break;
|
|
}
|
|
case ID_MB: {
|
|
MetaBall &mb = DRW_object_get_data_for_drawing<MetaBall>(*ref.object);
|
|
orco_add = mb.texspace_location;
|
|
orco_mul = mb.texspace_size;
|
|
break;
|
|
}
|
|
default:
|
|
orco_add = float3(0.0f);
|
|
orco_mul = float3(1.0f);
|
|
break;
|
|
}
|
|
}
|
|
|
|
inline std::ostream &operator<<(std::ostream &stream, const ObjectInfos &infos)
|
|
{
|
|
stream << "ObjectInfos(";
|
|
if (infos.flag == eObjectInfoFlag::OBJECT_NO_INFO) {
|
|
stream << "skipped)" << std::endl;
|
|
return stream;
|
|
}
|
|
stream << "orco_add=" << infos.orco_add << ", ";
|
|
stream << "orco_mul=" << infos.orco_mul << ", ";
|
|
stream << "ob_color=" << infos.ob_color << ", ";
|
|
stream << "index=" << infos.index << ", ";
|
|
stream << "random=" << infos.random << ", ";
|
|
stream << "flag=" << infos.flag << ")" << std::endl;
|
|
return stream;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name ObjectBounds
|
|
* \{ */
|
|
|
|
inline void ObjectBounds::sync()
|
|
{
|
|
#ifndef NDEBUG
|
|
/* Initialize to NaN for easier debugging of uninitialized data usage. */
|
|
bounding_corners[0] = float4(NAN_FLT);
|
|
bounding_corners[1] = float4(NAN_FLT);
|
|
bounding_corners[2] = float4(NAN_FLT);
|
|
bounding_corners[3] = float4(NAN_FLT);
|
|
bounding_sphere = float4(NAN_FLT);
|
|
#endif
|
|
bounding_sphere.w = -1.0f; /* Disable test. */
|
|
}
|
|
|
|
inline void ObjectBounds::sync(const Object &ob, float inflate_bounds)
|
|
{
|
|
const std::optional<blender::Bounds<float3>> bounds = BKE_object_boundbox_get(&ob);
|
|
if (!bounds) {
|
|
#ifndef NDEBUG
|
|
/* Initialize to NaN for easier debugging of uninitialized data usage. */
|
|
bounding_corners[0] = float4(NAN_FLT);
|
|
bounding_corners[1] = float4(NAN_FLT);
|
|
bounding_corners[2] = float4(NAN_FLT);
|
|
bounding_corners[3] = float4(NAN_FLT);
|
|
bounding_sphere = float4(NAN_FLT);
|
|
#endif
|
|
bounding_sphere.w = -1.0f; /* Disable test. */
|
|
return;
|
|
}
|
|
BoundBox bbox;
|
|
BKE_boundbox_init_from_minmax(&bbox, bounds->min, bounds->max);
|
|
*reinterpret_cast<float3 *>(&bounding_corners[0]) = bbox.vec[0];
|
|
*reinterpret_cast<float3 *>(&bounding_corners[1]) = bbox.vec[4];
|
|
*reinterpret_cast<float3 *>(&bounding_corners[2]) = bbox.vec[3];
|
|
*reinterpret_cast<float3 *>(&bounding_corners[3]) = bbox.vec[1];
|
|
bounding_sphere.w = 0.0f; /* Enable test. */
|
|
|
|
if (inflate_bounds != 0.0f) {
|
|
BLI_assert(inflate_bounds >= 0.0f);
|
|
float p = inflate_bounds;
|
|
float n = -inflate_bounds;
|
|
bounding_corners[0] += float4(n, n, n, 0.0f);
|
|
bounding_corners[1] += float4(p, n, n, 0.0f);
|
|
bounding_corners[2] += float4(n, p, n, 0.0f);
|
|
bounding_corners[3] += float4(n, n, p, 0.0f);
|
|
}
|
|
}
|
|
|
|
inline void ObjectBounds::sync(const float3 ¢er, const float3 &size)
|
|
{
|
|
*reinterpret_cast<float3 *>(&bounding_corners[0]) = center - size;
|
|
*reinterpret_cast<float3 *>(&bounding_corners[1]) = center + float3(+size.x, -size.y, -size.z);
|
|
*reinterpret_cast<float3 *>(&bounding_corners[2]) = center + float3(-size.x, +size.y, -size.z);
|
|
*reinterpret_cast<float3 *>(&bounding_corners[3]) = center + float3(-size.x, -size.y, +size.z);
|
|
bounding_sphere.w = 0.0; /* Enable test. */
|
|
}
|
|
|
|
inline std::ostream &operator<<(std::ostream &stream, const ObjectBounds &bounds)
|
|
{
|
|
stream << "ObjectBounds(";
|
|
if (bounds.bounding_sphere.w == -1.0f) {
|
|
stream << "skipped)" << std::endl;
|
|
return stream;
|
|
}
|
|
stream << std::endl;
|
|
stream << ".bounding_corners[0]"
|
|
<< *reinterpret_cast<const float3 *>(&bounds.bounding_corners[0]) << std::endl;
|
|
stream << ".bounding_corners[1]"
|
|
<< *reinterpret_cast<const float3 *>(&bounds.bounding_corners[1]) << std::endl;
|
|
stream << ".bounding_corners[2]"
|
|
<< *reinterpret_cast<const float3 *>(&bounds.bounding_corners[2]) << std::endl;
|
|
stream << ".bounding_corners[3]"
|
|
<< *reinterpret_cast<const float3 *>(&bounds.bounding_corners[3]) << std::endl;
|
|
stream << ".sphere=(pos=" << float3(bounds.bounding_sphere)
|
|
<< ", rad=" << bounds.bounding_sphere.w << std::endl;
|
|
stream << ")" << std::endl;
|
|
return stream;
|
|
}
|
|
|
|
/** \} */
|