zoukankan      html  css  js  c++  java
  • 简单的图形学(三)——光源

    参考自:用JavaScript玩转计算机图形学(二)基本光源 - Milo Yip - 博客园,主要讲述三种最基本的光源——平行光、点光源、聚光灯,其实就是三种数学模型。

    代码的调整

    先前的代码中,颜色是由几何物体自身计算得出,因此使用很有限。在Phong材质中,显示的效果已经很不错了,然而Phong材质是要假定有一个光源的。我们的代码需要从以面向物体渲染为面向光源渲染。

    新的逻辑:https://github.com/bajdcc/GameFramework/blob/master/CCGameFramework/base/pe2d/Render2DLight.cpp

    主逻辑 代码:

    void PhysicsEngine::RenderLightIntern(World& world, const PerspectiveCamera& camera, BYTE* buffer, cint width, cint height)
    {
        for (auto y = 0; y < height; y++)
        {
            const auto sy = 1.0f - (1.0f * y / height);
    
            for (auto x = 0; x < width; x++)
            {
                const auto sx = 1.0f * x / width;
    
                // sx和sy将屏幕投影到[0,1]区间
    
                // 产生光线
                const auto ray = camera.GenerateRay(sx, sy);
    
                // 测试光线与球是否相交
                auto result = world.Intersect(ray);
                if (result.body)
                {
                    color color;
                    for (auto & k : world.lights) { // 这里不一样了
                        auto lightSample = k->Sample(world, result.position);
    
                        if (!lightSample.empty()) {
                            auto NdotL = DotProduct(result.normal, lightSample.L); // 计算角度
    
                            // 夹角为锐角,光源在平面前面
                            if (NdotL >= 0)
                                // 累计所有光线
                                // NdotL 就是光源方向在法向量上的投影
                                color = color + (lightSample.EL * NdotL);
                        }
                    }
                    buffer[0] = BYTE(color.b * 255);
                    buffer[1] = BYTE(color.g * 255);
                    buffer[2] = BYTE(color.r * 255);
                    buffer[3] = 255;
                }
                else
                {
                    // 没有接触,就是背景色
                    buffer[0] = 0;
                    buffer[1] = 0;
                    buffer[2] = 0;
                    buffer[3] = 255;
                }
    
                buffer += 4;
            }
        }
    }
    

    简单来说,就是求出交点时,做:

    1. 计算各大光源基于该点的光线颜色和光线方向
    2. 计算光线在交点法向量上的投影,作为颜色混合比的依据
    3. 将所有颜色累计起来

    下面介绍三种光源

    平行光

    v2-eebcbf64e85256332bb0c0f94d8cbf4d_r平行光

    平行光的属性:

    // 平行光
    class DirectionalLight : public Light
    {
    public:
        DirectionalLight(color irradiance, vector3 direction);
    
        LightSample Sample(World& world, vector3 position) override;
    
        color irradiance;    // 幅照度
        vector3 direction;   // 光照方向
        vector3 L;           // 光源方向
    };
    
    DirectionalLight::DirectionalLight(color irradiance, vector3 direction)
        : irradiance(irradiance), direction(direction)
    {
        L = -Normalize(direction);
    }
    
    LightSample DirectionalLight::Sample(World& world, vector3 position)
    {
        static LightSample zero;
    
        if (shadow) {
            const Ray shadowRay(position, L);
            const auto shadowResult = world.Intersect(shadowRay);
            if (shadowResult.body)
                return zero;
        }
    
        return LightSample(L, irradiance); // 就返回光源颜色
    }
    

    这里注意L是光源方向单位向量。

    平行光我们只需要知道光源方向和光源颜色就可以了。非常简单,不用算投影,这是主逻辑的工作。

    这里说一下阴影,平行光有阴影,当从交点向光源方向看时,如果中间有障碍物,就返回黑色。

    点光源

    v2-359fc82a77ddc70179ca78a0ea62b2e8_r点光源
    // 点光源
    class PointLight : public Light
    {
    public:
        PointLight(color intensity, vector3 position);
    
        LightSample Sample(World& world, vector3 position) override;
    
        color intensity;     // 幅射强度
        vector3 position;    // 光源位置
    };
    
    static LightSample zero;
    
    LightSample PointLight::Sample(World& world, vector3 pos)
    {
        // 计算L,但保留r和r^2,供之后使用
        const auto delta = position - pos; // 距离向量
        const auto rr = SquareMagnitude(delta);
        const auto r = sqrtf(rr); // 算出光源到pos的距离
        const auto L = delta / r; // 距离单位向量
    
        if (shadow) {
            const Ray shadowRay(pos, L);
            const auto shadowResult = world.Intersect(shadowRay);
            // 在r以内的相交点才会遮蔽光源
            // shadowResult.distance <= r 表示:
            //   以pos交点 -> 光源位置 发出一条阴影测试光线
            //   如果阴影测试光线与其他物体有交点,那么相交距离 <= r
            //   说明pos位置无法直接看到光源
            if (shadowResult.body && shadowResult.distance <= r)
                return zero;
        }
    
        // 平方反比衰减
        const auto attenuation = 1 / rr;
    
        // 返回衰减后的光源颜色
        return LightSample(L, intensity * attenuation);
    }
    

    点光源有一个平方反比衰减规律,故而要先算光源到交点pos的距离r,然后求出L,实际上L就是光源位置到交点的方向单位向量。接着要计算颜色,点光源本身颜色intensity,由于有衰减,因此变成了intensity * attenuation。

    再说下阴影,如何计算点光源的阴影?这比平行光复杂些。从交点处向光源位置发出一条光线,如果当中有障碍物,那么被遮挡,返回黑色(就是遮挡测试)。

    聚光灯

    v2-96c3565e3ac1da63a35a4568df23530b_r聚光灯
    // 聚光灯
    class SpotLight : public Light
    {
    public:
        SpotLight(color intensity, vector3 position, vector3 direction, float theta, float phi, float falloff);
    
        LightSample Sample(World& world, vector3 position) override;
    
        color intensity;     // 幅射强度
        vector3 position;    // 光源位置
        vector3 direction;   // 光照方向
        float theta;         // 内圆锥的内角
        float phi;           // 外圆锥的内角
        float falloff;       // 衰减
    
        /* 以下为预计算常量 */
        vector3 S;           // 光源方向
        float cosTheta;      // cos(内圆锥角)
        float cosPhi;        // cos(外圆锥角)
        float baseMultiplier;// 1/(cosTheta-cosPhi)
    };
    
    SpotLight::SpotLight(color intensity, vector3 position, vector3 direction, float theta, float phi, float falloff)
        : intensity(intensity), position(position), direction(direction), theta(theta), phi(phi), falloff(falloff)
    {
        S = -Normalize(direction);
        cosTheta = cosf(theta * float(M_PI) / 360.0f);
        cosPhi = cosf(phi * float(M_PI) / 360.0f);
        baseMultiplier = 1.0f / (cosTheta - cosPhi);
    }
    
    LightSample SpotLight::Sample(World& world, vector3 pos)
    {
        // 计算L,但保留r和r^2,供之后使用
        const auto delta = position - pos; // 距离向量
        const auto rr = SquareMagnitude(delta);
        const auto r = sqrtf(rr); // 算出光源到pos的距离
        const auto L = delta / r; // 距离单位向量
    
        /*
         * spot(alpha) =
         *
         *     1
         *         where cos(alpha) >= cos(theta/2)
         *
         *     pow( (cos(alpha) - cos(phi/2)) / (cos(theta/2) - cos(phi/2)) , p)
         *         where cos(phi/2) < cos(alpha) < cos(theta/2)
         *
         *     0
         *         where cos(alpha) <= cos(phi/2)
         */
    
        // 计算spot
        auto spot = 0.0f;
        const auto SdotL = DotProduct(S, L);
        if (SdotL >= cosTheta)
            spot = 1.0f;
        else if (SdotL <= cosPhi)
            spot = 0.0f;
        else
            spot = powf((SdotL - cosPhi) * baseMultiplier, falloff);
    
        if (shadow) {
            const Ray shadowRay(pos, L);
            const auto shadowResult = world.Intersect(shadowRay);
            // 在r以内的相交点才会遮蔽光源
            // shadowResult.distance <= r 表示:
            //   以pos交点 -> 光源位置 发出一条阴影测试光线
            //   如果阴影测试光线与其他物体有交点,那么相交距离 <= r
            //   说明pos位置无法直接看到光源
            if (shadowResult.body && shadowResult.distance <= r)
                return zero;
        }
    
        // 平方反比衰减
        const auto attenuation = 1 / rr;
    
        // 返回衰减后的光源颜色
        return LightSample(L, intensity * (attenuation * spot));
    }
    

    聚光灯是非常复杂的数学模型,我们不去探究为什么公式这样的,只要实现就行。

    纯数学计算不多讲,这里主要有一个spot(聚光灯系数),所以最后的颜色是intensity * (attenuation * spot)。其它跟点光源的实现也差不多。

    三原色混合

    v2-41d32560b89247764d277c3eb5e4924d_r光的三原色

    原想这东西怎么实现啊,现在想通了,就是在某点处(plane上一点)三个聚光灯打上去,将最终的颜色混合起来(加起来)。

    简单表述:三个光源的光分别为RGB(255,0,0)、RGB(0,255,0)、RGB(0,0,255),混合起来,加一下就是RGB(255,255,255),白色。

    看到用JavaScript玩转计算机图形学(二)基本光源 - Milo Yip - 博客园 中的一个问题:

    如果,幅射强度是负值的话,会怎么样?(虽然未证实反光子(antiphoton)的存在,但读者能想到图形学上的功能么?)

    感觉就是PS中的正片叠底啊,见如何简单的理解正片叠底和滤色?

    接下来会探讨画光的实现。

    https://zhuanlan.zhihu.com/p/31015884备份。

  • 相关阅读:
    buuctf-web [强网杯 2019]随便注 1
    buuctf-crypto 引用可打印
    buuctf-crypto 变异凯撒
    buuctf-misc 文件中的秘密
    buuctf-misc LSB
    nginx 模块配置
    cloudflare CDN下nginx获取用户真实IP地址
    使用nginx后如何在web应用中获取用户ip及原理解释
    获取用户真实 ip 地址的 nginx 相关配置
    解决nginx: [emerg] bind() to [::]:80 failed (98: Address already in use)
  • 原文地址:https://www.cnblogs.com/bajdcc/p/8973009.html
Copyright © 2011-2022 走看看