提要
经过上次的学习,我们已经可以建立一个简单的光线追踪的场景,接下来,我们继续我们的征程。
今天要得到的最终效果如下:
光线与物体求交
在光线追踪算法中,最重要的就是求光线与物体的相交,也就是实现IntersectResult Object::isIntersected(CRay _ray)方法。 因为我求得交点之后就可以对该点的像素进行计算,然后显示,后续的很多效果(透明,反射....)还有算法的优化加速,都是在对相交算法的改进。
之前已经讨论了光线与球体的相交,今天讨论平面,三角形和多个物体,且不考虑折射和反射。
平面
平面在空间几何中可以用一个向量(法向量)和平面中的一点P0来表示。
平面就是满足下式的点集:n.(P-P0)=0
得到:n.P=d;d=n.P0;
给定射线p(t)=p0+tu,平面方程为n.p+d=0,将p(t)带入到平面方程,最后求得t:
t=(-d-(n.p0))/(n.u)
三角形
渲染中的基本图元 包括点,线,还有三角形,建模中多面体大部分都是用三角形的拼接来表示。
首先来看一下空间三角形的表示。
假设用空间上a,b,c三点来表示一个三角形。
则三角平面内的任意一点可以表示为
![](https://i-blog.csdnimg.cn/blog_migrate/565e3977c1b31dc3dda2b8eb238b4cfd.png)
![](https://i-blog.csdnimg.cn/blog_migrate/063129121fe9552551f2cc799870f844.png)
![](https://i-blog.csdnimg.cn/blog_migrate/8cab12061acee9af9a85872a5ca6c0b5.png)
![](https://i-blog.csdnimg.cn/blog_migrate/c9bcb3f43e3a0c9c41fc532db42984de.png)
![](https://i-blog.csdnimg.cn/blog_migrate/afac6bcfc8e338dccfe0df532587247b.png)
![](https://i-blog.csdnimg.cn/blog_migrate/a7528758ff23f84f445796d3b731b36a.png)
![](https://i-blog.csdnimg.cn/blog_migrate/8e36ee93812721e9629469f2040bca8e.png)
![](https://i-blog.csdnimg.cn/blog_migrate/051136682f734db89192a1d6470ec7da.png)
- boolean raytri (ray r, vector3 a, vector3 b, vector3 c, interval [t0 , t1 ])
- compute t
- if (t < t0 ) or (t > t1 ) then
- return false
- compute γ
- if (γ < 0) or (γ > 1) then
- return false
- compute β
- if (β < 0) or (β > 1 − γ) then
- return false
- return true
平面内的向量和法向量时垂直的,即点乘结果为0,可以且接方程:
![](https://i-blog.csdnimg.cn/blog_migrate/c4a93e108bc9413fed9473d89d6358ac.png)
- hit = false
- for each object o in the group do
- if (o is hit at ray parameter t and t ∈ [t0 , t1 ]) then
- hit = true
- hitobject = o
- t1 = t
- return hit
平面类的实现
平面类我们就可以用代码这样来描述:
- <span style="font-size:14px;">#ifndef Plane_H
- #define Plane_H
- #include "gvector3.h"
- #include "intersectresult.h"
- #include "cray.h"
- #include "checkermaterial.h"
- #include "cobject.h"
- class Plane:public CObject
- {
- public:
- Plane();
- Plane(const GVector3& _normal,float _d);
- virtual ~Plane();
- virtual IntersectResult isIntersected(CRay& RAY);
- protected:
- private:
- //法向量
- GVector3 normal;
- //到原点的距离
- float d;
- };
- #endif // Plane_H
- </span>
接下来是求光线到平面的距离。
代码实现如下:
- <span style="font-size:14px;">#include "plane.h"
- Plane::Plane()
- {
- //ctor
- }
- Plane::~Plane()
- {
- //dtor
- }
- Plane::Plane(const GVector3& _normal,float _d)
- {
- normal=_normal;
- d=_d;
- }
- IntersectResult Plane::isIntersected(CRay& ray)
- {
- IntersectResult result = IntersectResult::noHit();
- float a = ray.getDirection().dotMul(this->normal);
- if (a <0)
- {
- result.isHit=1;
- result.object = this;
- float b = this->normal.dotMul(ray.getOrigin()-normal*d);
- result.distance = -b / a;
- result.position = ray.getPoint(result.distance);
- result.normal = this->normal;
- return result;
- }
- }
- </span>
接下来我们在场景中创建一个平面,然后渲染深度,得到下面的效果:
材质
在真实世界中,白色物体在绿光照射下看起来是绿色而不是白色,红色物体在绿光照射下看起来是黑色,而有的同样颜色的物体在同样的光照下亮度却不同,这都是由物体的材质不同造成的。
首先在项目中添加一个颜色类,然后定义一些方法。
color.h
- #ifndef COLOR_H
- #define COLOR_H
- #include <stdlib.h>
- #include <stdio.h>
- #include <cmath>
- #include <iostream>
- using namespace std;
- class Color
- {
- public:
- float r;
- float g;
- float b;
- Color();
- Color(float _r,float _g,float _b);
- Color add(const Color& c);
- Color multiply(float s) const;
- Color modulate(const Color& c) const;
- void saturate();
- void show();
- virtual ~Color();
- static inline Color black(){ return Color(0,0,0); }
- static inline Color white(){ return Color(1,1,1); }
- static inline Color red() { return Color(1,0,0); }
- static inline Color green(){ return Color(0,1,0); }
- static inline Color blue() { return Color(0,0,1); }
- protected:
- private:
- };
- #endif // COLOR_H
color.cpp
- #include "color.h"
- Color::Color()
- {
- //ctor
- }
- Color::~Color()
- {
- //dtor
- }
- Color::Color(float _r,float _g,float _b)
- {
- r=_r;g=_g;b=_b;
- }
- Color Color::add(const Color& c)
- {
- return Color(r + c.r, g + c.g, b + c.b);
- }
- Color Color::multiply(float s) const
- {
- return Color(r * s, g * s, b * s);
- }
- Color Color::modulate(const Color&c) const
- {
- return Color(r * c.r, g * c.g, b * c.b);
- }
- void Color::saturate()
- {
- r = r>1.0?1.0:r;
- g = g>1.0?1.0:g;
- b = b>1.0?1.0:b;
- }
- void Color::show()
- {
- cout<<"r:"<<r<<"g:"<<g<<"b:"<<b<<endl;
- }
然后是定义一个材质的基类,后面要实现的各种材质都继承它:
material.h
- #ifndef Material_H
- #define Material_H
- #include "gvector3.h"
- #include "intersectresult.h"
- #include "cray.h"
- #include "color.h"
- class Material
- {
- public:
- Material();
- Material(float _reflectiveness);
- float getRef();
- void setRef(float _reflectiveness);
- virtual ~Material();
- virtual Color sample(const CRay& ray,const GVector3& position,const GVector3& normal);
- protected:
- float reflectiveness;
- private:
- };
- #endif // Material_H
material.cpp
- #include "material.h"
- Material::Material()
- {
- //ctor
- }
- Material::Material(float _reflectiveness)
- {
- reflectiveness=_reflectiveness;
- }
- Material::~Material()
- {
- //dtor
- }
- float Material::getRef()
- {
- return reflectiveness;
- }
- void Material::setRef(float _reflectiveness)
- {
- reflectiveness=_reflectiveness;
- }
- Color Material::sample(const CRay& ray,const GVector3& position,const GVector3& normal)
- {
- cout<<"Base sample!"<<endl;
- }
实现两种材质,一种是棋盘材质,一种phong材质。
checkermaterial.h
- #ifndef CHECKERMATERIAL_H
- #define CHECKERMATERIAL_H
- #include "material.h"
- #include "color.h"
- #include <stdlib.h>
- class CheckerMaterial:public Material
- {
- public:
- CheckerMaterial();
- CheckerMaterial(float _scale,float _reflectiveness=0);
- virtual ~CheckerMaterial();
- virtual Color sample(const CRay& ray,const GVector3& position,const GVector3& normal);
- protected:
- private:
- float scale;
- };
- #endif // CHECKERMATERIAL_H
checkermaterial.cpp
- #include "checkermaterial.h"
- CheckerMaterial::CheckerMaterial()
- {
- //ctor
- }
- CheckerMaterial::CheckerMaterial(float _scale,float _reflectiveness)
- {
- scale=_scale;
- reflectiveness=_reflectiveness;
- }
- CheckerMaterial::~CheckerMaterial()
- {
- //dtor
- }
- Color CheckerMaterial::sample(const CRay& ray,const GVector3& position,const GVector3& normal)
- {
- float d=abs((floor(position.x * this->scale) + floor(position.z * this->scale)));
- d=fmod(d,2);
- return d < 1 ? Color::black() : Color::white();
- }
phongmaterial.h
- #ifndef PHONGMATERIAL_H
- #define PHONGMATERIAL_H
- #include"gvector3.h"
- #include "color.h"
- #include "cray.h"
- #include "material.h"
- // global temp
- static GVector3 lightDir = GVector3(1, 1, 1).normalize();
- static Color lightColor = Color::white();
- class PhongMaterial:public Material
- {
- public:
- PhongMaterial();
- PhongMaterial(const Color& _diffuse,const Color& _specular,const float& _shininess,float _reflectiveness=0);
- virtual Color sample(const CRay& ray,const GVector3& position,const GVector3& normal);
- virtual ~PhongMaterial();
- protected:
- private:
- Color diffuse;
- Color specular;
- float shininess;
- };
- #endif // PHONGMATERIAL_H
phongmaterial.cpp
- #include "phongmaterial.h"
- PhongMaterial::PhongMaterial()
- {
- //ctor
- }
- PhongMaterial::PhongMaterial(const Color& _diffuse,const Color& _specular,const float& _shininess,float _reflectiveness)
- {
- diffuse=_diffuse;
- specular=_specular;
- shininess=_shininess;
- reflectiveness=_reflectiveness;
- }
- PhongMaterial::~PhongMaterial()
- {
- //dtor
- }
- Color PhongMaterial::sample(const CRay& ray,const GVector3& position,const GVector3& normal)
- {
- float NdotL = normal.dotMul(lightDir);
- GVector3 H = (lightDir-ray.getDirection()).normalize();
- float NdotH = normal.dotMul(H);
- Color diffuseTerm = this->diffuse.multiply(std::max(NdotL, (float)0));
- Color specularTerm = this->specular.multiply(pow(std::max(NdotH, (float)0), this->shininess));
- return lightColor.modulate(diffuseTerm.add(specularTerm));
- }
试着来渲染一下。
- void renderDepth()
- {
- glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
- glLoadIdentity(); // Reset The View
- glTranslatef(-0.5f,-0.5f,-1.0f);
- glPointSize(2.0);
- PerspectiveCamera camera( GVector3(0, 10, 10),GVector3(0, -0.5, -1),GVector3(0, 1, 0), 90);
- Plane plane1(GVector3(0, 1, 0),1.0);
- CSphere sphere1(GVector3(0, 5, -10), 5.0);
- plane1.material=new CheckerMaterial(0.1f);
- sphere1.material=new PhongMaterial(Color::red(), Color::white(), 16);
- long maxDepth=20;
- float dx=1.0f/WINDOW_WIDTH;
- float dy=1.0f/WINDOW_HEIGHT;
- float dD=255.0f/maxDepth;
- glBegin(GL_POINTS);
- for (long y = 0; y < WINDOW_HEIGHT; ++y)
- {
- float sy = 1 - dy*y;
- for (long x = 0; x < WINDOW_WIDTH; ++x)
- {
- float sx =dx*x;
- CRay ray(camera.generateRay(sx, sy));
- IntersectResult result = sphere1.isIntersected(ray);
- //IntersectResult result = plane1.isIntersected(ray);
- if (result.isHit)
- {
- Color color = sphere1.material->sample(ray, result.position, result.normal);
- //Color color =plane1.material->sample(ray, result.position, result.normal);
- color.saturate();
- //color.show();
- glColor3ub(color.r*255,color.g*255,color.b*255);
- glVertex2f(sx,sy);
- }
- }
- }
- glEnd();
- // 交换缓冲区
- glfwSwapBuffers();
- }
结果如下:
材质这一块有很多东西可以来探讨,而且它和光照联系的很紧密,这里先不探讨。
联合
之前的渲染测试我们都只渲染了单个的物体,现在我们需要在场景中显示多个物体,就上开篇的那副图一样。
创建一个union类,在渲染的时候将要渲染的东西都丟进去。
union.h
- #ifndef UNION_H
- #define UNION_H
- #include "cobject.h"
- #include <vector>
- using namespace std;
- class Union:public CObject
- {
- public:
- Union();
- virtual ~Union();
- void push(CObject* object);
- virtual IntersectResult isIntersected(CRay& _ray);
- protected:
- private:
- vector<CObject*> cobjects;
- };
- #endif // UNION_H
union.cpp
- #include "union.h"
- Union::Union()
- {
- //ctor
- }
- Union::~Union()
- {
- //dtor
- }
- void Union::push(CObject* object)
- {
- cobjects.push_back(object);
- }
- IntersectResult Union::isIntersected(CRay& _ray)
- {
- const float Infinity=1e30;
- float minDistance = Infinity;
- IntersectResult minResult = IntersectResult::noHit();
- long size=this->cobjects.size();
- for (long i=0;i<size;++i){
- IntersectResult result = this->cobjects[i]->isIntersected(_ray);
- if (result.object && (result.distance < minDistance)) {
- minDistance = result.distance;
- minResult = result;
- }
- }
- return minResult;
- }
最后的 渲染代码
- void renderUnion()
- {
- glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
- glLoadIdentity(); // Reset The View
- glTranslatef(-0.5f,-0.5f,-1.0f);
- glPointSize(2.0);
- PerspectiveCamera camera( GVector3(0, 10, 10),GVector3(0, -0.5, -1),GVector3(0, 1, 0), 90);
- Plane* plane1=new Plane(GVector3(0, 1, 0),1.0);
- CSphere* sphere1=new CSphere(GVector3(-2, 5, -10), 5.0);
- CSphere* sphere2=new CSphere(GVector3(5, 5, -10), 3.0);
- plane1->material=new CheckerMaterial(0.1f);
- sphere1->material=new PhongMaterial(Color::red(), Color::white(), 16);
- sphere2->material=new PhongMaterial(Color::blue(), Color::white(), 16);
- Union scence;
- scence.push(plane1);
- scence.push(sphere1);
- scence.push(sphere2);
- long maxDepth=20;
- float dx=1.0f/WINDOW_WIDTH;
- float dy=1.0f/WINDOW_HEIGHT;
- float dD=255.0f/maxDepth;
- glBegin(GL_POINTS);
- for (long y = 0; y < WINDOW_HEIGHT; ++y)
- {
- float sy = 1 - dy*y;
- for (long x = 0; x < WINDOW_WIDTH; ++x)
- {
- float sx =dx*x;
- CRay ray(camera.generateRay(sx, sy));
- IntersectResult result = scence.isIntersected(ray);
- //IntersectResult result = plane1.isIntersected(ray);
- if (result.isHit)
- {
- Color color = result.object->material->sample(ray, result.position, result.normal);
- //Color color =plane1.material->sample(ray, result.position, result.normal);
- color.saturate();
- //color.show();
- glColor3ub(color.r*255,color.g*255,color.b*255);
- glVertex2f(sx,sy);
- }
- }
- }
- glEnd();
- // 交换缓冲区
- glfwSwapBuffers();
- }
运行结果
最后我们来加一点反射的效果,在main.cpp中添加一个函数。
- Color rayTraceRecursive(CObject* scene,CRay& ray,long maxReflect)
- {
- IntersectResult result = scene->isIntersected(ray);
- if (result.object)
- {
- float reflectiveness = result.object->material->getRef();
- Color color = result.object->material->sample(ray, result.position, result.normal);
- color = color.multiply(1 - reflectiveness);
- if ((reflectiveness > 0) && (maxReflect > 0))
- {
- GVector3 r = result.normal*(-2 * result.normal.dotMul(ray.getDirection()))+ray.getDirection();
- CRay ray = CRay(result.position, r);
- Color reflectedColor = rayTraceRecursive(scene, ray, maxReflect - 1);
- color = color.add(reflectedColor.multiply(reflectiveness));
- }
- return color;
- }else return Color::black();
- }
渲染一下
- void renderRecursive()
- {
- glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
- glLoadIdentity(); // Reset The View
- glTranslatef(-0.5f,-0.5f,-1.0f);
- glPointSize(2.0);
- PerspectiveCamera camera( GVector3(0, 10, 10),GVector3(0, -0.5, -1),GVector3(0, 1, 0), 90);
- Plane* plane1=new Plane(GVector3(0, 1, 0),1.0);
- CSphere* sphere1=new CSphere(GVector3(-2, 5, -2), 4.0);
- CSphere* sphere2=new CSphere(GVector3(5, 5, -7), 3.0);
- plane1->material=new CheckerMaterial(0.1f,0.5f);
- sphere1->material=new PhongMaterial(Color::red(), Color::white(), 16,0.25);
- sphere2->material=new PhongMaterial(Color::green(), Color::white(), 16,0.25);
- Union scene;
- scene.push(plane1);
- scene.push(sphere1);
- scene.push(sphere2);
- long maxDepth=20;
- long maxReflect=5;
- float dx=1.0f/WINDOW_WIDTH;
- float dy=1.0f/WINDOW_HEIGHT;
- float dD=255.0f/maxDepth;
- glBegin(GL_POINTS);
- for (long y = 0; y < WINDOW_HEIGHT; ++y)
- {
- float sy = 1 - dy*y;
- for (long x = 0; x < WINDOW_WIDTH; ++x)
- {
- float sx =dx*x;
- CRay ray(camera.generateRay(sx, sy));
- IntersectResult result = scene.isIntersected(ray);
- //IntersectResult result = plane1.isIntersected(ray);
- if (result.isHit)
- {
- Color color = rayTraceRecursive(&scene, ray, maxReflect);
- //Color color = result.object->material->sample(ray, result.position, result.normal);
- //Color color =plane1.material->sample(ray, result.position, result.normal);
- color.saturate();
- //color.show();
- glColor3ub(color.r*255,color.g*255,color.b*255);
- glVertex2f(sx,sy);
- }
- }
- }
- glEnd();
- // 交换缓冲区
- glfwSwapBuffers();
- }
结果就是最上面的那幅图了。
结语
以前自己只玩过一些opengl的东西,不过那些都有现成的接口让你掉,原理上也不用理解得很深,往往一两句语句就可以实现一个简单的效果。
而现在,从原理到实现,每一句代码都需要先在真实世界中想清楚,然后抽象成代码,不管对编程技巧还是数学功底都会有很高的要求,所以在编写这些代码的时候我又回头去看C++ primer,学线性代数。。当然收获也很大。
源码可以点这里下载。
参考
光线追踪技术的理论和实践(面向对象)-http://blog.csdn.net/zhangci226/article/details/5664313
计算机图形学(第三版)(美)赫恩 著,(美)巴克 著。