OPENGL 射线拾取法
鑒于之前OPENGL的基礎功能應用開發差不多了,所以就準備加入交互模式,但是opengl使用射線拾取方法的網上資料也不多,故此主要參考了DX9的方法并記錄之。
環境如之前,矩陣運算庫GLM
雖然opengl有名字??臻g的方法選擇,但是這種方式拾取的是范圍的,一點都不精確。
因為之前接觸過DX9所以自然就知道射線拾取方法,畢竟這個是可以精確到對應面片的,同時這個方法和FPS射擊類游戲中的彈軌命中是如此的相似。
原理:就是在鼠標點擊的投影視口內以點擊坐標為起點產生一個垂直于視口面的射線,然后檢測與這條射線所通過的空間內的物體是否有相交。所以就產生了兩個問題需要解決:
1.由鼠標點擊產生的射線
2.相交檢測,及交點是否在對應面片內
一、坐標變換
常規的方法就是鼠標點擊了,鼠標點選的是最終投影在視口上的二維坐標,故要通過透視和視口矩陣逆變化成一個世界坐標,具體方法opengl 有 gluUnProject方法,DX雖然沒有提供API但是有直接的逆運算過程。
下面直接給出兩種方式的代碼,實際測試下來兩種方法偏差在0.002左右:
//轉換鼠標位置到3維空間 glm::vec3 getViewPos(float x, float y, glm::mat4 pro, glm::mat4 view) {GLint viewPort[4] = { 0, 0, SCR_WIDTH, SCR_HEIGHT };GLdouble modelView[16] = {view[0][0],view[0][1],view[0][2],view[0][3],view[1][0],view[1][1],view[1][2],view[1][3],view[2][0],view[2][1],view[2][2],view[2][3],view[3][0],view[3][1],view[3][2],view[3][3]};GLdouble projection[16] = {pro[0][0],pro[0][1],pro[0][2],pro[0][3],pro[1][0],pro[1][1],pro[1][2],pro[1][3],pro[2][0],pro[2][1],pro[2][2],pro[2][3],pro[3][0],pro[3][1],pro[3][2],pro[3][3]};//將glm::mat4類型轉化為方法所需的數組類型int mouse_x = x;int mouse_y = SCR_HEIGHT - y - 1;GLfloat win_x = (float)mouse_x;GLfloat win_y = (float)mouse_y;GLfloat win_z;GLdouble object_x, object_y, object_z;glReadBuffer(GL_BACK);glReadPixels(mouse_x, mouse_y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &win_z);//使用gluUnProject方法將結果傳入object_x,object_y,object_z中gluUnProject((GLdouble)win_x, (GLdouble)win_y, (GLdouble)win_z, modelView, projection, viewPort, &object_x, &object_y, &object_z);glm::vec3 p = glm::vec3(object_x, object_y, object_z);std::cout << "1.mouse =>3D Coord " << p.x << ",y:" << p.y << ",z:" << p.z << std::endl;//方法二直接逆運算float z = 1.0f;float xr = (SCR_WIDTH) / 2.0f;float yr = (SCR_HEIGHT) / 2.0f;x = (mouse_x - xr)/ xr;y = (mouse_y - yr) / yr;glm::vec3 ray_nds = glm::vec3(x, y, z);glm::vec4 ray_clip = glm::vec4(ray_nds.x, ray_nds.y, ray_nds.z, 1.0f);glm::vec4 ray_eye = glm::inverse(pro) * ray_clip;glm::vec4 ray_world = glm::inverse(view) * ray_eye;if (ray_world.w != 0.0){ray_world.x /= ray_world.w;ray_world.y /= ray_world.w;ray_world.z /= ray_world.w;}p = glm::vec3(ray_world.x, ray_world.y, ray_world.z);std::cout << "2.mouse =>3D Coord " << p.x << ",y:" << p.y << ",z:" << p.z << std::endl;return p; }通過上面的計算就已經有了射線的起點和方向了。
二、相交檢測
先上一些數學概念的幾何應用理解,這將有助于觀看計算過程的多方面理解:
向量的點乘,也叫向量的內積、數量積,對兩個向量執行點乘運算,就是對這兩個向量對應位一一相乘之后求和的操作,點乘的結果是一個標量。
點乘的幾何意義是可以用來表征或計算兩個向量之間的夾角,以及在b向量在a向量方向上的投影
兩個向量的叉乘,又叫向量積、外積、叉積,叉乘的運算結果是一個向量而不是一個標量。
并且兩個向量的叉積與這兩個向量組成的坐標平面垂直。
叉乘幾何意義
在三維幾何中,向量a和向量b的叉乘結果是一個向量,更為熟知的叫法是法向量,該向量垂直于a和b向量構成的平面。
相交檢測就涉及到數學了,高中的矩陣相關的幾何知識,證明過程筆者就不贅述,畢竟水平有限,同時網上有很多相關的數學原理說明,就不班門弄斧了,可以參考一下鏈接,除了數學原理過程,包括代碼照抄就可以了,筆者是直接ctrl+C? ?ctrl+V 以下鏈接中的代碼。
射線和三角形的相交檢測:https://www.cnblogs.com/graphics/archive/2010/08/09/1795348.html
判斷點是否在三角形內?:https://www.cnblogs.com/graphics/archive/2010/08/05/1793393.html
筆者采用的是方法三的重心法,所以也是直拷貝的第三種方式的代碼。
當然抄完上面的代碼是不能直接用的,還需要做個整合,重新大概理解一下上面鏈接中的原理如下:
1.射線在坐標中的矢量表達式1(射線參數方程)
2.面在空間中的定義表達式2(面參數方程)
有了這兩個表達式之后,相交的理解就是 1的表達式值域 與 2的表達式值域中存在同一個值,
換句話說存在一個值使表達式1和表達式2同時滿足。
同時上面抄的代碼中,已經計算出了射線方程的參數t,這個t 就是交點處的t,通過t即可以算出交點,有了交點就可以和模型面片計算校驗了,代碼如下:
//面片揀選算法 bool hasPickingFace(glm::vec3 d, glm::vec3 cameraPos, glm::vec3 pos, unsigned *indicess, unsigned indlen, float* vaGrps) {int index = 0;/* 首先遍歷頂點索引數組,獲取每個面片的頂點位置信息 因為采用的三角網格模型,所以每次遍歷3個頂點 */for (int i = 0; i < indlen; i += 3) {/*注意因為默認都是模型在(0,0,0)時的坐標,所以都要加上pos,變換到模型所在位置*/index = indicess[i] * 3;glm::vec3 v1 = glm::vec3(vaGrps[index], vaGrps[index + 1], vaGrps[index + 2]) + pos;index = indicess[i + 1] * 3;glm::vec3 v2 = glm::vec3(vaGrps[index], vaGrps[index + 1], vaGrps[index + 2]) + pos;index = indicess[i + 2] * 3;glm::vec3 v3 = glm::vec3(vaGrps[index], vaGrps[index + 1], vaGrps[index + 2]) + pos;std::cout << "..................................cur test :" << i / 3 << std::endl;std::cout << "V1: " << v1[0] << "," << v1[1] << "," << v1[2] << std::endl;std::cout << "V2: " << v2[0] << "," << v2[1] << "," << v2[2] << std::endl;std::cout << "V3: " << v3[0] << "," << v3[1] << "," << v3[2] << std::endl;float T, U, V;if (IntersectTriangle(cameraPos, d, v1, v2, v3, &T, &U, &V)) {glm::vec3 intpos = cameraPos + glm::vec3(d.x * T, d.y * T, d.z * T);if (PointinTriangle(v1, v2, v3, intpos)) {return true;}}}//遍歷所有面片仍未返回說明這個網格與鼠標無交點,返回falsereturn false; }好了,有了上面的基礎,接下來就可以愉快的玩耍了,增加擴展功能了!
總結
以上是生活随笔為你收集整理的OPENGL 射线拾取法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Unity的射线
- 下一篇: JAVA射线_用射线法实现判断点是否在多