光线追踪(RayTracing)算法理论与实践(一)入门

提要

本文先介绍光线追踪的理论,然后着重一步一步来搭建渲染场景,从最基本的向量类开始.采用的语言是c++,利用面向对象的思想,一些基础的线性代数和空间几何的知识也会用到,编程的框架用的是GLFW,渲染用到的是OpenGL。


原理

光线追踪,简单地说,就是从摄影机的位置,通过影像平面上的像素位置(比较正确的说法是取样(sampling)位置),发射一束光线到场景,求光线和几何图形间最近的交点,再求该交点的著色。如果该交点的材质是反射性的,可以在该交点向反射方向继续追踪。光线追踪除了容易支持一些全局光照效果外,亦不局限于三角形作为几何图形的单位。任何几何图形,能与一束光线计算交点(intersection point),就能支持。

示意图如下:


光线追踪有一些很棒的特性,比如:能够生成高度真实感的图形,特别是对于表面光滑的对象,缺点是所需的计算量大的惊人.

原理其实非常的简单,但具体实现起来的时候会有很多细节的地方.


代码实现

向量类

可以表示空上的点(x,y,z).

gvector3.h

[cpp]   view plain  copy
  1. <span style="font-size:14px;">#ifndef GVECTOR3_H  
  2. #define GVECTOR3_H  
  3. #include <iostream>  
  4. #include <cmath>  
  5. #define MIN(x,y) (x)>(y)?(y):(x);  
  6. #define MAX(x,y) (x)>(y)?(x):(y);  
  7. using namespace std;  
  8.   
  9.   
  10. class GVector3  
  11. {  
  12.   
  13. public:  
  14.     float x;  
  15.     float y;  
  16.     float z;  
  17.      // 缺省构造函数  
  18.     GVector3();  
  19.     ~GVector3();  
  20.      // 用户构造函数  
  21.     GVector3(float posX, float posY, float posZ);  
  22.     //输出向量信息  
  23.     void getInfo();  
  24.     //矢量加法  
  25.     GVector3 operator+(GVector3 v);  
  26.     //矢量减法  
  27.     GVector3 operator-(GVector3 v);  
  28.     //数乘  
  29.     GVector3 operator*(float n);  
  30.     //数除  
  31.     GVector3 operator/(float n);  
  32.     //向量点积  
  33.     float dotMul(GVector3 v2);  
  34.     //向量叉乘  
  35.     GVector3 crossMul(GVector3 v2);  
  36.     //绝对值化  
  37.     GVector3 abs();  
  38.     //获取分量中的最大值  
  39.     float max();  
  40.     //获取分量的最小值  
  41.     float min();  
  42.     //获取矢量长度  
  43.     float getLength();  
  44.     //向量单位化  
  45.     GVector3 normalize();  
  46.     //求两点之间的距离  
  47.     float getDist(GVector3 v);  
  48.     //返回零向量  
  49.     static inline GVector3 zero(){ return GVector3(0,0,0); }  
  50.     //打印向量的分量值  
  51.     void show();  
  52.   
  53. };  
  54.   
  55. #endif // GVECTOR3_H  
  56. </span>  

gvector3.cpp

