Skip to content

Commit 99e45d5

Browse files
committed
Add testrender volumes
Alongside the rest of the initialization for subpixel_radiance, an empty MediumStack is initialized. Its behavior is conceptually similar to the existing CompositeBSDF. Media are stored in a statically allocated char pool, with pointers maintained in mediums[MaxMediums]. In processing medium closures, either HomogeneousMedium or EmptyMedium are inserted into the medium stack. There is a simple priority handling scheme that will shuffle the incumbent medium pointers to give high priority mediums lower indices. They both inherit from a shared CRTP interface, Medium. When processing BSDF closures, intersections with MxDielectric or MxGeneralizedSchlick may only be added when priority < current_priority or are both 0 (the precious priority). MediumStack exposes an integrate method that operates on aggregated parameters from all media sharing the same priority as the topmost medium; these aggregated parameters are stored on the stack and updated whenever media are added. A CDF built from these parameters is then sampled to select one of the overlapping media for scattering. At present, only Henyey–Greenstein phase functions are supported, implemented as a BSDF subclass. Signed-off-by: Owen O'Malley <theowen@email.com>
1 parent d4e959a commit 99e45d5

35 files changed

Lines changed: 1093 additions & 58 deletions

File tree

src/cmake/testing.cmake

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,7 @@ macro (osl_add_all_tests)
362362
render-cornell
363363
render-displacement
364364
render-furnace-diffuse
365+
render-mx-anisotropic-vdf
365366
render-mx-furnace-burley-diffuse
366367
render-mx-furnace-oren-nayar
367368
render-mx-furnace-sheen
@@ -371,7 +372,9 @@ macro (osl_add_all_tests)
371372
render-mx-generalized-schlick render-mx-generalized-schlick-glass
372373
render-mx-layer
373374
render-mx-sheen
374-
render-microfacet render-oren-nayar
375+
render-mx-medium-vdf
376+
render-mx-medium-vdf-glass
377+
render-microfacet render-oren-nayar
375378
render-spi-thinlayer
376379
render-uv render-veachmis render-ward
377380
render-raytypes

src/testrender/shading.cpp

Lines changed: 202 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1513,6 +1513,92 @@ struct ZeltnerBurleySheen final : public BSDF, MxSheenParams {
15131513
}
15141514
};
15151515

