OpenGL课程设计 光线追踪

链接: https://pan.baidu.com/s/1cBTTbbzRCVBCX_H4jf6qMA 提取码: kj8w

一、实验内容与要求

1.1 实验内容

(1)实验描述

基于C++(也可选择其它编程语言,但需要在实现中体现面向对象的思想)实现完整的含递归调用的光线跟踪算法。

(2)实验环境

在Clion平台下结合OpenGL开发
操作系统:macOS Monterey 12.0 Beta版(21A5248p)
处理器:Apple M1
内存:16.00GB
系统类型:64位操作系统

1.2 实验要求

(1)场景要求

至少包含两个球体。

(2)效果要求

要求实现Phong局部光照明、物体之间的镜面反射、阴影这三种效果;如果想拿更好的成绩,可以加入折射的效果。

(3)材料要求

程序源代码和实验报告,实验报告包含1、实验原理;2、数据结构设计;3、场景说明+实验结果(包含程序运行结果图片和运行时间)展示和分析;5、实验结论(实验中遇到的问题及解决方法)。

二、实验原理

2.1 光线跟踪的基本原理

1. 渲染图像的组成

​假设在相机前面放置一个网格平面,称为视平面(View plane),网格平面中的每一个小格,就是渲染图像中的一个像素,小网格的多少有渲染输出图像的分辨率决定,如渲染输出图像的分辨率为800X600,则此网格平面就由800X600的小网格组成,如果从相机的位置去看小网格,每一个小网格都覆盖了场景中的一小块区域。可见,如果能计算出
在这里插入图片描述

每个小网格所覆盖区域的平均颜色,并将此颜色做为小网格的颜色,对小网格进行填充,将网格平面中的所有小格都填充完,也就得到了我们所需要的渲染结果。

​2. 如何出计算这些小网格的平均颜色呢?

​以相机的中点为起点,向小网格的中点发出一条辅助射线(Ray),此射线与场景中的物体相交(如没有相交,则视为与背景相交),如果计算出此交点的颜色,也就得到了小网格的颜色。
​从相机发出的辅助射线与我们的视线方向相同,与场景中物体反射到我们眼晴中的光线的方向相反,故应称为视线,为了方便说明,将此辅助射线,称为采样视线,辅助射线与场景的交点,称为采样点。
​采样点的颜色由采样点所在物体的材质、场景中的光源,场景中的其它物体及背景等多方面因素相互作用决定的。

​除了需要计算采样点在光源的直接照射下,所产生的颜色外:
​如果采样点的材质具有反射属性,则需计算出采样点的反射颜色。
​如果采样点的材质具有折射属性,则需计算出采样点的折射颜色。
​如果采样点与光源之间有其它物体,则需要计算出采样点的阴影颜色。
​如果采样点的周边有其它物体,还需要计算其它物体对此采样点所产生的间接照明效果。
​如果开启了焦散效果,还需要计算出采样点的焦散颜色。
​如果开启了相机的景深及运动模糊效果,还需要计算出采样点的相关模糊颜色。
​将上述采样点的所有颜色综合在一起,就会得到采样点的最终颜色,可见采样点的的最终颜色包含了许多种不同属性的颜色成分。

3. 如何计算采样点不同属性的颜色成分?

​3.1 采样点直接照明颜色的求法

​从采样点向光线发出采样视线,求出光源与采样点的位置关系,根据光源的亮度、颜色等参数再结果采样的材质属性,就可以求出采样点在光源直接照明下所产生的颜色。

​3.2 采样点反射颜色的求法

​如果采样点的材质具有反射属性,根据光线的反射原理,此采样点继续发出采样视线,去与场景中的物体相交,我们将新的交点称为二次采样点,求出二次采样点的颜色,就是此采样点反射的颜色。如果二次采样点还具有反射属性,则此采样点继续重复上面的采样计算,直到所规定的反射次数,或反射颜色减弱到一定阀值后终止。

3.3 采样点折射颜色的求法

​如果采样点的材质具有透明属性,根据光线的折射原理,此采样点继续发出采样视线,去与场景中的物体相交,我们将新的交点称为二次采样点,求出二次采样点的颜色,就是此采样点反射的颜色。
​如果二次采样点还具有透明属性,则此采样点继续重复上面的采样计算,直到所规定的折射次数,或折射颜色减弱到一定阀值后终止。

3.4 采样点阴影颜色的求法

