我正在使用LWJGL 2.8.5开发3D可视化应用程序。 在阅读了项目主页的第一篇教程之后,我还深入分析了阅读OpenGL书籍。 我看到OpenGL中的典型过程是在init函数中绘制场景,然后只是在循环中调用display的更新。
但是,当我用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);
}
}
答案 0 :(得分:4)
默认情况下,OpenGL维护两个缓冲区:前缓冲区和后缓冲区。前缓冲区是后缓冲区显示的内容。这称为双缓冲,可防止向用户显示部分渲染的场景。无论何时调用Display.update(),lwjgl都会告诉opengl将后台缓冲区的内容移动到前台缓冲区以供显示。这可以通过复制数据,或者将旧的前缓冲区标记为新的后缓冲区,将旧的后缓冲区标记为新的前缓冲区(缓冲区被交换)来完成。
这适用于正常情况,例如:在游戏中你想要画一些新的东西,如每秒60次。现在,如果你实际上没有绘制任何东西,但仍然调用Display.update()(例如获取输入),缓冲区仍然会被移动。现在,根据实现的方式,您仍然可以看到旧图像(如果数据被简单复制,因为您的后台缓冲区仍然包含您渲染的数据),或者您在缓冲区之间的每个Display.update()交替显示已经绘制并且在渲染时作为前缓冲区的缓冲区(可能只是黑色)。这会导致您看到的闪烁,因为图像仅显示每隔一帧(原始后缓冲区),而每隔一帧显示黑色原始前缓冲区。
通过一些驱动程序实现(例如来自Nvidia),Windows似乎甚至可以在活动前缓冲区上绘制,因此如果您不重绘场景,即使关闭后,对话框也会显示并显示。
最干净的解决方案是在init中简单地渲染纹理(自OpenGL 1.1起作用)或渲染缓冲区(在OpenGL 3.0中引入),然后在每帧渲染此纹理。这确实是OpenGL设计人员为此类事情考虑的解决方案。
编辑:要么取消注释上面代码中的draw方法,要么由于渲染时间而无法选择,您必须渲染到纹理。使用OpenGL 3或更高版本,您需要在init中添加类似的内容:
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);
答案 1 :(得分:0)
您应该在循环中使用Display.sync(60)(LWJGL命令,将屏幕速率降低到60FPS),并在渲染结束时使用Display.update()。 Display.update()将导致交换后台缓冲区和前台缓冲区,因此您不会看到图片本身堆积(这可能是您的闪烁造成的)。
答案 2 :(得分:0)
在更新显示之前,您没有清除屏幕。
在GL11.glClear(GL11.GL_COLOR_BUFFER_BIT);
GL11.glBegin(GL11.GL_QUADS);