Cycles: new setting and heuristics for mesh light importance sampling

Materials now have an enum to set the emission sampling method, to be
either None, Auto, Front, Back or Front & Back. This replace the
previous "Multiple Importance Sample" option.

Auto is the new default, and uses a heuristic to estimate the emitted
light intensity to determine of the mesh should be considered as a light
for sampling. Shaders sometimes have a bit of emission but treating them
as a light source is not worth the memory/performance overhead.

The Front/Back settings are not important yet, but will help when a
light tree is added. In that case setting emission to Front only on
closed meshes can help ignore emission from inside the mesh interior that
does not contribute anything.

Includes contributions by Brecht Van Lommel and Alaska.

Ref T77889
This commit is contained in:
Brecht Van Lommel 2022-11-30 20:50:11 +01:00
parent ac51d331df
commit 396b407c7d
Notes: blender-bot 2023-02-14 10:21:15 +01:00
Referenced by issue #77889, Cycles: Many Lights Sampling
24 changed files with 288 additions and 112 deletions

View File

@ -86,6 +86,14 @@ enum_sampling_pattern = (
('PROGRESSIVE_MULTI_JITTER', "Progressive Multi-Jitter", "Use Progressive Multi-Jitter random sampling pattern", 1),
)
enum_emission_sampling = (
('NONE', 'None', "Do not use this surface as a light for sampling", 0),
('AUTO', 'Auto', "Automatically determine if the surface should be treated as a light for sampling, based on estimated emission intensity", 1),
('FRONT', 'Front', "Treat only front side of the surface as a light, usually for closed meshes whose interior is not visible", 2),
('BACK', 'Back', "Treat only back side of the surface as a light for sampling", 3),
('FRONT_BACK', 'Front and Back', "Treat surface as a light for sampling, emitting from both the front and back side", 4),
)
enum_volume_sampling = (
('DISTANCE',
"Distance",
@ -1043,13 +1051,13 @@ class CyclesCameraSettings(bpy.types.PropertyGroup):
class CyclesMaterialSettings(bpy.types.PropertyGroup):
sample_as_light: BoolProperty(
name="Multiple Importance Sample",
description="Use multiple importance sampling for this material, "
"disabling may reduce overall noise for large "
"objects that emit little light compared to other light sources",
default=True,
emission_sampling: EnumProperty(
name="Emission Sampling",
description="Sampling strategy for emissive surfaces",
items=enum_emission_sampling,
default="AUTO",
)
use_transparent_shadow: BoolProperty(
name="Transparent Shadows",
description="Use transparent shadows for this material if it contains a Transparent BSDF, "

View File

@ -1832,9 +1832,9 @@ class CYCLES_MATERIAL_PT_settings_surface(CyclesButtonsPanel, Panel):
cmat = mat.cycles
col = layout.column()
col.prop(cmat, "sample_as_light", text="Multiple Importance")
col.prop(cmat, "use_transparent_shadow")
col.prop(cmat, "displacement_method", text="Displacement")
col.prop(cmat, "emission_sampling")
col.prop(cmat, "use_transparent_shadow")
def draw(self, context):
self.draw_shared(self, context.material)

View File

@ -99,7 +99,7 @@ def do_versions(self):
library_versions.setdefault(library.version, []).append(library)
# Do versioning per library, since they might have different versions.
max_need_versioning = (3, 0, 25)
max_need_versioning = (3, 5, 2)
for version, libraries in library_versions.items():
if version > max_need_versioning:
continue
@ -297,3 +297,8 @@ def do_versions(self):
cmat = mat.cycles
if not cmat.is_property_set("displacement_method"):
cmat.displacement_method = 'DISPLACEMENT'
if version <= (3, 5, 3):
cmat = mat.cycles
if not cmat.get("sample_as_light", True):
cmat.emission_sampling = 'NONE'

View File

@ -61,6 +61,12 @@ static DisplacementMethod get_displacement_method(PointerRNA &ptr)
ptr, "displacement_method", DISPLACE_NUM_METHODS, DISPLACE_BUMP);
}
static EmissionSampling get_emission_sampling(PointerRNA &ptr)
{
return (EmissionSampling)get_enum(
ptr, "emission_sampling", EMISSION_SAMPLING_NUM, EMISSION_SAMPLING_AUTO);
}
static int validate_enum_value(int value, int num_values, int default_value)
{
if (value >= num_values) {
@ -1559,7 +1565,7 @@ void BlenderSync::sync_materials(BL::Depsgraph &b_depsgraph, bool update_all)
/* settings */
PointerRNA cmat = RNA_pointer_get(&b_mat.ptr, "cycles");
shader->set_use_mis(get_boolean(cmat, "sample_as_light"));
shader->set_emission_sampling_method(get_emission_sampling(cmat));
shader->set_use_transparent_shadow(get_boolean(cmat, "use_transparent_shadow"));
shader->set_heterogeneous_volume(!get_boolean(cmat, "homogeneous_volume"));
shader->set_volume_sampling_method(get_volume_sampling(cmat));

View File

@ -69,7 +69,7 @@ ccl_device int bsdf_diffuse_sample(ccl_private const ShaderClosure *sc,
ccl_device int bsdf_translucent_setup(ccl_private DiffuseBsdf *bsdf)
{
bsdf->type = CLOSURE_BSDF_TRANSLUCENT_ID;
return SD_BSDF | SD_BSDF_HAS_EVAL;
return SD_BSDF | SD_BSDF_HAS_EVAL | SD_BSDF_HAS_TRANSMISSION;
}
ccl_device Spectrum bsdf_translucent_eval(ccl_private const ShaderClosure *sc,

View File

@ -34,7 +34,7 @@ ccl_device int bsdf_hair_transmission_setup(ccl_private HairBsdf *bsdf)
bsdf->type = CLOSURE_BSDF_HAIR_TRANSMISSION_ID;
bsdf->roughness1 = clamp(bsdf->roughness1, 0.001f, 1.0f);
bsdf->roughness2 = clamp(bsdf->roughness2, 0.001f, 1.0f);
return SD_BSDF | SD_BSDF_HAS_EVAL;
return SD_BSDF | SD_BSDF_HAS_EVAL | SD_BSDF_HAS_TRANSMISSION;
}
ccl_device Spectrum bsdf_hair_reflection_eval(ccl_private const ShaderClosure *sc,

View File

@ -196,7 +196,7 @@ ccl_device int bsdf_principled_hair_setup(ccl_private ShaderData *sd,
bsdf->extra->geom = make_float4(Y.x, Y.y, Y.z, h);
return SD_BSDF | SD_BSDF_HAS_EVAL | SD_BSDF_NEEDS_LCG;
return SD_BSDF | SD_BSDF_HAS_EVAL | SD_BSDF_NEEDS_LCG | SD_BSDF_HAS_TRANSMISSION;
}
#endif /* __HAIR__ */

View File

@ -346,7 +346,7 @@ ccl_device int bsdf_microfacet_ggx_refraction_setup(ccl_private MicrofacetBsdf *
bsdf->type = CLOSURE_BSDF_MICROFACET_GGX_REFRACTION_ID;
return SD_BSDF | SD_BSDF_HAS_EVAL;
return SD_BSDF | SD_BSDF_HAS_EVAL | SD_BSDF_HAS_TRANSMISSION;
}
ccl_device void bsdf_microfacet_ggx_blur(ccl_private ShaderClosure *sc, float roughness)
@ -776,7 +776,7 @@ ccl_device int bsdf_microfacet_beckmann_refraction_setup(ccl_private MicrofacetB
bsdf->alpha_y = bsdf->alpha_x;
bsdf->type = CLOSURE_BSDF_MICROFACET_BECKMANN_REFRACTION_ID;
return SD_BSDF | SD_BSDF_HAS_EVAL;
return SD_BSDF | SD_BSDF_HAS_EVAL | SD_BSDF_HAS_TRANSMISSION;
}
ccl_device void bsdf_microfacet_beckmann_blur(ccl_private ShaderClosure *sc, float roughness)

View File

@ -559,7 +559,7 @@ ccl_device int bsdf_microfacet_multi_ggx_glass_setup(ccl_private MicrofacetBsdf
bsdf->type = CLOSURE_BSDF_MICROFACET_MULTI_GGX_GLASS_ID;
return SD_BSDF | SD_BSDF_HAS_EVAL | SD_BSDF_NEEDS_LCG;
return SD_BSDF | SD_BSDF_HAS_EVAL | SD_BSDF_NEEDS_LCG | SD_BSDF_HAS_TRANSMISSION;
}
ccl_device int bsdf_microfacet_multi_ggx_glass_fresnel_setup(ccl_private MicrofacetBsdf *bsdf,

View File

@ -91,7 +91,10 @@ ccl_device_inline void path_state_init_integrator(KernelGlobals kg,
#endif
}
ccl_device_inline void path_state_next(KernelGlobals kg, IntegratorState state, int label)
ccl_device_inline void path_state_next(KernelGlobals kg,
IntegratorState state,
const int label,
const int shader_flag)
{
uint32_t flag = INTEGRATOR_STATE(state, path, flag);
@ -120,12 +123,12 @@ ccl_device_inline void path_state_next(KernelGlobals kg, IntegratorState state,
flag |= PATH_RAY_TERMINATE_AFTER_TRANSPARENT;
}
flag &= ~(PATH_RAY_ALL_VISIBILITY | PATH_RAY_MIS_SKIP);
flag &= ~(PATH_RAY_ALL_VISIBILITY | PATH_RAY_MIS_SKIP | PATH_RAY_MIS_HAD_TRANSMISSION);
#ifdef __VOLUME__
if (label & LABEL_VOLUME_SCATTER) {
/* volume scatter */
flag |= PATH_RAY_VOLUME_SCATTER;
flag |= PATH_RAY_VOLUME_SCATTER | PATH_RAY_MIS_HAD_TRANSMISSION;
flag &= ~PATH_RAY_TRANSPARENT_BACKGROUND;
if (!(flag & PATH_RAY_ANY_PASS)) {
flag |= PATH_RAY_VOLUME_PASS;
@ -188,6 +191,11 @@ ccl_device_inline void path_state_next(KernelGlobals kg, IntegratorState state,
flag |= PATH_RAY_GLOSSY | PATH_RAY_SINGULAR | PATH_RAY_MIS_SKIP;
}
/* Flag for consistent MIS weights with light tree. */
if (shader_flag & SD_BSDF_HAS_TRANSMISSION) {
flag |= PATH_RAY_MIS_HAD_TRANSMISSION;
}
/* Render pass categories. */
if (!(flag & PATH_RAY_ANY_PASS) && !(flag & PATH_RAY_TRANSPARENT_BACKGROUND)) {
flag |= PATH_RAY_SURFACE_PASS;

View File

@ -112,11 +112,13 @@ ccl_device_forceinline void integrate_surface_emission(KernelGlobals kg,
Spectrum L = surface_shader_emission(sd);
float mis_weight = 1.0f;
const bool has_mis = !(path_flag & PATH_RAY_MIS_SKIP) &&
(sd->flag & ((sd->flag & SD_BACKFACING) ? SD_MIS_BACK : SD_MIS_FRONT));
#ifdef __HAIR__
if (!(path_flag & PATH_RAY_MIS_SKIP) && (sd->flag & SD_USE_MIS) &&
(sd->type & PRIMITIVE_TRIANGLE))
if (has_mis && (sd->type & PRIMITIVE_TRIANGLE))
#else
if (!(path_flag & PATH_RAY_MIS_SKIP) && (sd->flag & SD_USE_MIS))
if (has_mis)
#endif
{
mis_weight = light_sample_mis_weight_forward_surface(kg, state, path_flag, sd);
@ -447,7 +449,7 @@ ccl_device_forceinline int integrate_surface_bsdf_bssrdf_bounce(
unguided_bsdf_pdf, INTEGRATOR_STATE(state, path, min_ray_pdf));
}
path_state_next(kg, state, label);
path_state_next(kg, state, label, sd->flag);
guiding_record_surface_bounce(kg,
state,

View File

@ -768,7 +768,7 @@ ccl_device_forceinline void integrate_volume_direct_light(
sd->time,
P,
zero_float3(),
0,
SD_BSDF_HAS_TRANSMISSION,
bounce,
path_flag,
ls)) {
@ -984,7 +984,7 @@ ccl_device_forceinline bool integrate_volume_phase_scatter(
INTEGRATOR_STATE_WRITE(state, path, min_ray_pdf) = fminf(
unguided_phase_pdf, INTEGRATOR_STATE(state, path, min_ray_pdf));
path_state_next(kg, state, label);
path_state_next(kg, state, label, sd->flag);
return true;
}

View File

@ -209,21 +209,26 @@ enum PathRayFlag : uint32_t {
PATH_RAY_SHADOW_TRANSPARENT = (1U << 9U),
PATH_RAY_SHADOW = (PATH_RAY_SHADOW_OPAQUE | PATH_RAY_SHADOW_TRANSPARENT),
/* Special flag to tag unaligned BVH nodes.
* Only set and used in BVH nodes to distinguish how to interpret bounding box information stored
* in the node (either it should be intersected as AABB or as OBBU). */
PATH_RAY_NODE_UNALIGNED = (1U << 10U),
/* Subset of flags used for ray visibility for intersection.
*
* NOTE: SHADOW_CATCHER macros below assume there are no more than
* 16 visibility bits. */
PATH_RAY_ALL_VISIBILITY = ((1U << 11U) - 1U),
PATH_RAY_ALL_VISIBILITY = ((1U << 10U) - 1U),
/* Special flag to tag unaligned BVH nodes.
* Only set and used in BVH nodes to distinguish how to interpret bounding box information stored
* in the node (either it should be intersected as AABB or as OBBU).
* So this can overlap with path flags. */
PATH_RAY_NODE_UNALIGNED = (1U << 10U),
/* --------------------------------------------------------------------
* Path flags.
*/
/* Surface had transmission component at previous bounce. Used for light tree
* traversal and culling to be consistent with MIS pdf at the next bounce. */
PATH_RAY_MIS_HAD_TRANSMISSION = (1U << 10U),
/* Don't apply multiple importance sampling weights to emission from
* lamp or surface hits, because they were not direct light sampled. */
PATH_RAY_MIS_SKIP = (1U << 11U),
@ -462,6 +467,16 @@ typedef enum ShaderFlag {
SHADER_EXCLUDE_ANY)
} ShaderFlag;
enum EmissionSampling {
EMISSION_SAMPLING_NONE = 0,
EMISSION_SAMPLING_AUTO = 1,
EMISSION_SAMPLING_FRONT = 2,
EMISSION_SAMPLING_BACK = 3,
EMISSION_SAMPLING_FRONT_BACK = 4,
EMISSION_SAMPLING_NUM
};
/* Light Type */
typedef enum LightType {
@ -775,14 +790,16 @@ enum ShaderDataFlag {
SD_TRANSPARENT = (1 << 9),
/* BSDF requires LCG for evaluation. */
SD_BSDF_NEEDS_LCG = (1 << 10),
/* BSDF has a transmissive component. */
SD_BSDF_HAS_TRANSMISSION = (1 << 11),
SD_CLOSURE_FLAGS = (SD_EMISSION | SD_BSDF | SD_BSDF_HAS_EVAL | SD_BSSRDF | SD_HOLDOUT |
SD_EXTINCTION | SD_SCATTER | SD_BSDF_NEEDS_LCG),
SD_EXTINCTION | SD_SCATTER | SD_BSDF_NEEDS_LCG | SD_BSDF_HAS_TRANSMISSION),
/* Shader flags. */
/* direct light sample */
SD_USE_MIS = (1 << 16),
/* Use front side for direct light sampling. */
SD_MIS_FRONT = (1 << 16),
/* Has transparent shadow. */
SD_HAS_TRANSPARENT_SHADOW = (1 << 17),
/* Has volume shader. */
@ -811,12 +828,14 @@ enum ShaderDataFlag {
SD_HAS_EMISSION = (1 << 29),
/* Shader has raytracing */
SD_HAS_RAYTRACE = (1 << 30),
/* Use back side for direct light sampling. */
SD_MIS_BACK = (1 << 31),
SD_SHADER_FLAGS = (SD_USE_MIS | SD_HAS_TRANSPARENT_SHADOW | SD_HAS_VOLUME | SD_HAS_ONLY_VOLUME |
SD_HETEROGENEOUS_VOLUME | SD_HAS_BSSRDF_BUMP | SD_VOLUME_EQUIANGULAR |
SD_VOLUME_MIS | SD_VOLUME_CUBIC | SD_HAS_BUMP | SD_HAS_DISPLACEMENT |
SD_HAS_CONSTANT_EMISSION | SD_NEED_VOLUME_ATTRIBUTES | SD_HAS_EMISSION |
SD_HAS_RAYTRACE)
SD_SHADER_FLAGS = (SD_MIS_FRONT | SD_HAS_TRANSPARENT_SHADOW | SD_HAS_VOLUME |
SD_HAS_ONLY_VOLUME | SD_HETEROGENEOUS_VOLUME | SD_HAS_BSSRDF_BUMP |
SD_VOLUME_EQUIANGULAR | SD_VOLUME_MIS | SD_VOLUME_CUBIC | SD_HAS_BUMP |
SD_HAS_DISPLACEMENT | SD_HAS_CONSTANT_EMISSION | SD_NEED_VOLUME_ATTRIBUTES |
SD_HAS_EMISSION | SD_HAS_RAYTRACE | SD_MIS_BACK)
};
/* Object flags. */

View File

@ -271,7 +271,7 @@ void Geometry::tag_update(Scene *scene, bool rebuild)
else {
foreach (Node *node, used_shaders) {
Shader *shader = static_cast<Shader *>(node);
if (shader->has_surface_emission) {
if (shader->emission_sampling != EMISSION_SAMPLING_NONE) {
scene->light_manager->tag_update(scene, LightManager::EMISSIVE_MESH_MODIFIED);
break;
}

View File

@ -162,7 +162,9 @@ bool Light::has_contribution(Scene *scene)
if (light_type == LIGHT_BACKGROUND) {
return true;
}
return (shader) ? shader->has_surface_emission : scene->default_light->has_surface_emission;
const Shader *effective_shader = (shader) ? shader : scene->default_light;
return !is_zero(effective_shader->emission_estimate);
}
/* Light Manager */
@ -256,7 +258,7 @@ bool LightManager::object_usable_as_light(Object *object)
*/
foreach (Node *node, geom->get_used_shaders()) {
Shader *shader = static_cast<Shader *>(node);
if (shader->get_use_mis() && shader->has_surface_emission) {
if (shader->emission_sampling != EMISSION_SAMPLING_NONE) {
return true;
}
}
@ -295,7 +297,7 @@ void LightManager::device_update_distribution(Device *device,
static_cast<Shader *>(mesh->get_used_shaders()[shader_index]) :
scene->default_surface;
if (shader->get_use_mis() && shader->has_surface_emission) {
if (shader->emission_sampling != EMISSION_SAMPLING_NONE) {
num_triangles++;
}
}
@ -359,7 +361,7 @@ void LightManager::device_update_distribution(Device *device,
static_cast<Shader *>(mesh->get_used_shaders()[shader_index]) :
scene->default_surface;
if (shader->get_use_mis() && shader->has_surface_emission) {
if (shader->emission_sampling != EMISSION_SAMPLING_NONE) {
distribution[offset].totarea = totarea;
distribution[offset].prim = i + mesh->prim_offset;
distribution[offset].mesh_light.shader_flag = shader_flag;

View File

@ -231,7 +231,7 @@ void Object::tag_update(Scene *scene)
foreach (Node *node, geometry->get_used_shaders()) {
Shader *shader = static_cast<Shader *>(node);
if (shader->get_use_mis() && shader->has_surface_emission)
if (shader->emission_sampling != EMISSION_SAMPLING_NONE)
scene->light_manager->tag_update(scene, LightManager::EMISSIVE_MESH_MODIFIED);
}
}

View File

@ -137,7 +137,7 @@ void OSLShaderManager::device_update_specific(Device *device,
compiler.compile(og, shader);
});
if (shader->get_use_mis() && shader->has_surface_emission)
if (shader->emission_sampling != EMISSION_SAMPLING_NONE)
scene->light_manager->tag_update(scene, LightManager::SHADER_COMPILED);
}
@ -819,8 +819,11 @@ void OSLCompiler::add(ShaderNode *node, const char *name, bool isfilepath)
if (current_type == SHADER_TYPE_SURFACE) {
if (info) {
if (info->has_surface_emission)
current_shader->has_surface_emission = true;
if (info->has_surface_emission && node->special_type == SHADER_SPECIAL_TYPE_OSL) {
/* Will be used by Shader::estimate_emission. */
OSLNode *oslnode = static_cast<OSLNode *>(node);
oslnode->has_emission = true;
}
if (info->has_surface_transparent)
current_shader->has_surface_transparent = true;
if (info->has_surface_bssrdf) {
@ -1120,8 +1123,6 @@ void OSLCompiler::generate_nodes(const ShaderNodeSet &nodes)
done.insert(node);
if (current_type == SHADER_TYPE_SURFACE) {
if (node->has_surface_emission())
current_shader->has_surface_emission = true;
if (node->has_surface_transparent())
current_shader->has_surface_transparent = true;
if (node->get_feature() & KERNEL_FEATURE_NODE_RAYTRACE)
@ -1213,7 +1214,6 @@ void OSLCompiler::compile(OSLGlobals *og, Shader *shader)
current_shader = shader;
shader->has_surface = false;
shader->has_surface_emission = false;
shader->has_surface_transparent = false;
shader->has_surface_bssrdf = false;
shader->has_bump = has_bump;
@ -1256,6 +1256,9 @@ void OSLCompiler::compile(OSLGlobals *og, Shader *shader)
}
else
shader->osl_displacement_ref = OSL::ShaderGroupRef();
/* Estimate emission for MIS. */
shader->estimate_emission();
}
/* push state to array for lookup */

View File

@ -147,7 +147,17 @@ NODE_DEFINE(Shader)
{
NodeType *type = NodeType::add("shader", create);
SOCKET_BOOLEAN(use_mis, "Use MIS", true);
static NodeEnum emission_sampling_method_enum;
emission_sampling_method_enum.insert("none", EMISSION_SAMPLING_NONE);
emission_sampling_method_enum.insert("auto", EMISSION_SAMPLING_AUTO);
emission_sampling_method_enum.insert("front", EMISSION_SAMPLING_FRONT);
emission_sampling_method_enum.insert("back", EMISSION_SAMPLING_BACK);
emission_sampling_method_enum.insert("front_back", EMISSION_SAMPLING_FRONT_BACK);
SOCKET_ENUM(emission_sampling_method,
"Emission Sampling Method",
emission_sampling_method_enum,
EMISSION_SAMPLING_AUTO);
SOCKET_BOOLEAN(use_transparent_shadow, "Use Transparent Shadow", true);
SOCKET_BOOLEAN(heterogeneous_volume, "Heterogeneous Volume", true);
@ -189,7 +199,6 @@ Shader::Shader() : Node(get_node_type())
has_surface = false;
has_surface_transparent = false;
has_surface_emission = false;
has_surface_raytrace = false;
has_surface_bssrdf = false;
has_volume = false;
@ -203,6 +212,10 @@ Shader::Shader() : Node(get_node_type())
has_volume_connected = false;
prev_volume_step_rate = 0.0f;
emission_estimate = zero_float3();
emission_sampling = EMISSION_SAMPLING_NONE;
emission_is_constant = true;
displacement_method = DISPLACE_BUMP;
id = -1;
@ -217,50 +230,141 @@ Shader::~Shader()
delete graph;
}
bool Shader::is_constant_emission(float3 *emission)
static float3 output_estimate_emission(ShaderOutput *output, bool &is_constant)
{
/* Only supports a few nodes for now, not arbitrary shader graphs. */
ShaderNode *node = (output) ? output->parent : nullptr;
if (node == nullptr) {
return zero_float3();
}
else if (node->type == EmissionNode::get_node_type() ||
node->type == BackgroundNode::get_node_type()) {
/* Emission and Background node. */
ShaderInput *color_in = node->input("Color");
ShaderInput *strength_in = node->input("Strength");
float3 estimate = one_float3();
if (color_in->link) {
is_constant = false;
}
else {
estimate *= node->get_float3(color_in->socket_type);
}
if (strength_in->link) {
is_constant = false;
estimate *= output_estimate_emission(strength_in->link, is_constant);
}
else {
estimate *= node->get_float(strength_in->socket_type);
}
return estimate;
}
else if (node->type == LightFalloffNode::get_node_type()) {
/* Light Falloff node. */
ShaderInput *strength_in = node->input("Strength");
is_constant = false;
return (strength_in->link) ? output_estimate_emission(strength_in->link, is_constant) :
make_float3(node->get_float(strength_in->socket_type));
}
else if (node->type == AddClosureNode::get_node_type()) {
/* Add Closure. */
ShaderInput *closure1_in = node->input("Closure1");
ShaderInput *closure2_in = node->input("Closure2");
const float3 estimate1 = (closure1_in->link) ?
output_estimate_emission(closure1_in->link, is_constant) :
zero_float3();
const float3 estimate2 = (closure2_in->link) ?
output_estimate_emission(closure2_in->link, is_constant) :
zero_float3();
return estimate1 + estimate2;
}
else if (node->type == MixClosureNode::get_node_type()) {
/* Mix Closure. */
ShaderInput *fac_in = node->input("Fac");
ShaderInput *closure1_in = node->input("Closure1");
ShaderInput *closure2_in = node->input("Closure2");
const float3 estimate1 = (closure1_in->link) ?
output_estimate_emission(closure1_in->link, is_constant) :
zero_float3();
const float3 estimate2 = (closure2_in->link) ?
output_estimate_emission(closure2_in->link, is_constant) :
zero_float3();
if (fac_in->link) {
is_constant = false;
return estimate1 + estimate2;
}
else {
const float fac = node->get_float(fac_in->socket_type);
return (1.0f - fac) * estimate1 + fac * estimate2;
}
}
else {
/* Other nodes, potentially OSL nodes with arbitrary code for which all we can
* determine is if it has emission or not. */
const bool has_emission = node->has_surface_emission();
float3 estimate;
if (output->type() == SocketType::CLOSURE) {
if (has_emission) {
estimate = one_float3();
is_constant = false;
}
else {
estimate = zero_float3();
}
foreach (const ShaderInput *in, node->inputs) {
if (in->type() == SocketType::CLOSURE && in->link) {
estimate += output_estimate_emission(in->link, is_constant);
}
}
}
else {
estimate = one_float3();
is_constant = false;
}
return estimate;
}
}
void Shader::estimate_emission()
{
/* If the shader has AOVs, they need to be evaluated, so we can't skip the shader. */
emission_is_constant = true;
foreach (ShaderNode *node, graph->nodes) {
if (node->special_type == SHADER_SPECIAL_TYPE_OUTPUT_AOV) {
return false;
emission_is_constant = false;
}
}
ShaderInput *surf = graph->output()->input("Surface");
emission_estimate = output_estimate_emission(surf->link, emission_is_constant);
if (surf->link == NULL) {
return false;
if (is_zero(emission_estimate)) {
emission_sampling = EMISSION_SAMPLING_NONE;
}
if (surf->link->parent->type == EmissionNode::get_node_type()) {
EmissionNode *node = (EmissionNode *)surf->link->parent;
assert(node->input("Color"));
assert(node->input("Strength"));
if (node->input("Color")->link || node->input("Strength")->link) {
return false;
}
*emission = node->get_color() * node->get_strength();
}
else if (surf->link->parent->type == BackgroundNode::get_node_type()) {
BackgroundNode *node = (BackgroundNode *)surf->link->parent;
assert(node->input("Color"));
assert(node->input("Strength"));
if (node->input("Color")->link || node->input("Strength")->link) {
return false;
}
*emission = node->get_color() * node->get_strength();
else if (emission_sampling_method == EMISSION_SAMPLING_AUTO) {
/* Automatically disable MIS when emission is low, to avoid weakly emitting
* using a lot of memory in the light tree and potentially wasting samples
* where indirect light samples are sufficient.
* Possible optimization: estimate front and back emission separately. */
emission_sampling = (reduce_max(emission_estimate) > 0.5f) ? EMISSION_SAMPLING_FRONT_BACK :
EMISSION_SAMPLING_NONE;
}
else {
return false;
emission_sampling = emission_sampling_method;
}
return true;
}
void Shader::set_graph(ShaderGraph *graph_)
@ -305,7 +409,7 @@ void Shader::tag_update(Scene *scene)
/* if the shader previously was emissive, update light distribution,
* if the new shader is emissive, a light manager update tag will be
* done in the shader manager device update. */
if (use_mis && has_surface_emission)
if (emission_sampling != EMISSION_SAMPLING_NONE)
scene->light_manager->tag_update(scene, LightManager::SHADER_MODIFIED);
/* Special handle of background MIS light for now: for some reason it
@ -491,9 +595,17 @@ void ShaderManager::device_update_common(Device * /*device*/,
foreach (Shader *shader, scene->shaders) {
uint flag = 0;
if (shader->get_use_mis())
flag |= SD_USE_MIS;
if (shader->has_surface_emission)
if (shader->emission_sampling == EMISSION_SAMPLING_FRONT) {
flag |= SD_MIS_FRONT;
}
else if (shader->emission_sampling == EMISSION_SAMPLING_BACK) {
flag |= SD_MIS_BACK;
}
else if (shader->emission_sampling == EMISSION_SAMPLING_FRONT_BACK) {
flag |= SD_MIS_FRONT | SD_MIS_BACK;
}
if (!is_zero(shader->emission_estimate))
flag |= SD_HAS_EMISSION;
if (shader->has_surface_transparent && shader->get_use_transparent_shadow())
flag |= SD_HAS_TRANSPARENT_SHADOW;
@ -531,8 +643,7 @@ void ShaderManager::device_update_common(Device * /*device*/,
flag |= SD_HAS_DISPLACEMENT;
/* constant emission check */
float3 constant_emission = zero_float3();
if (shader->is_constant_emission(&constant_emission))
if (shader->emission_is_constant)
flag |= SD_HAS_CONSTANT_EMISSION;
uint32_t cryptomatte_id = util_murmur_hash3(shader->name.c_str(), shader->name.length(), 0);
@ -540,9 +651,9 @@ void ShaderManager::device_update_common(Device * /*device*/,
/* regular shader */
kshader->flags = flag;
kshader->pass_id = shader->get_pass_id();
kshader->constant_emission[0] = constant_emission.x;
kshader->constant_emission[1] = constant_emission.y;
kshader->constant_emission[2] = constant_emission.z;
kshader->constant_emission[0] = shader->emission_estimate.x;
kshader->constant_emission[1] = shader->emission_estimate.y;
kshader->constant_emission[2] = shader->emission_estimate.z;
kshader->cryptomatte_id = util_hash_to_float(cryptomatte_id);
kshader++;
@ -627,8 +738,8 @@ void ShaderManager::add_default(Scene *scene)
shader->set_graph(graph);
scene->default_volume = shader;
shader->tag_update(scene);
/* No default reference for the volume to avoid compiling volume kernels if there are no actual
* volumes in the scene */
/* No default reference for the volume to avoid compiling volume kernels if there are no
* actual volumes in the scene */
}
/* default light */

View File

@ -34,6 +34,7 @@ struct float3;
enum ShadingSystem { SHADINGSYSTEM_OSL, SHADINGSYSTEM_SVM };
/* Keep those in sync with the python-defined enum. */
enum VolumeSampling {
VOLUME_SAMPLING_DISTANCE = 0,
VOLUME_SAMPLING_EQUIANGULAR = 1,
@ -73,7 +74,7 @@ class Shader : public Node {
NODE_SOCKET_API(int, pass_id)
/* sampling */
NODE_SOCKET_API(bool, use_mis)
NODE_SOCKET_API(EmissionSampling, emission_sampling_method)
NODE_SOCKET_API(bool, use_transparent_shadow)
NODE_SOCKET_API(bool, heterogeneous_volume)
NODE_SOCKET_API(VolumeSampling, volume_sampling_method)
@ -101,7 +102,6 @@ class Shader : public Node {
/* information about shader after compiling */
bool has_surface;
bool has_surface_emission;
bool has_surface_transparent;
bool has_surface_raytrace;
bool has_volume;
@ -114,6 +114,10 @@ class Shader : public Node {
bool has_volume_attribute_dependency;
bool has_integrator_dependency;
float3 emission_estimate;
EmissionSampling emission_sampling;
bool emission_is_constant;
/* requested mesh attributes */
AttributeRequestSet attributes;
@ -131,11 +135,12 @@ class Shader : public Node {
Shader();
~Shader();
/* Checks whether the shader consists of just a emission node with fixed inputs that's connected
* directly to the output.
* If yes, it sets the content of emission to the constant value (color * strength), which is
* then used for speeding up light evaluation. */
bool is_constant_emission(float3 *emission);
/* Estimate emission of this shader based on the shader graph. This works only in very simple
* cases. But it helps improve light importance sampling in common cases.
*
* If the emission is fully constant, returns true, so that shader evaluation can be skipped
* entirely for a light. */
void estimate_emission();
void set_graph(ShaderGraph *graph);
void tag_update(Scene *scene);

View File

@ -74,15 +74,15 @@ class ShaderInput {
{
}
ustring name()
ustring name() const
{
return socket_type.ui_name;
}
int flags()
int flags() const
{
return socket_type.flags;
}
SocketType::Type type()
SocketType::Type type() const
{
return socket_type.type;
}
@ -119,11 +119,11 @@ class ShaderOutput {
{
}
ustring name()
ustring name() const
{
return socket_type.ui_name;
}
SocketType::Type type()
SocketType::Type type() const
{
return socket_type.type;
}

View File

@ -7211,6 +7211,7 @@ void SetNormalNode::compile(OSLCompiler &compiler)
OSLNode::OSLNode() : ShaderNode(new NodeType(NodeType::SHADER))
{
special_type = SHADER_SPECIAL_TYPE_OSL;
has_emission = false;
}
OSLNode::~OSLNode()

View File

@ -1530,6 +1530,11 @@ class OSLNode final : public ShaderNode {
SHADER_NODE_NO_CLONE_CLASS(OSLNode)
bool has_surface_emission()
{
return has_emission;
}
/* Ideally we could better detect this, but we can't query this now. */
bool has_spatial_varying()
{
@ -1551,6 +1556,7 @@ class OSLNode final : public ShaderNode {
string filepath;
string bytecode_hash;
bool has_emission;
};
class NormalMapNode : public ShaderNode {

View File

@ -109,7 +109,7 @@ void SVMShaderManager::device_update_specific(Device *device,
Shader *shader = scene->shaders[i];
shader->clear_modified();
if (shader->get_use_mis() && shader->has_surface_emission) {
if (shader->emission_sampling != EMISSION_SAMPLING_NONE) {
scene->light_manager->tag_update(scene, LightManager::SHADER_COMPILED);
}
@ -516,8 +516,6 @@ void SVMCompiler::generate_closure_node(ShaderNode *node, CompilerState *state)
mix_weight_offset = SVM_STACK_INVALID;
if (current_type == SHADER_TYPE_SURFACE) {
if (node->has_surface_emission())
current_shader->has_surface_emission = true;
if (node->has_surface_transparent())
current_shader->has_surface_transparent = true;
if (node->has_surface_bssrdf()) {
@ -873,7 +871,6 @@ void SVMCompiler::compile(Shader *shader, array<int4> &svm_nodes, int index, Sum
current_shader = shader;
shader->has_surface = false;
shader->has_surface_emission = false;
shader->has_surface_transparent = false;
shader->has_surface_raytrace = false;
shader->has_surface_bssrdf = false;
@ -928,6 +925,9 @@ void SVMCompiler::compile(Shader *shader, array<int4> &svm_nodes, int index, Sum
summary->peak_stack_usage = max_stack_use;
summary->num_svm_nodes = svm_nodes.size() - start_num_svm_nodes;
}
/* Estimate emission for MIS. */
shader->estimate_emission();
}
/* Compiler summary implementation. */

View File

@ -25,7 +25,7 @@ extern "C" {
/* Blender file format version. */
#define BLENDER_FILE_VERSION BLENDER_VERSION
#define BLENDER_FILE_SUBVERSION 2
#define BLENDER_FILE_SUBVERSION 3
/* Minimum Blender version that supports reading file written with the current
* version. Older Blender versions will test this and show a warning if the file