PBRT_V2 总结记录 DipoleSubsurfaceIntegrator (Subsurface Reflection)

概述

The bidirectional scattering-surface reflectance distribution function (BSSRDF) was introduced
in Section 5.6.2; it gives exitant differential radiance at a point on a surface po
given incident differential irradiance at another point pi: S(po, ωo, pi , ωi). Accurately
rendering translucent surfaces with subsurface scattering requires integrating over both
area—points on the surface of the object being rendered—and incident direction, evaluating
the BSSRDF and computing reflection with the subsurface scattering equation

Figure 16.8 suggests the complexity of evaluating the integral. To compute the standard
Monte Carlo estimate of this equation given a point at which to compute outgoing
radiance, we need a technique to sample points pi on the surface and to compute the
incident radiance at these points, as well as an efficient way to compute the specific value
of the BSSRDF S(po, ωo, pi , ωi) for each sampled point pi and incident direction.

Figure 16.8: Computing Subsurface Reflection. When a surface is translucent, in order to compute
outgoing radiance from a point po in direction ωo, it’s necessary to sample a number of points pi and
compute incident radiance at them. Then, the BSSRDF, S(po, ωo, pi , ωi), must be evaluated in order
to determine the effect of incident radiance at one point on outgoing radiance at the point of interest.
The BSSRDF can be difficult to evaluate efficiently, since it represents all scattering within the volume
for light that enters at one point and exits at the other.

实现思路

The method we’ll implement in this section is based on two main ideas. First, the distribution
of light in the translucent medium is modeled with the diffusion(扩散) approximation,
which describes the equlibrium distribution of illumination in highly scattering optically
thick participating media. Second, a solution to the diffusion equation is found by composing(组成)
closed-form solutions of it for point light sources; a dipole of two light sources is
used to approximate the overall scattering.

This approach makes a number of simplifications—it assumes heterogeneous scattering
properties throughout the medium, and it implicitly assumes that the medium is semiinfinite
(it extends infinitely beneath a planar surface). In practice, however, it can still
accurately render many interesting surfaces. The “Further Reading” section and exercises
at the end of this chapter have references to improvements to this approach that
generalize it to handle more general settings more accurately.

Our implementation of subsurface scattering has three main components, each of which
will be described in its own section to follow:

1. A large number of random points on the surfaces of the translucent objects in the
scene are sampled and incident irradiance is computed at these points.

2. A hierarchical representation of these points is created, progressively clustering
nearby groups of them summing their irradiance values.

3. At rendering time, these points are used in a hierarchical integration algorithm
that evaluates the subsurface scattering equation at the point being shaded. To
evaluate the integrand, it uses an approximation based on the dipole diffusion
approximation that efficiently evaluates the BSSRDF for a given pair of points on
the surface.

思路第一步:

A large number of random points on the surfaces of the translucent objects in the
scene are sampled and incident irradiance is computed at these points.

For the hierarchical integration algorithm, we need to generate a collection of points
on the surfaces of the translucent objects in the scene at which we’ll compute incident
irradiance.
These points should be reasonably uniformly distributed so that they sample
the irradiance at a regular rate; if there were clumps of multiple points close together, the
hierarchical integration algorithm would compute poor results.

1. (Getting a collection of SurfacePoints on the surfaces of translucent objects.)

(SurfacePointRenderer 的思路,在pCamera 点上,360度发出ray,ray与场景中的BSSDF材质的物体进行相交,得出相交点,而且,这些相关点之间的距离不能小于 minDist,通过一系列的筛选,把与BSSDF物体相交的信息保存到SurfacePoint中,得到 vector<SurfacePoint> points 。那么 SurfacePointRenderer 最主要的工作,是放在 SurfacePointTask.Run 中进行)

In the implementation here, we first generate random candidate points on the surfaces of
translucent objects. These points are then either accepted or rejected based on a Poisson
sphere test: if no other accepted points are within a given 3D distance of the candidate
point, the point is accepted. This process continues until a few thousand candidate tests
have been rejected in a row, suggesting that no more will be found that pass the Poisson
sphere test. The approximation of using a 3D Poisson sphere criterion to approximate a
2D Poisson disk on the surface of a complex object generally works well, though for some
geometric configurations it can be significantly inaccurate;

