OpenGL---二维光线追踪

代码、原理:计算机图形学(OpenGL)第三版 第4章
我在其中加上了注释

OpenGL---二维光线追踪_第1张图片

流程

需要用户从文件中读取多边形数据

1、由用户画出第一条射线,需调用函数raytrace2d_mouse,raytrace2d_motion。

2、函数raytrace2d_keyboard中,当按下空格键时,针对每个多边形调用函数timeToHitPoly,计算射线击中该多边形的时间,取最小的那个时间,根据该时间得到射线的可见部分,放入射线列表rays中。接着计算下一条射线,即反射射线:记录反射射线的起点,计算反射射线的方向向量。

3、函数raytrace2d_render绘制各个多边形,并绘制保存在rays中的射线。

timeToHitPoly:针对当前多边形的所有边,调用LipInterval,将射线与每条相交的时间保存在hitTimes中,并不断更新射线留在多边中的时间[t_in, t_out]。判断当前多边形是最外围的多边形,还是里边的小多边形,如果是前者,则击中多边形的时间hit_t = t_out;否则,hit_t = t_in。根据hit_t与hitTimes中的元素比较,得出多边形的哪条边被击中了。

LipInterval:根据射线与当前边的法向量的夹角,得到射线是射入多边形,还是射出多边形,并求出与当前边相交的时间t_hit。如果是射入多边形,t_in = max(t_in, t_hit);否则,t_out = min(t_out, t_hit)

—————————————-原理———————————–

反射
OpenGL---二维光线追踪_第2张图片
这里写图片描述
这里写图片描述

直线的参数形式
OpenGL---二维光线追踪_第3张图片
这里写图片描述

射线与直线或平面的交点
P为交点
OpenGL---二维光线追踪_第4张图片
这里写图片描述
这里写图片描述
OpenGL---二维光线追踪_第5张图片
这里写图片描述

判断射线是射向物体,还是射出物体
OpenGL---二维光线追踪_第6张图片
这里写图片描述
则对于多边形P:(n是直线的外法向量)
这里写图片描述

射线与凸多边形的交点以及裁剪问题
OpenGL---二维光线追踪_第7张图片
进入是的击中点:A + c * t_in
出去是的击中点:A + c * t_out
射线在多边形中的部分对应的t在区间[t_in, t_out]中。

裁剪算法如下:
将[t_in, t_out]看成候选区间。
1、 初始化候选区间[0, 无穷]
2、对每一条直线,计算击中时间t_hit,并判断这一击中是射入还是射出:

  • 如果是射入,t_in = max(t_in, t_hit);
  • 如果是射出,t_out = min(t_out, t_hit)

如果t_in大于t_out,说明射线与多边形完全不相交,结束检测。
3、检测完多边形的所有边后,如果候选区间非空,那么从A + c * t_in
到A + c * t_out是在多边形内的。

裁剪例子:
OpenGL---二维光线追踪_第8张图片


RayTrace2D.cpp

#include 
#include 
#include 
#include 
#include 

#include "GlutWin.h"
#include "Line2.h"
#include "Vector2.h"

using namespace std;

// globals //////////////////////////////////////////////////////////
Vector2                     C, S,       // ray vars p(t) = C + tS
                            EndPt;      // for displaying
bool                        showNormals;// draw line norms or not
int                         numPoints;  // number of points placed by user
vector< vector >     polys;      // line lists
vector               rays;       // list of rays we already traced
GlutWin *                   win;

