从零开始的openGL——五、光线追踪
前言
注:代码已开源在 github 中, https://github.com/leo6033/CSU_CS_Experiment/tree/master/计算机图形学
前面介绍了基本图形、模型、曲线的绘制,但是,在好像还没有感受到那种3D游戏里一些能惊艳到自己的效果,即真实感还不是很足。这篇文章中介绍的光线追踪,是实现真实感必不可少的。拿下面的两张图片来对比
对比一下是不是被下面这张图片的效果惊艳到了?可以很明显感觉到,下面的这个图片效果要好的多。这篇博客将介绍如何实现这样的效果。
光线求交
这里暂时只介绍光线与球面和三角面片的求交
光线与球面相交
射线的方程:
\]
球面的隐式方程:
\]
联立两式:
\]
然后通过判别式:$$\Delta=4[(A-C) \cdot D]^2 - 4(A-C)2+r2$$来判断是否相交。
交点法向量:
\]
bool Sphere::intersectLocal( const ray& r, isect& i ) const
{
// YOUR CODE HERE:
// 光线与球面相交
// Add sphere intersection code here.
Vec3d A = r.getPosition();
Vec3d D = r.getDirection();
Vec3d C= Vec3<double>();
double _r = 1.0;
double a = D.length2();
double b = 2 * (A - C) * D;
double c = (A - C).length2() - _r;
double delta = b * b - 4 * a * c;
// it currently ignores all spheres and just return false.
if (delta >= 0) {
double t1 = (-b + sqrt(delta)) / (2 * a);
double t2 = (-b - sqrt(delta)) / (2 * a);
if (t1 <= RAY_EPSILON)
return false;
else {
double t;
if (t2 <= RAY_EPSILON) {
t = t1;
i.outsideTheObject = false;
}
else {
t = t2;
i.outsideTheObject = true;
}
// 焦点设置
i.obj = this;
i.setT(t);
Vec3d P = r.at(t);
Vec3d Normal = P;
if (D*Normal > 0)
Normal = -Normal;
Normal.normalize();
i.setN(Normal);
return true;
}
}
return false;
}
光线与三角面片相交
射线的方程:
\]
三角面片点法式方程:
\]
联立两式得:
\]
求出t后,便得到交点坐标,然后可通过同向法来判别交点是否在平面内。
// Calculates and returns the normal of the triangle too.
bool TrimeshFace::intersectLocal(const ray& r, isect& i) const
{
// YOUR CODE HERE:
// Add triangle intersection code here.
// it currently ignores all triangles and just return false.
//
// Note that you are only intersecting a single triangle, and the vertices
// of the triangle are supplied to you by the trimesh class.
//
// You should retrieve the vertices using code like this:
//
// const Vec3d& a = parent->vertices[ids[0]];
// const Vec3d& b = parent->vertices[ids[1]];
// const Vec3d& c = parent->vertices[ids[2]];
const Vec3d& a = parent->vertices[ids[0]];
const Vec3d& b = parent->vertices[ids[1]];
const Vec3d& c = parent->vertices[ids[2]];
Vec3d edge1 = b - a;
Vec3d edge2 = c - a;
// 计算平面法向量
Vec3d nor = edge1 ^ edge2;
nor.normalize();
// 判断是否与平面平行
float x = nor * r.getDirection();
if (x == 0)
return false;
// Ax + By + Cz = d
float d = nor * a;
float t = (d - nor * r.getPosition()) / x;
if (t <= RAY_EPSILON)
return false;
Vec3d intersection_point = r.at(t);
Vec3d edge3 = intersection_point - a;
// 同向法判断是否在平面内
if (((b - a) ^ (intersection_point - a)) * nor <= 0)
return false;
else if (((c - b) ^ (intersection_point - b)) * nor <= 0)
return false;
else if (((a - c) ^ (intersection_point - c)) * nor <= 0)
return false;
else {
//交点设置
i.obj = this;
i.setT(t);
i.setN(nor);
return true;
}
}
当然,这里还可以使用重心坐标法来实现
光线衰减
在现实场景中,光线也是会衰减的,比如看同一场景,距离远近不同看到的清晰度也就不同,这是距离衰减。还有阴影衰减,当有物体遮挡住部分光的时候,会形成一定的阴影,这就是阴影衰减产生的效果。
距离衰减
点光源:
\]
double PointLight::distanceAttenuation( const Vec3d& P ) const
{
// You'll need to modify this method to attenuate the intensity
// of the light based on the distance between the source and the
// point P. For now, we assume no attenuation and just return 1.0
Vec3d d = P - position;
double r = d.length(); //距离
return min(1.0, 1.0 / (constantTerm + linearTerm * r + quadraticTerm * r*r));
// return 1.0;
}
平行光源:
double DirectionalLight::distanceAttenuation( const Vec3d& P ) const
{
// distance to light is infinite, so f(di) goes to 0. Return 1.
return 1.0;
}
阴影衰减
点光源:
首先判断光线是否被遮挡,然后再判断是否超出光强所能打到的距离
Vec3d PointLight::shadowAttenuation(const Vec3d& P) const
{
// YOUR CODE HERE:
// You should implement shadow-handling code here.
Vec3d d = getDirection(P);
isect i;
ray shadowRay(P, d);
if (this->getScene()->intersect(shadowRay, i)) {
double tLight = (P - position).length();
if (i.t < tLight)
return Vec3d(0, 0, 0);
else
return Vec3d(1, 1, 1);
}
return Vec3d(1,1,1);
}
平行光:
只需判断是否被遮挡即可
Vec3d DirectionalLight::shadowAttenuation( const Vec3d& P ) const
{
// YOUR CODE HERE:
Vec3d d = getDirection(P);
isect i;
ray shadowRay(P, d);
if (this->getScene()->intersect(shadowRay, i)) {
return Vec3d(0, 0, 0);
}
// You should implement shadow-handling code here.
return Vec3d(1,1,1);
}
光线追踪
先来份伪代码
光线跟踪中的四种射线:
视线:由视点与象素(x,y)发出的射线
阴影测试线:物体表面上点与光源的连线
反射光线
折射光线
光线追踪的过程
phong光照模型
由物体表面上一点P反射到视点的光强I为环境光的反射光强\(I_e\)、理想漫反射光强\(I_d\)、和镜面反射光\(I_s\)的总和,即
\]
在washington CSE 457的课件中给出的公式为
\]
其中\(k_d\)项表示漫反射,采用Lamber模型,\(k_s\)项表示镜面反射
\]
\]
即可写出下列代码
// Apply the Phong model to this point on the surface of the object, returning
// the color of that point.
Vec3d Material::shade( Scene *scene, const ray& r, const isect& i ) const
{
// YOUR CODE HERE
// For now, this method just returns the diffuse color of the object.
// This gives a single matte color for every distinct surface in the
// scene, and that's it. Simple, but enough to get you started.
// (It's also inconsistent with the Phong model...)
// Your mission is to fill in this method with the rest of the phong
// shading model, including the contributions of all the light sources.
// You will need to call both distanceAttenuation() and shadowAttenuation()
// somewhere in your code in order to compute shadows and light falloff.
if( debugMode )
std::cout << "Debugging the Phong code (or lack thereof...)" << std::endl;
Vec3d pos = r.at(i.t);
Vec3d N = i.N;
N.normalize();
Vec3d Ip, L, H, Atten;
Vec3d shadow = ke(i) + prod(scene->ambient(), ka(i));
for (vector<Light*>::const_iterator litr = scene->beginLights();
litr != scene->endLights(); ++litr) {
Light* pLight = *litr;
Ip = pLight->getColor(pos);
L = pLight->getDirection(pos);
H = -r.getDirection() + L; H.normalize();
Atten = pLight->distanceAttenuation(pos)*pLight->shadowAttenuation(pos);
shadow += prod(Atten, prod(Ip, kd(i)*(L*N) + ks(i)*pow(H*N, 256)));
}
return shadow;
}
反射方向
这里的反射指的是镜面反射
计算公式:
\]
为什么是这样呢?首先来看\(V\cdot N\),这里N是交点处的法向量,并且是单位向量,那个即视线在法向量上的投影,再乘法向量的两倍,得到的是平行四边形的对角线,减去V便是反射后的光线的方向。
折射方向
跟反射方向一样都是公式推导
\]
终止条件
经过上述的介绍,很容易可以想到,什么时候终止光线追踪
该光线未碰到任何物体
该光线碰到了背景
光线在经过许多次反射和折射以后,就会产生衰减,光线对于视点的光强贡献很小(小于某个设定值)。
光线反射或折射次数即跟踪深度大于一定值
因此,光线追踪的代码实现如下
// Do recursive ray tracing! You'll want to insert a lot of code here
// (or places called from here) to handle reflection, refraction, etc etc.
Vec3d RayTracer::traceRay( const ray& r,
const Vec3d& thresh, int depth )
{
isect i;
if( scene->intersect( r, i ) && depth >= 0) {
const Material& m = i.getMaterial();
//计算光源直射
Vec3d I = m.shade(scene, r, i);
//计算反射递归
Vec3d Q = r.at(i.t);
Vec3d R = r.getDirection() - 2 * (r.getDirection()*i.N)*i.N;
R.normalize();
I += prod(m.kr(i), traceRay(ray(Q, R), thresh, depth - 1));
//计算折射递归
double cosThetaI = -i.N*r.getDirection();
double eta = (i.outsideTheObject) ? 1.0003 / m.index(i) : m.index(i) / 1.0003;
if (eta*eta*(1 - cosThetaI * cosThetaI) < 1) {
double cosThetaT = sqrt(1 - eta * eta*(1 - cosThetaI * cosThetaI));
Vec3d T = (eta*cosThetaI - cosThetaT)*i.N - eta * r.getDirection();
T.normalize();
I += prod(m.kt(i), traceRay(ray(Q, -T), thresh, depth - 1));
}
return I;
// An intersection occured! We've got work to do. For now,
// this code gets the material for the surface that was intersected,
// and asks that material to provide a color for the ray.
// This is a great place to insert code for recursive ray tracing.
// Instead of just returning the result of shade(), add some
// more steps: add in the contributions from reflected and refracted
// rays.
//const Material& m = i.getMaterial();
//return m.shade(scene, r, i);
} else {
// No intersection. This ray travels to infinity, so we color
// it according to the background color, which in this (simple) case
// is just black.
return Vec3d( 0.0, 0.0, 0.0 );
}
}
小节
到这里,光线追踪也就差不多介绍完了,这一系列博客也算是收尾了。那天在课上听其他同学展示的的时候,说是我的世界有部分的开源源码,里面有一个可以实现光追的接口,有兴趣的小伙伴可以去康康,似乎那个仅仅实现光追还无法达到很好的效果,还要加上路线追踪,emmmmm。。。。期末考完有空了我再去康康,明早图形学考试祝我好运 orz
从零开始的openGL——五、光线追踪的更多相关文章
- 从零开始学习jQuery (五) 事件与事件对象
本系列文章导航 从零开始学习jQuery (五) 事件与事件对象 一.摘要 事件是脚本编程的灵魂. 所以本章内容也是jQuery学习的重点. 本文将对jQuery中的事件处理以及事件对象进行详细的讲解 ...
- 从零开始的openGL——四、纹理贴图与n次B样条曲线
前言 在上篇文章中,介绍了如何加载绘制模型以及鼠标交互的实现,并且遗留了个问题,就是没有模型表面没有纹理,看起来很丑.这篇文章将介绍如何贴纹理,以及曲线的绘制. 纹理贴图 纹理加载 既然是贴图,那首先 ...
- 从零开始学习OpenGL ES之一 – 基本概念
我曾写过一些文章介绍iPhone OpenGL ES编程,但大部分针对的是已经至少懂得一些3D编程知识的人.作为起点,请下载我的OpenGL Xcode项目模板,而不要使用Apple提供的模板.你可以 ...
- [从零开始搭网站五]http网站Tomcat配置web.xml和server.xml
点击下面连接查看从零开始搭网站全系列 从零开始搭网站 上一章我们在CentOS下搭建了Tomcat,但是还是没有跑起来...那么这一章就把最后的配置给大家放上去. 有两种方式:一种是用 rm -f 给 ...
- Java从零开始学十五(继承)
一.继承作用 继承使用复用以前的代码非常容易,能够大大的缩短开发周期,降低开发成本,同时增加程序的易维护性 继承使重一个类A能够直接使用另外一个类B的属性和方法的一种途径 类A可以有自己的属性和方法 ...
- 从零开始学安全(五)●Vmware虚拟机三种网络模式详解
vmware为我们提供了三种网络工作模式,它们分别是:Bridged(桥接模式).NAT(网络地址转换模式).Host-Only(仅主机模式). NAT(网络地址转换模式) NAT(网络地址转换)vm ...
- docker从零开始 存储(五)存储驱动介绍
关于存储驱动程序 要有效地使用存储驱动程序,了解Docker如何构建和存储镜像以及容器如何使用这些镜像非常重要.您可以使用此信息做出明智的选择,以确定从应用程序中保留数据的最佳方法,并避免在此过程中出 ...
- docker从零开始网络(五)null网络
禁用容器的网络连接 预计阅读时间: 1分钟 如果要完全禁用容器上的网络堆栈,可以--network none在启动容器时使用该标志.在容器内,仅创建环回设备.以下示例说明了这一点. 1.创建容器. [ ...
- 从零开始学习jQuery(转)
本系列文章导航 从零开始学习jQuery (一) 开天辟地入门篇 从零开始学习jQuery (二) 万能的选择器 从零开始学习jQuery (三) 管理jQuery包装集 从零开始学习jQuery ( ...
随机推荐
- [LC] 112题 路径总和(在二叉树里判断是否有哪条路径之和等于某个值)
①题目 给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和. 说明: 叶子节点是指没有子节点的节点. 示例: 给定如下二叉树,以及目标和 sum ...
- paper sharing :学习特征演化的数据流
特征演化的数据流 数据流学习是近年来机器学习与数据挖掘领域的一个热门的研究方向,数据流的场景和静态数据集的场景最大的一个特点就是数据会发生演化,关于演化数据流的研究大多集中于概念漂移检测(有监督学习) ...
- python描述:链表
单链表结构: 链表是一种物理存储单元上非连续.非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的.链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成.每个结点 ...
- Springboot 系列(十六)你真的了解 Swagger 文档吗?
前言 目前来说,在 Java 领域使用 Springboot 构建微服务是比较流行的,在构建微服务时,我们大多数会选择暴漏一个 REST API 以供调用.又或者公司采用前后端分离的开发模式,让前端和 ...
- 听说PHP的生成器yield处理大量数据杠杠的
官方解释yield yield生成器是php5.5之后出现的,官方文档这样解释:yield提供了一种更容易的方法来实现简单的迭代对象,相比较定义类实现 Iterator 接口的方式,性能开销和复杂性大 ...
- 联想Y7000,I5-9300H+Nvidia GTX 1050, kali linux的nvidia显卡驱动安装
转载自,Linux安装NVIDIA显卡驱动的正确姿势 https://blog.csdn.net/wf19930209/article/details/81877822#NVIDIA_173 ,主要用 ...
- 2019-9-11:渗透测试,基础学习,vim编辑器,笔记
Linux快捷路径符号说明. 代表当前目录.. 上级目录- 代表前一个工作目录~ 表示当前用户的家目录 vmware tools 用来虚拟机和宿主机之间移动数据 vim/vi编辑器vim编辑器三种模式 ...
- JS三座大山再学习(三、异步和单线程)
本文已发布在西瓜君的个人博客,原文传送门 前言 写这一篇的时候,西瓜君查阅了很多资料和文章,但是相当多的文章写的都很简单,甚至互相之间有矛盾,这让我很困扰:同时也让我坚定了要写出一篇好的关于JS异步. ...
- day20191106
笔记: 一.#{}和${}的区别是什么 1)#{}是预编译处理,${}是字符串替换.2)Mybatis 在处理#{}时,会将 sql 中的#{}替换为?号,调用 PreparedStatement 的 ...
- 【JavaEE】之SSM-Maven依赖积累
本帖中收集JavaEE SSM框架使用的Maven版本库中的依赖.本帖持续更新...... 日志输出: <dependency> <groupId>commons-loggin ...