OpenGL射线拾取

用鼠标选择网格上的三角形。
主要步骤:
1. 每次渲染设置摄像机后,用gluUnProject得到拾取射线,然后和每个三角形所在平面求交。
2. 若拾取起点到交点的向量 和 拾取射线向量的数量积大于0(即三角形在观察者前方),则判断是点是否在三角形内。
3. 若在,则 这个三角形被选择。

由于要不断求交,实际用的时候应该考虑粗略判断,以便减少求交次数。
代码如下(c#):
namespace RayPick 

    
public partial class Form1 : Form 
    

        Vec2i mpos;  
//鼠标在窗体内的位置 
        CDevice decice;   //初始化OpenGL 
        Vec3 rayStart = new Vec3(), rayEnd = new Vec3();   //拾取射线的起点和终点 

        CTestMesh[] obj 
= new CTestMesh[4];  //物体 
        float yrot = 0;   

        
double[] mv = new double[16], prj = new double[16];  //模型变换矩阵和投影矩阵 
         
        ……………………………………………… 

        
//--------------------------------------核心代码------------------------------------- 
//渲染循环 
        private void timer1_Tick(object sender, EventArgs e) 
        

            GL.glLoadIdentity(); 

            GL.glClear(GL.GL_COLOR_BUFFER_BIT 
| GL.GL_DEPTH_BUFFER_BIT); 
            GL.glRotated(
15100); 

            GL.glRotated(yrot, 
010); 
            GL.glTranslated(
0-50); 
            yrot 
+= 0.3f

            PickRay(); 
//得到拾取射线的起点和终点 

            
for (int i = 0; i <= 3; i++{ obj[i].Render(); } 

            decice.EndRendering(); 
        }
 

        
private void PickRay() 
        

            
double o1x, o1y, o1z; 
            
double o2x, o2y, o2z; 

            WGL.GetCursorPos(
ref mpos); 
            Point p 
= this.PointToClient(new Point(mpos.x, mpos.y)); 
            mpos.x 
= p.X; mpos.y = p.Y; 

            
//更新矩阵 
            GL.glGetDoublev(GL.GL_MODELVIEW_MATRIX , mv); 
            GL.glGetDoublev(GL.GL_PROJECTION_MATRIX, prj); 

            
int[] viewPort = new int[4]; 
            GL.glGetIntegerv(GL.GL_VIEWPORT, viewPort); 

            GLU.gluUnProject(mpos.x, (viewPort[
3- mpos.y - 1), 0.0, mv, prj, viewPort, out o1x, out o1y, out o1z); 
            GLU.gluUnProject(mpos.x, (viewPort[
3- mpos.y - 1), 1.0, mv, prj, viewPort, out o2x, out o2y, out o2z); 

            rayStart 
= new Vec3(o1x, o1y, o1z); 
            rayEnd 
= new Vec3(o2x, o2y, o2z); 

        }
 

        
//计算平面法向量,用来确定平面点法式方程 
        private static Vec3 CalcPlaneNormal(Vec3 P1, Vec3 P2, Vec3 P3) 
        

            Vec3 V1, V2; 
            Vec3 result; 

            V1 
= P2 - P1; 
            V2 
= P3 - P1; 

            result 
= V1 % V2; 

            result.Normalise(); 

            
return result; 
        }
 

        
//判断点是否在三角形内 
        public static bool IsInTrg(Vec3 vtxA, Vec3 vtxB, Vec3 vtxC, TriEqaResult pHit) 
        

            
//把点代入三边的方程,符号一样则在三角形内 
            LineEqv l1, l2, l3;    //LineEqv 空间直线方程 

            l1 
= new LineEqv(vtxA, vtxB - vtxA); 
            l2 
= new LineEqv(vtxC, vtxA - vtxC); 
            l3 
= new LineEqv(vtxB, vtxC - vtxB); 

        ………………………… 
        }
 
         
        
public bool Intersect(Vec3 vtxA, Vec3 vtxB, Vec3 vtxC, ref Vec3 pos) 
        

            
//PlaneEqv 平面方程 
            PlaneEqv cu***ce = new PlaneEqv(vtxA, CalcPlaneNormal(vtxA, vtxB, vtxC)); 
            Vec3 ori 
= rayEnd - rayStart; 
            LineEqv curSpd 
= new LineEqv(rayStart, ori); 

            
//三元一次方程组的解,用来求平面和直线的交点 
            TriEqaResult pHit = new TriEqaResult(curSpd, cu***ce); 
            
//判断有无解 
            if (!pHit.hasSolution) return false; } 

            pos 
= new Vec3(pHit.x, pHit.y, pHit.z); 

            
//判断拾取起点到交点的向量 和 拾取射线向量的数量积 
            if ((pos - rayStart) * ori > 0
            

                
return IsInTrg(vtxA, vtxB, vtxC, pHit); 
            }
 
            
else return false; } 
        }
 

        
private void Form1_Click(object sender, EventArgs e) 
        

            
int bestIdx = -1;   //拾取到的最近物体的索引 
            double bestDist = double.MaxValue;        //拾取到的最近物体的距离 

            
for (int i = 0; i < 4; i++
            

                Vec3 intersect 
= new Vec3();  //交点 
                if (Intersect(obj[i].vtx[obj[i].faces.idxA], obj[i].vtx[obj[i].faces.idxB], obj[i].vtx[obj[i].faces.idxC], ref intersect)) 
                

                    
double dist = intersect & rayStart; 
                    
//拾取起点到焦点的距离 和一个最近距离值比较,小于则更新距离值和物体索引 
                    if (dist < bestDist) 
                    

                        bestDist 
= dist; bestIdx = i; 
                    }
 
                }
 
            }
 
            
if (bestIdx != -1{ System.Windows.Forms.MessageBox.Show(bestIdx.ToString()); } 
        }
 
         
………………………… 
源代码:https://files.cnblogs.com/Yuri/RayPick.rar
posted @ 2007-06-17 23:57  Yuri  阅读(11436)  评论(1编辑  收藏  举报