经过上次的学习,我们已经可以建立一个简单的光线追踪的场景,接下来,我们继续我们的征程。
今天要得到的最终效果如下:
在光线追踪算法中,最重要的就是求光线与物体的相交,也就是实现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三点来表示一个三角形。
则三角平面内的任意一点可以表示为
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
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
平面类我们就可以用代码这样来描述:
#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
代码实现如下:
#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;
}
}
在真实世界中,白色物体在绿光照射下看起来是绿色而不是白色,红色物体在绿光照射下看起来是黑色,而有的同样颜色的物体在同样的光照下亮度却不同,这都是由物体的材质不同造成的。
首先在项目中添加一个颜色类,然后定义一些方法。
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
#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
#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(); }
#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
#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
#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,学线性代数。。当然收获也很大。
源码可以点这里下载。