This task is handled by the SurfacePointsRenderer, defined in the files renderers/
surfacepoints.h and renderers/surfacepoints.cpp. This functionality is provided as
a Renderer so that point sets can be precomputed and stored in a file for later use with
the DipoleSubsurfaceIntegrator.

struct SurfacePoint {
    SurfacePoint() { }
    SurfacePoint(const Point &pp, const Normal &nn, float a, float eps)
        : p(pp), n(nn), area(a), rayEpsilon(eps) { }
    // SurfacePoint Data
    Point p;
    Normal n;
    float area, rayEpsilon;
};

class SurfacePointsRenderer : public Renderer {
public:
    // SurfacePointsRenderer Public Methods
    SurfacePointsRenderer(float md, const Point &pc, float t,
                          const string &fn)
        : minDist(md), time(t), pCamera(pc), filename(fn) { }
    void Render(const Scene *scene);
    Spectrum Li(const Scene *scene, const RayDifferential &ray,
        const Sample *sample, RNG &rng, MemoryArena &arena,
        Intersection *isect, Spectrum *T) const;
    Spectrum Transmittance(const Scene *scene, const RayDifferential &ray,
        const Sample *sample, RNG &rng, MemoryArena &arena) const;
private:
    // SurfacePointsRenderer Private Data
    float minDist, time;
    Point pCamera;
    string filename;
    friend void FindPoissonPointDistribution(const Point &pCamera, float time,
        float minDist, const Scene *scene, vector<SurfacePoint> *points);
    vector<SurfacePoint> points;
};



// SurfacePointsRenderer Local Declarations
class SurfacePointTask : public Task {
public:
    SurfacePointTask(const Scene *sc, const Point &org, float ti, int tn,
        float msd, int mf, RWMutex &m, int &rf, int &mrf,
        int &tpt, int &trt, int &npa, GeometricPrimitive &sph,
        Octree<SurfacePoint> &oct, vector<SurfacePoint> &sps,
        ProgressReporter &pr)
        : taskNum(tn), scene(sc), origin(org), time(ti),
          minSampleDist(msd), maxFails(mf), mutex(m),
          repeatedFails(rf), maxRepeatedFails(mrf), totalPathsTraced(tpt),
          totalRaysTraced(trt), numPointsAdded(npa), sphere(sph),
          octree(oct), surfacePoints(sps), prog(pr) { }
    void Run();

    int taskNum;
    const Scene *scene;
    Point origin;
    float time;
    float minSampleDist;
    int maxFails;

    RWMutex &mutex;
    int &repeatedFails, &maxRepeatedFails;
    int &totalPathsTraced, &totalRaysTraced, &numPointsAdded;
    GeometricPrimitive &sphere;
    Octree<SurfacePoint> &octree;
    vector<SurfacePoint> &surfacePoints;
    ProgressReporter &prog;
};

作用:

To implement the Poisson sphere approach, we need to be able to generate an arbitrary
number of random candidate points on translucent scene surfaces, and we need to 

have an efficient way to reject the candidate points that don’t fulfill the Poisson sphere
requirement. To generate candidate points, we use a random sampling algorithm based
on tracing rays through the scene and choosing a random scattered direction at each
intersection point. This point sampling algorithm was introduced by Lehtinen et al.
(2008) (though it wasn’t applied to subsurface scattering). An octree is then used to store
the accepted points, enabling efficient Poisson sphere tests for candidates. Because this
funcionality is implemented in the context of a Renderer implementation, this work is
done in the Render() method.

得到的的点类似这个效果:

2. (Incident irradiance is then computed at each point, and then an octree that encodes a hierarchical clustering of the points is created)


