光线追踪(RayTracing)算法理论与实践(二)平面、材质、联合光线与物体求交

提要

经过上次的学习,我们已经可以建立一个简单的光线追踪的场景,接下来,我们继续我们的征程。

今天要得到的最终效果如下:



光线与物体求交

在光线追踪算法中,最重要的就是求光线与物体的相交,也就是实现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三点来表示一个三角形。


则三角平面内的任意一点可以表示为




法线向量表示为

将光线向量= e + td带入,得到:

当B>0,r>0且B+r<1的时候,P在三角形内。
对方程进行变换并改写成矩阵的形式:




由克莱莫法则求得B,r,t



其中
 

则三角形和光线求交的伪代码可以表示为:

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

平面多边形
已知条件是平面的m个顶点(p1...pm)还有平面的法向量n

平面内的向量和法向量时垂直的,即点乘结果为0,可以且接方程:

                         (p − p1 ) · n = 0

其中p e + td,解得:

这样我们就可以 求得p点,如果p在平面内,则光线与平面相交,否则不相交。
但是如何知道p是否在平面内呢?
siggragh course上的解决方法是:从相交点往平面内的任意方向做一条射线,如果射线与多边形的边的交点数量为奇数则p在平面内,否则在平面外。
这个很好理解,用笔画一下 就知道了。

一个场景
 一个场景包含很多个图元,球体,正方体。。。
 回到光线追踪的原理,从摄像机发射一条射线之后,当场景中只有单个物体,只需计算一个,就可以得到一个result,当场景中又多个物体的时候,我们需要做的就是计算多次,取距离最近的那个result。
伪代码
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

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,学线性代数。。当然收获也很大。

源码可以点这里下载。

参考

用JavaScript玩转计算机图形学(一)光线追踪入门-http://www.cnblogs.com/miloyip/archive/2010/03/29/1698953.html
光线追踪技术的理论和实践(面向对象)-http://blog.csdn.net/zhangci226/article/details/5664313
Wikipedia, Ray Tracing
计算机图形学(第三版)(美)赫恩 著,(美)巴克 著。
Ray Object Intersections -  http://siggraph.org/education/materials/HyperGraph/raytrace/rtinter0.htm


  • 3
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值