// loadpolys ////////////////////////////////////////////////////////
bool loadPolys( const char * fileName ) 
{

    /* Expected file format:
     * 
     * x0 y0
     * x1 y1
     * x2 y2
     * .
     * x3 y3
     * etc...
     *
     * where . represents the end of a poly
     *
     * NOTE:
     * all polys must be wound CCW
     * this is to make normal generation less complicated
     */

    Vector2         first, curr, last;
    bool                newPoly = true;
    vector       v;
    Line2               currSeg;
    int                 n = 0;
    char c;
    ifstream            i( fileName );

    if( ! i.is_open() ) 
        return( false );

    while( ! i.eof() ) 
    {
        i >> c;

        if( c == '.' ) 
        {
            // end of a polygon
            newPoly = true;

            // set seg to last point and first one
            currSeg.setVectors( curr, first, LINE_SEGMENT );

            v.push_back( currSeg );

            // if less than 2 points in poly then error
            if( n < 1 ) 
            {
                cerr << "Bad Poly\n";
                i.close();
                return( false );
            }

            // put poly onto vector of polys
            polys.push_back( v );
            v.clear();

            // unset c (prevent bug out)
            c = 0;

            continue;

        } 
        else 
        {

            i.putback( c );

            if( newPoly ) 
            {
                // read first point
                i >> curr;
                first = last = curr;
                n = 0;
                newPoly = false;
            } 
            else 
            {
                last = curr;
                // read next point
                i >> curr;
                currSeg.setVectors( last, curr, LINE_SEGMENT );
                n++;

                // put on polys line list
                v.push_back( currSeg );
            }
        }   
    }   

    i.close();
    return( true );
}

// LipInterval /////////////////////////////////////////////////////////

//求出直线射入多边形的时间in,或射出多边形的时间out

//in:射线射入多边形的时间
//out:射线从多边形中射出的时间
//num:n * (B - A)  A为射入直线的起点,B为被射入直线上的点,n为被射入直线的法向量
//den:n * c        c为射入直线的方向向量
bool LipInterval( float & in, float & out, float num, float den ) 
{

    float hit = num / den;  //击中时间

    if( den < 0 )       //射线是射入的
    {
        if( hit > out ) return( false ); // 提前退出
        if( hit > in  ) in = hit;        // 选择较大的
    }
    else if( den > 0 )  //射线是射出的
    {
        if( hit < in  ) return( false ); // 提前退出
        if( hit < out ) out = hit;       // 选择较小的
    }
    //else if( num <= 0 ) return( false ); // 射线与直线平行

    return( true );

}

// timetohitpoly ////////////////////////////////////////////////////

//求射线击中多边形的时间

//nPolyInd:多边形的下标
//nLineInd:多边形中被击中边的下标
//hit:射线击中多边形的时间
bool timeToHitPoly( int nPolyInd, int & nLineInd, float & hit) 
{
    Vector2         t, p, n;
    float           num, den;
    float           out = 10000;
    float           in = 0.0f;
    bool            bHit = false;
    vector<float>   hitTimes;

    //计算射线与多边形每条边相交的时间
    for( int c = 0; c < polys[nPolyInd].size(); c++ ) 
    {
        polys[nPolyInd][c].getVectors( p, n, LINE_PTNORM );

        t = p - S;     //p为当前多边形中当前边上的点,S为射线的起点
        num = n * t;   //n为当前多边形中当前边的法向量
        den = n * C;   //C为射线的方向向量

        hitTimes.push_back( num / den );  //得到射线击中时间,并加入列表

        //得到射线与当前多边形中每条边相交的时间
        if( ! LipInterval( in, out, num, den ) )  //射线没有击中多边形
            return( false );

        if( num / den < 0 ) // ray goes backwards to hit line
            continue;

        bHit = true;
    }

    //得到多边形中哪条边被击中了

    // 多边形0是外围的最大多边形,包含了其他多边形
    if( nPolyInd == 0 ) 
    {
        if( out < 10000.0f ) 
        {
            hit = out;

            for( int c = 0; c < hitTimes.size(); c++ ) 
            {
                if( hitTimes[c] == hit ) 
                {
                    nLineInd = c;
                    break;
                }
            }
        }
    } 
    else 
    {
        // other polys would be entered by the ray
        if( in > 0.0f ) 
        {
            hit = in;

            for( int c = 0; c < hitTimes.size(); c++ ) 
            {
                if( hitTimes[c] == hit ) 
                {
                    nLineInd = c;
                    break;
                }
            }
        }
    }

    return( bHit );
}