void DipoleSubsurfaceIntegrator::Preprocess(const Scene *scene,
        const Camera *camera, const Renderer *renderer) {
    if (scene->lights.size() == 0) return;
    vector<SurfacePoint> pts;
    // Get _SurfacePoint_s for translucent objects in scene
    if (filename != "") {
        // Initialize _SurfacePoint_s from file
        vector<float> fpts;
        if (ReadFloatFile(filename.c_str(), &fpts)) {
            if ((fpts.size() % 8) != 0)
                Error("Excess values (%d) in points file \"%s\"", int(fpts.size() % 8),
                      filename.c_str());
            for (u_int i = 0; i < fpts.size(); i += 8)
                pts.push_back(SurfacePoint(Point(fpts[i], fpts[i+1], fpts[i+2]),
                                           Normal(fpts[i+3], fpts[i+4], fpts[i+5]),
                                           fpts[i+6], fpts[i+7]));
        }
    }
    if (pts.size() == 0) {
        Point pCamera = camera->CameraToWorld(camera->shutterOpen,
                                              Point(0, 0, 0));
        FindPoissonPointDistribution(pCamera, camera->shutterOpen,
                                     minSampleDist, scene, &pts);
    }

    // Compute irradiance values at sample points
    RNG rng;
    MemoryArena arena;
    PBRT_SUBSURFACE_STARTED_COMPUTING_IRRADIANCE_VALUES();
    ProgressReporter progress(pts.size(), "Computing Irradiances");
    for (uint32_t i = 0; i < pts.size(); ++i) {
        SurfacePoint &sp = pts[i];
        Spectrum E(0.f);
        for (uint32_t j = 0; j < scene->lights.size(); ++j) {
            // Add irradiance from light at point
            const Light *light = scene->lights[j];
            Spectrum Elight = 0.f;
            int nSamples = RoundUpPow2(light->nSamples);
            uint32_t scramble[2] = { rng.RandomUInt(), rng.RandomUInt() };
            uint32_t compScramble = rng.RandomUInt();
            for (int s = 0; s < nSamples; ++s) {
                float lpos[2];
                Sample02(s, scramble, lpos);
                float lcomp = VanDerCorput(s, compScramble);
                LightSample ls(lpos[0], lpos[1], lcomp);
                Vector wi;
                float lightPdf;
                VisibilityTester visibility;
                Spectrum Li = light->Sample_L(sp.p, sp.rayEpsilon,
                    ls, camera->shutterOpen, &wi, &lightPdf, &visibility);
                if (Dot(wi, sp.n) <= 0.) continue;
                if (Li.IsBlack() || lightPdf == 0.f) continue;
                Li *= visibility.Transmittance(scene, renderer, NULL, rng, arena);
                if (visibility.Unoccluded(scene))
                    Elight += Li * AbsDot(wi, sp.n) / lightPdf;
            }
            E += Elight / nSamples;
        }
        irradiancePoints.push_back(IrradiancePoint(sp, E));
        PBRT_SUBSURFACE_COMPUTED_IRRADIANCE_AT_POINT(&sp, &E);
        arena.FreeAll();
        progress.Update();
    }
    progress.Done();
    PBRT_SUBSURFACE_FINISHED_COMPUTING_IRRADIANCE_VALUES();

    // Create octree of clustered irradiance samples
    octree = octreeArena.Alloc<SubsurfaceOctreeNode>();
    for (uint32_t i = 0; i < irradiancePoints.size(); ++i)
        octreeBounds = Union(octreeBounds, irradiancePoints[i].p);
    for (uint32_t i = 0; i < irradiancePoints.size(); ++i)
        octree->Insert(octreeBounds, &irradiancePoints[i], octreeArena);
    octree->InitHierarchy();
}

作用:

We can now turn to the implementation of the Preprocess() method of the Dipole
SubsurfaceIntegrator. It starts by getting a collection of SurfacePoints on the surfaces
of translucent objects, either loading them from a file generated by the SurfacePoints
Renderer or generating them here. Incident irradiance is then computed at each point,
and then an octree that encodes a hierarchical clustering of the points is created.

细节

