PBRT阅读:第十三章 光源

PBRT阅读:第十三章 光源
http://www.opengpu.org/forum.php?mod=viewthread&tid=6762

第13章 光源

要使场景中的物体可见,就需要其中一些物体可以发出最终能够反射到相机里的光线。本章介绍一个抽象类Light,它定义了光源的接口,另外还介绍了一些有用的光源的实现。我们用一个精心设计的接口将不同类型光源的实现细节隐藏起来,这样光传输例程就可以无需知道场景中光源的类型就可以操作,这跟加速结构可以持有任意类型的体素而无需知道其实际类型的做法有异曲同工之妙。本章只定义光线的基本功能,因为许多跟复杂光源相关的量是没有封闭形式的解的。第15章介绍的Monte Carlo例程将使用数值近似的方法来计算这些量,从而完整地实现了光照接口。

本章介绍了一组光源的实现,虽然光源的多样性受到了pbrt的基于物理的设计的限制。在计算机图形学的领域中,人们已经开发了许多灵活的光源模型,并附加上对多种特性的控制,例如光线按距离衰减的速率,有哪些物体可以被光源照明,有哪些物体可以投射阴影,等等。诸如此类的控制就艺术效果而言是很有用的,但其中有许多的控制是跟基于物理的光传输算法不相容的。举个例子,考虑一个不投射阴影的光源:当在场景中加入越来越多的表面时,到达表面的总能量也会没有止境地增加。设想有围绕着这个光源的一系列的同心的球壳,如果不考虑遮挡,每加入一个球壳就会增加被吸收的总功率。这直接违背了能量守恒:即到达被光源照射的表面的总能量不应该大于光源所放射出的能量。

13.1 Light接口

所有光源有两个共同的参数:其中一个参数定义了光源坐标系在世界空间中的变换矩阵;另一个参数(nSamples)用来控制对光源的Monte Carloc采样。跟形体一样,在一个特定的坐标系下实现光源是很方便的(例如聚光灯总是位于其光源空间的原点,并朝+z轴方向发光)。我们使用光源--世界变换,就可以将光源放入场景中的任意位置,并具有任意的朝向。当我们要追踪到光源的多条阴影光线来计算软阴影时,就要用到参数nSamples;用户可以使用该参数对光源上的采样个数进行更细致的控制。

<Light Declarations> =
    class COREDLL Light {
    public:
        <Light Interface>
        <Light Public Data>
    protected:
        <Light Protected Data>
    };

虽然存储光源--世界变换和世界--光源变换有些冗余,但省去了求逆阵Transform::GetInverse()的调用,从而简化了实现。光源的采样个数的缺省值为1;所以要实现多个采样的光源就必须提供该值。

<Light Interface>
    Light(const Transform &l2w, int ns = 1)
            : nSamples(max(1, ns)), LightToWorld(l2w),
        WorldToLight(l2w.GetInverse()) {
    }

<Light Protected Data> =
    const Transform LightToWorld, WorldToLight;

<Light Public Data> =
    const int nSamples;


要实现光源类的核心函数是Sample_L()。调用这将场景中的一个点传入函数,函数返回该光源所发出的光线到该点的辐射亮度(假定两者之间没有遮挡)。光源类还要负责初始化到光源的入射方向wi,还有初始化VisibilityTester对象,该对象含有需要追踪的阴影光线(用来探测光源和点之间是否有遮挡)的信息。第13.1.1节将介绍VisibilityTester,如果返回黑色的辐射亮度,就没有必要初始化该对象了,例如当点p在聚光灯的照明圆锥范围之外就是这种情况。

对于有些类型的光源,光线可以从许多方向上到达点p,而不象点光源那样只有一个方向。对于这些光源,Sample_L()函数将随机第在光源表面上采样一个点,这样就可以使用Monte Carlo积分来找到p处的来自该光源的反射光。

<Light Interface> +=
    virtual Spectrum Sample_L(const Point &p, Vector *wi,
        VisibilityTester *vis) const = 0;

所有光源必须能够返回它们所释放的总功率,该值对光传输算法很有用,因为算法可以对场景中有最大照明的光源进行更多的计算。由于系统其它部分并不需要放射功率的精确值,本章所介绍的几个实现将计算该值的近似值。

<Light Interface> +=
    virtual Spectrum Power(const Scene *) const = 0;

