zoukankan      html  css  js  c++  java
  • 简单的图形学(二)——材质与反射

    在上一篇【游戏框架系列】简单的图形学(一)文章中,我们讲述了光线追踪的一个最简单的操作——依每个像素延伸出一条追踪光线,光线打到球上(产生交点),就算出这条线的长度,作为最终的灰度,打不到球上,就显示为黑色

    仓库:bajdcc/GameFramework

    本节代码:https://github.com/bajdcc/GameFramework/blob/master/CCGameFramework/base/pe2d/RenderMaterial.cpp

    几何图形算法接口:https://github.com/bajdcc/GameFramework/blob/master/CCGameFramework/base/pe2d/Geometries.h

    无论是材质,还是反射,还是折射等,无非就是在这个交点处依不同算法计算出颜色而已,这个颜色最终会显示到屏幕上对应的像素上。下面介绍的就是计算交点处颜色的方法。

    参考自miloyip的文章用JavaScript玩转计算机图形学(一)光线追踪入门 - Milo Yip - 博客园

    实现材质

    v2-406c25c9dbed9bf46ff6583f90e79a2a_rPhong材质效果

    这里就实现两种材质:棋盘和Phong,老实说,我都没听说过Phong这个东西,应该又是纯数学推导出来的一种结论吧。

    先写好接口:

    // 材质接口
    class Material
    {
    public:
        Material(float reflectiveness);
        virtual ~Material();
        virtual color Sample(Ray ray, vector3 position, vector3 normal) = 0;
    
        float reflectiveness;
    };
    
    // 棋盘材质
    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;
    };
    

    棋盘是在平面上的,所以我们事先要实现一个光线与平面的相交算法。

    判断直线与平面相交

    IntersectResult Plane::Intersect(Ray ray)
    {
        const auto a = DotProduct(ray.direction, normal);
    
        if (a >= 0)
            // 反方向看不到平面,负数代表角度为钝角
            // 举例,平面法向量n=(0,1,0),距离d=0,
            // 我从上面往下看,光线方向为y轴负向,而平面法向为y轴正向
            // 所以两者夹角为钝角,上面的a为cos(夹角)=负数,不满足条件
            // 当a为0,即视线与平面平行时,自然看不到平面
            // a为正时,视线从平面下方向上看,看到平面的反面,因此也看不到平面
            return IntersectResult();
    
        // 参考 http://blog.sina.com.cn/s/blog_8f050d6b0101crwb.html
        /* 将直线方程写成参数方程形式,即有:
           L(x,y,z) = ray.origin + ray.direction * t(t 就是距离 dist)
           将平面方程写成点法式方程形式,即有:
           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);
    }
    

    纯数学推导较多,这里有个优化:先看光线方向和平面法向量方向是否同向(夹角为锐角),是则直接判定不相交。

    确定好算法后,看看棋盘材质的实现:

    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;
    }
    

    简单来说,就是“x坐标+z坐标”取整是否是2的倍数。

    我们主要分析下材质接口需要哪些成分:

    1. 光线Ray,即入射光线的起点位置和方向、交点position、交点法向normal,光线方向和交点法向量是为了计算反射、折射。
    2. 交点位置 position
    3. 交点法向 normal

    接下来看看Phong材质,参考里面的网址说明,完全的数学公式。

    color PhongMaterial::Sample(Ray ray, vector3 position, vector3 normal)
    {
        /*
          参考 https://www.cnblogs.com/bluebean/p/5299358.html Blinn-Phong模型
            Ks:物体对于反射光线的衰减系数
            N:表面法向量
            H:光入射方向L和视点方向V的中间向量
            Shininess:高光系数
    
            Specular = Ks * lightColor * pow(dot(N, H), shininess)
    
            当视点方向和反射光线方向一致时,计算得到的H与N平行,dot(N,H)取得最大;当视点方向V偏离反射方向时,H也偏离N。
            简单来说,入射光与视线的差越接近法向量,镜面反射越明显
         */
    
        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 入射光在镜面法向上的投影 = 漫反射
        const auto specularTerm = specular * powf(fmax(NdotH, 0.0f), shininess);
        return lightColor * (diffuseTerm + specularTerm);
    }
    

    计算时需要四个参数:

    1. Ks:物体对于反射光线的衰减系数
    2. N:表面法向量
    3. H:光入射方向L和视点方向V的中间向量
    4. Shininess:高光系数

    我们看到的Phong材质颜色其实是它表面反射出的光,如何计算反射的光什么颜色?

    Phong材质有三个参数:diffuse漫反射颜色、specular镜面反射颜色、shininess高光系数(其实就是调节前两者的混合比例)。如题图中所示,diffuse就是球本身材质的颜色,而specular就是外界光打上去的颜色。因此,这里会假设有一个外界光源lightColor/lightDir,故而在上述计算过程中会用到它。

    实现反射

    v2-7cae800a62bc80f6fa158b3281c645fc_r反射效果

    看到反射,其实就是用递归实现的,同时要限制递归的深度。

    color PhysicsEngine::RenderReflectRecursive(World& world, const Ray& ray, int maxReflect)
    {
        static color black(Gdiplus::Color::Black);
    
        auto result = world.Intersect(ray);
    
        if (result.body) {
            // 参见 https://www.cnblogs.com/bluebean/p/5299358.html
    
            // 取得反射系数
            const auto reflectiveness = result.body->material->reflectiveness;
    
            // 先采样(取物体自身的颜色)
            auto color = result.body->material->Sample(ray, result.position, result.normal);
    
            // 加上物体自身的颜色成份(与反射的颜色相区分)
            color = color * (1.0f - reflectiveness);
    
            if (reflectiveness > 0 && maxReflect > 0) {
    
                // 公式 R = I - 2 * N * (N . I) ,求出反射光线
                const auto r = result.normal * (-2.0f * DotProduct(result.normal, ray.direction)) + ray.direction;
    
                // 以反射光线作为新的光线追踪射线
                const auto reflectedColor = RenderReflectRecursive(world, Ray(result.position, r), maxReflect - 1);
    
                // 加上反射光的成份
                color = color + (reflectedColor * reflectiveness);
            }
            return color;
        }
        return black;
    }
    

    如代码中所示,基本思路是:

    1. 光线与几何物体有交点吗?
    2. 没有交点:返回黑色
    3. 有交点:对自身材质采样,占比为(1-反射系数),计算出反射光线,继续追踪,并将追踪到的采样占比为(反射系数)

    即:

    color reflect_sample(world, ray, 深度)
    {
    __ 如果world和ray不相交, 返回 黑色
    __ 如果world和ray相交,假设这个相交物体为body
    ____ 1. 加上 body的材质颜色 * 比例(1.0f - reflectiveness)
    ____ 2. 计算出反射光线ray_f
    ____ 3. 加上反射颜色reflect_sample(world, ray_f, 深度-1) * 比例(reflectiveness)
    }

    这里重点是根据入射光线和法向量求反射光线,这是道数学題,参考过程在代码中的网址中。

    到这里,miloyip的光线追踪入门就尝试完成了,接下来是讲述基本光源。

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

  • 相关阅读:
    Quicksum -SilverN
    uva 140 bandwidth (好题) ——yhx
    uva 129 krypton factors ——yhx
    uva 524 prime ring problem——yhx
    uva 10976 fractions again(水题)——yhx
    uva 11059 maximum product(水题)——yhx
    uva 725 division(水题)——yhx
    uva 11853 paintball(好题)——yhx
    uva 1599 ideal path(好题)——yhx
    uva 1572 self-assembly ——yhx
  • 原文地址:https://www.cnblogs.com/bajdcc/p/8973003.html
Copyright © 2011-2022 走看看