[cpp]   view plain  copy
  1. #include "gvector3.h"  
  2.   
  3. GVector3::GVector3()  
  4. {  
  5. }  
  6. GVector3::~GVector3()  
  7. {  
  8. }  
  9. GVector3::GVector3(float posX, float posY, float posZ)  
  10. {  
  11.     x=posX;  
  12.     y=posY;  
  13.     z=posZ;  
  14. }  
  15.   
  16. GVector3 GVector3::operator+(GVector3 v)  
  17. {  
  18.     return GVector3(x+v.x,v.y+y,v.z+z);  
  19. }  
  20. GVector3 GVector3::operator-(GVector3 v)  
  21. {  
  22.     return GVector3(x-v.x,y-v.y,z-v.z);  
  23. }  
  24. GVector3 GVector3::operator*(float n)  
  25. {  
  26.     return GVector3(x*n,y*n,z*n);  
  27. }  
  28. GVector3 GVector3::operator/(float n)  
  29. {  
  30.     return GVector3(x/n,y/n,z/n);  
  31. }  
  32. void GVector3::getInfo()  
  33. {  
  34.     cout<<"x:"<<x<<" y:"<<y<<" z:"<<z<<endl;  
  35. }  
  36. GVector3 GVector3::abs()  
  37. {  
  38.     if(x<0) x*=-1;  
  39.     if(y<0) y*=-1;  
  40.     if(z<0) z*=-1;  
  41.     return GVector3(x,y,z);  
  42. }  
  43. float GVector3::dotMul(GVector3 v2)  
  44. {  
  45.     return (x*v2.x+y*v2.y+z*v2.z);  
  46. }  
  47. GVector3 GVector3::crossMul(GVector3 v2)  
  48. {  
  49.  GVector3 vNormal;  
  50.   // 计算垂直矢量  
  51.   vNormal.x = ((y * v2.z) - (z * v2.y));  
  52.   vNormal.y = ((z * v2.x) - (x * v2.z));  
  53.   vNormal.z = ((x * v2.y) - (y * v2.x));  
  54.   return vNormal;  
  55. }  
  56. float GVector3::getLength()  
  57. {  
  58.     return  (float)sqrt(x*x+y*y+z*z);  
  59. }  
  60. GVector3 GVector3::normalize()  
  61. {  
  62.     float length=getLength();  
  63.     x=x/length;  
  64.     y=y/length;  
  65.     z=z/length;  
  66.     return GVector3(x,y,z);  
  67. }  
  68. void GVector3::show()  
  69. {  
  70.     cout<<"x:"<<x<<"  y:"<<y<<"  z"<<z<<endl;  
  71. }  
  72. float GVector3::max()  
  73. {  
  74.     float tmp=MAX(y,z);  
  75.     return MAX(x,tmp);  
  76. }  
  77. float GVector3::min()  
  78. {  
  79.     float tmp=MIN(y,z);  
  80.     return MIN(x,tmp);  
  81. }  
  82. float GVector3::getDist(GVector3 v)  
  83. {  
  84.     float tmp=(x-v.x)*(x-v.x)+(y-v.y)*(y-v.y)+(z-v.z)*(z-v.z);  
  85.     return sqrt(tmp);  
  86. }  

着重解释一下向量的点乘和叉乘。

点乘,也叫向量的内积、数量积。顾名思义,求下来的结果是一个数。 
在物理学中,已知力与位移求功,实际上就是求向量F与向量s的内积,即要用点乘。 
叉乘,也叫向量的外积、向量积。顾名思义,求下来的结果是一个向量,记这个向量为c。 
向量c的方向与a,b所在的平面垂直,且方向要用“右手法则”判断(用右手的四指先表示向量a的方向,然后手指朝着手心的方向摆动到向量b的方向,大拇指所指的方向就是向量c的方向)。
在物理学中,已知力与力臂求力矩,就是向量的外积,即叉乘。 
数值上的计算,将向量用坐标表示(三维向量), 
若向量a=(a1,b1,c1),向量b=(a2,b2,c2), 
则 
向量a·向量b=a1a2+b1b2+c1c2 
向量a×向量b= 
| i j k| 
|a1 b1 c1| 
|a2 b2 c2| 
=(b1c2-b2c1,c1a2-a1c2,a1b2-a2b1) 
(i、j、k分别为空间中相互垂直的三条坐标轴的单位向量)。
 
 

光线类