​从采样点向光线求出阴影采样视线,如果光源与采样点间有物体遮挡,则根据光源的阴影参数及遮挡物体物属性,就可以计算出采样点的阴影颜色。光线追踪,简单地说,就是从摄影机的位置,通过影像平面上的像素位置(比较正确的说法是取样(sampling)位置),发射一束光线到场景,求光线和几何图形间最近的交点,再求该交点的颜色。如果该交点的材质是反射性的,可以在该交点向反射方向继续追踪。光线追踪除了容易支持一些全局光照效果外,亦不局限于三角形作为几何图形的单位。任何几何图形,能与一束光线计算交点(intersection point),就能支持。
从视点出发向屏幕上每一个像素发出一条光线,追踪此光路并计算其逆向光线的方向,映射到对应的像素上。如下图,通过计算光路上颜色衰减和叠加,即可基本确定每一个像素的颜色。
在这里插入图片描述

2.2 光线与物体相交

(1)光线的数学表达式

一条光线实际上只是一个起点和一个传播方向,因此光线表达式为:
p(t) = e + t (s-e)
具体的参数意义如下图所示。所以给定t,可以确定点p。
光线表达式
在这里插入图片描述

(2)光线与球相交

请添加图片描述

(3)光线与平面相交

平面在空间几何中可以用一个向量(法向量)和平面中的一点P0来表示。
=0
将光线p(t)=p0+tu,代入平面方程n·p+d=0,最后求得t:
请添加图片描述

故已知t,可以确定交点p。

2.3 反射光线与折射光线

(1)反射光线的计算

镜面反射光线的表达式为(法向量朝外):

反射光线的原理如下图:
请添加图片描述
在这里插入图片描述

(2)折射光线的计算

在这里插入图片描述

折射光线
请添加图片描述

三、数据结构设计

在这里插入图片描述

类定义详细代码:
自定义了一个Vec3类

template<typename T>
class Vec
{
public:
    T x, y, z;
    Vec() : x(T(0)), y(T(0)), z(T(0)) {}
    Vec(T xx) : x(xx), y(xx), z(xx) {}
    Vec(T xx, T yy, T zz) : x(xx), y(yy), z(zz) {}
    Vec& normalize()// 向量的规格化
    {
        T nor2 = length2();
        if (nor2 > 0)
        {
            T invNor = 1 / sqrt(nor2);
            x *= invNor, y *= invNor, z *= invNor;
        }
        return *this;
    }

    // Vec中部分运算符的重载

    Vec<T> operator * (const T &f) const { return Vec<T>(x * f, y * f, z * f); }
    Vec<T> operator * (const Vec<T> &v) const { return Vec<T>(x * v.x, y * v.y, z * v.z); }
    T dot(const Vec<T> &v) const { return x * v.x + y * v.y + z * v.z; }
    Vec<T> operator - (const Vec<T> &v) const { return Vec<T>(x - v.x, y - v.y, z - v.z); }
    Vec<T> operator + (const Vec<T> &v) const { return Vec<T>(x + v.x, y + v.y, z + v.z); }
    Vec<T>& operator += (const Vec<T> &v) { x += v.x, y += v.y, z += v.z; return *this; }
    Vec<T>& operator *= (const Vec<T> &v) { x *= v.x, y *= v.y, z *= v.z; return *this; }
    Vec<T> operator - () const { return Vec<T>(-x, -y, -z); }
    T length2() const { return x * x + y * y + z * z; }// 获取到原点位置平方数据
    T length() const { return sqrt(length2()); }
    friend std::ostream & operator << (std::ostream &os, const Vec<T> &v)
    {
        os << "[" << v.x << " " << v.y << " " << v.z << "]";
        return os;
    }
};

还有一个自定义的Orb类,用来组织简单的球体。
在这里插入图片描述

类定义详细代码:

class orb
{
public:
    Vecf center;                           // 球心坐标位置
    float radius, radius2;                  // 球半径以及半径的平方
    Vecf surfaceColor, emissionColor;      // 表面颜色和传递的颜色
    float transparency, reflection;         // 表面反射率和透明度

    /*orb* left = NULL;
    orb* right = NULL;*/

    orb() { }
    orb(
            const Vecf &c,
            const float &r,
            const Vecf &sc,
            const float &refl = 0,
            const float &transp = 0,
            const Vecf &ec = 0):
            center(c), radius(r), radius2(r * r), surfaceColor(sc), emissionColor(ec),
            transparency(transp), reflection(refl)
    { }

    // 用几何解计算光线与球体是否有交点集
    // @rayorig: 光线的原点
    // @raydir:  光线的方向
    // @t0:      第一个交点
    // @t1:      第二个交点