a.

    // Compute irradiance values at sample points
    RNG rng;
    MemoryArena arena;
    PBRT_SUBSURFACE_STARTED_COMPUTING_IRRADIANCE_VALUES();
    ProgressReporter progress(pts.size(), "Computing Irradiances");
    for (uint32_t i = 0; i < pts.size(); ++i) {
        SurfacePoint &sp = pts[i];
        Spectrum E(0.f);
        for (uint32_t j = 0; j < scene->lights.size(); ++j) {
            // Add irradiance from light at point
            const Light *light = scene->lights[j];
            Spectrum Elight = 0.f;
            int nSamples = RoundUpPow2(light->nSamples);
            uint32_t scramble[2] = { rng.RandomUInt(), rng.RandomUInt() };
            uint32_t compScramble = rng.RandomUInt();
            for (int s = 0; s < nSamples; ++s) {
                float lpos[2];
                Sample02(s, scramble, lpos);
                float lcomp = VanDerCorput(s, compScramble);
                LightSample ls(lpos[0], lpos[1], lcomp);
                Vector wi;
                float lightPdf;
                VisibilityTester visibility;
                Spectrum Li = light->Sample_L(sp.p, sp.rayEpsilon,
                    ls, camera->shutterOpen, &wi, &lightPdf, &visibility);
                if (Dot(wi, sp.n) <= 0.) continue;
                if (Li.IsBlack() || lightPdf == 0.f) continue;
                Li *= visibility.Transmittance(scene, renderer, NULL, rng, arena);
                if (visibility.Unoccluded(scene))
                    Elight += Li * AbsDot(wi, sp.n) / lightPdf;
            }
            E += Elight / nSamples;
        }
        irradiancePoints.push_back(IrradiancePoint(sp, E));
        PBRT_SUBSURFACE_COMPUTED_IRRADIANCE_AT_POINT(&sp, &E);
        arena.FreeAll();
        progress.Update();
    }
    progress.Done();




struct IrradiancePoint {
    IrradiancePoint() { }
    IrradiancePoint(const SurfacePoint &sp, const Spectrum &ee)
        : p(sp.p), n(sp.n), E(ee), area(sp.area),
          rayEpsilon(sp.rayEpsilon) { }
    Point p;
    Normal n;
    Spectrum E;
    float area, rayEpsilon;
};

作用:

(上面的主要是遍历所有的 SurfacePoint(BSSDF 物体的所有的采样 交点), 计算每一个SurfacePoint 的irradiance,生成一个IrradiancePoint , 保存到 irradiancePoints 数组中)

For each SurfacePoint, the incident irradiance from the light sources in the scene is
computed. An IrradiancePoint is then created for each point.

it computes the contribution of direct lighting to irradiance,
ignoring indirect illumination, taking a number of samples for each light to estimate the
integral fromEquation (5.3). This computation is currently done in a single thread of execution;
it is usually fast enough that breaking it into tasks is of limited value. For many
applications, it is desirable to instead compute irradiance including indirect illumination.
If the indirect illumination computation is more computationally intensive, parallelization
of this step may be appropriate.

思路第二步:

A hierarchical representation of these points is created, progressively clustering
nearby groups of them summing their irradiance values.

1.(接上面的 Preprocess 函数)

    // Create octree of clustered irradiance samples
    octree = octreeArena.Alloc<SubsurfaceOctreeNode>();
    for (uint32_t i = 0; i < irradiancePoints.size(); ++i)
        octreeBounds = Union(octreeBounds, irradiancePoints[i].p);
    for (uint32_t i = 0; i < irradiancePoints.size(); ++i)
        octree->Insert(octreeBounds, &irradiancePoints[i], octreeArena);
    octree->InitHierarchy();

作用:

After the irradiance values for the IrradiancePoints are computed, the octree is created.
The Preprocess() method allocates the root node of the tree, computes the bounding
box of all of the points, and inserts all of the points into the octree in turn. The root node
starts out as an empty leaf node but progressively refines itself to have children nodes
(which in turn refine themselves) as more points are added to it.

细节

a.

// DipoleSubsurfaceIntegrator Local Declarations
struct SubsurfaceOctreeNode {
    // SubsurfaceOctreeNode Methods
    SubsurfaceOctreeNode() {
        isLeaf = true;
        sumArea = 0.f;
        for (int i = 0; i < 8; ++i)
            ips[i] = NULL;
    }
    void Insert(const BBox &nodeBound, IrradiancePoint *ip,
                MemoryArena &arena) {
        ...
    }
    void InitHierarchy() {
        ...
    }
    Spectrum Mo(const BBox &nodeBound, const Point &p, const DiffusionReflectance &Rd,
                float maxError);