1516+
1517+
struct HenyeyGreenstein final : public BSDF {
1518+
const float g;
1519+
OSL_HOSTDEVICE HenyeyGreenstein(float g) : BSDF(this), g(g) {}
1520+
1521+
static OSL_HOSTDEVICE float PhaseHG(float cos_theta, float g)
1522+
{
1523+
const float denom = 1 + g * g + 2 * g * cos_theta;
1524+
return (1 - g * g) / (4 * M_PI * denom * sqrtf(denom));
1525+
}
1526+
1527+
OSL_HOSTDEVICE Sample eval(const Vec3& wo, const Vec3& wi) const
1528+
{
1529+
const float pdf = PhaseHG(dot(wo, wi), g);
1530+
return { wi, Color3(pdf), pdf, 0.0f };
1531+
}
1532+
1533+
OSL_HOSTDEVICE Sample sample(const Vec3& wo, float rx, float ry,
1534+
float rz) const
1535+
{
1536+
TangentFrame frame = TangentFrame::from_normal(wo);
1537+
1538+
float cos_theta;
1539+
if (abs(g) < 1e-3f) {
1540+
cos_theta = 1.0f - 2.0f * rx;
1541+
} else {
1542+
float sqr_term = (1 - g * g) / (1 - g + 2 * g * rx);
1543+
cos_theta = (1 + g * g - sqr_term * sqr_term) / (2 * g);
1544+
cos_theta = OIIO::clamp(cos_theta, -1.0f, 1.0f);
1545+
}
1546+
1547+
float sin_theta = sqrtf(
1548+
OIIO::clamp(1.0f - cos_theta * cos_theta, 0.0f, 1.0f));
1549+
float phi = 2 * M_PI * ry;
1550+
Vec3 local_wi = Vec3(sin_theta * cosf(phi), sin_theta * sinf(phi),
1551+
cos_theta);
1552+
1553+
Vec3 wi = frame.toworld(local_wi);
1554+
float pdf_val = PhaseHG(cos_theta, g);
1555+
1556+
return { wi, Color3(1.0f), pdf_val, 0.0f };
1557+
}
1558+
};
1559+
1560+
struct HomogeneousMedium final : public Medium {
1561+
MediumParams params;
1562+
HenyeyGreenstein phase_func;
1563+
1564+
OSL_HOSTDEVICE HomogeneousMedium(const MediumParams& params)
1565+
: Medium(this), params(params), phase_func(params.medium_g)
1566+
{
1567+
}
1568+
1569+
OSL_HOSTDEVICE BSDF::Sample sample_phase_func(const Vec3& wo, float rx,
1570+
float ry, float rz) const
1571+
{
1572+
return phase_func.sample(wo, rx, ry, rz);
1573+
}
1574+
1575+
OSL_HOSTDEVICE const MediumParams* get_params() const { return &params; }
1576+
};
1577+
1578+
struct EmptyMedium final : public Medium {
1579+
MediumParams params;
1580+
1581+
OSL_HOSTDEVICE EmptyMedium(const MediumParams& params)
1582+
: Medium(this), params(params)
1583+
{
1584+
}
1585+
1586+
OSL_HOSTDEVICE const MediumParams* get_params() const { return &params; }
1587+
1588+
OSL_HOSTDEVICE Medium::Sample sample(Ray& ray, Sampler& sampler,
1589+
Intersection& hit) const
1590+
{
1591+
return { 0.0f, Color3(1.0f), Color3(1.0f) };
1592+
}
1593+
1594+
OSL_HOSTDEVICE BSDF::Sample sample_phase_func(const Vec3& wo, float rx,
1595+
float ry, float rz) const
1596+
{
1597+
return { Vec3(1.0f), Color3(1.0f), 0.0f, 0.0f };
1598+
}
1599+
};
1600+
1601+
15161602
OSL_HOSTDEVICE Color3
15171603
evaluate_layer_opacity(const ShaderGlobalsType& sg, float path_roughness,
15181604
const ClosureColor* closure)
@@ -1606,8 +1692,8 @@ evaluate_layer_opacity(const ShaderGlobalsType& sg, float path_roughness,
16061692

16071693
OSL_HOSTDEVICE void
16081694
process_medium_closure(const ShaderGlobalsType& sg, float path_roughness,
1609-
ShadingResult& result, const ClosureColor* closure,
1610-
const Color3& w)
1695+
ShadingResult& result, MediumStack& medium_stack,
1696+
const ClosureColor* closure, const Color3& w)
16111697
{
16121698
if (!closure)
16131699
return;
@@ -1649,37 +1735,72 @@ process_medium_closure(const ShaderGlobalsType& sg, float path_roughness,
16491735
const ClosureComponent* comp = closure->as_comp();
16501736
Color3 cw = weight * comp->w;
16511737
const auto& params = *comp->as<MxAnisotropicVdfParams>();
1652-
result.sigma_t = cw * params.extinction;
1653-
result.sigma_s = params.albedo * result.sigma_t;
1654-
result.medium_g = params.anisotropy;
1655-
closure = nullptr;
1738+
result.medium_data.sigma_t = cw * params.extinction;
1739+
result.medium_data.sigma_s = params.albedo
1740+
* result.medium_data.sigma_t;
1741+
result.medium_data.medium_g = params.anisotropy;
1742+
result.medium_data.priority = 0;
1743+
1744+
if (!sg.backfacing) { // if entering
1745+
if (result.medium_data.is_vaccum()) {
1746+
medium_stack.add_medium<EmptyMedium>(result.medium_data);
1747+
} else {
1748+
medium_stack.add_medium<HomogeneousMedium>(
1749+
result.medium_data);
1750+
}
1751+
}
1752+
1753+
closure = nullptr;
16561754
break;
16571755
}
16581756
case MX_MEDIUM_VDF_ID: {
16591757
const ClosureComponent* comp = closure->as_comp();
16601758
Color3 cw = weight * comp->w;
16611759
const auto& params = *comp->as<MxMediumVdfParams>();
1662-
result.sigma_t = { -OIIO::fast_log(params.transmission_color.x),
1663-
-OIIO::fast_log(params.transmission_color.y),
1664-
-OIIO::fast_log(params.transmission_color.z) };
1665-
// NOTE: closure weight scales the extinction parameter
1666-
result.sigma_t *= cw / params.transmission_depth;
1667-
result.sigma_s = params.albedo * result.sigma_t;
1668-
result.medium_g = params.anisotropy;
1669-
// TODO: properly track a medium stack here ...
1670-
result.refraction_ior = sg.backfacing ? 1.0f / params.ior
1671-
: params.ior;
1672-
result.priority = params.priority;
1673-
closure = nullptr;
1760+
1761+
result.medium_data.sigma_t
1762+
= Color3(-OIIO::fast_log(params.transmission_color.x),
1763+
-OIIO::fast_log(params.transmission_color.y),
1764+
-OIIO::fast_log(params.transmission_color.z));
1765+
1766+
result.medium_data.sigma_t *= cw / params.transmission_depth;
1767+
result.medium_data.sigma_s = params.albedo
1768+
* result.medium_data.sigma_t;
1769+
result.medium_data.medium_g = params.anisotropy;
1770+
1771+
result.medium_data.refraction_ior = sg.backfacing
1772+
? 1.0f / params.ior
1773+
: params.ior;
1774+
result.medium_data.priority = params.priority;
1775+
1776+
if (!sg.backfacing) { // if entering
1777+
if (result.medium_data.is_vaccum()) {
1778+
medium_stack.add_medium<EmptyMedium>(result.medium_data);
1779+
} else {
1780+
medium_stack.add_medium<HomogeneousMedium>(
1781+
result.medium_data);
1782+
}
1783+
}
1784+
1785+
closure = nullptr;
16741786
break;
16751787
}
16761788
case MxDielectric::closureid(): {
16771789
const ClosureComponent* comp = closure->as_comp();
16781790
const MxDielectric::Data& params = *comp->as<MxDielectric::Data>();
16791791
if (!is_black(weight * comp->w * params.refr_tint)) {
1680-
// TODO: properly track a medium stack here ...
1681-
result.refraction_ior = sg.backfacing ? 1.0f / params.IOR
1682-
: params.IOR;
1792+
float new_ior = sg.backfacing ? 1.0f / params.IOR : params.IOR;
1793+
1794+
result.medium_data.refraction_ior = new_ior;
1795+
1796+
const MediumParams* current_params
1797+
= medium_stack.get_current_params();
1798+
if (current_params
1799+
&& result.medium_data.priority
1800+
<= current_params->priority) {
1801+
result.medium_data.refraction_ior
1802+
= current_params->refraction_ior;
1803+
}
16831804
}
16841805
closure = nullptr;
16851806
break;
@@ -1688,13 +1809,23 @@ process_medium_closure(const ShaderGlobalsType& sg, float path_roughness,
16881809
const ClosureComponent* comp = closure->as_comp();
16891810
const auto& params = *comp->as<MxGeneralizedSchlickParams>();
16901811
if (!is_black(weight * comp->w * params.transmission_tint)) {
1691-
// TODO: properly track a medium stack here ...
16921812
float avg_F0 = clamp((params.f0.x + params.f0.y + params.f0.z)
16931813
/ 3.0f,
16941814
0.0f, 0.99f);
16951815
float sqrt_F0 = sqrtf(avg_F0);
16961816
float ior = (1 + sqrt_F0) / (1 - sqrt_F0);
1697-
result.refraction_ior = sg.backfacing ? 1.0f / ior : ior;
1817+
float new_ior = sg.backfacing ? 1.0f / ior : ior;
1818+
1819+
result.medium_data.refraction_ior = new_ior;
1820+
1821+
const MediumParams* current_params
1822+
= medium_stack.get_current_params();
1823+
if (current_params
1824+
&& result.medium_data.priority
1825+
<= current_params->priority) {
1826+
result.medium_data.refraction_ior
1827+
= current_params->refraction_ior;
1828+
}
16981829
}
16991830
closure = nullptr;
17001831
break;
@@ -1711,8 +1842,9 @@ process_medium_closure(const ShaderGlobalsType& sg, float path_roughness,
17111842
// recursively walk through the closure tree, creating bsdfs as we go
17121843
OSL_HOSTDEVICE void
17131844
process_bsdf_closure(const ShaderGlobalsType& sg, float path_roughness,
1714-
ShadingResult& result, const ClosureColor* closure,
1715-
const Color3& w, bool light_only)
1845+
ShadingResult& result, MediumStack& medium_stack,
1846+
const ClosureColor* closure, const Color3& w,
1847+
bool light_only)
17161848
{
17171849
static const ustringhash uh_ggx("ggx");
17181850
static const ustringhash uh_beckmann("beckmann");
@@ -1845,9 +1977,16 @@ process_bsdf_closure(const ShaderGlobalsType& sg, float path_roughness,
18451977
case MxDielectric::closureid(): {
18461978
const MxDielectric::Data& params
18471979
= *comp->as<MxDielectric::Data>();
1848-
ok = result.bsdf.add_bsdf<MxDielectric>(cw, params, -sg.I,
1849-
sg.backfacing,
1850-
path_roughness);
1980+
1981+
if (medium_stack.false_intersection_with(
1982+
result.medium_data)) {
1983+
ok = result.bsdf.add_bsdf<Transparent>(cw);
1984+
} else {
1985+
ok = result.bsdf.add_bsdf<MxDielectric>(cw, params,
1986+
-sg.I,
1987+
sg.backfacing,
1988+
path_roughness);
1989+
}
18511990
break;
18521991
}
18531992
case MxConductor::closureid(): {
@@ -1860,15 +1999,21 @@ process_bsdf_closure(const ShaderGlobalsType& sg, float path_roughness,
18601999
case MX_GENERALIZED_SCHLICK_ID: {
18612000
const MxGeneralizedSchlickParams& params
18622001
= *comp->as<MxGeneralizedSchlickParams>();
1863-
if (is_black(params.transmission_tint))
1864-
ok = result.bsdf.add_bsdf<MxMicrofacet<
1865-
MxGeneralizedSchlickParams, GGXDist, false>>(cw,
1866-
params,
1867-
1.0f);
1868-
else
1869-
ok = result.bsdf.add_bsdf<MxMicrofacet<
1870-
MxGeneralizedSchlickParams, GGXDist, true>>(
1871-
cw, params, result.refraction_ior);
2002+
2003+
if (medium_stack.false_intersection_with(
2004+
result.medium_data)) {
2005+
ok = result.bsdf.add_bsdf<Transparent>(cw);
2006+
} else {
2007+
if (is_black(params.transmission_tint)) {
2008+
ok = result.bsdf.add_bsdf<MxMicrofacet<
2009+
MxGeneralizedSchlickParams, GGXDist, false>>(
2010+
cw, params, 1.0f);
2011+
} else {
2012+
ok = result.bsdf.add_bsdf<MxMicrofacet<
2013+
MxGeneralizedSchlickParams, GGXDist, true>>(
2014+
cw, params, result.medium_data.refraction_ior);
2015+
}
2016+
}
18722017
break;
18732018
};
18742019
case MX_TRANSLUCENT_ID: {
@@ -1957,11 +2102,14 @@ process_bsdf_closure(const ShaderGlobalsType& sg, float path_roughness,
19572102

19582103
OSL_HOSTDEVICE void
19592104
process_closure(const ShaderGlobalsType& sg, float path_roughness,
1960-
ShadingResult& result, const ClosureColor* Ci, bool light_only)
2105+
ShadingResult& result, MediumStack& medium_stack,
2106+
const ClosureColor* Ci, bool light_only)
19612107
{
19622108
if (!light_only)
1963-
process_medium_closure(sg, path_roughness, result, Ci, Color3(1));
1964-
process_bsdf_closure(sg, path_roughness, result, Ci, Color3(1), light_only);
2109+
process_medium_closure(sg, path_roughness, result, medium_stack, Ci,
2110+
Color3(1));
2111+
process_bsdf_closure(sg, path_roughness, result, medium_stack, Ci,
2112+
Color3(1), light_only);
19652113
}
19662114

19672115
OSL_HOSTDEVICE Vec3
@@ -2022,5 +2170,19 @@ BSDF::sample_vrtl(const Vec3& wo, float rx, float ry, float rz) const
20222170
return dispatch([&](auto bsdf) { return bsdf.sample(wo, rx, ry, rz); });
20232171
}
20242172

2173+
OSL_HOSTDEVICE BSDF::Sample
2174+
Medium::sample_phase_func_vrtl(const Vec3& wo, float rx, float ry,
2175+
float rz) const
2176+
{
2177+
return dispatch([&](const auto& medium) {
2178+
return medium.sample_phase_func(wo, rx, ry, rz);
2179+
});
2180+
}
2181+
2182+
OSL_HOSTDEVICE const MediumParams*
2183+
Medium::get_params_vrtl() const
2184+
{
2185+
return dispatch([&](const auto& medium) { return medium.get_params(); });
2186+
}
20252187

20262188
OSL_NAMESPACE_END

0 commit comments

Comments
 (0)