首先来看一下光线的表示方法。
 
 
 
 
当t=0时,p=e, 当t=1时,p=s.0<t1<t2则t1更接近眼睛。
这个结论在求交的时候会用到。
有两个私有成员,原点和方向,数学上可用参数函数来表示:r(t)=o+td;t>=0cray.h
 
 
[cpp]   view plain  copy
  1. <span style="font-size:14px;">#ifndef CRAY_H  
  2. #define CRAY_H  
  3. #include <iostream>  
  4. #include "gvector3.h"  
  5. #define PI 3.14159  
  6. using namespace std;  
  7. class CRay  
  8. {  
  9.     private:  
  10.     GVector3 origin;  
  11.     GVector3 direction;  
  12.     public:  
  13.     CRay();  
  14.     CRay(GVector3 o,GVector3 d);  
  15.     ~CRay();  
  16.     void setOrigin(GVector3 o);  
  17.     void setDirection(GVector3 d);  
  18.     GVector3 getOrigin();  
  19.     GVector3 getDirection();  
  20.     //通过向射线的参数方程传入参数t而获得在射线上的点  
  21.     GVector3 getPoint(double t);  
  22.   
  23. };  
  24. #endif  
  25. </span>  
cray.cpp
 
 
[cpp]   view plain  copy
  1. <span style="font-size:14px;">#include "cray.h"  
  2. CRay::CRay()  
  3. {  
  4. }  
  5. CRay::~CRay()  
  6. {  
  7. }  
  8. CRay::CRay(GVector3 o,GVector3 d)  
  9. {  
  10.     origin=o;  
  11.     direction=d;  
  12. }  
  13. void CRay::setDirection(GVector3 d)  
  14. {  
  15.     direction=d;  
  16. }  
  17. void CRay::setOrigin(GVector3 o)  
  18. {  
  19.     origin=o;  
  20. }  
  21. GVector3 CRay::getDirection()  
  22. {  
  23.     return direction;  
  24. }  
  25. GVector3 CRay::getOrigin()  
  26. {  
  27.     return origin;  
  28. }  
  29. GVector3 CRay::getPoint(double t)  
  30. {  
  31.     return origin+direction*t;  
  32. }  
  33. </span>  


初试画板

这里用GLFW作为opengl的编程框架。

GLFW是一个自由,开源,多平台的图形库,可用于创建窗口,渲染OpenGL,管理输入。

GLFW的配置见《 GLFW入门学习》.这里主要要做的就是将我们窗口映射成像素的点阵,然后填充颜色。