    // SubsurfaceOctreeNode Public Data
    Point p;
    bool isLeaf;
    Spectrum E;
    float sumArea;
    union {
        SubsurfaceOctreeNode *children[8];
        IrradiancePoint *ips[8];
    };
};

作用:

(octree 的叶子最多保存 8个 IrradiancePoint,如果不是叶子节点的话,也是最多保存 8个 SubsurfaceOctreeNode ,SubsurfaceOctreeNode  保存 这些irradiance的聚合信息,例如平均位置等, )

The SubsurfaceOctreeNode class represents a node of the octree that stores the hierarchical(层次)
clustered(成群) irradiance points.

The leaf nodes of the octree hold pointers to up to(最多) eight IrradiancePoints. Interior nodes
hold pointers to the eight potential children.
This octree follows the conventions of the
Octree template class in Section A.7 in terms of the ordering of the children. Each node
of the octree also holds aggregate information(聚合信息) about the IrradiancePoints in the tree
beneath it, including the average position and irradiance of the points underneath it as
well as the sum of their areas.

b. SubsurfaceOctreeNode::Insert

    void Insert(const BBox &nodeBound, IrradiancePoint *ip,
                MemoryArena &arena) {
        Point pMid = .5f * nodeBound.pMin + .5f * nodeBound.pMax;
        if (isLeaf) {
            // Add _IrradiancePoint_ to leaf octree node
            for (int i = 0; i < 8; ++i) {
                if (!ips[i]) {
                    ips[i] = ip;
                    return;
                }
            }

            // Convert leaf node to interior node, redistribute points
            isLeaf = false;
            IrradiancePoint *localIps[8];
            for (int i = 0; i < 8; ++i) {
                localIps[i] = ips[i];
                children[i] = NULL;
            }
            for (int i = 0; i < 8; ++i)  {
                IrradiancePoint *ip = localIps[i];
                // Add _IrradiancePoint_ _ip_ to interior octree node
                int child = (ip->p.x > pMid.x ? 4 : 0) +
                    (ip->p.y > pMid.y ? 2 : 0) + (ip->p.z > pMid.z ? 1 : 0);
                if (!children[child])
                    children[child] = arena.Alloc<SubsurfaceOctreeNode>();
                BBox childBound = octreeChildBound(child, nodeBound, pMid);
                children[child]->Insert(childBound, ip, arena);
            }
            /* fall through to interior case to insert the new point... */
        }
        // Add _IrradiancePoint_ _ip_ to interior octree node
        int child = (ip->p.x > pMid.x ? 4 : 0) +
            (ip->p.y > pMid.y ? 2 : 0) + (ip->p.z > pMid.z ? 1 : 0);
        if (!children[child])
            children[child] = arena.Alloc<SubsurfaceOctreeNode>();
        BBox childBound = octreeChildBound(child, nodeBound, pMid);
        children[child]->Insert(childBound, ip, arena);
    }


inline BBox octreeChildBound(int child, const BBox &nodeBound,
                             const Point &pMid) {
    BBox childBound;
    childBound.pMin.x = (child & 4) ? pMid.x : nodeBound.pMin.x;
    childBound.pMax.x = (child & 4) ? nodeBound.pMax.x : pMid.x;
    childBound.pMin.y = (child & 2) ? pMid.y : nodeBound.pMin.y;
    childBound.pMax.y = (child & 2) ? nodeBound.pMax.y : pMid.y;
    childBound.pMin.z = (child & 1) ? pMid.z : nodeBound.pMin.z;
    childBound.pMax.z = (child & 1) ? nodeBound.pMax.z : pMid.z;
    return childBound;
}

思路:

