抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

由于工作需求,我们的采集流程中使用的是基于Mitsuba的可微渲染,而采集得到的高光贴图需要在Blender中进行渲染成图,两边的BSDF渲染效果不统一问题困扰了我们许久。因此本文的核心需求是,阅读Blender和Mitsuba的源码,找出其差异,并在源码级别实现在Blender中复现Mitsuba的高光渲染效果。最后本文对Blender源码做了修改和重编译,用新的Blender编译版本实现了该渲染效果对齐的需求。

Blender和Mitsuba渲染场景及参数的对齐

为了方便对齐两边的渲染效果,我需要首先把两边的渲染场景对齐,其中Mitsuba使用xml文件储存场景信息,而Blender则是工程文件,我具体进行的对齐有:

  1. 两边都取消所有点光源、面光源等,都只使用同一张exr格式的环境贴图,对贴图亮度的scale都设置为0.05,使用的环境贴图是Equirectangular Mapping格式。
  2. 两边camera的参数对齐,包括外参:相机的姿态矩阵和内参:相机的fov、cx、cy等,都使用perspective透视模式。( 这部分其实包含很多具体的学习内容和计算,可以参考我的博客文章:https://yaelcassini.github.io/2023/11/07/Camera-Realted-Knowledge/
  3. 两边渲染分辨率的对齐,都使用6960*4640。
  4. 两边spp(sample per pixel)的对齐,实验过程中都使用1024。
  5. 两边渲染结果都使用exr格式输出。( 这里一开始由于Blender的颜色管理出问题导致误以为两边光照不相同,浪费了很多时间,具体可以参考我的博客文章:https://yaelcassini.github.io/2023/04/24/Color-Management/
  6. 两边的最大bounce次数对齐,其中,Blender中Max Bounces都设置为0时,才是只有直接光照的结果,而不是设置为1时。
  7. 两边渲染使用的Mesh完全相同,(载入Blender时要注意Blender的坐标系是z轴朝上的,import obj会默认做一个绕x轴的90度旋转。)
  8. 模型贴图都使用exr格式输入,不做颜色空间转换避免出现问题。

Mistuba源码阅读及分析

Mitsuba场景文件中的bsdf信息如下:

1
2
3
4
5
6
7
8
9
10
11
12
<bsdf id="obj_bsdf" type="roughconductor">
<boolean name="sample_visible" value="false"/>
<string name="distribution" value="beckmann"/>
<boolean name="fresnel_schlick" value="true"/>
<boolean name="forward" value="true"/>
<texture id="f0" name="f0" type="bitmap">
<string name="filename" value="/f0.exr"/>
</texture>
<texture id="alpha" name="alpha" type="bitmap">
<string name="filename" value="alpha.exr"/>
</texture>
</bsdf>

可以看到使用的是roughconductor模型,主要代码在mitsuba2\src\bsdfs\roughconductor.cpp文件中,bsdf的相关计算在RoughConductor类的eval函数中,我们项目中使用的是学长修改过的mitsuba,因此可以支持fresnel_schlick近似的计算,学习源码后转化的公式为:

$$ result = \frac{F * D * G}{4 * cos \theta_i} $$

$$ D = D_{NDF_Beckmann} (n,h,α)=\frac{1}{(\pi α^2 (n·h)^4 )} e^{(\frac{(n·h)^2-1}{α^2 (n·h)^2 })} $$

$$ G = G_2(l,v,h) = G_1(l) * G_1(v) $$

$$ G_1(v) = G_{GGX_Beckmann}(v) = \left {
\begin{array}{l l}
\frac{ 3.535 c + 2.181 c^2 }{ 1 + 2.276 c + 2.577 c^2 } & \quad \text{if $c < 1.6$}\
1 & \quad \text{if $c \geq 1.6$}
\end{array} \right. $$

$$ 其中:c = \frac{n·v}{ \alpha \sqrt{1 - (n·v)^2} } $$
$$ F = F_{Schlick}(v,h) = f0 + (1 - f0) * (1-v·h)^5 $$

Blender源码阅读及分析(主要针对Glossy BSDF着色节点的跟踪)

首先,放一下根据阅读源码和调试整理出的关于Blender一次渲染从顶端到底端调用的调用堆栈:
Blender Glossy Bsdf调用堆栈

Blender中“Principled BSDF”节点是没有Beckmann分布的,只提供GGX和Multiscatter GGX两个微表面模型,因此我们流程中使用的是提供Beckmann分布的“Glossy BSDF”节点。因此需求转化为分析“Glossy BSDF”节点的具体实现公式,因此在大致学习了Blender源码的编写结构之后就从“Glossy BSDF”结点入手进行具体分析。
首先,Blender支持OSL(Open Shader Language),源码中有内置shader nodes的osl实现版本。因此在一开始通过关键词检索源码的阶段我自然而然的找到了osl实现的部分,虽然后来发现只关注osl是显然不行的,但此处还是顺着我的探索历程来整理。
(PS: 这里说一嘴,虽然osl模式有很多限制,但是胜在相比于普通渲染模式的代码阅读起来更类似GLSL等Shader语言,非常流畅,而且Blender应该是做了两种模式下渲染效果的对齐的,因此OSL可以作为辅助功能,比如想查看某个BSDF的具体计算过程,就可以先看OSL代码。)

  1. “Glossy BSDF”节点的底层实现使用OSL编写,在blender源码的intern\cycles\kernel\osl\shaders\node_glossy_bsdf.osl文件中,其中使用到了microfacet函数。
  2. 接下来自然而然地去找microfacet函数的实现,找到intern\cycles\kernel\osl\closures_template.h文件中使用OSL_CLOSURE_STRUCT_系列关键字做了Microfacet、microfacet和用到的参数及其类型的声明。
  3. 由于工程里面没有找到其他的Microfacet关键字,因此下一步我选择搜索哪里用到了closures_template.h,找到了以下几个文件:
  • closures_setup.h
  • closures.cpp
  • osl.h
  • types.h
    Blender源码使用的方式是以上的每个文件里使用#define重新定义OSL_CLOSURE_STRUCT_系列关键字的含义,以减轻代码的重复性和冗余性。
    1. closures_setup.h中引入template用于创建Closure结构体。
    1. closures.cpp中引入了两次template,一次用于创建返回值为ClosureParam的函数,猜测是用于返回参数列表,另一次是用于注册闭包。
    1. osl.h中引入template用于flatten_closure_tree函数中,根据函数名及函数体内容猜测,该函数用于展开OSL闭包树,将其转换为单一BSDF表示。
    1. types.h中引入template用于在枚举类型OSLClosure中声明不同shading类型的名字和值。
  1. 根据关键词osl_closure_microfacet搜索只能找到closures_setup.h中的osl_closure_microfacet_setup函数。该函数体内,在判断distribution为beckmann时,分三种情况讨论,折射,glass和else,我们需要的显然是最后一种情况。

    1. 用正则表达式搜索osl_closure_microfacet_setup函数,发现在osl.h中使用了switch判断,case为OSL_CLOSURE_##Upper##ID时,调用了osl_closure##lower##setup函数,猜测调用的方式时渲染时,在flatten_closure_tree函数中判断closure->id(应该是一个枚举类型OSLClosureType的变量),发现节点的枚举值是OSL_CLOSURE##Upper##ID(types.h),调用osl_closure##lower##setup(osl.h)。而closures.cpp中的register_closures函数则把小写的shading node名字#lower,大写的OSL_CLOSURE##Upper##ID,返回参数列表的osl_closure##lower##_params()函数绑定了。
  2. 最后一种情况调用bsdf_microfacet_beckmann_setup函数,并把bsdf类的fresnel_type变量赋值为MicrofacetFresnel::NONE,变量赋值为CLOSURE_BSDF_MICROFACET_BECKMANN_ID,从这里开始不再出现CLOSURE_BSDF_MICROFACET_ID,而需要顺着CLOSURE_BSDF_MICROFACET_BECKMANN_ID找。

  3. 搜索CLOSURE_BSDF_MICROFACET_BECKMANN_ID找到的bsdf_sample函数,位于intern\cycles\kernel\closure\bsdf.h文件中,在判断ShaderClosure->type后调用bsdf_microfacet_beckmann_sample函数。

  4. bsdf_eval函数中调用的bsdf_microfacet_beckmann_eval是计算bsdf的核心,位于intern\cycles\kernel\closure\bsdf_microfacet.h文件中。

  5. 位于intern\cycles\scene的shader_nodes.cpp中的NODE_DEFINE(GlossyBsdfNode)中定义了glossy_bsdf节点,并在其中进行了分布枚举类型的名字和ID注册对应,比如:distribution_enum.insert("beckmann", CLOSURE_BSDF_MICROFACET_BECKMANN_ID);

  6. 由于“Principled BSDF”节点是有Fresnel项的,因此看一下Pricipled BSDF的代码做一个对比,发现该节点的osl实现里面用到了generalized_schlick_bsdf函数,我发现该函数是可以输出beckmann并且有fresnel项的,因此我使用Blender的OSL模式,调用该函数进行渲染,在使用如下设置和参数时,实现了同光照同场景下和Mitsuba渲染效果的对齐:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    shader simple_material(
    string distribution = "beckmann",
    color f0 = color(0.04, 0.04, 0.04),
    float roughness = 0.1,
    normal Normal = N,
    output closure color BSDF = 0)
    {
    color reflection_tint = color(1, 1, 1);
    color transmission_tint = color(0, 0, 0);
    float roughness_x = roughness;
    float roughness_y = roughness;
    vector tangent = vector(0, 0, 1);

    color f90 = color(1, 1, 1);
    float exponent = 5;
    BSDF = generalized_schlick_bsdf(Normal, tangent, reflection_tint, transmission_tint, roughness_x, roughness_y, f0, f90, exponent, distribution);
    }
  7. 在OSL模式下对齐渲染效果是一个巨大的进步,之后我就只需要对照osl的实现修改普通渲染模式下的实现。在微表面模型设置为beckmann,fresnel项使用schlick近似,使用generalized_schlick_bsdf渲染时,Blender中的bsdf计算可以转化为以下公式:

$$ result = \frac{F * D}{4 * cos\theta_i * (1 + G_{l} + G(v))} $$

$$ D = D_{NDF_Beckmann} (n,h,α)=\frac{1}{(\pi α^2 (n·h)^4 )} e^{(\frac{(n·h)^2-1}{α^2 (n·h)^2 })} $$

$$ G(v) = G_{GSF_Beckmann} = \frac{1.0 - 1.259c + 0.369c^2}{3.535c + 2.181c^2} $$

$$ 其中:c = \frac{n·v}{ \alpha \sqrt{1 - (n·v)^2} } $$
$$ F = F_{Schlick}(v,h) = f0 + (1 - f0) * (1-v·h)^5 $$

  1. 结论:目前通过对比Blender和Mitsuba源码得到的结论是:在我们的流程中,Blender使用的Glossy BSDF里面不包含Fresnel项的赋值和计算,因此Fresnel值在进行beckmann_setup的时候被设置为MicrofacetFresnel::NONE(BSDF计算对比如下图)。在修改时要注意两边的Roughness有没有进行平方映射要对齐,通过对源码的阅读,Mistuba的Roughness是不进行平方映射的,因此Blender中也要去掉平方映射。其中,G项的不同我本来以为是Blender写错了,后来才发现还是自己太无知了,Blender使用的是高度相关遮蔽阴影型,而Mitsuba使用的是分离遮蔽阴影型。虽然具体计算不同但原理相同,计算结果差异也可忽略。具体可以参考毛星云大佬的文章:https://zhuanlan.zhihu.com/p/81708753bsdf公式对比

Blender 调试流程

  1. 下载Blender源码
    最好在Blender官方Git项目上选择一个稳定的branch,比如我选择的是blender-v4.0-release和blender-v3.6-release。可以用命令行git clone也可以直接download zip。笔者由于当时git网络不好使都是直接下载代码压缩包解压的,主要用到的是blender-v4.0-release这个分支。
  1. 下载SVN,并使用SVN下载Blender编译需要的库,SVN可以理解为另一种形式的git,具体的区别笔者也米有深入了解,主打一个能用就行。笔者用的是TortoiseSVN,下载的是LTS 64位版本。下载后就可以像git一样在文件资源管理器右键寻找svn使用。
  1. 在Blender源码的同级路径新建lib文件夹,再在其中新建win64_vc15文件夹,之后文件资源管理器右键选择SVN Checkout,在URL of repository一栏输入Blender官方提供lib下载地址,在这里要注意,SVN也有版本管理,默认是HEAD revision,不同的Blender版本最好选择不同的revision,可以在SVN的GUI页面选择Show log,在其中查看自己下载的Blender版本对应的revision代号。比如,笔者下载的blender-v4.0-release分支,打开revision log之后,选择的是Message为“Windows: 4.0 Liabrary Update”的revision,代号为63491,所以直接把63491填到revision后面的框内点击ok就可以下载对应的lib。
  1. 完成源码和library下载后的文件夹结构:
    ├────blender/
    │ ├────.git/
    │ ├────build_files/
    │ ├────extern/
    │ ├────intern/
    │ ├────locale/
    │ ├────release/
    │ ├────scripts/
    │ ├────tests/
    │ ├────tools/
    │ ├────make.bat
    │ ├────CMakeLists.txt
    │ ├────pyproject.toml
    │ ├────README.md
    │ └────···
    ├────lib/
    │ ├────win64_vc15/
    │ │ ├────alembic
    │ │ ├────blosc
    │ │ ├────boost
    │ │ ├────brotli
    └────└────└────···

  2. 编译构建,在blender源码目录下,使用cmd进行命令行操作。
    4.1.用命令行直接编译,生成文件在 build_windows_Full_x64_vc16_Release\bin\Release

  • 命令:make

4.2. 生成IDE工程,在 build_windows_Full_x64_vc16_Release 文件夹中生成VS工程。打开 Blender.sln,

  • 命令:make full nobuild
    4.2.1. CMakePredefinedTargets/INSTALL 工程上右键,执行 Build,这步会将一些文件放到输出目录。这个只需执行一次,不过切换Debug/Release,也要执行一次。
    4.2.2. blender工程是默认工程,直接点击绿色三角。

Blender 源码修改步骤

  1. 把输入的Color值作为f0传递给bsdf,Blender中用了一些很神奇的操作传值,主要的读值操作在intern\cycles\kernel\svm\closure.hsvm_node_closure_bsdf函数中,集中在函数体的开头,当时也读了一段时间才读懂,由于篇幅限制此处不做详细解释,后续可能会单独写一篇。而传值操作在intern\cycles\scene\shader_nodes.cpp中的void GlossyBsdfNode::compile函数中,对该函数的修改:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
      closure = distribution;

    /* TODO: Just use weight for legacy MultiGGX? Would also simplify OSL. */
    if (closure == CLOSURE_BSDF_MICROFACET_MULTI_GGX_ID) {
    BsdfNode::compile(
    compiler, input("Roughness"), input("Anisotropy"), input("Rotation"), input("Color"));
    }
    else {
    - BsdfNode::compile(compiler, input("Roughness"), input("Anisotropy"), input("Rotation"));
    + BsdfNode::compile(compiler, input("Roughness"), input("Anisotropy"), input("Rotation"), input("Color"));
    }
  2. intern\cycles\kernel\svm\closure.hsvm_node_closure_bsdf函数中添加Fresnel项的内存分配以及材质信息赋值,这个函数相当于是设置材质固有信息,比如roughness和Normal等,没有拿到入射光线出射光线等信息,并不直接进行bsdf计算。(这里由于我是和指定的mistuba模型对齐,使用schlick近似,因此直接把exponent设置为5,如果有别的需求也可以添加node输入节点后传值设置。)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
        case CLOSURE_BSDF_MICROFACET_GGX_ID:
    case CLOSURE_BSDF_MICROFACET_BECKMANN_ID:
    case CLOSURE_BSDF_ASHIKHMIN_SHIRLEY_ID:
    case CLOSURE_BSDF_MICROFACET_MULTI_GGX_ID: {
    #ifdef __CAUSTICS_TRICKS__
    if (!kernel_data.integrator.caustics_reflective && (path_flag & PATH_RAY_DIFFUSE))
    break;
    #endif
    Spectrum weight = closure_weight * mix_weight;
    ccl_private MicrofacetBsdf *bsdf = (ccl_private MicrofacetBsdf *)bsdf_alloc(
    sd, sizeof(MicrofacetBsdf), weight);
    + ccl_private FresnelGeneralizedSchlick *fresnel =
    + (bsdf != NULL) ? (ccl_private FresnelGeneralizedSchlick *)closure_alloc_extra(
    + sd, sizeof(FresnelGeneralizedSchlick)) : NULL;
    - if (!bsdf) {
    + if (!bsdf || !fresnel) {
    break;
    }
    - float roughness = sqr(param1);
    + float roughness = param1;

    - bsdf->ior = 1.0f;
    + bsdf->ior = 0.0f;

    + kernel_assert(stack_valid(data_node.w));
    + fresnel->f0 = rgb_to_spectrum(stack_load_float3(stack, data_node.w));
    + fresnel->f90 = one_spectrum();
    + fresnel->exponent = 5;
    + fresnel->reflection_tint = one_spectrum();
    + fresnel->transmission_tint = zero_spectrum();

    + bsdf->ior = ior_from_F0(fresnel->f0.x);

    /* compute roughness */
    float anisotropy = clamp(param2, -0.99f, 0.99f);
    if (data_node.y == SVM_STACK_INVALID || fabsf(anisotropy) <= 1e-4f) {
    /* Isotropic case. */
    bsdf->T = zero_float3();
    bsdf->alpha_x = roughness;
    bsdf->alpha_y = roughness;
    }
    else {
    bsdf->T = stack_load_float3(stack, data_node.y);

    /* rotate tangent */
    float rotation = stack_load_float(stack, data_node.z);
    if (rotation != 0.0f) {
    bsdf->T = rotate_around_axis(bsdf->T, bsdf->N, rotation * M_2PI_F);
    }

    if (anisotropy < 0.0f) {
    bsdf->alpha_x = roughness / (1.0f + anisotropy);
    bsdf->alpha_y = roughness * (1.0f + anisotropy);
    }
    else {
    bsdf->alpha_x = roughness * (1.0f - anisotropy);
    bsdf->alpha_y = roughness / (1.0f - anisotropy);
    }
    }
    /* setup bsdf */
    if (type == CLOSURE_BSDF_MICROFACET_BECKMANN_ID) {
    sd->flag |= bsdf_microfacet_beckmann_setup(bsdf);
    + bsdf_microfacet_setup_fresnel_generalized_schlick(kg, bsdf, sd, fresnel, false);
    }
    else if (type == CLOSURE_BSDF_ASHIKHMIN_SHIRLEY_ID) {
    sd->flag |= bsdf_ashikhmin_shirley_setup(bsdf);
    }
    else {
    sd->flag |= bsdf_microfacet_ggx_setup(bsdf);
    if (type == CLOSURE_BSDF_MICROFACET_MULTI_GGX_ID) {
    kernel_assert(stack_valid(data_node.w));
    const Spectrum color = rgb_to_spectrum(stack_load_float3(stack, data_node.w));
    bsdf_microfacet_setup_fresnel_constant(kg, bsdf, sd, color);
    }
    }

    break;
    }
  3. 加了上面的代码之后发现Blender无法正常渲染,整个人脸部分都是全黑色的,printf输出debug之后发现是上述增加的closure_alloc_extra无法为Fresnel项分配内存,到函数内部debug发现是sd->num_closure变量不够用,因此找到了设置该变量的位置:intern\cycles\scene\shader_graph.cpp中的int ShaderGraph::get_num_closures(),修改如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
      int num_closures = 0;
    foreach (ShaderNode *node, nodes) {
    ClosureType closure_type = node->get_closure_type();
    if (closure_type == CLOSURE_NONE_ID) {
    continue;
    }
    else if (CLOSURE_IS_BSSRDF(closure_type)) {
    num_closures += 3;
    }
    else if (CLOSURE_IS_BSDF_MULTISCATTER(closure_type)) {
    num_closures += 2;
    }
    else if (CLOSURE_IS_PRINCIPLED(closure_type)) {
    num_closures += 12;
    }
    else if (CLOSURE_IS_VOLUME(closure_type)) {
    /* TODO(sergey): Verify this is still needed, since we have special minimized volume storage
    * for the volume steps. */
    num_closures += MAX_VOLUME_STACK_SIZE;
    }
    else if (closure_type == CLOSURE_BSDF_MICROFACET_BECKMANN_GLASS_ID ||
    closure_type == CLOSURE_BSDF_MICROFACET_GGX_GLASS_ID ||
    closure_type == CLOSURE_BSDF_HAIR_CHIANG_ID ||
    closure_type == CLOSURE_BSDF_HAIR_HUANG_ID)
    {
    num_closures += 2;
    }
    + else if (closure_type == CLOSURE_BSDF_MICROFACET_BECKMANN_ID)
    + {
    + num_closures += 2;
    + }
    else {
    // printf("this is else, num_closures+1.\n");
    ++num_closures;
    }
    }
    return num_closures;

  4. 修改之后,Blender可以正常渲染了,但是整体的高光效果非常暗,整体亮度偏低,这里我又做了很多对照实现,分层渲染分析高光的Color、直接光、间接光,最后发现高光部分的直接光和Ground Truth对照组(OSL渲染的generalized_bsdf)有一个大致的线性倍数关系,实验后发现倍数刚好是f0的值,查看glossy bsdf node的OSL代码后发现,Blender的逻辑似乎是在BSDF计算后再乘上Color值,而我把f0值直接输入Color就会导致错误产生,为了避免这种干扰,我选择修改Glossy BSDF Node的输入节点设置,增加一个f0输入接口,而Color值设为1.0,修改位置如下:

  • intern\cycles\scene\shader_nodes.cpp中的NODE(GlossyBsdfNode):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
      NodeType *type = NodeType::add("glossy_bsdf", create, NodeType::SHADER);

    SOCKET_IN_COLOR(color, "Color", make_float3(0.8f, 0.8f, 0.8f));
    SOCKET_IN_NORMAL(normal, "Normal", zero_float3(), SocketType::LINK_NORMAL);
    SOCKET_IN_FLOAT(surface_mix_weight, "SurfaceMixWeight", 0.0f, SocketType::SVM_INTERNAL);

    static NodeEnum distribution_enum;
    distribution_enum.insert("beckmann", CLOSURE_BSDF_MICROFACET_BECKMANN_ID);
    distribution_enum.insert("ggx", CLOSURE_BSDF_MICROFACET_GGX_ID);
    distribution_enum.insert("ashikhmin_shirley", CLOSURE_BSDF_ASHIKHMIN_SHIRLEY_ID);
    distribution_enum.insert("multi_ggx", CLOSURE_BSDF_MICROFACET_MULTI_GGX_ID);
    SOCKET_ENUM(distribution, "Distribution", distribution_enum, CLOSURE_BSDF_MICROFACET_GGX_ID);

    SOCKET_IN_VECTOR(tangent, "Tangent", zero_float3(), SocketType::LINK_TANGENT);

    SOCKET_IN_FLOAT(roughness, "Roughness", 0.5f);
    SOCKET_IN_FLOAT(anisotropy, "Anisotropy", 0.0f);
    SOCKET_IN_FLOAT(rotation, "Rotation", 0.0f);

    + SOCKET_IN_COLOR(f0, "F0", make_float3(0.5f, 0.5f, 0.5f));

    SOCKET_OUT_CLOSURE(BSDF, "BSDF");

    return type;
  • intern\cycles\scene\shader_nodes.cpp中的void GlossyBsdfNode::compile,把原来的input("Color")改为input("F0"):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
      closure = distribution;

    /* TODO: Just use weight for legacy MultiGGX? Would also simplify OSL. */
    if (closure == CLOSURE_BSDF_MICROFACET_MULTI_GGX_ID) {
    BsdfNode::compile(
    compiler, input("Roughness"), input("Anisotropy"), input("Rotation"), input("Color"));
    }
    else {
    - BsdfNode::compile(compiler, input("Roughness"), input("Anisotropy"), input("Rotation"));
    + BsdfNode::compile(compiler, input("Roughness"), input("Anisotropy"), input("Rotation"), input("F0"));
    }
  • intern\cycles\scene\shader_nodes.h中的GlossyBsdfNode定义:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    class GlossyBsdfNode : public BsdfNode {
    public:
    SHADER_NODE_CLASS(GlossyBsdfNode)

    void simplify_settings(Scene *scene);
    ClosureType get_closure_type()
    {
    return distribution;
    }

    NODE_SOCKET_API(float3, tangent)
    NODE_SOCKET_API(float, roughness)
    NODE_SOCKET_API(float, anisotropy)
    NODE_SOCKET_API(float, rotation)
    + NODE_SOCKET_API(float3, f0)
    NODE_SOCKET_API(ClosureType, distribution)

    void attributes(Shader *shader, AttributeRequestSet *attributes);
    bool has_attribute_dependency()
    {
    return true;
    }

    bool is_isotropic();
    };
  • source\blender\nodes\shader\nodes\node_shader_bsdf_glossy.cc中的static void node_declare函数中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
      b.add_input<decl::Color>("Color").default_value({0.8f, 0.8f, 0.8f, 1.0f});
    b.add_input<decl::Float>("Roughness")
    .default_value(0.5f)
    .min(0.0f)
    .max(1.0f)
    .subtype(PROP_FACTOR);
    b.add_input<decl::Float>("Anisotropy").default_value(0.0f).min(-1.0f).max(1.0f);
    b.add_input<decl::Float>("Rotation")
    .default_value(0.0f)
    .min(0.0f)
    .max(1.0f)
    .subtype(PROP_FACTOR);
    b.add_input<decl::Vector>("Normal").hide_value();
    b.add_input<decl::Vector>("Tangent").hide_value();
    b.add_input<decl::Float>("Weight").unavailable();
    + b.add_input<decl::Color>("F0").default_value({0.5f, 0.5f, 0.5f, 1.0f});
    b.add_output<decl::Shader>("BSDF");

增加输入节点后的Glossy BSDF:
重编译后的Glossy BSD

  1. 为了保持OSL模式下渲染效果也一致,修改了intern\cycles\kernel\osl\shaders\node_glossy_bsdf.osl,如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    shader node_glossy_bsdf(color Color = 0.8,
    string distribution = "ggx",
    float Roughness = 0.2,
    float Anisotropy = 0.0,
    float Rotation = 0.0,
    normal Normal = N,
    normal Tangent = 0.0,
    + color F0 = 0.0,
    output closure color BSDF = 0)
    {
    /* compute roughness */
    float roughness = Roughness * Roughness;
    float roughness_u, roughness_v;
    float aniso = clamp(Anisotropy, -0.99, 0.99);

    /* rotate tangent around normal */
    vector T = Tangent;

    if (abs(aniso) <= 1e-4) {
    roughness_u = roughness;
    roughness_v = roughness;
    }
    else {
    if (Rotation != 0.0)
    T = rotate(T, Rotation * M_2PI, point(0.0, 0.0, 0.0), Normal);

    if (aniso < 0.0) {
    roughness_u = roughness / (1.0 + aniso);
    roughness_v = roughness * (1.0 + aniso);
    }
    else {
    roughness_u = roughness * (1.0 - aniso);
    roughness_v = roughness / (1.0 - aniso);
    }
    }

    if (distribution == "Multiscatter GGX")
    BSDF = Color * microfacet_multi_ggx_aniso(Normal, T, roughness_u, roughness_v, Color);
    + else if (distribution == "Beckmann" || distribution == "beckmann")
    + {
    + color reflection_tint = color(1, 1, 1);
    + color transmission_tint = color(0, 0, 0);
    + float roughness_x = Roughness;
    + float roughness_y = Roughness;
    + color F90 = color(1, 1, 1);
    + BSDF = generalized_schlick_bsdf(Normal, T, reflection_tint, transmission_tint, roughness_x, roughness_y, F0, F90, 5, distribution);
    + }
    else
    BSDF = Color * microfacet(distribution, Normal, T, roughness_u, roughness_v, 0.0, 0);
    }

  2. Blender中创建ShaderData变量的位置在:intern\cycles\kernel\integrator\shader_surface.hccl_device int integrate_surface函数中

  3. source\blender\nodes\shader\nodes\node_shader_bsdf_glossy.cc文件中有一部分也用了glossy bsdf node的输入,不过这部分被包裹在#ifdef WITH_MATERIALX定义中,因此应该只有materialX时才会用到。

另一种可能的Blender源码修改方法

在后面做其他工作的时候又想到因为Principled BSDF用的是generalized bsdf,是否可以直接在里面加一个beckmann分布,有了之前的经验,这次很快就把该分布加上了,但是渲染效果偏暗并不能对齐,这里由于牵扯的变量太多了,时间原因暂且不深究,只在下面记录在Principled BSDF加入beckmann分布选项的方法:

  1. source\blender\makesrna\intern\rna_nodetree.cc中:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    static const EnumPropertyItem node_principled_distribution_items[] = {
    + {SHD_GLOSSY_BECKMANN, "BECKMANN", 0, "Beckmann", ""},
    {SHD_GLOSSY_GGX, "GGX", 0, "GGX", ""},
    {SHD_GLOSSY_MULTI_GGX,
    "MULTI_GGX",
    0,
    "Multiscatter GGX",
    "GGX with additional correction to account for multiple scattering, preserve energy and "
    "prevent unexpected darkening at high roughness"},
    {0, nullptr, 0, nullptr, nullptr},
    };
  2. intern\cycles\blender\shader.cpp中:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
      else if (b_node.is_a(&RNA_ShaderNodeBsdfPrincipled)) {
    BL::ShaderNodeBsdfPrincipled b_principled_node(b_node);
    PrincipledBsdfNode *principled = graph->create_node<PrincipledBsdfNode>();
    switch (b_principled_node.distribution()) {
    + case BL::ShaderNodeBsdfPrincipled::distribution_BECKMANN:
    + principled->set_distribution(CLOSURE_BSDF_MICROFACET_BECKMANN_GLASS_ID);
    + break;
    case BL::ShaderNodeBsdfPrincipled::distribution_GGX:
    principled->set_distribution(CLOSURE_BSDF_MICROFACET_GGX_GLASS_ID);
    break;
    case BL::ShaderNodeBsdfPrincipled::distribution_MULTI_GGX:
    principled->set_distribution(CLOSURE_BSDF_MICROFACET_MULTI_GGX_GLASS_ID);
    break;
    }

前期学习阶段中使用到的阅读资料和学到的新知识点

Cycles官方开发文档

Next Event Estimation(NEE)

  1. 在查看Blender源码的过程中发现了一个DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_MNEE,查询后认为MNEE指的应该是Manifold Next Event Estimation,这是一种可以正确的渲染焦散(caustic)的路径追踪方式。
  1. 其中,NEE是Next Event Estimation的缩写,指的是次事件估计。次事件估计是一种多重重要性采样的方法,简而言之就是对于每次bounce,直接光照通过光源采样得到,间接光照通过BSDF采样得到,并且最后通过多重重要性采样结合起来。

HIP

ROCm平台是平行于CUDA的概念,是AMD的软件平台,用于A卡上面加速GPU计算,在源码级别支持CUDA程序。而HIP是在A卡上使用的编程模型,对标CUDA编程模型。HIP几乎是克隆CUDA,大多数情况下稍加修改就能转换,并且可以在N卡上运行。

clang-fromat

clang-fromat是一种代码风格统一管理工具,Blender用其进行代码的风格统一和规范,所谓代码风格指的是:修饰符偏移,括号对齐,宏定义对齐等格式上的规范。

Open Shading Language(OSL)

OSL是一种语法类似C语言的开放式着色语言,在blender中可以用OSL实现自己创建shading node。在渲染时.osl文件被编译为用于渲染的.oso文件,大多数支持OSL着色器的渲染引擎都附带OSL编译器。
参考资料:

OptiX

OpitX和Cuda都是NVidia的渲染框架,OptiX是专为光线追踪设计的,相比与Cuda在渲染更复杂的场景或者材质时可以提供更快的渲染时间。另外,目前在Blender中要使用OSL编程着色模型并且使用GPU渲染的话,似乎只支持OptiX不支持Cuda(截至2023.12.21,Blender4.0)。

Cycles Kernel Language

Wavefront Path Tracing:

Blender源码中,一种为了解决GPU编程时Kernel函数过大遇到的问题,把一个很大的Kernel拆分成很多小的Kernel的技术。

SIMD

SIMD(Single Instruction Multiple Data)即单指令流多数据流,是一种采用一个控制器来控制多个处理器,同时对一组数据(又称“数据向量”)中的每一个分别执行相同的操作从而实现空间上的并行性的技术。简单来说就是一个指令能够同时处理多个数据。

Open Shading Language 教程

https://github.com/AcademySoftwareFoundation/OpenShadingLanguage/blob/main/src/doc/osl-languagespec.pdf

遇到的其他问题和尝试的其他方案

  1. 一开始不确定重编译Blender会不会遇到其他的问题,试图直接使用OSL模式渲染,就是在osl脚本里面调用generalized_schlick_bsdf,但是发现Blender对OSL的支持并不完备,使用GPU渲染只能选择OptiX框架,当时在OSL模式下无法渲染我们的材质球,或许是因为里面节点太多了,显示的报错是:

    1
    Error: Requested OSL group data size (6456) is greater than the maximum supported with OptiX (2048)
  2. 之后尝试的解决方案是把用shader graph做的节点分模块改写为OSL,再进行渲染,这样的好处是可以通过OptiX编译限制,并且Shader Graph更简洁,缺点是不利于美术人员后续对材质继续修改。但是试验后发现,虽然这样改写后可以渲染,但是Blender4.0使用OptiX渲染SSS的效果是错误的,不能直接使用。

  3. 后面又考虑了把Diffuse(包括SSS)和Specular分开渲染再合起来,后面通过实践和思考证明这样行不通,一起渲染时,Specular部分的间接光照会带上Diffuse的颜色并且较亮,而分开渲染时Specular的间接光照则是纯黑白的,且比较暗。分开渲染是行不通的,因此综上所述,只能走重编译的道路。

  4. Blender3.6到4.0升级遇到的Principled Bsdf节点变化以及自动转换的评估:
    3.6 Base Color 和Subsurface两个颜色输入,Subsurface是浮点数输入。
    4.0 只有一个Base Color,自动添加了Mix Color节点,混合之前的两个输入作为Base Color的输入,该节点混合的Factor为原来的Subsurface输入(Clamp Factor)。

Roughness和Normal连接不变。

3.6的Subsurface除了作为mix factor还会连接在4.0的Subsurface模块的Scale输入上。

3.6的Specular输入在4.0连接在Specular模块的IOR Level上。

源码中IOR的描述:

1
2
3
4
.description(
"Adjustment to the IOR to increase or decrease specular intensity "
"(0.5 means no adjustment, 0 removes all reflections, 1 doubles them at normal "
"incidence)");

自动转换后Specular的渲染会有一个很小的亮度差异,但我认为不影响最终效果。
但是在测试的过程中发现Blender4.0的SSS效果和之前有很大的差异,但是不能判断该差异是变好了还是变坏了,需要美术人员测试决定。

评论