    bool intersect_new(const Vecf &rayorig, const Vecf &raydir, float *t0, float *t1) const
    {
        Vecf l = center - rayorig;         // 光线原点到球心向量l
        float cos = l.dot(raydir);          // 入射方向和光线到球心向量的夹角余弦,

        if (cos < 0) return false;          // 如果夹角大于90度,光线不可能射中球体,直接返回错误,默认光线原点在球体外
        float d2 = l.dot(l) - (cos * cos);  //自身模的平方-cos^2   cos=l*l*cos夹角
        if (d2 > radius2) return false;     // 光线和球无交点
        float thc = sqrt(radius2 - d2);
        if (t0 != NULL && t1 != NULL)
        {
            // 到前一个交点的距离
            *t0 = cos - thc;
            // 到后一个交点的距离
            *t1 = cos + thc;
        }
        return true;
    }
};

四、重要函数分析

(1)判断光线相交

bool intersect(const Vecf &rayorig, //光线原点
                   const Vecf &raydir,  //光线方向
                   float *t0, //第一个交点
                   float *t1 //第二个交点
                   ) const
    {
        Vecf l = center - rayorig;         // 光线原点到球心向量l
        float cos = l.dot(raydir);          // 入射方向和光线到球心向量的夹角余弦,cos = l*cos夹角
        if (cos < 0) return false;          // 如果夹角大于90度,光线不可能射中球体
        float d2 = l.dot(l) - (cos * cos);  // d2 = l^2-l^2*cos^2 = l^2*sin^2
        if (d2 > radius2) return false;     // 光线和球无交点
        float thc = sqrt(radius2 - d2); // radius2 = r^2
        if (t0 != nullptr && t1 != nullptr)
        {
            *t0 = cos - thc;                // 到前一个交点的距离
            *t1 = cos + thc;                // 到后一个交点的距离
        }
        return true;
    }

(2)递归测试光线是否和物体相交

这是主跟踪函数。它以光线作为参数(由原点和方向表示光线)。我们将测试此光线是否与场景中的球相交。
如果光线与物体相交,我们在交点处计算交点坐标,法向,并使用此信息对该点进行着色。着色取决于表面属性(透明、反射、漫反射),函数返回光线的颜色。光线颜色为交叉点处对象的颜色,或者背景色。
本次实验考虑了菲涅尔效应,不同光波分量被折射和反射 视线垂直于表面时,反射较弱,而当视线非垂直表面时,夹角越小,反射越明显,如果目标是圆球,那圆球中心的反射较弱,靠近边缘较强。不过这种过度关系被折射率影响。