(1. 所有的 IrradiancePoint 的 p 位置,组成一个大的包围盒,那么这个大的包围盒就是 octree 根节点的 包围盒。

   2. 根节点一开始 是作为一个叶子节点,当根节点执行 Insert 的时候,就要先判断 根节点的 是否 保存够了8个  IrradiancePoint ,如果是保存够了 8个 IrradiancePoint 的话,那么 根节点 就要从 叶子节点 变成 内部节点,根节点变成内部节点的时候,会根据自己的包围盒的中心点,分成8个区域,每一个区域也是一个 SubsurfaceOctreeNode,然后原先根节点的 8个 IrradiancePoint  会 比较自己的 p 点位置和 根节点的 包围盒的中心点,来确定 自己到底是属于哪一个区域的 SubsurfaceOctreeNode,添加到 SubsurfaceOctreeNode 下面。以后继续添加 节点的时候,是递归上面的过程。

3. 代码中: int child = (ip->p.x > pMid.x ? 4 : 0) + (ip->p.y > pMid.y ? 2 : 0) + (ip->p.z > pMid.z ? 1 : 0);

BBox childBound = octreeChildBound(child, nodeBound, pMid);

根据坐标位置,比较 点p 与 父节点包围盒的中心,确定 子节点 在 父节点 中的 的索引(0-7),这里其实比较好理解,就是把一个父节点的包围盒 以中心切成 8 个子包围盒, 每一个子包围盒对应 子节点 的包围盒)

理解:所有的 IrradiancePoint 组成一个大的包围盒,那么 这个大的包围盒会 以中心点进行切分,一个包围盒 按中心点切分可以切成8个子包围盒,那么每一个子包围盒里面可以最多只 包含 了8个 IrradiancePoint , 再细分这8个子包围盒的话,就可以细分为 8*8 个子包围盒,也可以判断每一个包围盒区域包含了多少个IrradiancePoint,这就是所谓的,A hierarchical representation of these points。

c.

    void InitHierarchy() {
        if (isLeaf) {
            // Init _SubsurfaceOctreeNode_ leaf from _IrradiancePoint_s
            float sumWt = 0.f;
            uint32_t i;
            for (i = 0; i < 8; ++i) {
                if (!ips[i]) break;
                float wt = ips[i]->E.y();
                E += ips[i]->E;
                p += wt * ips[i]->p;
                sumWt += wt;
                sumArea += ips[i]->area;
            }
            if (sumWt > 0.f) p /= sumWt;
            E /= i;
        }
        else {
            // Init interior _SubsurfaceOctreeNode_
            float sumWt = 0.f;
            uint32_t nChildren = 0;
            for (uint32_t i = 0; i < 8; ++i) {
                if (!children[i]) continue;
                ++nChildren;
                children[i]->InitHierarchy();
                float wt = children[i]->E.y();
                E += children[i]->E;
                p += wt * children[i]->p;
                sumWt += wt;
                sumArea += children[i]->sumArea;
            }
            if (sumWt > 0.f) p /= sumWt;
            E /= nChildren;
        }
    }

作用:

(InitHierarchy 计算 每一个节点的聚合值,例如 the average of the positions and irradiance values,the sum of the areas of the children. )

Once all of the points have been added to the octree, InitHierarchy() is called. This
method computes the aggregate values at each node: the average of the positions and
irradiance values and the sum of the areas of the children.

At leaf nodes, the average irradiance and the total area that the points represent are both
easily computed from the individual IrradiancePoints. The representative position for
the samples is computed as a luminance-weighted average of the positions of the points;
the idea is that points with relatively high irradiance values should have more weight
in computing the average position used by the forthcoming hierarchical integration
algorithm.

At interior nodes, the Init interior SubsurfaceOctreeNode fragment first computes aggregate
values for the children nodes via recursive calls to InitHierarchy(). Then, it
computes aggregate values directly from the children’s values.

思路第三步

(At rendering time, these points are used in a hierarchical integration algorithm
that evaluates the subsurface scattering equation at the point being shaded. To
evaluate the integrand, it uses an approximation based on the dipole diffusion
approximation that efficiently evaluates the BSSRDF for a given pair of points on
the surface.)

这里直接给结果,具体细节可以参考PBRT : P906