[cpp]   view plain  copy
  1. <span style="font-size:14px;">#include <GL/glfw.h>  
  2. #include <stdlib.h>  
  3. #include<stdio.h>  
  4. #define WINDOW_WIDTH  600  
  5. #define WINDOW_HEIGHT 600  
  6. void initScene(int w,int h)  
  7. {  
  8.     // 启用阴影平滑  
  9.     glShadeModel( GL_SMOOTH );  
  10.     // 黑色背景  
  11.     glClearColor( 0.0, 0.0, 0.0, 0.0 );  
  12.     // 设置深度缓存  
  13.     glClearDepth( 1.0 );  
  14.     // 启用深度测试  
  15.     glEnable( GL_DEPTH_TEST );  
  16.     // 所作深度测试的类型  
  17.     glDepthFunc( GL_LEQUAL );  
  18.     // 告诉系统对透视进行修正  
  19.     glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST );  
  20. }  
  21. //这里进行所有的绘图工作  
  22. void  drawScene() {  
  23. float colorSpan=0.0005f;  
  24.     float color=0.0f;  
  25.     float pixelSize=2.0f;  
  26.     float posY=-1.0f;  
  27.     float posX=-1.0f;  
  28.     long maxDepth=20;  
  29.     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);  
  30.     glLoadIdentity();  
  31.     //将原点移动到左下角  
  32.     glTranslatef(-0.5f,-0.5f,-1.0f);  
  33.     glPointSize(2.0);  
  34.     glBegin(GL_POINTS);  
  35.     double dx=1.0f/WINDOW_WIDTH;  
  36.     double dy=1.0f/WINDOW_HEIGHT;  
  37.     float dD=255.0f/maxDepth;  
  38.     glBegin(GL_POINTS);  
  39.   
  40.     for (long y = 0; y < WINDOW_HEIGHT; ++y)  
  41.     {  
  42.         double sy = 1-dy*y;  
  43.         for (long x = 0; x < WINDOW_WIDTH; ++x)  
  44.         {  
  45.             double sx =dx*x;  
  46.             float colorR=x*1.0/WINDOW_WIDTH*255;  
  47.             float colorB=y*1.0/WINDOW_HEIGHT*255;  
  48.            glColor3ub(colorR,0,colorB);  
  49.            glVertex2f(sx,sy);  
  50.         }  
  51.     }  
  52.     // 交换缓冲区  
  53.     glfwSwapBuffers();  
  54. }  
  55. //重置窗口大小后的回调函数  
  56. void GLFWCALL resizeGL(int width, int height )  
  57. {  
  58.     // 防止窗口大小变为0  
  59.     if ( height == 0 )  
  60.     {  
  61.         height = 1;  
  62.     }  
  63.     // 重置当前的视口  
  64.     glViewport( 0, 0, (GLint)width, (GLint)height );  
  65.     // 选择投影矩阵  
  66.     glMatrixMode( GL_PROJECTION );  
  67.     // 重置投影矩阵  
  68.     glLoadIdentity();  
  69.     // 设置视口的大小  
  70.     gluPerspective( 45.0, (GLfloat)width/(GLfloat)height, 0.1, 100.0 );  
  71.     // 选择模型观察矩阵  
  72.     glMatrixMode( GL_MODELVIEW );  
  73.     glLoadIdentity();  
  74. }  
  75.   
  76. int main( void )  
  77. {  
  78.     //记录程序运行状态  
  79.     int running = GL_TRUE;  
  80.     //初始化 GLFW  
  81.     if( !glfwInit() )  
  82.     {  
  83.         exit( EXIT_FAILURE );  
  84.     }  
  85.     // 创建一个OpenGL 窗口  
  86.     if( !glfwOpenWindow( WINDOW_WIDTH,WINDOW_HEIGHT,6,6,6,0,32,0,GLFW_WINDOW) )  
  87.     {  
  88.         glfwTerminate();  
  89.         exit( EXIT_FAILURE );  
  90.     }  
  91.     //初始化OpenGL窗口  
  92.     initScene(WINDOW_WIDTH, WINDOW_HEIGHT);  
  93.     //设置窗口大小发生变化时的回调函数  
  94.     glfwSetWindowSizeCallback(resizeGL);  
  95.     //主循环  
  96.     while( running )  
  97.     {  
  98.         // OpenGL rendering goes here...  
  99.         glClear( GL_COLOR_BUFFER_BIT );  
  100.         // 当按下ESC键的时候触发  
  101.         running = !glfwGetKey( GLFW_KEY_ESC ) &&glfwGetWindowParam( GLFW_OPENED );  
  102.     drawScene();  
  103.         //延时0.05秒  
  104.     glfwSleep(0.05 );  
  105.     }  
  106.     glfwTerminate();  
  107.     //退出程序  
  108.     exit( EXIT_SUCCESS );  
  109. }  
  110. </span>  

渲染结果:


球体

球面的几何定义是到空间某点距离一定的点的集合。

这里着重需要说明的是球体的isIntersected函数,用于求光线是否与球体相交,并将结果存在result中返回。

中心点为c、半径为r的球体表面可用等式(equation)表示:||x-c||=r

只要把x=r(t)带入,求解即可得交点。具体的运算过程如下(令v=o-c)。



若根号内为负数,即相交不发生。另外,由于这里只需要取最近的交点,因此正负号只需取负号。

而球体在交点的法向量可表示为n=(p
-
c),单位法向量为(p -
c)/R


所以空间球体可以用两个参数来表示:圆心位置和半径。

代码如下。

csphere.h

