Previously, we used precomputed Gaussian fits to the XYZ CMFs, performed
the spectral integration in that space, and then converted the result
to the RGB working space.
That worked because we're only supporting dielectric base layers for
the thin film code, so the inputs to the spectral integration
(reflectivity and phase) are both constant w.r.t. wavelength.
However, this will no longer work for conductive base layers.
We could handle reflectivity by converting to XYZ, but that won't work
for phase since its effect on the output is nonlinear.
Therefore, it's time to do this properly by performing the spectral
integration directly in the RGB primaries. To do this, we need to:
- Compute the RGB CMFs from the XYZ CMFs and XYZ-to-RGB matrix
- Resample the RGB CMFs to be parametrized by frequency instead of wavelength
- Compute the FFT of the CMFs
- Store it as a LUT to be used by the kernel code
However, there's two optimizations we can make:
- Both the resampling and the FFT are linear operations, as is the
XYZ-to-RGB conversion. Therefore, we can resample and Fourier-transform
the XYZ CMFs once, store the result in a precomputed table, and then just
multiply the entries by the XYZ-to-RGB matrix at runtime.
- I've included the Python script used to compute the table under
`intern/cycles/doc/precompute`.
- The reference implementation by the paper authors [1] simply stores the
real and imaginary parts in the LUT, and then computes
`cos(shift)*real + sin(shift)*imag`. However, the real and imaginary parts
are oscillating, so the LUT with linear interpolation is not particularly
good at representing them. Instead, we can convert the table to
Magnitude/Phase representation, which is much smoother, and do
`mag * cos(phase - shift)` in the kernel.
- Phase needs to be unwrapped to handle the interpolation decently,
but that's easy.
- This requires an extra trig operation in the kernel in the dielectric case,
but for the conductive case we'll actually save three.
Rendered output is mostly the same, just slightly different because we're
no longer using the Gaussian approximation.
[1] "A Practical Extension to Microfacet Theory for the Modeling of
Varying Iridescence" by Laurent Belcour and Pascal Barla,
https://belcour.github.io/blog/research/publication/2017/05/01/brdf-thin-film.html
Pull Request: https://projects.blender.org/blender/blender/pulls/140944
268 lines
7.2 KiB
C++
268 lines
7.2 KiB
C++
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0 */
|
|
|
|
#pragma once
|
|
|
|
#ifdef WITH_OSL
|
|
# include <cstdint> /* Needed before `sdlexec.h` for `int32_t` with GCC 15.1. */
|
|
/* So no context pollution happens from indirectly included windows.h */
|
|
# ifdef _WIN32
|
|
# include "util/windows.h"
|
|
# endif
|
|
# include <OSL/oslexec.h>
|
|
#endif
|
|
|
|
#include "kernel/types.h"
|
|
#include "scene/attribute.h"
|
|
|
|
#include "graph/node.h"
|
|
|
|
#include "util/map.h"
|
|
#include "util/param.h"
|
|
#include "util/string.h"
|
|
#include "util/thread.h"
|
|
#include "util/types.h"
|
|
#include "util/unique_ptr.h"
|
|
|
|
CCL_NAMESPACE_BEGIN
|
|
|
|
class Device;
|
|
class DeviceScene;
|
|
class Mesh;
|
|
class Progress;
|
|
class Scene;
|
|
class ShaderGraph;
|
|
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,
|
|
VOLUME_SAMPLING_MULTIPLE_IMPORTANCE = 2,
|
|
|
|
VOLUME_NUM_SAMPLING,
|
|
};
|
|
|
|
enum VolumeInterpolation {
|
|
VOLUME_INTERPOLATION_LINEAR = 0,
|
|
VOLUME_INTERPOLATION_CUBIC = 1,
|
|
|
|
VOLUME_NUM_INTERPOLATION,
|
|
};
|
|
|
|
enum DisplacementMethod {
|
|
DISPLACE_BUMP = 0,
|
|
DISPLACE_TRUE = 1,
|
|
DISPLACE_BOTH = 2,
|
|
|
|
DISPLACE_NUM_METHODS,
|
|
};
|
|
|
|
/* Shader describing the appearance of a Mesh, Light or Background.
|
|
*
|
|
* While there is only a single shader graph, it has three outputs: surface,
|
|
* volume and displacement, that the shader manager will compile and execute
|
|
* separately. */
|
|
|
|
class Shader : public Node {
|
|
public:
|
|
NODE_DECLARE
|
|
|
|
/* shader graph */
|
|
unique_ptr<ShaderGraph> graph;
|
|
|
|
NODE_SOCKET_API(int, pass_id)
|
|
|
|
/* sampling */
|
|
NODE_SOCKET_API(EmissionSampling, emission_sampling_method)
|
|
NODE_SOCKET_API(bool, use_transparent_shadow)
|
|
NODE_SOCKET_API(bool, use_bump_map_correction)
|
|
NODE_SOCKET_API(bool, heterogeneous_volume)
|
|
NODE_SOCKET_API(VolumeSampling, volume_sampling_method)
|
|
NODE_SOCKET_API(int, volume_interpolation_method)
|
|
NODE_SOCKET_API(float, volume_step_rate)
|
|
|
|
/* displacement */
|
|
NODE_SOCKET_API(DisplacementMethod, displacement_method)
|
|
|
|
float prev_volume_step_rate;
|
|
|
|
/* synchronization */
|
|
bool need_update_uvs;
|
|
bool need_update_attribute;
|
|
bool need_update_displacement;
|
|
|
|
/* If the shader has only volume components, the surface is assumed to
|
|
* be transparent.
|
|
* However, graph optimization might remove the volume subgraph, but
|
|
* since the user connected something to the volume output the surface
|
|
* should still be transparent.
|
|
* Therefore, has_volume_connected stores whether some volume sub-tree
|
|
* was connected before optimization. */
|
|
bool has_volume_connected;
|
|
|
|
/* information about shader after compiling */
|
|
bool has_surface;
|
|
bool has_surface_transparent;
|
|
bool has_surface_raytrace;
|
|
bool has_volume;
|
|
bool has_displacement;
|
|
bool has_surface_bssrdf;
|
|
bool has_bump;
|
|
bool has_bssrdf_bump;
|
|
bool has_surface_spatial_varying;
|
|
bool has_volume_spatial_varying;
|
|
bool has_volume_attribute_dependency;
|
|
|
|
float3 emission_estimate;
|
|
EmissionSampling emission_sampling;
|
|
bool emission_is_constant;
|
|
|
|
/* requested mesh attributes */
|
|
AttributeRequestSet attributes;
|
|
|
|
/* determined before compiling */
|
|
uint id;
|
|
|
|
#ifdef WITH_OSL
|
|
/* osl shading state references */
|
|
OSL::ShaderGroupRef osl_surface_ref;
|
|
OSL::ShaderGroupRef osl_surface_bump_ref;
|
|
OSL::ShaderGroupRef osl_volume_ref;
|
|
OSL::ShaderGroupRef osl_displacement_ref;
|
|
#endif
|
|
|
|
Shader();
|
|
|
|
/* 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(unique_ptr<ShaderGraph> &&graph);
|
|
void tag_update(Scene *scene);
|
|
void tag_used(Scene *scene);
|
|
|
|
/* Return true when either of the surface or displacement socket of the output node is linked.
|
|
* This should be used to ensure that surface attributes are also requested even when only the
|
|
* displacement socket is linked. */
|
|
bool has_surface_link() const
|
|
{
|
|
return has_surface || has_displacement;
|
|
}
|
|
|
|
bool need_update_geometry() const;
|
|
};
|
|
|
|
/* Shader Manager virtual base class
|
|
*
|
|
* From this the SVM and OSL shader managers are derived, that do the actual
|
|
* shader compiling and device updating. */
|
|
|
|
class ShaderManager {
|
|
public:
|
|
enum : uint32_t {
|
|
SHADER_ADDED = (1 << 0),
|
|
SHADER_MODIFIED = (1 << 2),
|
|
|
|
/* tag everything in the manager for an update */
|
|
UPDATE_ALL = ~0u,
|
|
|
|
UPDATE_NONE = 0u,
|
|
};
|
|
|
|
static unique_ptr<ShaderManager> create(const int shadingsystem);
|
|
virtual ~ShaderManager();
|
|
|
|
virtual bool use_osl()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/* device update */
|
|
void device_update_pre(Device *device, DeviceScene *dscene, Scene *scene, Progress &progress);
|
|
void device_update_post(Device *device, DeviceScene *dscene, Scene *scene, Progress &progress);
|
|
virtual void device_free(Device *device, DeviceScene *dscene, Scene *scene) = 0;
|
|
|
|
/* get globally unique id for a type of attribute */
|
|
virtual uint64_t get_attribute_id(ustring name);
|
|
virtual uint64_t get_attribute_id(AttributeStandard std);
|
|
|
|
/* get shader id for mesh faces */
|
|
int get_shader_id(Shader *shader, bool smooth = false);
|
|
|
|
/* add default shaders to scene, to use as default for things that don't
|
|
* have any shader assigned explicitly */
|
|
static void add_default(Scene *scene);
|
|
|
|
/* Selective nodes compilation. */
|
|
uint get_kernel_features(Scene *scene);
|
|
|
|
float linear_rgb_to_gray(const float3 c);
|
|
float3 rec709_to_scene_linear(const float3 c);
|
|
|
|
string get_cryptomatte_materials(Scene *scene);
|
|
|
|
void tag_update(Scene *scene, const uint32_t flag);
|
|
|
|
bool need_update() const;
|
|
|
|
void init_xyz_transforms();
|
|
|
|
protected:
|
|
ShaderManager();
|
|
|
|
uint32_t update_flags;
|
|
|
|
using AttributeIDMap = unordered_map<ustring, uint64_t>;
|
|
AttributeIDMap unique_attribute_id;
|
|
|
|
static thread_mutex lookup_table_mutex;
|
|
|
|
unordered_map<const float *, size_t> bsdf_tables;
|
|
size_t thin_film_table_offset_;
|
|
|
|
thread_spin_lock attribute_lock_;
|
|
|
|
float3 xyz_to_r;
|
|
float3 xyz_to_g;
|
|
float3 xyz_to_b;
|
|
float3 rgb_to_y;
|
|
float3 white_xyz;
|
|
float3 rec709_to_r;
|
|
float3 rec709_to_g;
|
|
float3 rec709_to_b;
|
|
bool is_rec709;
|
|
vector<float> thin_film_table;
|
|
|
|
template<std::size_t n>
|
|
size_t ensure_bsdf_table(DeviceScene *dscene, Scene *scene, const float (&table)[n])
|
|
{
|
|
return ensure_bsdf_table_impl(dscene, scene, table, n);
|
|
}
|
|
size_t ensure_bsdf_table_impl(DeviceScene *dscene,
|
|
Scene *scene,
|
|
const float *table,
|
|
const size_t n);
|
|
|
|
void compute_thin_film_table(const Transform &xyz_to_rgb);
|
|
|
|
uint get_graph_kernel_features(ShaderGraph *graph);
|
|
|
|
virtual void device_update_specific(Device *device,
|
|
DeviceScene *dscene,
|
|
Scene *scene,
|
|
Progress &progress) = 0;
|
|
|
|
void device_update_common(Device *device, DeviceScene *dscene, Scene *scene, Progress &progress);
|
|
void device_free_common(Device *device, DeviceScene *dscene, Scene *scene);
|
|
};
|
|
|
|
CCL_NAMESPACE_END
|