链接: https://pan.baidu.com/s/1cBTTbbzRCVBCX_H4jf6qMA 提取码: kj8w
基于C++(也可选择其它编程语言,但需要在实现中体现面向对象的思想)实现完整的含递归调用的光线跟踪算法。
在Clion平台下结合OpenGL开发
操作系统:macOS Monterey 12.0 Beta版(21A5248p)
处理器:Apple M1
内存:16.00GB
系统类型:64位操作系统
至少包含两个球体。
要求实现Phong局部光照明、物体之间的镜面反射、阴影这三种效果;如果想拿更好的成绩,可以加入折射的效果。
程序源代码和实验报告,实验报告包含1、实验原理;2、数据结构设计;3、场景说明+实验结果(包含程序运行结果图片和运行时间)展示和分析;5、实验结论(实验中遇到的问题及解决方法)。
假设在相机前面放置一个网格平面,称为视平面(View plane),网格平面中的每一个小格,就是渲染图像中的一个像素,小网格的多少有渲染输出图像的分辨率决定,如渲染输出图像的分辨率为800X600,则此网格平面就由800X600的小网格组成,如果从相机的位置去看小网格,每一个小网格都覆盖了场景中的一小块区域。可见,如果能计算出
每个小网格所覆盖区域的平均颜色,并将此颜色做为小网格的颜色,对小网格进行填充,将网格平面中的所有小格都填充完,也就得到了我们所需要的渲染结果。
以相机的中点为起点,向小网格的中点发出一条辅助射线(Ray),此射线与场景中的物体相交(如没有相交,则视为与背景相交),如果计算出此交点的颜色,也就得到了小网格的颜色。
从相机发出的辅助射线与我们的视线方向相同,与场景中物体反射到我们眼晴中的光线的方向相反,故应称为视线,为了方便说明,将此辅助射线,称为采样视线,辅助射线与场景的交点,称为采样点。
采样点的颜色由采样点所在物体的材质、场景中的光源,场景中的其它物体及背景等多方面因素相互作用决定的。
除了需要计算采样点在光源的直接照射下,所产生的颜色外:
如果采样点的材质具有反射属性,则需计算出采样点的反射颜色。
如果采样点的材质具有折射属性,则需计算出采样点的折射颜色。
如果采样点与光源之间有其它物体,则需要计算出采样点的阴影颜色。
如果采样点的周边有其它物体,还需要计算其它物体对此采样点所产生的间接照明效果。
如果开启了焦散效果,还需要计算出采样点的焦散颜色。
如果开启了相机的景深及运动模糊效果,还需要计算出采样点的相关模糊颜色。
将上述采样点的所有颜色综合在一起,就会得到采样点的最终颜色,可见采样点的的最终颜色包含了许多种不同属性的颜色成分。
从采样点向光线发出采样视线,求出光源与采样点的位置关系,根据光源的亮度、颜色等参数再结果采样的材质属性,就可以求出采样点在光源直接照明下所产生的颜色。
如果采样点的材质具有反射属性,根据光线的反射原理,此采样点继续发出采样视线,去与场景中的物体相交,我们将新的交点称为二次采样点,求出二次采样点的颜色,就是此采样点反射的颜色。如果二次采样点还具有反射属性,则此采样点继续重复上面的采样计算,直到所规定的反射次数,或反射颜色减弱到一定阀值后终止。
如果采样点的材质具有透明属性,根据光线的折射原理,此采样点继续发出采样视线,去与场景中的物体相交,我们将新的交点称为二次采样点,求出二次采样点的颜色,就是此采样点反射的颜色。
如果二次采样点还具有透明属性,则此采样点继续重复上面的采样计算,直到所规定的折射次数,或折射颜色减弱到一定阀值后终止。
从采样点向光线求出阴影采样视线,如果光源与采样点间有物体遮挡,则根据光源的阴影参数及遮挡物体物属性,就可以计算出采样点的阴影颜色。光线追踪,简单地说,就是从摄影机的位置,通过影像平面上的像素位置(比较正确的说法是取样(sampling)位置),发射一束光线到场景,求光线和几何图形间最近的交点,再求该交点的颜色。如果该交点的材质是反射性的,可以在该交点向反射方向继续追踪。光线追踪除了容易支持一些全局光照效果外,亦不局限于三角形作为几何图形的单位。任何几何图形,能与一束光线计算交点(intersection point),就能支持。
从视点出发向屏幕上每一个像素发出一条光线,追踪此光路并计算其逆向光线的方向,映射到对应的像素上。如下图,通过计算光路上颜色衰减和叠加,即可基本确定每一个像素的颜色。
一条光线实际上只是一个起点和一个传播方向,因此光线表达式为:
p(t) = e + t (s-e)
具体的参数意义如下图所示。所以给定t,可以确定点p。
光线表达式
平面在空间几何中可以用一个向量(法向量)和平面中的一点P0来表示。
=0
将光线p(t)=p0+tu,代入平面方程n·p+d=0,最后求得t:
故已知t,可以确定交点p。
镜面反射光线的表达式为(法向量朝外):
折射光线
类定义详细代码:
自定义了一个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;
}
};
类定义详细代码:
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;
}
};
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;
}
这是主跟踪函数。它以光线作为参数(由原点和方向表示光线)。我们将测试此光线是否与场景中的球相交。
如果光线与物体相交,我们在交点处计算交点坐标,法向,并使用此信息对该点进行着色。着色取决于表面属性(透明、反射、漫反射),函数返回光线的颜色。光线颜色为交叉点处对象的颜色,或者背景色。
本次实验考虑了菲涅尔效应,不同光波分量被折射和反射 视线垂直于表面时,反射较弱,而当视线非垂直表面时,夹角越小,反射越明显,如果目标是圆球,那圆球中心的反射较弱,靠近边缘较强。不过这种过度关系被折射率影响。
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); //背景为白色
}
}
渲染函数用来根据创建的窗口设置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();
}
球体模型一共有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
#include
#include
#include
#include
#include
#include
#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);
}