// raytrace2d_render ///////////////////////////////////////////////
void raytrace2d_render() 
{
    glClear( GL_COLOR_BUFFER_BIT );
    glMatrixMode( GL_MODELVIEW );
    glLoadIdentity();
    glColor3f( 1.0f, 0.0f, 0.0f );

    Vector2 a, b, m, n;

    //画出各个多边形
    for( int x = 0; x < polys.size(); x++ ) 
    {
        glBegin( GL_LINES );

            for( int y = 0; y < polys[x].size(); y++ ) 
            {
                polys[x][y].getVectors( a, b, LINE_SEGMENT );
                polys[x][y].getVectors( m, n, LINE_PTNORM );

                glVertex2f( a.x, a.y );
                glVertex2f( b.x, b.y );

                //是否画出多边形上的法向量
                if( showNormals ) 
                {
                    glVertex2f( (a + (0.5f * (b - a))).x, (a + (0.5f * (b - a))).y );  
                    glVertex2f( ((a + (0.5f * (b - a)) + (10 * n))).x, ((a + (0.5f * (b - a)) + (10 * n))).y ); 
                }
            }

        glEnd();
    }

    glColor3f( 0.0f, 0.0f, 1.0f );

    glBegin( GL_LINES );

    //画射线
    if( numPoints == 0 )  //第一条射线已经绘制完
    {
        for( int c = 0; c < rays.size(); c++ ) 
        {
            rays[c].getVectors( a, b, LINE_SEGMENT );

            glVertex2f( a.x, a.y );
            glVertex2f( b.x, b.y );
        }
    } 
    else   //画第一条射线
    {
        glVertex2f( S.x, S.y );
        glVertex2f( EndPt.x, EndPt.y );
    }

    glEnd();

    glutSwapBuffers();
    glFlush();
}

// raytrace2d_keyboard //////////////////////////////////////////////
void raytrace2d_keyboard( unsigned char key, int x, int y ) 
{
    Vector2 a, b, norm;
    Line2       l;
    bool        h;
    float       t, f = 10000;
    int         nPolyHit, nFaceHit, n;

    //如果按下空格键
    if( key == ' ' && numPoints != 1 ) 
    {
        //求射线击中每个多边形的时间,取时间最小的那个
        for( int x = 0; x < polys.size(); x++ ) 
        {
            h = timeToHitPoly(x, n, t);

            cout << x << ":" << (h ? "true" : "false") << " " << t << endl;

            if( h && t < f ) 
            {
                f = t;   //射线击中多边形的时间
                nPolyHit = x;
                nFaceHit = n;
            }
        }

        cout << "=-=-==-=-=-=-=-=-=-=-=-=-=-=-=-" << endl;

        // 计算当前射线的可见部分(线段形式)
        a = S;            //射线起点
        b = S + (f * C);  //射线击中点,C为射线的方向向量
        l = Line2( a, b, LINE_SEGMENT );  //得到射线的可见部分

        // 放入rays中
        rays.push_back( l );

        // 计算反射射线的方向
        S   = b;          //下一条射线的起点
        polys[nPolyHit][nFaceHit].getVectors( a, norm, LINE_PTNORM );
        C = C - ( ( 2.0f * ( C * norm ) ) * norm );  //下一条射线的方向向量
    }

    if( key == 'n' )
        showNormals = !showNormals;

    glutPostRedisplay();
}

// raytrace2d_mouse /////////////////////////////////////////////////
void raytrace2d_mouse( int button, int state, int x, int y ) 
{
    //鼠标按下事件
    if( button == GLUT_LEFT_BUTTON && state == GLUT_DOWN ) 
    {
        numPoints++;

        if( numPoints == 1 ) 
        {
            // user is placing the starting point for the ray
            S = Vector2( x, (600 - y) );
            rays.clear();
            EndPt = Vector2( x, 600 - y );
        }

        if( numPoints == 2 ) 
        {
            // user is placing point deining rays direction
            C = Vector2( x, (600 - y) ) - S;
            if( C == Vector2( 0, 0 ) )
                C = Vector2( 1, 0 );
            numPoints = 0;
            raytrace2d_keyboard( ' ', 0, 0 ); // cheese tactics
        }
    }

    glutPostRedisplay();
}

// raytrace2d_motion ///////////////////////////////////////////////

//鼠标移动事件
void raytrace2d_motion( int x, int y ) 
{
    if( numPoints == 1 ) 
    {
        EndPt = Vector2( x, 600 - y );

        glutPostRedisplay();
    }
}

