The distance sampling is mostly based on weighted delta tracking from
[Monte Carlo Methods for Volumetric Light Transport Simulation]
(http://iliyan.com/publications/VolumeSTAR/VolumeSTAR_EG2018.pdf).
The recursive Monte Carlo estimation of the Radiative Transfer Equation is
\[\langle L \rangle=\frac{\bar T(x\rightarrow y)}{\bar p(x\rightarrow
y)}(L_e+\sigma_s L_s + \sigma_n L).\]
where \(\bar T(x\rightarrow y) = e^{-\bar\sigma\Vert x-y\Vert}\) is the
majorant transmittance between points \(x\) and \(y\), \(p(x\rightarrow
y) = \bar\sigma e^{-\bar\sigma\Vert x-y\Vert}\) is the probability of
sampling point \(y\) from point \(x\) following exponential
distribution.
At each recursive step, we randomly pick one of the two events
proportional to their weights:
* If \(\xi < \frac{\sigma_s}{\sigma_s+\vert\sigma_n\vert}\), we sample
scatter event and evaluate \(L_s\).
* Otherwise, no real collision happens and we continue the recursive
process.
The emission \(L_e\) is evaluated at each step.
This also removes some unused volume settings from the UI:
* "Max Steps" is removed, because the step size is automatically specified
by the volume octree. There is a hard-coded threshold `VOLUME_MAX_STEPS`
to prevent numerical issues.
* "Homogeneous" is automatically detected during density evaluation
An option "Unbiased" is added to the UI. When enabled, densities above
the majorant are clamped.
The Principled BSDF has a ton of inputs, and the previous SVM code just always
allocated stack space for all of them. This results in a ton of additional
NODE_VALUE_x SVM nodes, which slow down execution.
However, this is not really needed for two reasons:
- First, many inputs are only used consitionally. For example, if the
subsurface weight is zero, none of the other subsurface inputs are used.
- Many of the inputs have a "usual" value that they will have in most
materials, so if they happen to have that value we can just indicate that
by not allocating space for them.
This is a bit similar to the standard "pack the fixed value and provide
a stack offset if there's a link" pattern, except that the fixed value
is a constant in the code and we allocate a NODE_VALUE_x if a different
fixed value is used.
Therefore, this PR re-implements the parameter packing in a more efficient way:
- If we can determine that a component is disabled, all conditional inputs are
disconnected (to avoid generating upstream nodes).
- If we can determine that a component is disabled, we skip allocating all
conditional inputs on the stack.
- The inputs for which a reasonable "usual" value exists are changed to
respect that, and to only be allocated if they differ.
- param1 and param2 (which are fixed-value-packed as on all BSDF nodes) are
used to store IOR and roughness, which have a decent chance to be fixed
values.
- The parameter packing is more aggressive about using uchar4, which allows
to get rid of two SVM nodes while still storing the same inputs.
The result is a considerable speedup in scenes that make heavy use of the
Principled BSDF:
| Scene | CPU speedup | OptiX speedup |
| --- | --- | --- |
| attic | 5% | 9% |
| bistro | 5% | 8% |
| junkshop | 5% | 10% |
| monster | 3% | 4% |
| spring | 1% | 6% |
Pull Request: https://projects.blender.org/blender/blender/pulls/143910
This PR adds a new `fresnel_conductor_polarized` function, which calculates reflectance and phase shift (if requested) for both parallel and perpendicular polarized light. This is needed for applying thin film iridescence to conductors (see !141131).
For consistency, this PR also makes `fresnel_conductor` call `fresnel_conductor_polarized` instead of using a fast approximation of the Fresnel equations that is inaccurate at lower n and k values. This will change the output of some Metallic BSDF renders using Physical Conductor and prevent discrepancies when enabling thin film iridescence.
I didn't do any rigorous performance testing, but from timing the functions outside of Blender, `fresnel_conductor_polarized` is significantly slower than the approximation, between 1.5-3x depending on the compiler. This makes sense because it has three square roots and the approximation has none. In some informal tests with metallic_multiggx_physical.blend modified to have more spheres, the new renders took around 1-2% longer on both CPU and GPU.
There are some avoidable inefficiencies in this approach of just calling `fresnel_conductor_polarized`:
- one of the three square roots could be saved since `fresnel_conductor` never needs the phase shift and there are simplifications possible when only calculating the reflectance
- there are several unnecessary multiplications by 1.0 since `fresnel_conductor` uses relative IOR and `fresnel_conductor_polarized` doesn't, though those could get optimized out if inlined
Pull Request: https://projects.blender.org/blender/blender/pulls/143903
Add new "Linear 3D Curves" option in the Curves panel in the render
properties. This renders curves as linear segments rather than smooth
curves, for faster render time at the cost of accuracy.
On NVIDIA Blackwell GPUs, this can give a 6x speedup compared to smooth
curves, due to hardware acceleration. On NVIDIA Ada there is still
a 3x speedup, and CPU and other GPU backends will also render this
faster.
A difference with smooth curves is that these have end caps, as this
was simpler to implement and they are usually helpful anyway.
In the future this functionality will also be used to properly support
the CURVE_TYPE_POLY on the new curves object.
Pull Request: https://projects.blender.org/blender/blender/pulls/139735
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
Multi-bounce was mainly disabled for disk sampling where the probability of
hitting something is relatively low even with high albedo, but this is not so
much an issue with random walk.
This reduces darkening artifacts at the cost of some extra render time. The
difference is mainly visible when using a high radius.
Pull Request: https://projects.blender.org/blender/blender/pulls/140665
This was broken by !138632, the refactor of the microfacet code to no longer
check the "geometric normal", which in reality was the smoothed normal.
Since the logic is now the same for all closure types, it seemed weird that
the light leak only affects Microfacet closures, not Diffuse.
Turns out that for diffuse closures, the relevant paths were rejected by
the initial hemisphere check in the smooth bump terminator code, which also
incorporates the smoothed but non-bump/normal-mapped normal sd->N.
So, we can detect and prevent the new light leaks by extending this check to
all closure types for the eval case. Sampling already has stricter checks,
so this doesn't apply there.
With this change, we can revert the two test cases back to their pre-refactor
version. In hindsight it was a mistake to just shrug off these changes as okay,
I should have looked closer into the difference.
Pull Request: https://projects.blender.org/blender/blender/pulls/140415
In Cycles, the convention is that reflection vs. refraction are classified
based on the hemisphere defined by the *shading* normal (N).
In general, most closure code uses the shading normal for most operations,
as is expected since using the geometric normal (Ng) would break normal maps
and smooth shading.
However, there are two places that use Ng: On the one hand, BSDF sampling
functions generally reject reflections that fall below the Ng hemisphere, since
they'd intersect the geometry when tracing the bounce. This is required, and
we can't do much about it.
On the other hand, the Microfacet evaluation code also checked that the ray
is in the same hemisphere w.r.t. both shading and geometric normal.
Theoretically, this is the right thing to do, since sampling and evaluation code
are supposed to be consistent. However, doing so breaks smooth shading, since
now direct light evaluation near the terminator will sometimes be rejected.
This didn't cause problems in practice because of another inconsistency: While
the parameter of the eval functions was named Ng, the caller actually provided
N (unclear whether by mistake or as a hacky workaround to the terminator).
When this was fixed in 063a9e89, users quickly reported issues with the shadow
terminator, so it was reverted to the hacky inconsistency in 1c50dd8b.
So, let's clean this mess up properly. If we don't want to do the Ng hemisphere
check in _eval, then instead of passing in a misleading value that ends up
making it a no-op, just remove the check. After all, the other closures don't
perform it either.
This way, we avoid the mislabeled Ng, we get rid of the special case for
microfacets, and the shadow terminator continues to be fine.
Technically, we still have the _sample vs. _eval mismatch. However, this is just
unavoidable, and is irrelevant in practice: For a strongly directional light
that makes the shadow terminator noticeable, the MIS weights will be massively
in favor of eval, to the point that it doesn't really matter what sample does.
To support this argument: You can actually reproduce a broken shadow terminator
in pretty much every Cycles version going back to 2011 by just setting up a
small intense mesh emitter, turning off MIS on it to disable _eval, and then
rendering a diffuse smooth-shaded sphere with >100000 samples so that the
fireflies resolve into somewhat consistent lighting.
If nobody has complained about this affecting all closures for 11 years,
I guess it's fine.
Pull Request: https://projects.blender.org/blender/blender/pulls/138632
Previously spell checker ignored text in single quotes however this
meant incorrect spelling was ignored in text where it shouldn't have
been.
In cases single quotes were used for literal strings
(such as variables, code & compiler flags),
replace these with back-ticks.
In cases they were used for UI labels,
replace these with double quotes.
In cases they were used to reference symbols,
replace them with doxygens symbol link syntax (leading hash).
Apply some spelling corrections & tweaks (for check_spelling_* targets).
The new correction avoids washed out areas near the shadow terminator,
preserving more detail from normal and bump maps.
It implements the method from the paper "A Microfacet-Based Shadowing
Function to Solve the Bump Terminator Problem" by Alejandro Conty Estevez,
Pascal Lecocq, and Clifford Stein.
Pull Request: https://projects.blender.org/blender/blender/pulls/135380
This implements three improvements to the energy preservation and albedo
scaling logic, which help the Principled BSDF pass the white-furnace test
when using the coat layers at high roughness.
Specifically, at roughness 0.3, the albedo scaling brings it from 60% at
the edge to 95%, and with the energy preservation it's 99.8%.
Pull Request: https://projects.blender.org/blender/blender/pulls/134620
To align better with the pixel and reduce the samples needed.
The paper was using gamma because the jacobian |d_gamma/d_h| approaches
infinity at the boundaries, but it seems that clamping at 0.999 is
enough for numerical stability.
In practice I did not notice a change in the noise level, but it
simplifies the range computation and renders faster due to reduced
sample amount.
Co-authored-by: Olivier Maury <omaury@meta.com>
Ref: !129616
Pull Request: https://projects.blender.org/blender/blender/pulls/134130
The oren_nayar_diffuse_bsdf closure in OSL had two issues:
- It broke when used with roughness of zero
- It only used the provided albedo for energy compensation, so it still
required the user to multiply with the albedo
Therefore, this handles the zero roughness corner case and includes
the albedo in the closure weight.
This makes no difference when using the closure through the Diffuse
or Principled BSDF nodes, only for custom OSL shaders.
Pull Request: https://projects.blender.org/blender/blender/pulls/133597
It is possible that the valid range computed from `theta` and the range
visible to the current pixel do not overlap. In this case the hair
section has no contribution.
Pull Request: https://projects.blender.org/blender/blender/pulls/133337
In Cycles, the subsurface scattering shader will switch to a
diffuse shader under a few different conditions to improve performance
and reduce noise.
This commit removes the "switch back to diffuse if the scattering
radius is less than a quarter of a pixel" optimization because in some
scenes, this can result in noticable lines as the shader transitions
between subsurface scattering and diffuse.
Pull Request: https://projects.blender.org/blender/blender/pulls/133245
Check was misc-const-correctness, combined with readability-isolate-declaration
as suggested by the docs.
Temporarily clang-format "QualifierAlignment: Left" was used to get consistency
with the prevailing order of keywords.
Pull Request: https://projects.blender.org/blender/blender/pulls/132361
* Use .empty() and .data()
* Use nullptr instead of 0
* No else after return
* Simple class member initialization
* Add override for virtual methods
* Include C++ instead of C headers
* Remove some unused includes
* Use default constructors
* Always use braces
* Consistent names in definition and declaration
* Change typedef to using
Pull Request: https://projects.blender.org/blender/blender/pulls/132361
The reason for this probably was the const nature of the shader data.
However, this is something counter-intuitive, as it potentially leads
to multiple BSDFs re-using the same LCG state.
Pull Request: https://projects.blender.org/blender/blender/pulls/132456
Previous implemenation of 5 < d < 50 was taken from the main paper,
fitting for smaller sizes are found in the supplemental. They are less
forward-scattering.
Pull Request: https://projects.blender.org/blender/blender/pulls/130234
`CLOSURE_WEIGHT_CUTOFF` avoids allocating a closure when its weight is
too small. It makes sense for surface closures, but for volume closures
the contribution also depends on the object size/ray length, such a
cutoff seems random and is causing problem in atmospheric scatterings.
Therefore remove the cutoff for volume, just make sure the weight is
positive.
Pull Request: https://projects.blender.org/blender/blender/pulls/131696
The cause is numerical issues with `fast_sinf()`. While fixing
`fast_sinf()` would ultimately fix the problem, it involves more
complications in other code paths, and it is safer to clamp the
integration range anyway.
Pull Request: https://projects.blender.org/blender/blender/pulls/131689
Turns out that with `-fassociative-math`, GCC turns
`(1.0f - cos_NH2) + alpha2 * cos_NH2` into
`cos_NH2 * (alpha2 - 1.0f) + 1.0f`.
Not sure why since the operation count is the same, but if alpha2 is very
small, `alpha2 - 1.0f` will be exactly -1.0f, which then causes issues.
Luckily, having one_minus_cos_NH2 as its own variable appears to be enough to
make GCC keep the original formulation.
Just to be safe, I've also used one_minus_cos_NH2 in the other branch to
hopefully reduce the chance of it being folded in again. Also turns a
division into a reciprocal, which is in theory slightly faster.
Pull Request: https://projects.blender.org/blender/blender/pulls/130469
Draine phase function sampling internally use Henyey-Greenstein and
Rayleigh sampling for degenerated cases, but the sampling pattern was
different between Draine and Rayleigh. The commit effectively replace
`rand` with `1 - rand` in Rayleigh sampling.
Pull Request: https://projects.blender.org/blender/blender/pulls/129261
When `g == 0`, the Draine phase function from
https://doi.org/10.1051/0004-6361/202142437 simplifies to
\[\Phi(\theta)=\frac{3}{4\pi(3+\alpha)}(1+\alpha\cos^2\theta).\]
Similar as Rayleigh sampling in https://doi.org/10.1364/JOSAA.28.002436,
The solution to the CDF of the marginal density function is
\[\cos^3\theta+a\cos\theta+b=0,\]
with
\[a=\frac{3}{\alpha},\quad b=\frac{3+\alpha}{\alpha}(2\xi_1-1),\]
which has only one real root since \(\alpha > 0\),
resulting in the sample technique
\[\cos\theta=u-\frac{1}{\alpha u}.\]
Pull Request: https://projects.blender.org/blender/blender/pulls/129259
- Deduplicate Fisheye projection code
- Replace spherical/cartesian conversions with shared helpers
- Replace transforms from/to local coordinate systems with shared helpers
The main type of repeated transform that's not covered here is `to/from_coords`, but with separate values for xy and z (e.g. BSDFs that already computed `dot(wi, N)` earlier, so they only need `dot(wi, X)` and `dot(wi, Y)` later). Could also be replaced, but it would feel weirdly specific for a helper function.
Pull Request: https://projects.blender.org/blender/blender/pulls/125999
Previously, Cycles only supported the Henyey-Greenstein phase function for volume scattering.
While HG is flexible and works for a wide range of effects, sometimes a more physically accurate
phase function may be needed for realism.
Therefore, this adds three new phase functions to the code:
Rayleigh: For particles with a size below the wavelength of light, mostly athmospheric scattering.
Fournier-Forand: For realistic underwater scattering.
Draine: Fairly specific on its own (mostly for interstellar dust), but useful for the next entry.
Mie: Approximates Mie scattering in water droplets using a mix of Draine and HG phase functions.
These phase functions can be combined using Mix nodes as usual.
Co-authored-by: Lukas Stockner <lukas@lukasstockner.de>
Pull Request: https://projects.blender.org/blender/blender/pulls/123532
The same random number was used for sampling color channel at each step,
which leads to bias. Fixed by rescaling the random number.
Another possibility would be to scramble `rng_offset` and use a new
random number each time, similar as in subsurface scattering, but
rescaling random number should be faster than computing a new one, and
is favorable here since the precision here is not very important
Pull Request: https://projects.blender.org/blender/blender/pulls/127454
This decreases BSDF_ROUGHNESS_SQ_THRESH so that the microfacet
roughness has a cutoff at much lower values and fixes a precision
issue in the bsdf_sample code that prevented this previously.
Pull Request: https://projects.blender.org/blender/blender/pulls/125919