Vecf trace(
        const Vecf &rayorig, // 光线原点
        const Vecf &raydir, // 光线的单位方向向量
        const std::vector<orb *> &orbs, // 球体集合
        const int &depth) // 递归深度
{
    float tnear = INFINITY; // 一开始定义初始距离为无穷
    const orb* orb = nullptr; // 此处相当于temp

    // 在场景中找到此光线与球体最前面的交点
    for (auto i : orbs) // 对每一个球体进行相交判断
    {
        float t0 = INFINITY, t1 = INFINITY; // 直线与球面要么两个交点,要么没有交点
        if (i->intersect(rayorig, raydir, &t0, &t1)) // 进行光线与球体的相交判断
        {
            if (t0 < 0) t0 = t1; // 如果光线在球的里面,就采用前面的交点

            if (t0 < tnear)
            {
                // 判断 tnear 是否是最近的交点
                tnear = t0; // 将最近的交点设置为t0
                orb = i; // 设置球体
            }
        }
    }

    if(orb != NULL)
    {
        Vecf surfaceColor = 1.0;              // 球体表面的颜色
        Vecf phit = rayorig + raydir * tnear; // 通过光线原点+t*单位方向向量获得交点
        Vecf nhit = phit - orb->center;    // 计算交点法向量
        nhit.normalize();                      // 交点法向量规范化
        float bias = 1e-4;                     // 在要跟踪的点上添加一些偏差
        if (raydir.dot(nhit) > 0)              //如果法线和视图方向不相反,反转法线方向
        {
            nhit = -nhit;
        }
        if ((orb->transparency > 0 || orb->reflection > 0) && depth < MAX_RAY_DEPTH)
        {
            // 进行反射计算
            float IdotN = raydir.dot(nhit); // 光线方向规范化
            float facingratio = std::max(float(0), -IdotN); // 如果 -IdotN为负,说明在视点背面,不用显示,取0
            // 不同光波分量被折射和反射,当视线垂直于表面时,反射较弱,而当视线非垂直表面时,夹角越小,反射越明显(菲涅尔效应)
            float fresneleffect = mix(pow(1 - facingratio, 3), 1, 0.1); //菲涅尔效应
            Vecf refldir; // 反射光线
            refldir = raydir - nhit * 2 * raydir.dot(nhit); // r = d + 2 ( d·n ) n 反射光线计算公式(PPT里出现的
            refldir.normalize(); //反射光线向量规范化
            // 递归调用
            Vecf reflection = trace(phit + nhit * bias, refldir, orbs, depth + 1); // 交点作为原点,进行光线追踪,递归深度++ 返回颜色

            Vecf refraction = 0; //初始化折射率
            // 如果透明度不为零,进行折射计算
            if (orb->transparency > 0)
            {
                // 折射
                float ior = 1.2; //折射系数
                float eta = 1 / ior; //折射率
                float k = 1 - eta * eta * (1 - IdotN * IdotN); // 菲涅尔折射系数
                Vecf refrdir = raydir * eta - nhit * (eta *  IdotN + sqrt(k)); // 方向向量乘上折射率,然后加上菲涅尔效应的影响
                refrdir.normalize(); //折射光线规范化
                refraction = trace(phit - nhit * bias, refrdir, orbs, depth + 1); // 交点作为原点,进行光线追踪,递归深度++ 返回颜色
            }
            // 结果是反射和折射的混合(如果球体是透明的)
            surfaceColor =
                    (reflection * fresneleffect + //反射部分的颜色
                     refraction * (1 - fresneleffect) * orb->transparency)//折射部分的颜色
                     * orb->surfaceColor; //两者的和乘上物体表面颜色,得到最后颜色
                     //R(color(p+tr)+(1-R)color(p+t*t))
        }
        else
        {
            // 这是一个折射率和反射率为0的物体,不需要再进行光线追踪
            double shadow = 1.0;
            for (unsigned i = 0; i < orbs.size(); ++i) //遍历每个物体,依次计算是否相交,相交的话更新阴影
            {
                if (orbs[i]->emissionColor.x > 0)
                {
                    Vecf transmission = 1.0; //初始化
                    Vecf lightDirection = orbs[i]->center - phit; //球体法向量
                    lightDirection.normalize(); //法向量规范化

                    for (unsigned j = 0; j < orbs.size(); ++j)
                    {
                        if (i != j)
                        {
                            float t0, t1;
                            // 判断该点的光是否和源光线相交,如果相交,计算阴影
                            if (orbs[j]->intersect(phit + (nhit * bias), lightDirection, &t0, &t1))
                            {
                                shadow = std::max(0.0, shadow - (1.0 - orbs[j]->transparency)); //相交的话更新折射率
                                transmission = transmission * shadow; //计算转化率
                            }
                        }
                    }

                    // 用phong模型计算每一条对这点像素造成影响的光线
                    surfaceColor += orb->surfaceColor * transmission * orbs[i]->emissionColor; //加上最后传递的颜色
                }
            }
        }
        return surfaceColor; //返回最终的颜色
    }
    else
    {
        return Vecf(1.0, 1.0, 1.0); //背景为白色
    }
}  

(3)渲染函数

渲染函数用来根据创建的窗口设置vertices向量,然后进行光线追踪递归,while循环渲染绘制图像,将函数放到主函数中调用。

void render(const std::vector<orb *>& orbs, GLFWwindow* window)
{
    // 一些用于计算的参数设置
    Vecf *image = new Vecf[WIDTH * HEIGHT], *pixel = image;
    float invWidth = 1 / float(WIDTH), invHeight = 1 / float(HEIGHT); //计算屏占比
    float fov = 40, aspectratio = WIDTH / float(HEIGHT); // 设定视场角(视野范围) 和 纵横比
    float angle = tan(M_PI * 0.5 * fov / 180.0); // 把市场角转化为普通的角度

    // 光线追踪开始,逐像素点进行光线追踪
    for (unsigned y = 0; y < HEIGHT; ++y)
    {
        for (unsigned x = 0; x < WIDTH; ++x, ++pixel)
        {
            //进行坐标系的转换
            float xx = (2 * ((x + 0.5) * invWidth) - 1) * angle * aspectratio;
            float yy = (1 - 2 * ((y + 0.5) * invHeight)) * angle;
            Vecf raydir(xx, yy, -1); //z的值决定
            raydir.normalize();
            *pixel = trace(Vecf(0), raydir, orbs, 0);
        }
    }

    std::vector<float> vertices;
    for (unsigned int i = 0; i < HEIGHT; i++)
    {
        for (unsigned int j = 0; j < WIDTH; j++)
        {
            //坐标转换
            auto a = -2*(float(j)/WIDTH-0.5);
            auto b = -2*(float(i)/HEIGHT-0.5);
            vertices.push_back(a);
            vertices.push_back(b);
            vertices.push_back(0);
            //设置颜色值
            vertices.push_back(min(image[i*WIDTH + j].x, float(1)));
            vertices.push_back(min(image[i*WIDTH + j].y, float(1)));
            vertices.push_back(min(image[i*WIDTH + j].z, float(1)));
        }
    }
    Shader OurShader("/Users/bloodsvery/Desktop/学习/大二下/计算机图形学/Curriculumdesign2/3.3.shader.vert", "/Users/bloodsvery/Desktop/学习/大二下/计算机图形学/Curriculumdesign2/3.3.shader.frag");
    OurShader.use();
    glGenVertexArrays(1, &VAO);
    glBindVertexArray(VAO);
    glGenBuffers(1, &VBO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, vertices.size()*4, &vertices[0], GL_STATIC_DRAW);

    // position attribute
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    // normal attribute
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);
    while (!glfwWindowShouldClose(window))
    {
        //响应事件
        glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
        glClearDepth(1.0);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glDrawArrays(GL_POINTS, 0, vertices.size()/6);
        glEnable(GL_DEPTH_TEST);//深度测试
        glfwSwapBuffers(window);
        glfwPollEvents();
    }
    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);
    glfwTerminate();
}

