Simple Graphics (2) - Material and Reflection

In the previous [Game Framework Series] Simple Graphics (1) article, we described one of the simplest operations of ray tracing - extending a tracing ray from each pixel, and the ray hits the ball (producing an intersection point). ), calculate the length of this line, as the final grayscale, it will be displayed as black if it cannot hit the ball .

Repository: bajdcc/GameFramework

Code in this section: https://github.com/bajdcc/GameFramework/blob/master/CCGameFramework/base/pe2d/RenderMaterial.cpp

Geometries Algorithm Interface: https://github.com/bajdcc/GameFramework/blob/master/CCGameFramework/base/pe2d/Geometries.h .

Whether it is material, reflection, refraction, etc., it is nothing more than calculating the color according to different algorithms at this intersection , and this color will eventually be displayed on the corresponding pixel on the screen. The following describes how to calculate the color at the intersection.

Refer to Miloyip's article Playing Computer Graphics with JavaScript (1) Introduction to Ray Tracing - Milo Yip - Blog Park .

material

v2-406c25c9dbed9bf46ff6583f90e79a2a_r Phong material effect

Two materials are implemented here: chessboard and Phong. To be honest, I have never heard of Phong. It should be a conclusion derived from pure mathematics.

First write the interface:

// Material interface 
class Material
{
public:
    Material(float reflectiveness);
    virtual ~Material();
    virtual color Sample(Ray ray, vector3 position, vector3 normal) = 0;

    float reflectiveness;
};

// Checkerboard material 
class CheckerMaterial : public Material
{
public:
    CheckerMaterial(float scale, float reflectiveness);

    color Sample(Ray ray, vector3 position, vector3 normal) override;

    float scale;
};

// Phong材质
class PhongMaterial : public Material
{
public:
    PhongMaterial(color diffuse, color specular, float shininess, float reflectiveness);

    color Sample(Ray ray, vector3 position, vector3 normal) override;

    color diffuse;
    color specular;
    float shininess;
};

The chessboard is on a plane, so we need to implement a ray-plane intersection algorithm in advance.

Determine the intersection of a line and a plane

IntersectResult Plane::Intersect(Ray ray)
{
    const auto a = DotProduct(ray.direction, normal);

    if (a >= 0)
         // The plane cannot be seen in the opposite direction, a negative number means the angle is an obtuse angle 
        // For example, the plane normal vector n=(0,1,0), the distance d=0, 
        // I go down from above Look, the direction of the light is the negative y-axis, and the plane normal is the positive y-axis 
        // so the angle between the two is an obtuse angle, and the above a is cos (angle) = negative number, which does not meet the condition 
        // when a is 0 , that is, when the line of sight is parallel to the plane, the plane cannot be seen naturally 
        // When a is positive, the line of sight looks up from the bottom of the plane and sees the opposite side of the plane, so the plane cannot be seen either. 
        return IntersectResult();

    // Refer to http://blog.sina.com.cn/s/blog_8f050d6b0101crwb.html 
    /* Write the equation of the straight line in the form of a parametric equation, that is:
       L(x,y,z) = ray.origin + ray.direction * t(t is the distance dist)
       The plane equation can be written in point French equation form, that is:
       plane.normal . (P(x,y,z) - plane.position) = 0
       解得 t = {(plane.position - ray.origin) . normal} / (ray.direction . plane.normal )
    */
    const auto b = DotProduct(normal, ray.origin - position);

    const auto dist = -b / a;
    return IntersectResult(this, dist, ray.Eval(dist), normal);
}

There are many pure mathematical derivations. Here is an optimization: first, check whether the direction of the light and the normal vector of the plane are in the same direction (the included angle is an acute angle), and if it is, it is directly judged that they do not intersect.

After determining the algorithm, look at the implementation of the chessboard material:

color CheckerMaterial::Sample(Ray ray, vector3 position, vector3 normal)
{
    static color black(Gdiplus::Color::Black);
    static color white(Gdiplus::Color::White);
    return fabs(int(floorf(position.x * 0.1f) + floorf(position.z * scale)) % 2) < 1 ? black : white;
}

Simply put, it is whether the rounding of "x coordinate + z coordinate" is a multiple of 2.

We mainly analyze what components are required for the material interface:

  1. Ray Ray, that is, the starting point position and direction of the incident light, the position of the intersection point, the normal direction of the intersection point, and the direction of the light rays and the normal vector of the intersection point are used to calculate reflection and refraction.
  2. intersection position
  3. intersection normal