Spectrum DipoleSubsurfaceIntegrator::Li(const Scene *scene, const Renderer *renderer,
        const RayDifferential &ray, const Intersection &isect,
        const Sample *sample, RNG &rng, MemoryArena &arena) const {
    Spectrum L(0.);
    Vector wo = -ray.d;
    // Compute emitted light if ray hit an area light source
    L += isect.Le(wo);

    // Evaluate BSDF at hit point
    BSDF *bsdf = isect.GetBSDF(ray, arena);
    const Point &p = bsdf->dgShading.p;
    const Normal &n = bsdf->dgShading.nn;
    // Evaluate BSSRDF and possibly compute subsurface scattering
    BSSRDF *bssrdf = isect.GetBSSRDF(ray, arena);
    if (bssrdf && octree) {
        Spectrum sigma_a  = bssrdf->sigma_a();
        Spectrum sigmap_s = bssrdf->sigma_prime_s();
        Spectrum sigmap_t = sigmap_s + sigma_a;
        if (!sigmap_t.IsBlack()) {
            // Use hierarchical integration to evaluate reflection from dipole model
            PBRT_SUBSURFACE_STARTED_OCTREE_LOOKUP(const_cast<Point *>(&p));
            DiffusionReflectance Rd(sigma_a, sigmap_s, bssrdf->eta());
            Spectrum Mo = octree->Mo(octreeBounds, p, Rd, maxError);
            FresnelDielectric fresnel(1.f, bssrdf->eta());
            Spectrum Ft = Spectrum(1.f) - fresnel.Evaluate(AbsDot(wo, n));
            float Fdt = 1.f - Fdr(bssrdf->eta());
            L += (INV_PI * Ft) * (Fdt * Mo);
            PBRT_SUBSURFACE_FINISHED_OCTREE_LOOKUP();
        }
    }
    L += UniformSampleAllLights(scene, renderer, arena, p, n,
        wo, isect.rayEpsilon, ray.time, bsdf, sample, rng, lightSampleOffsets,
        bsdfSampleOffsets);
    if (ray.depth < maxSpecularDepth) {
        // Trace rays for specular reflection and refraction
        L += SpecularReflect(ray, bsdf, rng, isect, renderer, scene, sample,
                             arena);
        L += SpecularTransmit(ray, bsdf, rng, isect, renderer, scene, sample,
                              arena);
    }
    return L;
}

细节