[cpp]   view plain  copy
  1. <span style="font-size:14px;">#ifndef CSPHERE_H  
  2. #define CSPHERE_H  
  3. #include "gvector3.h"  
  4. #include "intersectresult.h"  
  5. #include "cray.h"  
  6. class CSphere  
  7. {  
  8.     public:  
  9.         CSphere();  
  10.         CSphere(GVector3 center,double radius);  
  11.         CSphere(CSphere& s);  
  12.         void setCenter(GVector3& c);  
  13.         void setRadius(double r);  
  14.         GVector3 getCenter();  
  15.         double getRadius();  
  16.         //获取物体表面一点的法线  
  17.          virtual GVector3 getNormal(GVector3 point);  
  18.         //用于判断射线和该物体的交点  
  19.         virtual IntersectResult isIntersected(CRay RAY);  
  20.         virtual ~CSphere();  
  21.     protected:  
  22.     private:  
  23.         GVector3 center;  
  24.         double radius;  
  25. };  
  26.   
  27. #endif // CSPHERE_H  
  28. </span>  

csphere.cpp

[cpp]   view plain  copy
  1. <span style="font-size:14px;">#include "csphere.h"  
  2. #include "intersectresult.h"  
  3. CSphere::CSphere()  
  4. {  
  5.     //ctor  
  6. }  
  7. CSphere::CSphere(GVector3 c,double r)  
  8. {  
  9.     center=c;  
  10.     radius=r;  
  11. }  
  12. CSphere::CSphere(CSphere& s)  
  13. {  
  14.     center=s.getCenter();  
  15.     radius=s.getRadius();  
  16. }  
  17. CSphere::~CSphere()  
  18. {  
  19.     //dtor  
  20. }  
  21. void CSphere::setCenter(GVector3& c)  
  22. {  
  23.     center=c;  
  24. }  
  25. void CSphere::setRadius(double r)  
  26. {  
  27.     radius=r;  
  28. }  
  29. GVector3 CSphere::getCenter()  
  30. {  
  31.     return center;  
  32. }  
  33. double CSphere::getRadius()  
  34. {  
  35.     return radius;  
  36. }  
  37. GVector3 CSphere::getNormal(GVector3 p)  
  38. {  
  39.     return p-center;  
  40. }  
  41. IntersectResult CSphere::isIntersected(CRay _ray)  
  42. {  
  43.       IntersectResult result = IntersectResult::noHit();  
  44.       GVector3 v = _ray.getOrigin() - center;  
  45.         float a0 = v.dotMul(v) - radius*radius;  
  46.         float DdotV = _ray.getDirection().dotMul(v);  
  47.   
  48.   
  49.         if (DdotV <= 0) {  
  50.             float discr = DdotV * DdotV - a0;  
  51.             if (discr >= 0) {  
  52.                 //  
  53.                 result.isHit=1;  
  54.                 result.distance=-DdotV - sqrt(discr);  
  55.                 result.position=_ray.getPoint(result.distance);  
  56.                 result.normal = result.position-center;  
  57.                 result.normal.normalize();  
  58.             }  
  59.         }  
  60. <span style="white-space:pre;">     </span>return result;  
  61. }  
  62. </span>  


还有一个结构体,用来表示体和光线相交的结果。

[cpp]   view plain  copy
  1. <span style="font-size:14px;">#ifndef INTERSECTRESULT_H_INCLUDED  
  2. #define INTERSECTRESULT_H_INCLUDED  
  3. #include "gvector3.h"  
  4. struct IntersectResult{  
  5.     float distance;  
  6.     bool isHit;  
  7.     GVector3 position;  
  8.     GVector3 normal;  
  9.     static inline IntersectResult noHit() { return IntersectResult(); }  
  10. };  
  11.   
  12. #endif // INTERSECTRESULT_H_INCLUDED  
  13. </span>  


摄像机

摄影机在光线追踪系统里,负责把影像的取样位置,生成一束光线。

由于影像的大小是可变的(多少像素宽x多少像素高),为方便计算,这里设定一个统一的取样座标(sx, sy),以左下角为(0,0),右上角为(1 ,1)。

从数学角度来说,摄影机透过投影(projection),把三维空间投射到二维空间上。常见的投影有正投影(orthographic projection)、透视投影(perspective projection)等等。这里首先实现透视投影。