(4)主函数中构建模型

球体模型一共有7个,根据Orb类建立了模型进行渲染,模型构建并加入球体.

int main(int argc, char **argv)
{
    glfwInit();//初始化GLFW
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);//OpenGL主版本号 3
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);//OpenGL副版本号 .3
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);//OpenGL模式 OpenGL核心模式
    // 构建自己的场景
    #ifdef __APPLE__//MacOS 下必须的调用
        glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
    #endif
    GLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "LearnOpenGL", nullptr, nullptr);//窗口宽、高、标题
    if (window == nullptr)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSetKeyCallback(window, key_call_back);
    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }

    std::vector<orb *> orbs;
    // 底面的大球
    orbs.push_back(new orb(Vecf(0, -10004, -20), 10000, Vecf(1.0, 1.0, 1.0), 1.0, 0.0));
    // 6个小球
    //红球
    orbs.push_back(new orb(Vecf(6, 0, -15), 1.5, Vecf(1.00, 0.1, 0.1), 0.2, 0.95));
    //蓝球
    orbs.push_back(new orb(Vecf(7.5, 2.5, -25), 2, Vecf(0.1, 0.1, 1.0), 0.2, 0.7));
    //透明球
    orbs.push_back(new orb(Vecf(1, -1, -18), 1, Vecf(1.0, 1.0, 1.0), 0.1, 1.0));
    //黑球
    orbs.push_back(new orb(Vecf(2, 1, -25), 2, Vecf(1.0, 1.0, 1.0), 0.3, 0.0));
    //黄球
    orbs.push_back(new orb(Vecf(-2, 2, -15), 1, Vecf(1.0, 1.0, 0.1), 0.5, 0.5));
    //绿球
    orbs.push_back(new orb(Vecf(-4, 3, -18), 1, Vecf(0.1, 1.0, 0.1), 0.3, 0.7));
    //orbs.push_back(new orb(Vecf(-8, 0, -25), 1, Vecf(0.36, 0.84, 1.0), 0.15, 0.95));
    orbs.push_back(new orb(Vecf(-8.5, -1.5, -25), 1, Vecf(1.00, 0.1, 0.1), 0.15, 0.5));


    render(orbs, window);
    return 0;
}

五、场景说明和实验结果

最终绘制效果如下图:
在这里插入图片描述

在黑色的背景场景中包括底部的巨大白色球体和六个彩色的小球体(半径为1~2个单位),经过复杂的三维位置放置以及相互遮盖,可以看到光线追踪中反射和折射的明显效果。
底部的白色球体设置成了全反射,所以可以看到4个反射出来的球体。同时中间那个透明球体也是可以看出反射出了周围各个球体的模型。

六、实验结论

(1)glDrawArrays使用问题
原本想使用glDrawPixels函数,但因为glDrawPixels在3.3版本被弃用,智能使用glDrawArrays了,然后将所有点储存在了vertices里,尝试使用了但画出来发现只有背景色。之后才发现shader着色器没有使用上。
(2)画出来的图像是反着的
在这里插入图片描述

查资料发现,我使用的方法坐标原点是在界面左下方,而原本的则是在正中心。把坐标取相反数后成功颠倒回来。

七、完整代码:

#include "glad/glad.h"
#include <sstream>
#include <vector>
#include <iostream>
#include <cmath>
#include <fstream>
#include <algorithm>
#include <GLFW/glfw3.h>
#include "shader.h"
using namespace std;

GLuint VBO, VAO;
const GLuint WIDTH = 1200, HEIGHT = 800;

// 定义一个三维向量类
template<typename T>
class Vec
{
public:
    T x, y, z;
    Vec() : x(T(0)), y(T(0)), z(T(0)) {}
    Vec(T xx) : x(xx), y(xx), z(xx) {}
    Vec(T xx, T yy, T zz) : x(xx), y(yy), z(zz) {}
    Vec& normalize()// 向量的danwei1化
    {
        T nor2 = length2();
        if (nor2 > 0)
        {
            T invNor = 1 / sqrt(nor2);
            x *= invNor, y *= invNor, z *= invNor;
        }
        return *this;
    }

