2013-01-21 136 views
3

我正在開發一個使用LWJGL 2.8.5的3D可視化應用程序。 在閱讀項目主頁的第一篇教程之後,我還深入分析了閱讀OpenGL書籍。 我看到OpenGL中的典型過程是在init函數中繪製場景,然後簡單地調用循環中的顯示更新。LWJGL - 渲染週期的屏幕閃爍

但是,當我嘗試使用LWJGL時,我在顯示屏上產生閃爍效果。 消除閃爍的唯一方法是在顯示更新週期中重新繪製場景。 這是怎麼回事?

爲了更好地解釋我的問題,我創建了一個簡單的類來重現問題。 它只是在屏幕中心畫一個四邊形,然後進入無盡的屏幕更新循環。

請注意,如果我取消註釋週期內的繪圖調用,則閃爍消失一切正常。 爲什麼?

我的期望是否有任何錯誤只繪製一次物體,只是移動相機以獲得靜態場景的不同視圖?

這是代碼:

package test; 
import org.lwjgl.LWJGLException; 
import org.lwjgl.opengl.Display; 
import org.lwjgl.opengl.DisplayMode; 
import org.lwjgl.opengl.GL11; 
import org.lwjgl.util.glu.GLU; 

import static org.lwjgl.opengl.GL11.*; 

public class DisplayTest 
{ 



public static void initGL() 
{ 
    GL11.glViewport(0, 0, 640, 480); 
    glMatrixMode(GL_PROJECTION); 
    GLU.gluPerspective(45.0f, 640f/480f,0.1f, 100.0f); 

    draw(); 


} 

public static void draw() 
{ 
    glMatrixMode(GL_MODELVIEW); 

    GL11.glLoadIdentity(); // Reset The Current Modelview Matrix 
    GL11.glTranslatef(0, 0, -6.0f);//Place at the center at -6 depth units 


    //Start drawing a quad 
    //-------------------------------------------------- 
    GL11.glBegin(GL11.GL_QUADS); 
    int size=1; 
    GL11.glColor3f(.3f, .5f, .8f); 

    GL11.glVertex3f(-size/2f,-size/2f,+size/2f); 
    GL11.glVertex3f(+size/2f,-size/2f,+size/2f); 
    GL11.glVertex3f(+size/2f,+size/2f,+size/2f); 
    GL11.glVertex3f(-size/2f,+size/2f,+size/2f); 
    glEnd(); 
} 


public static void main(String[] args) 
{ 
    try 
    { 
     // Sets the width of the display to 640 and the height to 480 
     Display.setDisplayMode(new DisplayMode(640, 480)); 
     // Sets the title of the display 
     Display.setTitle("Drawing a quad"); 
     // Creates and shows the display 
     Display.create(); 
    } 
    catch (LWJGLException e) 
    { 
     e.printStackTrace(); 
     Display.destroy(); 
     System.exit(1); 
    } 

    initGL(); 

    // While we aren't pressing the red button on the display 
    while (!Display.isCloseRequested()) 
    { 
     //draw(); 

     // Update the contents of the display and check for input 
     Display.update(); 
     // Wait until we reach 60 frames-per-second 
     Display.sync(60); 
    } 
    // Destroy the display and render it invisible 
    Display.destroy(); 
    System.exit(0); 
    } 
} 

回答

4

默認情況下,OpenGL維護兩個緩衝區:一個前端緩衝區和一個後端緩衝區。前臺緩衝區是顯示內容,而後臺緩衝區是您繪製的內容。這稱爲雙緩衝,並防止向用戶顯示部分呈現的場景。無論何時調用Display.update(),lwjgl都會通知opengl將後臺緩衝區的內容移動到前臺緩衝區以供顯示。這可以通過複製數據完成,也可以通過將舊前緩衝區標記爲新後緩衝區,並將舊後緩衝區標記爲新前緩衝區(緩衝區交換)來完成。