透视摄影机比较像肉眼和真实摄影机的原理,能表现远小近大的观察方式。透视投影从视点(view point/eye position),向某个方向观察场景,观察的角度范围称为视野(field of view, FOV)。除了定义观察的向前(forward)是那个方向,还需要定义在影像平面中,何谓上下和左右。为简单起见,暂时不考虑宽高不同的影像,FOV同时代表水平和垂直方向的视野角度。


上图显示,从摄影机上方显示的几个参数。 forward和right分别是向前和向右的单位向量。

因为视点是固定的,光线的起点不变。要生成光线,只须用取样座标(sx, sy)计算其方向d。留意FOV和s的关系为:tan(fov/2).

把sx从[0, 1]映射到[-1,1],就可以用right向量和s,来计算r向量。

代码实现:

perspectiveCamera.h

[cpp]   view plain  copy
  1. #ifndef PERSPECTIVECAMERA_H  
  2. #define PERSPECTIVECAMERA_H  
  3. #include "cray.h"  
  4. class perspectiveCamera{  
  5.     public:  
  6.     perspectiveCamera();  
  7.     ~perspectiveCamera();  
  8.     perspectiveCamera(const GVector3& _eye,const GVector3& _front,const GVector3& _refUp,float _fov);  
  9.     CRay generateRay(float x,float y);  
  10.   
  11.     private:  
  12.     GVector3 eye;  
  13.     GVector3 front;  
  14.     GVector3 refUp;  
  15.     float   fov;  
  16.     GVector3 right;  
  17.     GVector3 up;  
  18.     float   fovScale;  
  19. };  
  20. #endif  

perspectiveCamera.cpp

[cpp]   view plain  copy
  1. #include"perspectiveCamera.h"  
  2.   
  3. perspectiveCamera::perspectiveCamera()  
  4. {  
  5.   
  6. }  
  7. perspectiveCamera::~perspectiveCamera()  
  8. {  
  9. }  
  10. perspectiveCamera::perspectiveCamera(const GVector3& _eye,const GVector3& _front,const GVector3& _refUp,float _fov)  
  11. {  
  12.     eye=_eye;  
  13.     front=_front;  
  14.     refUp=_refUp;  
  15.     fov=_fov;  
  16.     right=front.crossMul(refUp);  
  17.     up = right.crossMul(front);  
  18.     fovScale = tan(fov* (PI  * 0.5f / 180)) * 2;  
  19. }  
  20. CRay perspectiveCamera::generateRay(float x,float y)  
  21. {  
  22.     GVector3 r = right*((x - 0.5f) * fovScale);  
  23.     GVector3 u = up*((y - 0.5f) * fovScale);  
  24.     GVector3 tmp=front+r+u;  
  25.     tmp.normalize();  
  26.     return CRay(eye,tmp);  
  27. }  


渲染一个球

基本的工具类都准备好之后,我们就可以开始渲染一些东西,这里先把之前定义的球体渲染到窗口之中。

基本的做法是遍历影像的取样座标(sx, sy),用Camera把(sx, sy)转为Ray3,和场景(例如Sphere)计算最近交点,把该交点的属性转为颜色,写入影像的相对位置里。

首先我们来渲染深度,深度(depth)就是从IntersectResult取得最近相交点的距离,因深度的范围是从零至无限,为了把它显示出来,可以把它的一个区间映射到灰阶。这里用[0, maxDepth]映射至[255, 0],即深度0的像素为白色,深度达maxDepth的像素为黑色。

代码实现:

