链接: 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;         // 光线原点到球心向量lfloat 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;         // 光线原点到球心向量lfloat 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^2if (d2 > radius2) return false;     // 光线和球无交点float thc = sqrt(radius2 - d2); // radius2 = r^2if (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; // 将最近的交点设置为t0orb = 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 attributeglVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);glEnableVertexAttribArray(0);// normal attributeglVertexAttribPointer(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();//初始化GLFWglfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);//OpenGL主版本号 3glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);//OpenGL副版本号 .3glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);//OpenGL模式 OpenGL核心模式// 构建自己的场景#ifdef __APPLE__//MacOS 下必须的调用glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);#endifGLFWwindow* 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;         // 光线原点到球心向量lfloat 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^2if (d2 > radius2) return false;     // 光线和球无交点float thc = sqrt(radius2 - d2); // radius2 = r^2if (t0 != nullptr && t1 != nullptr){*t0 = cos - thc;                // 到前一个交点的距离*t1 = cos + thc;                // 到后一个交点的距离}return true;}
};// 此变量控制最大递归深度
#define MAX_RAY_DEPTH 10float 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; // 将最近的交点设置为t0orb = 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 attributeglVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);glEnableVertexAttribArray(0);// normal attributeglVertexAttribPointer(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();//初始化GLFWglfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);//OpenGL主版本号 3glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);//OpenGL副版本号 .3glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);//OpenGL模式 OpenGL核心模式// 构建自己的场景#ifdef __APPLE__//MacOS 下必须的调用glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);#endifGLFWwindow* 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);
}

OpenGL课程设计 光线追踪相关推荐

  1. 【计算机图形学】OpenGL递归实现光线追踪

    计算机图形学课程设计:基于面向对象的光线跟踪算法设计与实现 目录 一.前言 二.项目实现与说明 1. 数据结构设计 1.1 光线 Ray 1.2 材质 Material 1.3 光照 Light 1. ...

  2. 计算机动画课程设计,计算机动画课程设计.doc

    计算机动画课程设计.doc 计算机动画制作报告 第 PAGE \* MERGEFORMAT 10 页 计算机动画课程设计制作报告 学 院: 明 德 学 院 专 业: 计算机科学与技术 班 级: 计 科 ...

  3. Python程序设计,pygame飞机大战课程设计

    *飞机大战游戏设计 摘 要:根据课程要求,以及面向对象程序设计的编程思想,在Windows操作系统环境下,运用PyCharm编译程序,以Python语言为开发语言,最终实现飞机大战游戏相应的游戏操作功 ...

  4. 计算机毕业设计、计算机课程设计怎么做?计算机设计1900套来帮你!

    计算机毕业设计.计算机课程设计怎么做?计算机设计1900套来帮你! 人生做什么事都有套路,大学毕业设计.课程设计通常比较简单,大多数都是找个项目做参考,有的人随便抄一抄糊弄一下,只要查重,格式别出错就 ...

  5. 计算机图像学基础课程设计,计算机图形学课程设计

    <计算机图形学课程设计>这本书覆盖了计算机图形学基础知识,其内容分为三个层次:编程基础.中级知识.高级话题.以配合计算机图形学课程的主要内容,及时反映OpenGL的最新进展. 书    名 ...

  6. android连连看课设报告,宠物连连看课程设计报告

    宠物连连看课程设计报告 (26页) 本资源提供全文预览,点击全文预览即可全文预览,如果喜欢文档就下载吧,查找使用更方便哦! 19.90 积分 宠物连连看1.问题描述连连看是一款简单有趣的小游戏,曾经风 ...

  7. 【Python飞机大战课程设计及源代码】

    摘 要:根据课程要求,以及面向对象程序设计的编程思想,在Windows操作系统环境下,运用PyCharm编译程序,以Python语言为开发语言,最终实现飞机大战游戏相应的游戏操作功能.Python是一 ...

  8. c语言连连看实验报告,连连看C语言课程设计报告

    连连看C语言课程设计报告 目录 问题描述 连连看是一款简单有趣的小游戏,曾经风靡一时,玩家要将相同的两张牌用三根以内的直线连在一起就可以消除,规则简单容易上手,游戏速度节奏快,画面清晰可爱,适合细心的 ...

  9. python贪吃蛇代码课程设计_c语言课程设计之贪吃蛇代码及思路 c语言课程设计报告之贪吃蛇...

    原文作者:aircraft 原文地址:https://www.cnblogs.com/DOMLX/p/8846529.html 注:本文档需与c语言课程设计之贪吃蛇文档配套使用.c语言实现贪吃蛇代码可 ...

最新文章

  1. leetcode 287. 寻找重复数(Find the Duplicate Number)
  2. 【转】每天一个linux命令(39):grep 命令
  3. 洛谷 P2704 [NOI2001]炮兵阵地
  4. Makefile之嵌套执行(9)
  5. Linux 下的几个游戏模拟器
  6. JDK源码(7)-Boolean
  7. 用循环输出以下数列:斐波那契数列(要输出20个数字)_Python学习之“为女朋友解释hash是个什么东西”
  8. 发展壮大:帮助独立游戏开发商解决分销难题
  9. 热电偶测温方案 AD7124+Pt100冷端补偿
  10. 智能制造-汽车行业线体工艺模拟仿真应用
  11. error: Microsoft Visual C++ 14.0 is required. Get it with “Build Tools for Visual Studio“: https://
  12. matlab 求特征值的命令,matlab怎么求特征值
  13. 刘永鑫报告|微生物组数据分析与科学传播(晚7点半)
  14. MySQL Flashback拯救手抖党
  15. 冠词,a/an/the --- 元音字母
  16. php循环实现金字塔,PHP中使用循环实现的金字塔图形
  17. NGS可变剪切之STAR+rmats软件使用
  18. kso经验积累 -- c#发送邮件
  19. 个性化lightswitch登录屏幕(附源码)
  20. 如何去掉Qt布局(Layout)内控件之间的空隙

热门文章

  1. 修11代12代希捷坏道
  2. 互联网日报 | 华为鸿蒙OS 2.0正式发布;微信小程序日活用户超4亿;百胜中国回港上市首日破发...
  3. 新加坡区块链ICO公司是如何注册新加坡公司的
  4. 知乎网页(用php从数据库读取数据)
  5. 【C语言】打印杨辉三角形
  6. 【高端黑】软件工程师去理发店
  7. 提供 Android 酷炫的开屏动画 (awesome-opening-animation)
  8. 沉舟侧畔千帆过,病树前头万木春
  9. GitHub注册教程(图文详解)
  10. Unity3D 参数曲线 实现曲线上的匀速运动