最后,函数IsDeltaLight()说明光源是否是由一个delta分布来描述的。这类光源的例子包括从一个单个点放光的点光源,还有所有光来自同一方向的方向光源。探测这类光源照明的唯一方法是调用其Sample_L()函数,因为不可能在点p处随机地选择一个方向而恰好能找到这个光源。Monte Carlo算法需要知道光源是否是由delta分布来描述的,因为这会影响到其中的某些计算。

<Light Interface> +=
    virtual bool IsDeltaLight() const = 0;

1.3.1 可见性测试

VisibilityTester是一个闭包(closure)--即该对象封装了少部分的数据,还有没有做的某些计算。它在假定光源和点p相互可见的情况下返回一个辐射亮度值。然后在进行阴影光线追踪之前,积分器确定是否考虑来自方向w上的照明。例如,不透明表面的背面的入射光线不会对另一面的反射有任何贡献值。如果确实需要,则调用VisibilityTester其中的一个函数来做阴影光线追踪。

<Light Declarations> +=
    struct COREDLL VisibilityTester {
        <VisibilityTester Public Methods>
        Ray r;
    }

有两个初始化VisibilityTester的函数。第一个是VisibilityTester::SetSegment(),指明是否在两个点之间进行做可见性测试。
<VisibilityTester Public Methods> =
    void SetSegment(const Point &p1, const Point &p2) {
        r = Ray(p1, p2-p1, RAY_EPSILON, 1.f - RAY_EPSILON);
    }

另一个初始化函数是VisibilityTeser::SetRay(),它指定用于可见性测试的从给定点开始的方向。这个函数对计算方向光源的所产生的阴影特别有用。

<VisibilityTester Public Methods> +=
    void SetRay(const Point &p, const Vector &w) {
        r = Ray(p, w, RAY_EPSILON);
    }

下面两个函数追踪相应的光线。首先,VisibilityTester::Unoccluded()追踪阴影光线并返回一个布尔值。有些光线追踪程序有可以从半透明物体上投影彩色阴影的机制,并从类似该函数的某个函数返回一个光谱值。pbrt并没有这种机制,因为这些系统多半是使用了非物理的技巧,所以VisibilityTester::Unoccluded()只返回一个布尔值。在场景中照明穿透一个透明物体的效果应该由支持这种效果的积分器(例如PhotonIntegrator)来完成。

<Light Method Definitions> =
    bool VisibilityTester::Unoccluded(const Scene *scene) const {
        return !scene->IntersectP(r);
    }

VisibilityTester::Transmittance()用来确定通过场景中的参与介质到达某个点所剩下的照明度的比率。如果场景中没有参与介质,该函数返回一个值为1的常量光谱值。

<Light Method Definitions> +=
    Spectrum VisibilityTester::Transmittance(const Scene *scene) const {
    return scene->Transmittance(r);
    }


13.2 点光源

本节描述几个有意思的光源,它们都是从空间中的一个点发光,并且出射光有按角度变化的分布。我们从PointLight开始,该类表示了一个各向同性的点光源,它在所有方向上放射出相同能量的光线。以此为基础,我们可以引人更多类型的点光源,包括聚光灯和可以投射图像的光源。

<PointLight Classes> =
    class PointLight : public Light {
    public:
        <PointLight Public Methods>
    private:
        <PointLight Private Data>
    };

PointLight位于光源空间的原点。为了将它们放置在任意的地方,就需要光源--世界变换。构造器使用这个变换,将(0,0,0)从光源空间变换到世界空间来得到光源在世界空间的位置,并存放起来以备后用。构造器还存放光源的强度,它是单位立体角的功率。因为光源是各向同性的,故它是个常量。

<PointLight Method Definitions> =
    PointLight::PointLight(const Transform &light2world,
            const Spectrum &intensity)
        : Light(light2world) {
        lightPos = LightToWorld(Point(0,0,0));
        Intensity = intensity;
    }

<PointLight Private Data> =
    Point lightPos;
    Spectrum Intensity;

严格地讲,用辐射亮度单位来描述点光源到达一个点的光线是不正确的。正如第5.2节所讲的,辐射强度(Radiant intensity)才是正确的单位。但在这里的接口代码中,我们对术语有所滥用,并用Sample_L()函数来得到所有类型光源到达某点的照明情况,返回辐射强度与“光源位置和点之间距离平方”之比。我们在第15.6节中讨论delta分布对散射方程的积分求值的影响时再回顾这个问题。这种混乱情况对最终的计算结果没有什么影响,这使得光传输算法的实现更直接了当,因为我们无需对不同类型的光源使用不同的接口。