    // Vec中部分运算符的重载

    Vec<T> operator * (const T &f) const { return Vec<T>(x * f, y * f, z * f); }
    Vec<T> operator * (const Vec<T> &v) const { return Vec<T>(x * v.x, y * v.y, z * v.z); }
    T dot(const Vec<T> &v) const { return x * v.x + y * v.y + z * v.z; }
    Vec<T> operator - (const Vec<T> &v) const { return Vec<T>(x - v.x, y - v.y, z - v.z); }
    Vec<T> operator + (const Vec<T> &v) const { return Vec<T>(x + v.x, y + v.y, z + v.z); }
    Vec<T>& operator += (const Vec<T> &v) { x += v.x, y += v.y, z += v.z; return *this; }
    Vec<T>& operator *= (const Vec<T> &v) { x *= v.x, y *= v.y, z *= v.z; return *this; }
    Vec<T> operator - () const { return Vec<T>(-x, -y, -z); }
    T length2() const { return x * x + y * y + z * z; }// 获取到原点位置平方数据
    T length() const { return sqrt(length2()); }
    friend std::ostream & operator << (std::ostream &os, const Vec<T> &v)
    {
        os << "[" << v.x << " " << v.y << " " << v.z << "]";
        return os;
    }
};

typedef Vec<float> Vecf;

// 定义一个球类用于绘制,与光线求交
class orb
{
public:
    Vecf center;                           // 球心坐标位置
    float radius, radius2;                  // 球半径以及半径的平方
    Vecf surfaceColor, emissionColor;      // 表面颜色和传递的颜色
    float transparency, reflection;         // 表面反射率和透明度

    /*orb* left = NULL;
    orb* right = NULL;*/

    orb() { }
    orb(
            const Vecf &c, //中心点向量
            const float &r, //半径
            const Vecf &sc, //表面颜色
            const float &refl = 0, //折射率
            const float &transp = 0, //反射率
            const Vecf &ec = 0):
            center(c), radius(r), radius2(r * r), surfaceColor(sc), emissionColor(ec),
            transparency(transp), reflection(refl)
    { }

    bool intersect(const Vecf &rayorig, //光线原点
                   const Vecf &raydir,  //光线方向
                   float *t0, //第一个交点
                   float *t1 //第二个交点
                   ) const
    {
        Vecf l = center - rayorig;         // 光线原点到球心向量l
        float cos = l.dot(raydir);          // 入射方向和光线到球心向量的夹角余弦,cos = l*cos夹角
        if (cos < 0) return false;          // 如果夹角大于90度,光线不可能射中球体
        float d2 = l.dot(l) - (cos * cos);  // d2 = l^2-l^2*cos^2 = l^2*sin^2
        if (d2 > radius2) return false;     // 光线和球无交点
        float thc = sqrt(radius2 - d2); // radius2 = r^2
        if (t0 != nullptr && t1 != nullptr)
        {
            *t0 = cos - thc;                // 到前一个交点的距离
            *t1 = cos + thc;                // 到后一个交点的距离
        }
        return true;
    }
};

// 此变量控制最大递归深度
#define MAX_RAY_DEPTH 10

float mix(const float &a, const float &b, const float &mix)
{
    return b * mix + a * (1 - mix);
}