[cpp]   view plain  copy
  1. void renderDepth()  
  2. {  
  3.     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);  
  4.     glLoadIdentity();                                   // Reset The View  
  5.     glTranslatef(-0.5f,-0.5f,-1.0f);  
  6.     glPointSize(2.0);  
  7.     float horiz=0.0;  
  8.     float dep=10;  
  9.     PerspectiveCamera camera( GVector3(horiz, 10, dep),GVector3(0, 0, -1),GVector3(0, 1, 0), 90);  
  10.     long maxDepth=18;  
  11.     CSphere* sphere1 = new CSphere(GVector3(0, 10, -10), 10.0);  
  12.     float dx=1.0f/WINDOW_WIDTH;  
  13.     float dy=1.0f/WINDOW_HEIGHT;  
  14.     float dD=255.0f/maxDepth;  
  15.     glBegin(GL_POINTS);  
  16.     for (long y = 0; y < WINDOW_HEIGHT; ++y)  
  17.     {  
  18.         float sy = 1 - dy*y;  
  19.         for (long x = 0; x < WINDOW_WIDTH; ++x)  
  20.         {  
  21.             float sx =dx*x;  
  22.             CRay ray(camera.generateRay(sx, sy));  
  23.             IntersectResult result = sphere1->isIntersected(ray);  
  24.             if (result.isHit)  
  25.             {  
  26.                 double t=MIN(result.distance*dD,255.0f);  
  27.                 int depth = (int)(255 -t);  
  28.                 glColor3ub(depth,depth,depth);  
  29.                 glVertex2f(sx,sy);  
  30.             }  
  31.   
  32.         }  
  33.     }  
  34.   
  35.     glEnd();  
  36.     // 交换缓冲区  
  37.     glfwSwapBuffers();  
  38. }  

渲染结果



接下来我们来渲染一下交点法向量,法向量是一个单位向量,在计算交点的时候们就将其存储在result.normal中了,其每个元素的范围是[-1, 1]。把单位向量映射到颜色的常用方法为,把(x, y, z)映射至(r, g, b),范围从[-1, 1]映射至[0,
255]。

代码实现:

[cpp]   view plain  copy
  1. void renderDepth()  
  2. {  
  3.     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);  
  4.     glLoadIdentity();                                   // Reset The View  
  5.     glTranslatef(-0.5f,-0.5f,-1.0f);  
  6.     glPointSize(2.0);  
  7.     PerspectiveCamera camera( GVector3(0, 10, 10),GVector3(0, 0, -1),GVector3(0, 1, 0), 90);  
  8.     long maxDepth=20;  
  9.     CSphere* sphere1 = new CSphere(GVector3(0, 10, -10), 10.0);  
  10.     camera.initialize();  
  11.     float dx=1.0f/WINDOW_WIDTH;  
  12.     float dy=1.0f/WINDOW_HEIGHT;  
  13.     float dD=255.0f/maxDepth;  
  14.     glBegin(GL_POINTS);  
  15.     for (long y = 0; y < WINDOW_HEIGHT; ++y)  
  16.     {  
  17.         float sy = 1 - dy*y;  
  18.         for (long x = 0; x < WINDOW_WIDTH; ++x)  
  19.         {  
  20.             float sx =dx*x;  
  21.             CRay ray(camera.generateRay(sx, sy));  
  22.             IntersectResult result = sphere1->isIntersected(ray);  
  23.             if (result.isHit)  
  24.             {  
  25.                 //double t=MIN(result.distance*dD,255.0f);  
  26.                 //int depth = (int)(255 -t);  
  27.                 //xuanranshengdu  
  28.                 //glColor3ub(depth,depth,depth);  
  29.                 //xuanran normal  
  30.                 glColor3ub(128*(result.normal.x+1),128*(result.normal.y+1),128*(result.normal.z+1));  
  31.                 glVertex2f(sx,sy);  
  32.             }  
  33.         }  
  34.     }  
  35.     glEnd();  
  36.     // 交换缓冲区  
  37.     glfwSwapBuffers();  
  38. }  

渲染结果:


球体上方的法向量是接近(0, 1, 0),所以是浅绿色(0.5, 1, 0.5)。

结语
入门篇就先到这里,通过一步步搭建我们的场景,对光线追踪有了一个基础的理解,
接下来我们会一步步深入一些高级的主题,比如材质,光照,雾...

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


猜你喜欢

转载自blog.csdn.net/szt292069892/article/details/80720331