這是面向正常情況,例如在遊戲中你想要畫出像每秒60次的新東西。現在,如果您實際上沒有繪製任何東西,但仍然調用Display.update()(例如獲取輸入),緩衝區仍然會移動。現在根據實現的方式,要麼你仍然看到你的舊圖像(如果數據是簡單的複製,因爲你的後臺緩衝區仍然包含你所渲染的數據),或者你替換每個Display.update()已經繪製,並且緩衝區在您渲染時是前端緩衝區(可能只是黑色)。這會導致您看到的閃爍,因爲圖像僅顯示在每個第二幀(原始後緩衝區)中,並且每個其他幀都顯示黑色原始前端緩衝區。

對於某些驅動程序實現(例如來自Nvidia),似乎Windows甚至可以在活動的前端緩衝區上繪圖,因此如果不重繪​​場景,對話框即使在關閉後也可以顯示並顯示。

最簡潔的解決方案是在init中渲染紋理(自OpenGL 1.1開始工作)或渲染緩衝區(在OpenGL 3.0中引入),然後每幀渲染此紋理。這實際上是OpenGL設計人員爲這種類型而考慮的解決方案。

編輯:要麼取消註釋上述代碼中的繪製方法,或者如果由於渲染時間而沒有選項,則必須渲染到紋理。使用OpenGL 3或以上,你就需要把這樣的事情在你的初始化:

fbo = GL30.glGenFramebuffers(); 
GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, fbo); 

rbo = GL30.glGenRenderbuffers(); 
GL30.glBindRenderbuffer(GL30.GL_RENDERBUFFER, rbo); 
GL30.glRenderbufferStorage(GL30.GL_RENDERBUFFER, GL11.GL_RGBA8, 640, 480); 
GL30.glFramebufferRenderbuffer(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL30.GL_RENDERBUFFER, rbo); 

assert(GL30.glCheckFramebufferStatus(GL30.GL_FRAMEBUFFER) == GL30.GL_FRAMEBUFFER_COMPLETE); 

GL30.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, fbo); 
GL20.glDrawBuffers(GL30.GL_COLOR_ATTACHMENT0); 
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT); 

drawScene(); //draw here 

您的抽獎方法來調用每一幀將被簡化,從幀緩衝區FBO簡單和快速複製:

GL30.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, fbo); 
GL11.glReadBuffer(GL30.GL_COLOR_ATTACHMENT0); 
GL30.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, 0); 
GL20.glDrawBuffers(GL11.GL_BACK_LEFT); 

GL30.glBlitFramebuffer(0, 0, 640, 480, 0, 0, 640, 480, GL11.GL_COLOR_BUFFER_BIT, GL11.GL_NEAREST); 
GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0); 
+0

你能更具體嗎?也就是說,我應該如何修改上面張貼的簡單代碼? – Matteo

+0

用一些複製粘貼的示例代碼編輯答案 –

+0

謝謝,它工作正常!但是現在,它對視角的變化沒有反應(通過在循環中使用不同的值調用GLU.gluLookAt(eyeX,eyeY,eyeZ,centerX,centerY,centerZ,upX,upY,upZ)) – Matteo

0

應使用Display.sync(60)(LWJGL命令,減少屏幕率到60FPS)詮釋環路和Display.update()以結束渲染。 Display.update()會導致交換背景緩衝區和前景緩衝區,所以你看不到圖片本身的構建(這可能是你的閃爍造成的)。

+0

如果我把Display.update放到循環之外,應用程序會自行凍結,它不會對任何輸入做出反應。 – Matteo

+0

你應該在循環中做渲染,而不是一次...... – th3falc0n

+0

當你說「渲染」時,你是什麼意思?在循環的每個步驟重新繪製平局? – Matteo

0

在更新顯示之前,您沒有清除屏幕。 在GL11.glBegin(GL11.GL_QUADS);之前加上GL11.glClear(GL11.GL_COLOR_BUFFER_BIT);