// 这是主跟踪函数。它以光线作为参数(用原点和方向的方式表示光线)
// 测试该光线是否与场景中的任何几何体相交
// 如果光线与一个物体相交,计算交点,在交点处的法线,并对该点进行着色。
// 着色取决于曲面特性(是否透明、反射、漫反射)
// 光线不交于物体的话返回背景色
Vecf trace(
        const Vecf &rayorig, // 光线原点
        const Vecf &raydir, // 光线的单位方向向量
        const std::vector<orb *> &orbs, // 球体集合
        const int &depth) // 递归深度
{
    float tnear = INFINITY; // 一开始定义初始距离为无穷
    const orb* orb = nullptr; // 此处相当于temp

    // 在场景中找到此光线与球体最前面的交点
    for (auto i : orbs) // 对每一个球体进行相交判断
    {
        float t0 = INFINITY, t1 = INFINITY; // 直线与球面要么两个交点,要么没有交点
        if (i->intersect(rayorig, raydir, &t0, &t1)) // 进行光线与球体的相交判断
        {
            if (t0 < 0) t0 = t1; // 如果光线在球的里面,就采用前面的交点

            if (t0 < tnear)
            {
                // 判断 tnear 是否是最近的交点
                tnear = t0; // 将最近的交点设置为t0
                orb = i; // 设置球体
            }
        }
    }

    if(orb != NULL)
    {
        Vecf surfaceColor = 1.0;              // 球体表面的颜色
        Vecf phit = rayorig + raydir * tnear; // 通过光线原点+t*单位方向向量获得交点
        Vecf nhit = phit - orb->center;    // 计算交点法向量
        nhit.normalize();                      // 交点法向量规范化
        float bias = 1e-4;                     // 在要跟踪的点上添加一些偏差
        if (raydir.dot(nhit) > 0)              //如果法线和视图方向不相反,反转法线方向
        {
            nhit = -nhit;
        }
        if ((orb->transparency > 0 || orb->reflection > 0) && depth < MAX_RAY_DEPTH)
        {
            // 进行反射计算
            float IdotN = raydir.dot(nhit); // 光线方向规范化
            float facingratio = std::max(float(0), -IdotN); // 如果 -IdotN为负,说明在视点背面,不用显示,取0
            // 不同光波分量被折射和反射,当视线垂直于表面时,反射较弱,而当视线非垂直表面时,夹角越小,反射越明显(菲涅尔效应)
            float fresneleffect = mix(pow(1 - facingratio, 3), 1, 0.1); //菲涅尔效应
            Vecf refldir; // 反射光线
            refldir = raydir - nhit * 2 * raydir.dot(nhit); // r = d + 2 ( d·n ) n 反射光线计算公式(PPT里出现的
            refldir.normalize(); //反射光线向量规范化
            // 递归调用
            Vecf reflection = trace(phit + nhit * bias, refldir, orbs, depth + 1); // 交点作为原点,进行光线追踪,递归深度++ 返回颜色

            Vecf refraction = 0; //初始化折射率
            // 如果透明度不为零,进行折射计算
            if (orb->transparency > 0)
            {
                // 折射
                float ior = 1.2; //折射系数
                float eta = 1 / ior; //折射率
                float k = 1 - eta * eta * (1 - IdotN * IdotN); // 菲涅尔折射系数
                Vecf refrdir = raydir * eta - nhit * (eta *  IdotN + sqrt(k)); // 方向向量乘上折射率,然后加上菲涅尔效应的影响
                refrdir.normalize(); //折射光线规范化
                refraction = trace(phit - nhit * bias, refrdir, orbs, depth + 1); // 交点作为原点,进行光线追踪,递归深度++ 返回颜色
            }
            // 结果是反射和折射的混合(如果球体是透明的)
            surfaceColor =
                    (reflection * fresneleffect + //反射部分的颜色
                     refraction * (1 - fresneleffect) * orb->transparency)//折射部分的颜色
                     * orb->surfaceColor; //两者的和乘上物体表面颜色,得到最后颜色
                     //R(color(p+tr)+(1-R)color(p+t*t))
        }
        else
        {
            // 这是一个折射率和反射率为0的物体,不需要再进行光线追踪
            double shadow = 1.0;
            for (unsigned i = 0; i < orbs.size(); ++i) //遍历每个物体,依次计算是否相交,相交的话更新阴影
            {
                if (orbs[i]->emissionColor.x > 0)
                {
                    Vecf transmission = 1.0; //初始化
                    Vecf lightDirection = orbs[i]->center - phit; //球体法向量
                    lightDirection.normalize(); //法向量规范化

                    for (unsigned j = 0; j < orbs.size(); ++j)
                    {
                        if (i != j)
                        {
                            float t0, t1;
                            // 判断该点的光是否和源光线相交,如果相交,计算阴影
                            if (orbs[j]->intersect(phit + (nhit * bias), lightDirection, &t0, &t1))
                            {
                                shadow = std::max(0.0, shadow - (1.0 - orbs[j]->transparency)); //相交的话更新折射率
                                transmission = transmission * shadow; //计算转化率
                            }
                        }
                    }

                    // 用phong模型计算每一条对这点像素造成影响的光线
                    surfaceColor += orb->surfaceColor * transmission * orbs[i]->emissionColor; //加上最后传递的颜色
                }
            }
        }
        return surfaceColor; //返回最终的颜色
    }
    else
    {
        return Vecf(1.0, 1.0, 1.0); //背景为白色
    }
}