// main /////////////////////////////////////////////////////////////
int main( int argc, char **argv ) 
{
    // initialize GLUT class
    win = new GlutWin( 600, 800,
                       100, 100,
                       GLUT_DOUBLE | GLUT_RGB,
                       "2d ray tracing demo" );

    if( ! loadPolys( "polys.txt" ) )
        cerr << "unable to open file: polys.txt" << endl;

    C = Vector2( 10, 0 );
    S = Vector2( 150, 300 );

    showNormals = false;
    numPoints = 0;

    glutDisplayFunc( raytrace2d_render );
    glutKeyboardFunc( raytrace2d_keyboard );
    glutMouseFunc( raytrace2d_mouse );
    glutPassiveMotionFunc( raytrace2d_motion );

    // start rendering loop
    glutMainLoop();

    delete win;

    return( 0 );
}

Line2.h

#ifndef _LINE2_H_
#define _LINE2_H_

#include "Vector2.h"

#include 
using namespace std;

enum LineType 
{
    LINE_SEGMENT, 
    LINE_PTNORM,
    LINE_PARAM
};

class Line2 
{
public:

    Line2( const Vector2 & a, const Vector2 & b, LineType t );  // totally utility
    Line2( float x1, float y1, float x2, float y2 );            // nasty line seg
    Line2(){};

    bool getVectors( Vector2 & a, Vector2 & b, LineType t ) const;
    void setVectors( const Vector2 & a, const Vector2 & b, LineType t );

    friend ostream & operator << ( ostream & o, Line2 & l )
    {
        o << l.A << " " << l.B;
        return( o );
    }


protected:

    Vector2 C, S,   // param form,p(t) = S + Ct,直线的参数形式 
            A, B,   // line seg form,A -> B,线段形式
            P, N;   // point normal form,P为直线上的点,N为法线

    bool    isSeg;  // is a segment or a line?
};

#endif

Line2.cpp

#include "Line2.h"

Line2::Line2( const Vector2 & a, const Vector2 & b, LineType t ) 
{
    setVectors( a, b, t );
}

Line2::Line2( float x1, float y1, float x2, float y2 ) 
{
    setVectors( Vector2( x1, y1 ),
                Vector2( x2, y2 ),
                LINE_SEGMENT );
}

void Line2::setVectors( const Vector2 & a, const Vector2 & b, LineType t ) 
{
    switch( t ) 
    {

    case LINE_SEGMENT:

        // init segment vars
        A = a;
        B = b;
        isSeg = true;

        // init param form
        C = (a - b);
        C.normalize();
        S = a;

        // init PN form 
        P = a;
        N = C.perp();
        break;

    case LINE_PTNORM:

        // init PV vars
        P = a;
        N = b;
        N.normalize();

        // init param form
        S = a;
        C = N.perp();

        // segment form makse no sense here
        isSeg = false;
        break;

    case LINE_PARAM:

        // init param vars
        S = a;
        C = b;
        C.normalize();

        // init PV vars
        P = a;
        N = C.perp();

        // segment form makse no sense here
        isSeg = false;
        break;

    default: break;

    }
}

bool Line2::getVectors( Vector2 & a, Vector2 & b, LineType t ) const 
{
    switch( t ) 
    {
    case LINE_SEGMENT:

        if( isSeg ) 
        {
            a = A;
            b = B;
        } 
        else 
        {
            // user asked for segment endpoints, and this is not a seg
            return( false ); // = error
        }
        break;

    case LINE_PTNORM:

        a = P;
        b = N;
        break;

    case LINE_PARAM:

        a = S;
        b = C;
        break;

    default:

        return( false );

    }

    return( true );

}

Vector2.h

#ifndef _Vector2_H
#define _Vector2_H

#include 
#include 

using namespace std;

class Vector2 
{
public:

    // vars
    float x, y;

    // constructors
    Vector2() {}

    Vector2( float x1, float y1 ) : x(x1), y(y1) {}

    // vector ops
    void normalize() 
    {
        float temp = 1 / length();

        x *= temp;
        y *= temp;
    }

    inline double length() const 
    {
        return( sqrt((x * x) + (y * y)) );
    }