<PointLight Method Definitions> +=
    Spectrum PointLight::Sample_L(const Point &p, Vector *wi,
            VisibilityTester *visibility) const {
        *wi = Normalize(lightPos - p);
        visibility->SetSegment(p, lightPos);
        return Intensity / DistanceSquared(lightPos, p);
    }

光源发出的总功率可以通过对球面上所有方向的辐射强度进行积分来求得:


<PointLight Public Methods> =
    Spectrum Power(const Scene *) const {
        return Intensity * 4.f * M_PI;
    }

<PointLight Public Methods> +=
    bool IsDeltaLight const { return true;}

13.2.1 聚光灯

聚光灯是点光源的一种使用很方便的变种;聚光灯只向一个圆锥区域所规定的方向发光。为了方便,我们在光源坐标系中定义聚光灯的位置为(0,0,0)并指向+z轴。为了将它放置到世界空间的任意位置并具备任意朝向,我们需要设置Light::WorldToLight变换。

<Spotlight Declarations> =
    class SpotLight : public Light {
    public:
        <SpotLight Public Methods>
    private:
        <SpotLight Private Data>
    };

构造器需要两个角度来设置聚光灯的圆锥区域范围:圆锥的总角度和衰减区的起始角度。构造器将预计算出这些角度的余弦值以备后用。