a.

    BSSRDF *bssrdf = isect.GetBSSRDF(ray, arena);
    if (bssrdf && octree) {
        Spectrum sigma_a = bssrdf->sigma_a();
        Spectrum sigmap_s = bssrdf->sigma_prime_s();
        Spectrum sigmap_t = sigmap_s + sigma_a;

The DipoleSubsurfaceIntegrator::Li() method performs standard direct lighting calculations
for objects that don’t have BSSRDFs. For those that do, the following fragment
gets the medium’s scattering properties from the BSSRDF object. The σa variable corresponds
to the absorption coefficient introduced in Section 11.1, and σs' and σt' are the
reduced scattering coefficient and reduced extinction coefficient, respectively, from Secton
16.5.3.

b.

    if (!sigmap_t.IsBlack()) {
            // Use hierarchical integration to evaluate reflection from dipole model
            PBRT_SUBSURFACE_STARTED_OCTREE_LOOKUP(const_cast<Point *>(&p));
            DiffusionReflectance Rd(sigma_a, sigmap_s, bssrdf->eta());
            Spectrum Mo = octree->Mo(octreeBounds, p, Rd, maxError);
            FresnelDielectric fresnel(1.f, bssrdf->eta());
            Spectrum Ft = Spectrum(1.f) - fresnel.Evaluate(AbsDot(wo, n));
            float Fdt = 1.f - Fdr(bssrdf->eta());
            L += (INV_PI * Ft) * (Fdt * Mo);
            PBRT_SUBSURFACE_FINISHED_OCTREE_LOOKUP();
        }

作用:

With this approach to computing Mo, we have the expression for computing outgoing
radiance due to subsurface scattering that is used in the implementation below
.

The terms other than Mo(po) are computed trivially, and Mo(po) is computed using the
approximation of Equation (16.7), which is evaluated with the SubsurfaceOctreeNode::
Mo() method.

c.


Spectrum SubsurfaceOctreeNode::Mo(const BBox &nodeBound, const Point &pt,
        const DiffusionReflectance &Rd, float maxError) {
    // Compute $M_\roman{o}$ at node if error is low enough
    float dw = sumArea / DistanceSquared(pt, p);
    if (dw < maxError && !nodeBound.Inside(pt))
    {
        PBRT_SUBSURFACE_ADDED_INTERIOR_CONTRIBUTION(const_cast<SubsurfaceOctreeNode *>(this));
        return Rd(DistanceSquared(pt, p)) * E * sumArea;
    }

    // Otherwise compute $M_\roman{o}$ from points in leaf or recursively visit children
    Spectrum Mo = 0.f;
    if (isLeaf) {
        // Accumulate $M_\roman{o}$ from leaf node
        for (int i = 0; i < 8; ++i) {
            if (!ips[i]) break;
            PBRT_SUBSURFACE_ADDED_POINT_CONTRIBUTION(const_cast<IrradiancePoint *>(ips[i]));
            Mo += Rd(DistanceSquared(pt, ips[i]->p)) * ips[i]->E * ips[i]->area;
        }
    }
    else {
        // Recursively visit children nodes to compute $M_\roman{o}$
        Point pMid = .5f * nodeBound.pMin + .5f * nodeBound.pMax;
        for (int child = 0; child < 8; ++child) {
            if (!children[child]) continue;
            BBox childBound = octreeChildBound(child, nodeBound, pMid);
            Mo += children[child]->Mo(childBound, pt, Rd, maxError);
        }
    }
    return Mo;
}

作用:

The Mo() method is called first with the root node of the tree. This method returns the
radiant exitance at the point pt due to all of the IrradiancePoints in the nodes beneath
it.
At each node, it uses the aggregate values for the points beneath it, computed earlier in
InitHierarchy(), if the error from using the clustered values would be low. Otherwise,
it either sums the Mo values returned by its children nodes, or, if at a leaf, sums the
contributions from each of the IrradiancePoints the leaf stores.

d. 

DiffusionReflectance Rd(sigma_a, sigmap_s, bssrdf->eta());

作用:

we finally have the dipole approximation to diffuse subsurface reflectance, which we’ll use
for rendering in the implementation below.

Figure 16.16 is a graph of this function. Diffuse subsurface reflectance Rd is the analog(类似)
of the reflectance of BSDFs ρhd and ρhh introduced in Section 8.1.1, in that it represents
directionally averaged scattering.

Figure 16.16: Graph of the Rd Function from Equation (16.6), Using Measured Scattering
Properties of Milk. The value of Rd falls off rapidly as a function of distance d between the points pi
and po.


struct DiffusionReflectance {
    // DiffusionReflectance Public Methods
    DiffusionReflectance(const Spectrum &sigma_a, const Spectrum &sigmap_s,
                         float eta) {
        A = (1.f + Fdr(eta)) / (1.f - Fdr(eta));
        sigmap_t = sigma_a + sigmap_s;
        sigma_tr = Sqrt(3.f * sigma_a * sigmap_t);
        alphap = sigmap_s / sigmap_t;
        zpos = Spectrum(1.f) / sigmap_t;
        zneg = -zpos * (1.f + (4.f/3.f) * A);
    }
    Spectrum operator()(float d2) const {
        Spectrum dpos = Sqrt(Spectrum(d2) + zpos * zpos);
        Spectrum dneg = Sqrt(Spectrum(d2) + zneg * zneg);
        Spectrum Rd = (alphap / (4.f * M_PI)) *
            ((zpos * (dpos * sigma_tr + Spectrum(1.f)) *
              Exp(-sigma_tr * dpos)) / (dpos * dpos * dpos) -
             (zneg * (dneg * sigma_tr + Spectrum(1.f)) *
              Exp(-sigma_tr * dneg)) / (dneg * dneg * dneg));
        return Rd.Clamp();
    }

    // DiffusionReflectance Data
    Spectrum zpos, zneg, sigmap_t, sigma_tr, alphap;
    float A;
};

作用:

DiffusionReflectance is a small utility class that computes the value of Rd using Equation
(16.6). Rather than encapsulating this computation in a utility function, using a class
makes it easier to precompute a number of common terms that only depend on the scattering
properties of the medium. Their values can then be reused many times as Rd is
evaluated for many different points.

细节

operator() 方法: 

The method to compute the reflectance takes the squared distance in the z = 0 plane
between the point of incident illumination and the outgoing point of interest.

猜你喜欢

转载自blog.csdn.net/aa20274270/article/details/86469008