// 为图像的每个像素计算一条光线,跟踪它并返回一个颜色
// 如果光线击中球体,则返回相交点处球体的颜色,否则返回背景色
void render(const std::vector<orb *>& orbs, GLFWwindow* window)
{
    // 一些用于计算的参数设置
    Vecf *image = new Vecf[WIDTH * HEIGHT], *pixel = image;
    float invWidth = 1 / float(WIDTH), invHeight = 1 / float(HEIGHT); //计算屏占比
    float fov = 40, aspectratio = WIDTH / float(HEIGHT); // 设定视场角(视野范围) 和 纵横比
    float angle = tan(M_PI * 0.5 * fov / 180.0); // 把市场角转化为普通的角度

    // 光线追踪开始,逐像素点进行光线追踪
    for (unsigned y = 0; y < HEIGHT; ++y)
    {
        for (unsigned x = 0; x < WIDTH; ++x, ++pixel)
        {
            //进行坐标系的转换
            float xx = (2 * ((x + 0.5) * invWidth) - 1) * angle * aspectratio;
            float yy = (1 - 2 * ((y + 0.5) * invHeight)) * angle;
            Vecf raydir(xx, yy, -1); //z的值决定
            raydir.normalize();
            *pixel = trace(Vecf(0), raydir, orbs, 0);
        }
    }

    std::vector<float> vertices;
    for (unsigned int i = 0; i < HEIGHT; i++)
    {
        for (unsigned int j = 0; j < WIDTH; j++)
        {
            //坐标转换
            auto a = -2*(float(j)/WIDTH-0.5);
            auto b = -2*(float(i)/HEIGHT-0.5);
            vertices.push_back(a);
            vertices.push_back(b);
            vertices.push_back(0);
            //设置颜色值
            vertices.push_back(min(image[i*WIDTH + j].x, float(1)));
            vertices.push_back(min(image[i*WIDTH + j].y, float(1)));
            vertices.push_back(min(image[i*WIDTH + j].z, float(1)));
        }
    }
    Shader OurShader("/Users/bloodsvery/Desktop/学习/大二下/计算机图形学/Curriculumdesign2/3.3.shader.vert", "/Users/bloodsvery/Desktop/学习/大二下/计算机图形学/Curriculumdesign2/3.3.shader.frag");
    OurShader.use();
    glGenVertexArrays(1, &VAO);
    glBindVertexArray(VAO);
    glGenBuffers(1, &VBO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, vertices.size()*4, &vertices[0], GL_STATIC_DRAW);

    // position attribute
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    // normal attribute
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);
    while (!glfwWindowShouldClose(window))
    {
        //响应事件
        glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
        glClearDepth(1.0);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glDrawArrays(GL_POINTS, 0, vertices.size()/6);
        glEnable(GL_DEPTH_TEST);//深度测试
        glfwSwapBuffers(window);
        glfwPollEvents();
    }
    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);
    glfwTerminate();
}


void key_call_back(GLFWwindow* window, int key, int scancode, int action, int mode);

int main(int argc, char **argv)
{
    glfwInit();//初始化GLFW
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);//OpenGL主版本号 3
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);//OpenGL副版本号 .3
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);//OpenGL模式 OpenGL核心模式
    // 构建自己的场景
    #ifdef __APPLE__//MacOS 下必须的调用
        glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
    #endif
    GLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "LearnOpenGL", nullptr, nullptr);//窗口宽、高、标题
    if (window == nullptr)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSetKeyCallback(window, key_call_back);
    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }

    std::vector<orb *> orbs;
    // 底面的大球
    orbs.push_back(new orb(Vecf(0, -10004, -20), 10000, Vecf(1.0, 1.0, 1.0), 1.0, 0.0));
    // 6个小球
    //红球
    orbs.push_back(new orb(Vecf(6, 0, -15), 1.5, Vecf(1.00, 0.1, 0.1), 0.2, 0.95));
    //蓝球
    orbs.push_back(new orb(Vecf(7.5, 2.5, -25), 2, Vecf(0.1, 0.1, 1.0), 0.2, 0.7));
    //透明球
    orbs.push_back(new orb(Vecf(1, -1, -18), 1, Vecf(1.0, 1.0, 1.0), 0.1, 1.0));
    //黑球
    orbs.push_back(new orb(Vecf(2, 1, -25), 2, Vecf(1.0, 1.0, 1.0), 0.3, 0.0));
    //黄球
    orbs.push_back(new orb(Vecf(-2, 2, -15), 1, Vecf(1.0, 1.0, 0.1), 0.5, 0.5));
    //绿球
    orbs.push_back(new orb(Vecf(-4, 3, -18), 1, Vecf(0.1, 1.0, 0.1), 0.3, 0.7));
    //orbs.push_back(new orb(Vecf(-8, 0, -25), 1, Vecf(0.36, 0.84, 1.0), 0.15, 0.95));
    orbs.push_back(new orb(Vecf(-8.5, -1.5, -25), 1, Vecf(1.00, 0.1, 0.1), 0.15, 0.5));


    render(orbs, window);
    return 0;
}

void key_call_back(GLFWwindow* window, int key, int scancode, int action, int mode)
{
    if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
        glfwSetWindowShouldClose(window, GL_TRUE);
}
  • 12
    点赞
  • 76
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值