OpenGL3.3鼠標拾取物體

OpenGL3.3鼠標拾取物體

本文翻譯自:http://www.lighthouse3d.com/tutorials/opengl-selection-tutorial/

在3D場景中拾取或選擇特定項目可能對某些應用程序很有用。可以通過單擊一個對象來執行選擇,這需要一種確定鼠標放置在哪個對象上的方法。

實現此目的的簡單解決方案是使用顏色編碼,以特定顏色繪製每個可拾取對象。讀取鼠標所在的像素以提供顏色,從而可以識別物體。

選擇模式下的渲染使用非常簡單的着色器,將恆定的顏色應用於像素。顏色是一個統一變量,在繪製每個對象之前應將其設置爲唯一值。

頂點着色器可以如下所示:

#version 330
 
uniform mat4 m_pvm;
 
in vec4 position;
 
void main()
{
    gl_Position = m_pvm * position ;
}

片段着色器很簡單:

#version 330
 
uniform int code;
 
out vec4 outputF;
 
void main()
{
    outputF = vec4(code/255.0, 0, 0, 0);
}

重要的是,我們收到的code是整數而不是浮點數。在RGB模式下,每個分量只有256個可能的值,因此當您使用浮點數設置顏色時,OpenGL將選擇最接近的可能顏色,這可能與您提供的值不完全相同。

請注意,我們將除以255.0,而不是除以255。使用後一種方法將是整數除法,結果也將是整數(0或1)。

如果有超過255個對象可供選擇,則 uniformcode可以是ivec4類型。

假設我們的場景有4個國際象棋棋子,如下圖所示:

在這裏插入圖片描述

在此示例中,我們有四個可點擊的對象,我們將爲其分配從1到4的代碼。選擇渲染例程的背景可以設置爲黑色,因此零表示沒有任何選擇。使用選擇程序時,我們將獲得如下圖像(對比度大大提高):

在這裏插入圖片描述
該圖像將永遠不會呈現給用戶,因爲選擇渲染不會交換緩衝區。

以下例程接收鼠標窗口座標,並執行必要的步驟來確定選擇了哪個對象。首先調用選擇渲染程序,然後再進行選擇。然後,它從後臺緩衝區讀取像素並檢查返回的顏色。

要讀取像素,我們將使用函數glReadPixels
void glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *data);

參數:

  • x,y:要讀取的矩形塊的第一個像素的座標
  • width,height:塊的大小。
  • format:像素數據的格式:GL_STENCIL_INDEX, GL_DEPTH_COMPONENT, GL_DEPTH_STENCIL, GL_RED, GL_GREEN, GL_BLUE, GL_RGB, GL_BGR, GL_RGBA, GL_BGRA
  • type:像素分量的數據類型:GL_UNSIGNED_BYTE, GL_BYTE, GL_UNSIGNED_SHORT, GL_SHORT, GL_UNSIGNED_INT, GL_INT, GL_HALF_FLOAT, GL_FLOAT, GL_UNSIGNED_BYTE_3_3_2, GL_UNSIGNED_BYTE_2_3_3_REV, GL_UNSIGNED_SHORT_5_6_5, GL_UNSIGNED_SHORT_5_6_5_REV, GL_UNSIGNED_SHORT_4_4_4_4, GL_UNSIGNED_SHORT_4_4_4_4_REV, GL_UNSIGNED_SHORT_5_5_5_1, GL_UNSIGNED_SHORT_1_5_5_5_REV, GL_UNSIGNED_INT_8_8_8_8, GL_UNSIGNED_INT_8_8_8_8_REV, GL_UNSIGNED_INT_10_10_10_2, GL_UNSIGNED_INT_2_10_10_10_REV, GL_UNSIGNED_INT_24_8, GL_UNSIGNED_INT_10F_11F_11F_REV, GL_UNSIGNED_INT_5_9_9_9_REV, or GL_FLOAT_32_UNSIGNED_INT_24_8_REV
  • data:返回的像素數據。
void processSelection(int xx, int yy) {
 
    unsigned char res[4];
    GLint viewport[4]; 
 
    renderSelection();
 
    glGetIntegerv(GL_VIEWPORT, viewport);
    glReadPixels(xx, viewport[3] - yy, 1,1,GL_RGBA, GL_UNSIGNED_BYTE, &res);
    switch(res[0]) {
        case 0: printf("Nothing Picked \n"); break;
        case 1: printf("Picked yellow\n"); break;
        case 2: printf("Picked red\n"); break;
        case 3: printf("Picked green\n"); break;
        case 4: printf("Picked blue\n"); break;
        default:printf("Res: %d\n", res[0]);
    }
}

爲了獲得正確的像素,我們必須將鼠標窗口座標(左上角爲原點)轉換爲幀緩衝區座標(左下角的原點)。這要求我們知道視口的高度。函數glGetIntegerv可用於此目的,將GL_VIEWPORT作爲第一個參數。返回變量viewport是一個包含4個項目的數組,該數組提供x和y視口窗口座標(起點:左上角),然後是視口的寬度和高度。然後,可以讀取像素,並正確解碼檢索到的顏色。

選擇渲染程序必須遵循與常規程序有關幾何變換的相同步驟,以使對象位於屏幕上的同一位置。通常,選擇渲染程序是常規渲染程序的簡化版本,其中僅繪製對象的子集,並且未設置任何圖形效果,例如照明。

對象子集包括所有可點擊的對象以及相關的遮擋物。這個想法是,如果某些棋子在用戶看不見的空間內,則它們不應出現在顏色編碼的圖像中。這可以通過用與背景相同的顏色繪製遮擋物(例如房間的牆壁)來輕鬆實現。

void renderSelection(void) {
 
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 
    //set matrices to identity
    ...
    // set camera as in the regular rendering function
    ....
 
    // use the selection shader
    glUseProgram(selectionProgramID);
 
    //perform the geometric transformations to place the first pawn
    ...
    // set the uniform with the appropriate color code
    glProgramUniform1i(selectionProgramID, codeVarLocation, 1);
    // draw first pawn
    ...
     
    // repeat the above steps for the remaining objects, using different codes
 
    //don't swap buffers
    //glutSwapBuffers();
 
    // restore clear color if needed
    glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
}

下載:

  • 帶有完整源代碼和着色器的VS2010項目(ZIP

該項目需要glewfreeglutglut


歡迎關注我的公衆號 江達小記

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章