    Vector2 perp() const 
    {
        return( Vector2( y, -x ) );
    }

    //  operators
    Vector2 operator + ( const Vector2 & rhs ) const 
    {
        return( Vector2(x + rhs.x, y + rhs.y) );
    }

    Vector2 operator - ( const Vector2 & rhs ) const 
    {
        return( Vector2( x - rhs.x, y - rhs.y ) );
    }

    Vector2 operator / ( float k ) const 
    {
        return( Vector2(x / k, y / k) );
    }

    float operator * ( const Vector2 & rhs ) const 
    {
        // dot product
        return( (x * rhs.x) + (y * rhs.y) );
    }

    Vector2 operator * ( const float & rhs ) const 
    {
        // scale by scalar
        return( Vector2( x * rhs, y * rhs ) );
    }

    bool operator == ( const Vector2 & rhs ) 
    {
        return( rhs.x == x && rhs.y == y );
    }

    friend Vector2 operator * ( const float & lhs, const Vector2 & rhs ) 
    {
        // scale by scalar
        return( Vector2( rhs.x * lhs, rhs.y * lhs ) );
    }

    float & operator [] ( int n ) 
    {
        // access like an array
        switch( n ) 
        {
        case 0  : return( x );
        case 1  : return( y );
        default : /* bug out */;
        }
    }

    friend istream & operator >> ( istream & i, Vector2 & v ) 
    {
        i >> v.x >> v.y;
        return( i );
    }

    friend ostream & operator << ( ostream & o, Vector2 & v ) 
    {
        o << v.x << " " << v.y;
        return( o );
    }

}; // end class

#endif // _Vector2_H

GlutWin.h

#ifndef _GLUTWIN_H_
#define _GLUTWIN_H_

#include 
#include 
#include 
#include 

////////////////////////////////////////////////////////////
//                       GlutWin                          //
//                                                        //
// our class for general purpose GLUT initialization      //
////////////////////////////////////////////////////////////
class GlutWin 
{
public:

    // constructor
    GlutWin( int windowHeight, int windowWidth,
              int windowPosX, int windowPosY,
              unsigned int displayMode,
              const char * windowTitle );

    ~GlutWin() {};


private:

    const char *    windowTitle;
    int             windowHeight, windowWidth;
    int             windowPosX,   windowPosY;
    int             windowID;
    unsigned int    displayMode;
    bool            fullScreen;
};

#endif // _CGLUTWIN_H_

GlutWin.cpp

#include "GlutWin.h"

GlutWin::GlutWin( int windowHeight, int windowWidth,
                  int windowPosX, int windowPosY,
                  unsigned int displayMode,
                  const char * windowTitle ) 
{
    // initialize members
    windowTitle = windowTitle;
    windowHeight= windowHeight;
    windowWidth = windowWidth;
    windowPosX  = windowPosX;
    windowPosY  = windowPosY;
    displayMode = displayMode;
    fullScreen  = false;

    // make some dummy command line for glut
    char    cmd_line[8];
    char *  argv[1];
    argv[0] = cmd_line;
    int     argc = 1;

    // initialize glut
    glutInit( &argc, argv );

    // initialize window
    glutInitWindowSize( windowWidth, windowHeight );
    glutInitWindowPosition( windowPosX, windowPosY );
    glutInitDisplayMode( displayMode );

    // create window
    windowID    = glutCreateWindow( windowTitle );

    // set the view frustum
    glMatrixMode( GL_PROJECTION ); 
    glLoadIdentity();
    gluOrtho2D( 0, windowWidth, 0, windowHeight );

    glMatrixMode( GL_MODELVIEW );
    // clear rendering surface
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);  // background is black
    glViewport(0, 0, windowWidth, windowHeight);
}

OpenGL---二维光线追踪_第9张图片

ploys.txt

70 260
150 550
600 550
700 250
450 75
.
300 240
275 260
300 300
350 250
340 240
.
500 440
475 460
500 500
550 450
540 440
.
500 240
475 260
500 300
550 250
540 240
.
500 440
475 460
500 500
550 450
540 440
.

你可能感兴趣的:(图形学,OpenGL,游戏)