<SpotLight Method Definitions> =
    SpotLight::SpotLight(const Transform &light2world,
        const Spectrum &intensity, float width, float fall)
    : Light(light2world) {
    lightPos = LightToWorld(Point(0,0,0));
    Intensity = intensity;
    cosTotalWidth = cosf(Radians(width));
    cosFalloffStart = cosf(Radians(fall));
   
<SpotLight Private Data> =
    float cosTotalWidth, cosFalloffStart;
    Point lightPos;
    Spectrum Intensity;


SpotLight::Sample_L()函数跟PointLight::Sample_L()几乎相同,唯一不同的是它调用Falloff()函数来计算光的分布。

<SpotLight Method Definitions> +=
    Spectrum SpotLight::Sample_L(const Point &p, Vector *wi,
            VisibilityTester * visibility ) const {
        *wi = Normalize(lightPos - p);
        visibility->SetSegment(p, lightPos);
        return Intensity * Falloff(-*wi) / DistanceSquared(lightPos, p);
    }


为了计算聚光灯在点p的影响,第一步是计算聚光灯原点到p的向量和聚光灯圆锥中心轴的夹角余弦。为此,我们用下列公式:


我们再将该值跟衰减起始角的余弦以及总宽度角(width angle)的余弦相比较。如果该值大于衰减起始角的余弦,那么该点受到完全照明;如果该值小于宽度角余弦,则点落在圆锥之外。

<SpotLight Method Definitions> +=
    float SpotLight::Falloff(const Vector &w) const {
        Vector wl = Normalize(WorldToLight(w));
        float costheta = wl.z;
        if (costheta < cosTotalWidth)
            return 0.;
        if (costheta > cosFalloffStart)
            return 1.;
        <Compute falloff inside spotlight cone>
    }

对于落在从完全照明区域到圆锥之外的过渡区域的点,我们需要将强度进行比例变换,使之平滑地从完全照明过渡到黑暗(无照明)。

<Compute falloff inside spotlight cone> =
    float delta = (costheta - cosTotalWidth) /
        (cosFalloffStart - cosTotalWidth);
    return delta*delta*delta*delta;


具有扩展角(spread angle)为θ的圆锥,其立体角为2π(1 - cosθ)。因此,前面我们用对球面方向进行辐射强度的积分来计算功率,我们可以用同样的方法计算通过一个圆锥来提供照明的光源的总功率。对于聚光灯而言,我们可以利用覆盖width和fall两个角度余弦的一半的立体角来近似估算光源的总功率:

<Spotlight Public Methods> =
    Spectrum Power(const Scene *) const {
        return Intensity * 2.f * M_PI *
            ( 1.f - .5f * (cosFalloffStart + cosTotalWidth));
    }

13.2.2 纹理投射光源

另一个很有用的光源跟幻灯投影仪很相似。它使用纹理贴图并将其投射到场景中去。ProjectionLight类使用了一个投射变换,并根据给定的视野角将场景中的点投影到光源投射平面上去。

<ProjectionLight Declarations> =
    class ProjectionLight : public Light {
    public:
        <ProjectionLight Public Methods>
    private:
        <ProjectionLight Private Data>
    };

<ProjectionLight Method Definitions> =
    ProjectionLight::ProjectionLight(const Transform &light2world,
            const Spectrum Sintensity, const string &texname,
            float fov)
        : Light(light2world) {
        lightPos = LightToWorld(Point(0,0,0));
        Intensity = intensity;
        <Create ProjectionLight MIP-map>
        <Initialize ProjectionLight projection matrix>
        <Compute cosine of cone surrounding projection directions>
    }

我们可以使用一个纹理对象来表示光源的投影分布,这样就可以使用过程性的投影样式(procedural projection patterns)。然而,如果我们有投影函数的精确表示(我们使用MIPMap中的一个图像时就是这样的情况),就可以用Monte Carlo技术对投影分布进行采样,下面我们就使用这样的表示。

<Create ProjectionLight MIP-map> =
    int width, height;
    Spectrum *texels = ReadImage(texname, &width, &height);
    if (texels)
        projectionMap = new MIPMap<Spectrum>(width, height, texels);
    else
        projectionMap = NULL;

    delete[] texels;

<ProjectionLight Private Data> =
    MIPMap<Spectrum> *projectionMap;
    Point lightPos;
    Spectrum Intensity;

跟PerspectiveCamera类似,ProjectionLight构造器计算一个投影矩阵还有投影的屏幕空间范围。

<Initialize ProjectionLight projection matrix> =
    float aspect = float(width) / float(height);
    if (aspect > 1.f ) {
        screenX0 = -aspect; screenXl = aspect;
        screenY0 = - 1.f ; screenYl = 1.f;
    }
    else{
        screenX0 = -1.f;
        screenY0 = -1.f / aspect;
        screenX1 = 1.f;
        screenY1 = 1.f / aspect;
    }
    hither = RAY_EPSILON;
    yon = 1e30f;
    lightProjection = Perspective(fov, hither, yon);

<ProjectionLight Private Data> +=
    Transform lightProjection;
    float hither, yon;
    float screenX0, screenX1, screenY0, screenY1;

最后,构造器还要计算+z轴和到屏幕窗口一角的向量的夹角余弦。该值会在另一个地方定义光源所投射的最小方向圆锥(minimal cone of directions)时被用到。该圆锥会在诸如光子映射的算法(需要随机地采样离开光源的光线)中被用到。这里不做推导了。

<Compute cosine of cone surrounding projection directions> =
    float opposite = tanf(Radians(fov) / 2.f);
    float tanDiag = opposite * sqrtf (1.f + 1.f/(aspect*aspect));
    cosTotalWidth = cosf(atanf(tanDiag));
   
<ProjectionLighl Private Data> +=
    float cosTotalWidth;

跟聚光灯类似,ProjectionLight::Sample_L()调用一个工具函数ProjectionLight::Projection()来确定给定方向上投射光的量。这里不再列出具体实现了。

<ProjectionLight Method Definitions> +=
    Spectrum ProjectionLight::Projection(const Vector &w) const {
        Vector wl = WorldToLight(w);
        <Discard directions behind projection light>
        <Project point onto projection plane and compute light>
    }

因为投影变换可以将投影中心后面的点投影到前面来,所以我们必须舍掉有负值的z值。因此,对于在后裁剪面的点,函数返回“没有照明”。如果没有这项检查,就无法区分被投影的点是在光源之前(因此无照明)还是之后。

<Discard directions behind projection light> =
    if (wl.z < hither) return 0.;

点被投影到投影平面之后,如果坐标落在屏幕窗口之外,也需将之舍弃。通过这项测试的点将被变换, 得到在[0,1]x[0,1]之内的可用于查找图像贴图的纹理坐标。

<Project point onto projection plane and compute light> =
    Point Pl = lightProjection(Point(wl.x, wl.y, wl.z));
    if (Pl.x < screenX0 || Pl.x > screenX1 ||
        Pl.y < screenY0 || Pl.y > screenY1) return 0.;
    if (!projectionMap) return 1;
    float s = (Pl.x - screenX0) / (screenX1 - screenX0);
    float t = (Pl.y - screenY0) / (screenY1 - screenY0);
    return projectionMap->Lookup(s, t );

该光源的总功率是这样近似计算的:将之视为一个聚光灯,它具有能够包含投影图像的对角线的宽度角,将聚光灯的功率再乘以图像贴图的平均强度。如果投影图像的长宽比增大时,这个近似就不那么精确了,同时也没有考虑这样的事实:当使用透视投影时,靠近图像贴图边缘的纹素被中间的纹素包含有更大的角度。不管怎么说,这还是比较合理的一阶近似。

<ProjectionLigh t Public Methods> =
    Spectrum Power(const Scene *) const {
        return Intensity * 2.f * M_PI * (1.f - cosTotalWidth)
                 * projectionMap->Lookup(.5f, .5f, .5f);
    }


13.2.3 配光图(goniophotometric diagram)光源

一个配光图描述了一个点光源的照度的角向分布;它被广泛地应用于照明工程中的灯光设计中。在本节中,我们将实现使用配光图的光源,其中配光图的编码方式是一个描述光源放射分布的二维图像贴图;它根据配光图上的值来对出射方向的照射强度做比例变换。

<GonioPhotometricLight Declarations> =
    class GonioPhotometricLight : public Light {
    public:
        <GonioPhotometricLight Public Methods>
    private:
        <GonioPhotometricLight Private Data>
    };

配光图光源构造器的参数包括一个基准辐射强度和一个根据光源角向分布来对强度进行缩比的图像贴图。

<GonioPhotometricLight Method Definitions> =
    GonioPhotometricLight::GonioPhotometricLight
            (const Transform &light2world,
            const Spectrum Sintensity, const string &texname)
            : Light(light2world) {
        lightPos = LightToWorld(Point(0,0,0));
        Intensity = intensity;
        <Create mipmap for GonioPhotometricLight>
    }

跟ProjectionLight一样,GonioPhotometricLight 也要构造一个MIPMap:

<Create mipmap for GonioPhotometricLight> =
    int width, height;
    Spectrum *texels = Readlmage(texname, &width, &height);
    if (texels) {
        mipmap = new MIPMap<Spectrum>(width, height, texels);
        delete[] texels;
    }
    else mipmap = NULL;

<GonioPhotometricLight Private Data> =
    Point lightPos;
    Spectrum Intensity;
    MIPMap<Spectrum> *mipmap;

这里不再列出GonioPhotometricLight::Sample_L()函数了。它实际上跟SpotLight::Sample_L()和Projection::Sample_L()相同,也要一个辅助函数来对辐射照度进行比例变换。该函数假定比例纹理(scale texture)是用球面坐标来编码的,给定的方向需要被转换为θ和Φ值,并缩放到[0,1]之间,然后再用之对纹理进行索引。配光图是在y轴朝上的坐标空间中定义的,并且球面坐标辅助函数假定z轴是朝上的,所以在做转换之前需要交换y和z轴。

<GonioPhotometricLight Public Methods> =
    Spectrum Scale(const Vector &w) const {
        Vector wp = Normalize(WorldToLight(w));
        swap(wp.y, wp.z);
        float theta = SphericalTheta(wp);
        float phi = SphericalPhi(wp);
        float s = phi * INV_TWOPI, t = theta * INV_PI;
        return mipmap ? mipmap->Lookup(s, t) : 1.f;

Power()函数中的计算并不精确,因为球面坐标对方向的参数化形式会产生各种扭曲,特别是对那些接近+z和-z的方向而言。 当然,这个错误在pbrt中还是能够接受的。

<GonioPhotometricLight Public Methods> +=
    Spectrum Power(const Scene *) const {
        return 4.f * M PI * Intensity * mipmap->Lookup(.5f, .5f, .5f);
    }

13.3 远距光源(Distant Lights)

另一种有用的光源类型是远距光源,也被称为方向光源。它描述了在同一个方向上对空间中所有的点实施照明的光源。该类光源也被称为处于“无穷远”处的点光源, 因为当点光源距离越远时,就越象一个方向光源。例如,太阳就可以被视为一个方向光源。虽然它实际上是个面光源,但到达地球的光线是平行的,因为它太远了。

<DistantLight Declarations> =
    class DistantLight : public Light {
    public:
        <DistantLight Public Methods>
    private:
        <DistantLight Private Data>
    };

<DistantLight Method Definitions> =
    DistantLight::DistantLight(const Transform &light2world,
            const Spectrum &radiance, const Vector &dir)
        : Light(light2world) {
        lightDir = Normalize(LightToWorld(dir));
        L = radiance;
    }

<DistantLight Private Data> =
    Vector lightDir;
    Spectrum L;

<DistantLight Method Definitions> +=
    Spectrum DistantLight::Sample_L(const Point &p, Vector *wi,
            VisibilityTester Visibility) const {
        *wi = lightDir;
        visibility->SetRay(p, *wi);
        return L;
    }

远距光源有一个不同寻常之处:它所放出的(跟场景相关的)能量跟场景的空间范围相关。实际上,它跟能接受到光的面积成正比。为了弄明白这一点,考虑一个圆盘区域A受到一个辐射亮度为L的远距光源照射,入射光线方向为圆盘的法向量。到达圆盘的总功率为 Φ = AL。当能接受的光的面积变化时,所接受到的总功率也跟着变化。

为了得出远距光源的释放功率,计算场景中所有受光的总面积是不现实的。实际上,我们可以用一个场景包围球内的一个圆盘来模拟这个区域。当然,这会高估实际上的面积,但对于系统而言已经足够好了。

<DistantLight Public Methods> =
    Spectrum Power(const Scene *scene) const {
        Point worldCenter;
        float worldRadius;
        scene->WorldBound().BoundingSphere(&worldCenter, &worldRadius);
        return L * M_PI * worldRadius * worldRadius;


13.4 面光源(Area Lights)

面光源是一个由Shape对象来定义的光源,它依照表面上每个点的辐射亮度来发光。一般来讲,计算面光源的辐射量值需要在表面上做积分计算,但这种积分常常没有封闭解。我们在第15章再用Monte Carlo积分技术回顾这个问题。这个工作的回报是有了软阴影效果和其它更真实的光照效果,而不是硬阴影和僵硬的点光源光照效果。

AreaLight类实现了一个形体所有的点上有均匀辐射亮度分布的基本面光源。它只在朝外的表面法向量的方向一侧发光,而另一侧不发光。

<Light Declarations> +=
    class AreaLight : public Light {
    public:
        <AreaLight Interface>
    protected:
        <AreaLight Protected Data>
    };

构造器还暂存了光源的表面积,因为这些面积计算还是很耗时的。

<AreaLight Method Definitions> =
    AreaLight::AreaLight(const Transform &light2world,
            const Spectrum &le, int ns,
            const Reference<Shape> &s)
                : Light(light2wor1d, ns) {
        Lemit = le ;
        if (s->CanIntersect())
            shape = s;
        else {
            <Create ShapeSet for Shape>
            area = shape->Area();
        }
    }

<AreaLight Protected Data> =
    Spectrum Lemit;
    Reference<Shape> shape;
    float area;

某些形体无法直接被用作面光源,因为它们没有实现AreaLight所需要的所有Shape函数。其中一个例子是细分表面,它无法计算表面积(在通常情况下,细分表面没有计算面积的封闭形式的表达式)。在第15节的Monte Carlo采样的代码中,AreaLight将调用Shape的函数在形体表面上进行采样,而有些类型的形体并没有实现这些函数。

象这样的一类形体必须加细成更简单的形体,直到CanIntersect()函数返回真。如果需要的话,构造器将完成这个加细过程,如果产生了多个形体,就创建一个ShapeSet的容器对象来存放它们。这个容器类实现了Shape接口,所以AreaLight可以只存放一个Shape(因为它可以表示一个ShapeSet),这样就简化了实现。

ShapeSet类只是一个简单的关于Shape的vector的包装类。它的大部分实现将在第15.6节中介绍。

<Shape Declarations> +=
    class ShapeSet : public Shape {
    public:
        <ShapeSet Public Methods>
    private:
        <ShapeSet Private Data>
    };

<ShapeSet Private Data> =
    vector<Reference<Shape> > shapes;

AreaLight::Sample_L()函数的实现并不像其它类型的光源那么直接了当。特别是对于场景中的每一个点而言,来自面光源的入射方向可以有很多,而不是象其它光源那样来自单一的方向。这就产生了这样一个问题:到底使用哪一个方向呢?我们在第15.6节介绍过Monte Carlo积分之后再回答这个问题。

AreaLight增加了一个只用于面光源的新函数, AreaLight::L()。它计算在光源表面上某点在给定方向上的出射辐射亮度L。因为面光源只在一侧发光,所以该函数只需确定出射方向跟法向量在同一个半球。

<AreaLight Interface> =
    virtual Spectrum L(const Point &p, const Normal &n,
                const Vector &w) const {
        return Dot(n, w) > 0 ? Lemit : 0.;
    }

为了方便起见,我们在Intersection类中定义了一个函数来计算一条光线跟表面交点处的出射辐射亮度。它从体素中取得一个AreaLight指针,并调用其L()函数:

<Intersection Method Definitions> +=
    Spectrum Intersection::Le(const Vector &w) const {
        const AreaLight *area = primitive->GetAreaLight();
        return area ? area->L(dg.p, dg.nn, w) : Spectrum(0.);
    }

有均匀出射辐射亮度分布的面光源所发出的总功率可以用下列封闭形式来得到:

<AreaLight Interface> +=
    Spectrum Power(const Scene *) const {
        return Lemit * area * M_PI;
    }

最后,面光源显然没有任何奇点(singularities),所以IsDeltaLight()返回false。

<AreaLight Interface> +=
    bool IsDeltaLight() const { return false; }



13.5 无限面光源(Infinite Area Lights)

另一种很有用的光源是无限面光源 -- 即一个位于无限远处的可以覆盖整个场景的面光源。一种对这种光源的形象化描述是将其看作一个巨大的球体,它在各个方向上向场景投射光线。无限面光源的一个重要用途是作为环境照明(environment lighting),其中用一幅图像来代表环境中的照明,对场景中的合成物体提供照明,令人感觉它们真的是处在该环境中。

这种光源的一个常用表示是经纬辐射亮度图(latitude-longitude radiance map)。EnvironmentCamera类可以用来场景这样的图像贴图。跟其它光源一样,InfiniteAreaLight有一个变换矩阵,它在这里的用途是控制图像贴图的朝向。他使用球面坐标将方向映射到(θ,Φ),然后再映射到纹理坐标。

<InfiniteAreaLight Declarations> =
    class InfiniteAreaLight : public Light {
    public:
        <InfiniteAreaLight Public Methods>
    private:
        <InfiniteAreaLight Private Data>
    };

<InfiniteAreaLight Method Definitions> =
    InfiniteAreaLight::InfiniteAreaLight(const Transform &light2world,
                    const Spectrum &L, int ns,
                    const string &texmap)
            : Light(light2world, ns) {
        radianceMap = NULL;
        if (texmap != "") {
            int width, height;
            Spectrum *texels = ReadImage(texmap, &width, &height);
            if (texels)
                radianceMap = new MIPMap<Spectrum>(width, height, texels);
            delete[] texels;
        }
        Lbase = L;
    }

<InfiniteAreaLight Private Data> =
    Spectrum Lbase;
    MIPMap<Spectrum> *radianceMap;

因为InfiniteAreaLight在所有方向上投射光线,所以需要用Monte Carlo积分即反射光。 InfiniteAreaLight::Sample_L()函数将在第15.6节中介绍。

跟方向光源一样,无限面光源的总功率跟场景的表面积相关:

<InfiniteAreaLight Public Methods> =
    Spectrum Power(const Scene *scene) const {
        Point worldCenter;
        float worldRadius;
        scene->WorldBound().BoundingSphere(&worldCenter, &worldRadius);
        return Lbase * radianceMap->Lookup(.5f, .5f, .5f) *
            M_PI * worldRadius * worldRadius;
    }

<InfiniteAreaLight Public Methods> +=
    bool IsDeltaLight() const { return false; }


因为无限面光源需要对没有碰到任何几何体的光线产生辐射亮度,我们需要在Light基类中添加一个函数,它返回没有任何交点的光线的辐射亮度(缺省实现是返回0)。积分器负责对这类的光线调用这个函数。

<Light Method Definitions> +=
    Spectrum Light::Le(const RayDifferential &) const {
        return Spectrum(0.);
    }
下面是InfiniteAreaLight对该函数的实现:

<InfiniteAreaLight Method Definitions> +=
    Spectrum InfiniteAreaLight::Le(const RayDifferential &r) const {
        Vector w = r.d;
        <Compute infinite light radiance for direction>
        return L;
    }

<Compute infinite light radiance for direction> =
    Spectrum L = Lbase;
    if (radianceMap != NULL) {
        Vector wh = Normalize(WorldToLight(w));
        float s = SphericalPhi(wh) * INV_TWOPI;
        float t = SphericalTheta(wh) * INV_PI;
        L *= radianceMap->Lookup(s, t);
    }


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值