Next, take a look at the Phong material , refer to the URL description inside, and complete the mathematical formula.

color PhongMaterial::Sample(Ray ray, vector3 position, vector3 normal)
{
    /*
      Refer to https://www.cnblogs.com/bluebean/p/5299358.html Blinn-Phong model
        Ks: the attenuation coefficient of the object for the reflected light
        N: surface normal vector
        H: the intermediate vector between the light incident direction L and the viewpoint direction V
        Shininess: Specular factor

        Specular = Ks * lightColor * pow(dot(N, H), shininess)

        When the direction of the viewpoint is consistent with the direction of the reflected light, the calculated H is parallel to N, and dot(N, H) is maximized; when the viewpoint direction V deviates from the reflection direction, H also deviates from N.
        Simply put, the closer the difference between the incident light and the line of sight is to the normal vector, the more obvious the specular reflection is.
     */

    const  auto NdotL = DotProduct(normal, lightDir);
     const  auto H = Normalize(lightDir - ray.direction);
     const  auto NdotH = DotProduct(normal, H);
     const  auto diffuseTerm = diffuse * fmax(NdotL, 0.0f); / / N * L projection of incident light on specular normal = diffuse 
    const  auto specularTerm = specular * powf(fmax(NdotH, 0.0f), shininess);
     return lightColor * (diffuseTerm + specularTerm);
}

Four parameters are required for the calculation:

  1. Ks: the attenuation coefficient of the object for the reflected light
  2. N: surface normal vector
  3. H: the intermediate vector between the light incident direction L and the viewpoint direction V
  4. Shininess: Specular factor

The color of the Phong material we see is actually the light reflected from its surface. How to calculate the color of the reflected light?

The Phong material has three parameters: diffuse color, specular specular color, and shininess specular coefficient (in fact, adjusting the mixing ratio of the first two). As shown in the title image, diffuse is the color of the material of the ball itself, and specular is the color of the outside light. Therefore, it will be assumed that there is an external light source lightColor/lightDir, so it will be used in the above calculation process.

implement reflection

v2-7cae800a62bc80f6fa158b3281c645fc_r reflection effect

Seeing reflection is actually implemented by recursion, and at the same time, the depth of recursion must be limited.

color PhysicsEngine::RenderReflectRecursive(World& world, const Ray& ray, int maxReflect)
{
    static color black(Gdiplus::Color::Black);

    auto result = world.Intersect(ray);

    if (result.body) {
         // see https://www.cnblogs.com/bluebean/p/5299358.html

        // Get reflection coefficient 
        const  auto reflectiveness = result.body->material->reflectiveness;

        // First sample (take the color of the object itself) 
        auto color = result.body->material->Sample(ray, result.position, result.normal);

        // Add the color component of the object itself (distinguished from the reflected color)
        color = color * (1.0f - reflectiveness);

        if (reflectiveness > 0 && maxReflect > 0) {

            // Formula R = I - 2 * N * (N . I), find the reflected light 
            const  auto r = result.normal * (-2.0f * DotProduct(result.normal, ray.direction)) + ray.direction;

            // Use reflected rays as new ray tracing rays 
            const  auto reflectedColor = RenderReflectRecursive(world, Ray(result.position, r), maxReflect - 1);

            // Add the reflected light component
            color = color + (reflectedColor * reflectiveness);
        }
        return color;
    }
    return black;
}

As shown in the code, the basic idea is:

  1. Do rays have intersections with geometric objects?
  2. no intersection: returns black
  3. There is an intersection: sample the own material, the ratio is (1-reflection coefficient), calculate the reflected ray, continue to trace, and the traced sampling ratio is (reflection coefficient)

which is:

color reflect_sample(world, ray, depth)
{
__ If world and ray do not intersect, return black
__ If world and ray intersect, assume the intersecting object is body
____ 1. Add body's material color * scale (1.0f - reflectiveness)
____ 2. Calculate the reflected ray ray_f
____ 3. Add the reflected color reflect_sample(world, ray_f, depth-1) * scale(reflectiveness)
}

The focus here is to find the reflected light based on the incident light and the normal vector. This is a math problem, and the reference process is in the URL in the code.

At this point, the introduction of miloyip's ray tracing has been tried and completed, and the next step is to describe the basic light source.

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

Guess you like

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