Simple Graphics (3) - Light Source

Reference from: Playing Computer Graphics with JavaScript (2) Basic Light Sources - Milo Yip - Blog Park , mainly about the three most basic light sources - parallel light, point light source, spotlight, which are actually three mathematical models.

code adjustment

In the previous code, the color was calculated by the geometry itself, so its use was limited. In the Phong material, the effect of the display is already very good, but the Phong material is to assume a light source. Our code needs to change from object-oriented rendering to light-oriented rendering.

New logic: https://github.com/bajdcc/GameFramework/blob/master/CCGameFramework/base/pe2d/Render2DLight.cpp

Main logic code:

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 and sy project the screen to the [0,1] interval

            // Generate rays 
            const  auto ray = camera.GenerateRay(sx, sy);

            // Test if the ray intersects the ball 
            auto result = world.Intersect(ray);
             if (result.body)
            {
                color color;
                for ( auto & k : world.lights) { // this is different 
                    auto lightSample = k->Sample(world, result.position);

                    if (!lightSample.empty()) {
                         auto NdotL = DotProduct(result.normal, lightSample.L); // Calculate the angle

                        // The included angle is an acute angle, the light source is in front of the plane 
                        if (NdotL >= 0)
                             // Accumulate all rays 
                            // NdotL is the projection of the light source direction on the normal vector
                            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
            {
                // no contact, just background color
                buffer[0] = 0;
                buffer[1] = 0;
                buffer[2] = 0;
                buffer[3] = 255;
            }

            buffer += 4;
        }
    }
}

Simply put, when finding the intersection, do:

  1. Calculate the light color and light direction of each light source based on this point
  2. Calculate the projection of the ray on the normal vector of the intersection as the basis for the color mixing ratio
  3. add up all colors

Three light sources are described below

Parallel light

v2-eebcbf64e85256332bb0c0f94d8cbf4d_r Parallel light

Properties of directional light:

// Parallel light 
class DirectionalLight : public Light
{
public:
    DirectionalLight(color irradiance, vector3 direction);

    LightSample Sample(World& world, vector3 position) override;

    color irradiance;     // Amplitude 
    vector3 direction;    // Light direction 
    vector3 L;            // Light source direction
};

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); // just return the light source color
}

Note here that L is the light direction unit vector.

For parallel light, we only need to know the direction of the light source and the color of the light source. Very simple, don't count projections, it's the job of the main logic.

Let's talk about shadows here. Parallel lights have shadows. When looking from the intersection to the direction of the light source, if there is an obstacle in the middle, it will return to black.

Point light source

v2-359fc82a77ddc70179ca78a0ea62b2e8_r Point light source
// Point light 
class PointLight : public Light
{
public:
    PointLight(color intensity, vector3 position);

    LightSample Sample(World& world, vector3 position) override;

    color intensity;      // radiation intensity 
    vector3 position;     // light source position
};

static LightSample zero;

LightSample PointLight::Sample(World& world, vector3 pos)
{
    // Calculate L, but keep r and r^2 for later use 
    const  auto delta = position - pos; // distance vector 
    const  auto rr = SquareMagnitude(delta);
     const  auto r = sqrtf(rr); // calculate light source distance to pos 
    const  auto L = delta / r; // distance unit vector

    if (shadow) {
         const Ray shadowRay(pos, L);
         const  auto shadowResult = world.Intersect(shadowRay);
         // The intersection point within r will block the light source 
        // shadowResult.distance <= r means: 
        // with pos intersection point -> the light source position emits a shadow test ray 
        // If the shadow test ray intersects with other objects, then the intersection distance <= r 
        // Indicates that the pos position cannot directly see the light source 
        if (shadowResult.body && shadowResult.distance <= r)
             return zero;
    }

    // Inverse square attenuation 
    const  auto attenuation = 1 / rr;

    // Return the light color after attenuation 
    return LightSample(L, intensity * attenuation);
}

The point light source has an inverse square decay law, so it is necessary to first calculate the distance r from the light source to the intersection point pos, and then find L. In fact, L is the unit vector of the direction from the light source position to the intersection point. Then to calculate the color, the color intensity of the point light source itself becomes intensity * attenuation due to attenuation.

Let's talk about the shadow, how to calculate the shadow of the point light source? This is more complicated than directional light. A ray is emitted from the intersection to the light source position. If there is an obstacle in it, it is blocked and returns to black (that is, the occlusion test).

spotlight

v2-96c3565e3ac1da63a35a4568df23530b_r spotlight
// Spotlight 
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;      // radiation intensity 
    vector3 position;     // light source position 
    vector3 direction;    // light direction 
    float theta;          // inner angle of inner cone 
    float phi;            // inner angle of outer cone 
    float falloff;        // attenuation

    /* The following are precomputed constants*/ 
    vector3 S;            // light source direction 
    float cosTheta;       // cos(inner cone angle) 
    float cosPhi;         // cos(outer cone angle) 
    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)
{
    // Calculate L, but keep r and r^2 for later use 
    const  auto delta = position - pos; // distance vector 
    const  auto rr = SquareMagnitude(delta);
     const  auto r = sqrtf(rr); // calculate light source distance to pos 
    const  auto L = delta / r; // distance unit vector

    /*
     * 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);
         // The intersection point within r will block the light source 
        // shadowResult.distance <= r means: 
        // with pos intersection point -> the light source position emits a shadow test ray 
        // If the shadow test ray intersects with other objects, then the intersection distance <= r 
        // Indicates that the pos position cannot directly see the light source 
        if (shadowResult.body && shadowResult.distance <= r)
             return zero;
    }

    // Inverse square attenuation 
    const  auto attenuation = 1 / rr;

    // Return the light source color after attenuation 
    return LightSample(L, intensity * (attenuation * spot));
}

Spotlight is a very complex mathematical model, we don't explore why the formula is like this, just implement it.

There is not much to talk about pure mathematical calculations, there is mainly a spot (spotlight coefficient), so the final color is intensity * (attenuation * spot). Others are similar to the implementation of point light sources.

three primary color mixing

v2-41d32560b89247764d277c3eb5e4924d_r three primary colors of light

I originally thought about how to achieve this, but now I figured it out, that is, put three spotlights at a certain point (a point on the plane), and mix the final colors (add up).

A simple statement: the light of the three light sources are RGB(255,0,0), RGB(0,255,0), RGB(0,0,255), mixed together, and the addition is RGB(255,255,255), white.

See a question in Playing Computer Graphics with JavaScript (2) Basic Light Source - Milo Yip - Blog Park :

What if the radiation intensity is negative? (Although the existence of antiphotons has not been confirmed, can the reader think of a graphical function?)

It feels like multiplication in PS. See How to simply understand multiplication and color filtering? .

Next, we will discuss the realization of painted light.

Backed up by https://zhuanlan.zhihu.com/p/31015884 .

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325159072&siteId=291194637