OpenGL 在错误的位置渲染四边形
OpenGL Rendering Quads at Wrong Position
背景信息:
我正在使用 OpenGL 和 LWJGL 3 在屏幕上绘制一些四边形。我需要知道鼠标何时位于四边形上。当我将四边形渲染到屏幕上时,我使用 OpenGL 坐标,X 和 Y 的范围从 -1 到 1,并且 (0,0) 位于屏幕的中心。当我获得鼠标位置时,我使用
glfwSetCursorPosCallback();
它给我的坐标范围从 0 到 window 的宽度或高度,并且 (0,0) 位于左上角(标题栏下方)。然后我获取鼠标坐标并计算 OpenGL 坐标。
例如,如果我的 window 尺寸是 (800, 600) 而我的鼠标是 (200, 200),我会得到 (-0.5, 0.33) [因为 (400, 300) 会映射到(0, 0) 在 OpenGL 的坐标中。
所以这是我的问题:
OpenGL 在其坐标中包含标题栏,而 glfwSetCursorPosCallback();
则没有。这意味着如果我在 (-0.5, 0.33) 处渲染一个顶点 [就像在我的示例中] 它会在 (200, ~210) 左右渲染。
可以看到,由于两个坐标系覆盖的区域不同,所以坐标系之间的切换比较困难。
我已经搜索了从 OpenGL 坐标中排除标题栏的方法,以完全摆脱标题栏并获得标题栏的高度(这样我就可以将它包括在我的计算中并进行正确的调整).我一直无法弄清楚如何做这些中的任何一个,所以我正在寻找一种方法,或者一种可以解决我的问题的不同方法。
编辑 1:添加代码
@Nicol Bolas 告诉我,这不是 OpenGL 通常的工作方式,所以我的代码中一定有什么原因导致了这种情况。我相信我已经提供了对我的问题负责的部分代码:
这是我的渲染器class[我正在使用 drawQuad() 方法]
注意:我目前没有在我的着色器中使用视图、模型或投影矩阵。
public class Renderer {
private VertexArrayObject vao;
private VertexBufferObject vbo;
private ShaderProgram shaderProgram;
private FloatBuffer vertices;
private int numVertices;
private boolean drawing;
//private Font font;
//private Font debugFont;
public void drawQuad(float x, float y, float width, float height, Color c) {
/* Calculate Vertex positions */
float x1 = x;
float y1 = y;
float x2 = x + width;
float y2 = y - height;
/* Calculate color */
float r = c.getRed();
float g = c.getGreen();
float b = c.getBlue();
/* Put data into buffer */
vertices.put(x1).put(y1).put(0.0f).put(r).put(g).put(b);
vertices.put(x1).put(y2).put(0.0f).put(r).put(g).put(b);
vertices.put(x2).put(y2).put(0.0f).put(r).put(g).put(b);
vertices.put(x2).put(y1).put(0.0f).put(r).put(g).put(b);
/* We drawed X vertices */
numVertices += 4;
}
// Initialize renderer
public void init(){
// Set up shader programs
setupShaderProgram();
// Enable blending (?????)
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
// Clears drawing area
public void clear() {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
// Begin rendering
public void begin() {
if (drawing) throw new IllegalStateException("Renderer is already drawing.");
drawing = true;
numVertices = 0;
}
// End rendering
public void end() {
if (!drawing) throw new IllegalStateException("Renderer is not drawing.");
drawing = false;
flush();
}
// Flushes data to GPU to get rendered
public void flush() {
if (numVertices > 0) {
vertices.flip();
if (vao != null) vao.bind();
else vbo.bind(GL_ARRAY_BUFFER);
specifyVertexAttributes();
}
shaderProgram.use();
// Upload the new vertex data
vbo.bind(GL_ARRAY_BUFFER);
vbo.uploadSubData(GL_ARRAY_BUFFER, 0, vertices);
// Draw batch
glDrawArrays(GL_QUADS, 0, numVertices);
// Clear vertex data for next batch
vertices.clear();
numVertices = 0;
}
private void setupShaderProgram() {
// Generate VertexArrayObject
if (Game.is32Supported()) {
vao = new VertexArrayObject();
vao.bind();
} else {
throw new RuntimeException("OpenGL 3.2 not supported.");
}
// Generate VertexBufferObject
vbo = new VertexBufferObject();
vbo.bind(GL_ARRAY_BUFFER);
// Create FloatBuffer
vertices = MemoryUtil.memAllocFloat(4096);
// Upload null data to allocate storage for the VBO
long size = vertices.capacity() * Float.BYTES;
vbo.uploadData(GL_ARRAY_BUFFER, size, GL_DYNAMIC_DRAW);
// Initialize variables
numVertices = 0;
drawing = false;
// Load Shaders:
Shader vertexShader, fragmentShader;
if (Game.is32Supported()) {
vertexShader = Shader.loadShader(GL_VERTEX_SHADER, "res/shaders/vshader.vert");
fragmentShader = Shader.loadShader(GL_FRAGMENT_SHADER, "res/shaders/fshader.frag");
} else {
throw new RuntimeException("OpenGL 3.2 not supported.");
}
// Create ShaderProgram
shaderProgram = new ShaderProgram();
shaderProgram.attachShader(vertexShader);
shaderProgram.attachShader(fragmentShader);
if (Game.is32Supported()) {
shaderProgram.bindFragmentDataLocation(0, "fragColor");
}
shaderProgram.link();
shaderProgram.use();
// Delete linked shaders
vertexShader.delete();
fragmentShader.delete();
// Get width & height of framebuffer
long window = GLFW.glfwGetCurrentContext();
int width, height;
try (MemoryStack stack = MemoryStack.stackPush()) {
IntBuffer widthBuffer = stack.mallocInt(1);
IntBuffer heightBuffer = stack.mallocInt(1);
GLFW.glfwGetFramebufferSize(window, widthBuffer, heightBuffer);
width = widthBuffer.get();
height = heightBuffer.get();
}
// Specify vertex pointers
specifyVertexAttributes();
// Set Model Matrix to identity matrix
Matrix4f model = new Matrix4f();
int uniModel = shaderProgram.getUniformLocation("model");
shaderProgram.setUniform(uniModel, model);
// Set View Matrix to identity matrix
Matrix4f view = new Matrix4f();
int uniView = shaderProgram.getUniformLocation("view");
shaderProgram.setUniform(uniView, view);
// Set Projection Matrix to an orthographic projection
Matrix4f projection = Matrix4f.orthographic(0f, width, 0f, height, -1f, 1f);
int uniProjection = shaderProgram.getUniformLocation("projection");
shaderProgram.setUniform(uniProjection, projection);
}
// Specifies the vertex shader pointers (attributes)
private void specifyVertexAttributes() {
int posAttrib = shaderProgram.getAttributeLocation("position");
shaderProgram.enableVertexAttribute(posAttrib);
shaderProgram.pointVertexAttribute(posAttrib, 3, 6 * Float.BYTES, 0);
int colAttrib = shaderProgram.getAttributeLocation("color");
shaderProgram.enableVertexAttribute(colAttrib);
shaderProgram.pointVertexAttribute(colAttrib, 3, 6 * Float.BYTES, 3 * Float.BYTES);
}
}
这是我的 init() 方法,它创建和设置我的 window:
private void init() {
// Setup an error callback. The default implementation
// will print the error message in System.err.
GLFWErrorCallback.createPrint(System.err).set();
// Initialize GLFW. Most GLFW functions will not work before doing this.
if ( !glfwInit() )
throw new IllegalStateException("Unable to initialize GLFW");
// Configure GLFW
glfwDefaultWindowHints(); // optional, the current window hints are already the default
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); // the window will stay hidden after creation
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); // the window will be resizable
// ONLY ON MAC OSX (?)
//glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); // Tell GLFW to use OpenGL verison 3.x
//glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2); // Tell GLFW to use OpenGL version x.2 (combined -> 3.2)
//glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
//glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE); // Should be forward compatible
// Create the window
window = glfwCreateWindow(WIDTH, HEIGHT, "Game_19_v0.0.1", NULL, NULL);
if ( window == NULL )
throw new RuntimeException("Failed to create the GLFW window");
// Setup a key callback. It will be called every time a key is pressed, repeated or released.
glfwSetKeyCallback(window, (window, key, scancode, action, mods) -> {
if ( key == GLFW_KEY_ESCAPE && action == GLFW_RELEASE )
glfwSetWindowShouldClose(window, true); // We will detect this in the rendering loop
});
// Get the thread stack and push a new frame
try ( MemoryStack stack = stackPush() ) {
IntBuffer pWidth = stack.mallocInt(1); // int*
IntBuffer pHeight = stack.mallocInt(1); // int*
// Get the window size passed to glfwCreateWindow
glfwGetWindowSize(window, pWidth, pHeight);
// Get the resolution of the primary monitor
GLFWVidMode vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor());
// Center the window
glfwSetWindowPos(
window,
(vidmode.width() - pWidth.get(0)) / 2,
(vidmode.height() - pHeight.get(0)) / 2
);
} // the stack frame is popped automatically
// Make the OpenGL context current
glfwMakeContextCurrent(window);
// Enable v-sync
glfwSwapInterval(1);
// Make the window visible
glfwShowWindow(window);
// This line is critical for LWJGL's interoperation with GLFW's
// OpenGL context, or any context that is managed externally.
// LWJGL detects the context that is current in the current thread,
// creates the GLCapabilities instance and makes the OpenGL
// bindings available for use.
GL.createCapabilities();
// Input
glfwSetCursorPosCallback(window, cursorPosCallback = new MouseInput());
// Create renderer
renderer = new Renderer();
renderer.init();
// To Render:
buttonManager = new ButtonManager();
}
编辑 2:临时解决方案
我能够使用 glfwWindowHint(GLFW_DECORATED, GLFW_FALSE);
从 window 移除整个边框,包括标题栏,这解决了问题。然而现在,我显然没有关闭、最小化等选项,在我的 window 上,尽管我想我可以在必要时自己编写这些选项。如果我发现任何其他解决方案,将会更新。
GLFW 函数通常使用 window 的客户区(内部 window 区域不包括标题栏、滚动条等),因此 glfwSetCursorPosCallback
为您提供预期值.如果您的 OpenGL 帧缓冲区由于某种原因在标题栏后面渲染内容(无论是不正确的设置还是平台特定的细节),您仍然应该能够使用 glfwGetWindowFrameSize:
获取标题栏大小
IntBuffer pLeft = stack.mallocInt(1); // int*
IntBuffer pTop = stack.mallocInt(1); // int*
IntBuffer pRight = stack.mallocInt(1); // int*
IntBuffer pBottom = stack.mallocInt(1); // int*
// Get the window border sizes
glfwGetWindowFrameSize(window, pLeft, pTop, pRight, pBottom);
(免责声明:我只是遵循上面代码中的语法,因为我只熟悉 C++ API。)
标题栏的大小将存储在 top
变量中,然后可以添加到您从 glfwSetCursorPosCallback
和 glfwGetWindowSize
.
中获得的任何值
float adjustedYpos = ypos + top;
float adjustedHeight = height + top;
float normalizedY = adjustedYpos / adjustedHeight;
float openglY = normalizedY * -2.0f - 1.0f
这个openglY
值应该是根据标题栏大小调整的OpenGL [-1, 1] clip-space坐标。
背景信息:
我正在使用 OpenGL 和 LWJGL 3 在屏幕上绘制一些四边形。我需要知道鼠标何时位于四边形上。当我将四边形渲染到屏幕上时,我使用 OpenGL 坐标,X 和 Y 的范围从 -1 到 1,并且 (0,0) 位于屏幕的中心。当我获得鼠标位置时,我使用
glfwSetCursorPosCallback();
它给我的坐标范围从 0 到 window 的宽度或高度,并且 (0,0) 位于左上角(标题栏下方)。然后我获取鼠标坐标并计算 OpenGL 坐标。
例如,如果我的 window 尺寸是 (800, 600) 而我的鼠标是 (200, 200),我会得到 (-0.5, 0.33) [因为 (400, 300) 会映射到(0, 0) 在 OpenGL 的坐标中。
所以这是我的问题:
OpenGL 在其坐标中包含标题栏,而 glfwSetCursorPosCallback();
则没有。这意味着如果我在 (-0.5, 0.33) 处渲染一个顶点 [就像在我的示例中] 它会在 (200, ~210) 左右渲染。
可以看到,由于两个坐标系覆盖的区域不同,所以坐标系之间的切换比较困难。
我已经搜索了从 OpenGL 坐标中排除标题栏的方法,以完全摆脱标题栏并获得标题栏的高度(这样我就可以将它包括在我的计算中并进行正确的调整).我一直无法弄清楚如何做这些中的任何一个,所以我正在寻找一种方法,或者一种可以解决我的问题的不同方法。
编辑 1:添加代码
@Nicol Bolas 告诉我,这不是 OpenGL 通常的工作方式,所以我的代码中一定有什么原因导致了这种情况。我相信我已经提供了对我的问题负责的部分代码:
这是我的渲染器class[我正在使用 drawQuad() 方法]
注意:我目前没有在我的着色器中使用视图、模型或投影矩阵。
public class Renderer {
private VertexArrayObject vao;
private VertexBufferObject vbo;
private ShaderProgram shaderProgram;
private FloatBuffer vertices;
private int numVertices;
private boolean drawing;
//private Font font;
//private Font debugFont;
public void drawQuad(float x, float y, float width, float height, Color c) {
/* Calculate Vertex positions */
float x1 = x;
float y1 = y;
float x2 = x + width;
float y2 = y - height;
/* Calculate color */
float r = c.getRed();
float g = c.getGreen();
float b = c.getBlue();
/* Put data into buffer */
vertices.put(x1).put(y1).put(0.0f).put(r).put(g).put(b);
vertices.put(x1).put(y2).put(0.0f).put(r).put(g).put(b);
vertices.put(x2).put(y2).put(0.0f).put(r).put(g).put(b);
vertices.put(x2).put(y1).put(0.0f).put(r).put(g).put(b);
/* We drawed X vertices */
numVertices += 4;
}
// Initialize renderer
public void init(){
// Set up shader programs
setupShaderProgram();
// Enable blending (?????)
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
// Clears drawing area
public void clear() {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
// Begin rendering
public void begin() {
if (drawing) throw new IllegalStateException("Renderer is already drawing.");
drawing = true;
numVertices = 0;
}
// End rendering
public void end() {
if (!drawing) throw new IllegalStateException("Renderer is not drawing.");
drawing = false;
flush();
}
// Flushes data to GPU to get rendered
public void flush() {
if (numVertices > 0) {
vertices.flip();
if (vao != null) vao.bind();
else vbo.bind(GL_ARRAY_BUFFER);
specifyVertexAttributes();
}
shaderProgram.use();
// Upload the new vertex data
vbo.bind(GL_ARRAY_BUFFER);
vbo.uploadSubData(GL_ARRAY_BUFFER, 0, vertices);
// Draw batch
glDrawArrays(GL_QUADS, 0, numVertices);
// Clear vertex data for next batch
vertices.clear();
numVertices = 0;
}
private void setupShaderProgram() {
// Generate VertexArrayObject
if (Game.is32Supported()) {
vao = new VertexArrayObject();
vao.bind();
} else {
throw new RuntimeException("OpenGL 3.2 not supported.");
}
// Generate VertexBufferObject
vbo = new VertexBufferObject();
vbo.bind(GL_ARRAY_BUFFER);
// Create FloatBuffer
vertices = MemoryUtil.memAllocFloat(4096);
// Upload null data to allocate storage for the VBO
long size = vertices.capacity() * Float.BYTES;
vbo.uploadData(GL_ARRAY_BUFFER, size, GL_DYNAMIC_DRAW);
// Initialize variables
numVertices = 0;
drawing = false;
// Load Shaders:
Shader vertexShader, fragmentShader;
if (Game.is32Supported()) {
vertexShader = Shader.loadShader(GL_VERTEX_SHADER, "res/shaders/vshader.vert");
fragmentShader = Shader.loadShader(GL_FRAGMENT_SHADER, "res/shaders/fshader.frag");
} else {
throw new RuntimeException("OpenGL 3.2 not supported.");
}
// Create ShaderProgram
shaderProgram = new ShaderProgram();
shaderProgram.attachShader(vertexShader);
shaderProgram.attachShader(fragmentShader);
if (Game.is32Supported()) {
shaderProgram.bindFragmentDataLocation(0, "fragColor");
}
shaderProgram.link();
shaderProgram.use();
// Delete linked shaders
vertexShader.delete();
fragmentShader.delete();
// Get width & height of framebuffer
long window = GLFW.glfwGetCurrentContext();
int width, height;
try (MemoryStack stack = MemoryStack.stackPush()) {
IntBuffer widthBuffer = stack.mallocInt(1);
IntBuffer heightBuffer = stack.mallocInt(1);
GLFW.glfwGetFramebufferSize(window, widthBuffer, heightBuffer);
width = widthBuffer.get();
height = heightBuffer.get();
}
// Specify vertex pointers
specifyVertexAttributes();
// Set Model Matrix to identity matrix
Matrix4f model = new Matrix4f();
int uniModel = shaderProgram.getUniformLocation("model");
shaderProgram.setUniform(uniModel, model);
// Set View Matrix to identity matrix
Matrix4f view = new Matrix4f();
int uniView = shaderProgram.getUniformLocation("view");
shaderProgram.setUniform(uniView, view);
// Set Projection Matrix to an orthographic projection
Matrix4f projection = Matrix4f.orthographic(0f, width, 0f, height, -1f, 1f);
int uniProjection = shaderProgram.getUniformLocation("projection");
shaderProgram.setUniform(uniProjection, projection);
}
// Specifies the vertex shader pointers (attributes)
private void specifyVertexAttributes() {
int posAttrib = shaderProgram.getAttributeLocation("position");
shaderProgram.enableVertexAttribute(posAttrib);
shaderProgram.pointVertexAttribute(posAttrib, 3, 6 * Float.BYTES, 0);
int colAttrib = shaderProgram.getAttributeLocation("color");
shaderProgram.enableVertexAttribute(colAttrib);
shaderProgram.pointVertexAttribute(colAttrib, 3, 6 * Float.BYTES, 3 * Float.BYTES);
}
}
这是我的 init() 方法,它创建和设置我的 window:
private void init() {
// Setup an error callback. The default implementation
// will print the error message in System.err.
GLFWErrorCallback.createPrint(System.err).set();
// Initialize GLFW. Most GLFW functions will not work before doing this.
if ( !glfwInit() )
throw new IllegalStateException("Unable to initialize GLFW");
// Configure GLFW
glfwDefaultWindowHints(); // optional, the current window hints are already the default
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); // the window will stay hidden after creation
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); // the window will be resizable
// ONLY ON MAC OSX (?)
//glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); // Tell GLFW to use OpenGL verison 3.x
//glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2); // Tell GLFW to use OpenGL version x.2 (combined -> 3.2)
//glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
//glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE); // Should be forward compatible
// Create the window
window = glfwCreateWindow(WIDTH, HEIGHT, "Game_19_v0.0.1", NULL, NULL);
if ( window == NULL )
throw new RuntimeException("Failed to create the GLFW window");
// Setup a key callback. It will be called every time a key is pressed, repeated or released.
glfwSetKeyCallback(window, (window, key, scancode, action, mods) -> {
if ( key == GLFW_KEY_ESCAPE && action == GLFW_RELEASE )
glfwSetWindowShouldClose(window, true); // We will detect this in the rendering loop
});
// Get the thread stack and push a new frame
try ( MemoryStack stack = stackPush() ) {
IntBuffer pWidth = stack.mallocInt(1); // int*
IntBuffer pHeight = stack.mallocInt(1); // int*
// Get the window size passed to glfwCreateWindow
glfwGetWindowSize(window, pWidth, pHeight);
// Get the resolution of the primary monitor
GLFWVidMode vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor());
// Center the window
glfwSetWindowPos(
window,
(vidmode.width() - pWidth.get(0)) / 2,
(vidmode.height() - pHeight.get(0)) / 2
);
} // the stack frame is popped automatically
// Make the OpenGL context current
glfwMakeContextCurrent(window);
// Enable v-sync
glfwSwapInterval(1);
// Make the window visible
glfwShowWindow(window);
// This line is critical for LWJGL's interoperation with GLFW's
// OpenGL context, or any context that is managed externally.
// LWJGL detects the context that is current in the current thread,
// creates the GLCapabilities instance and makes the OpenGL
// bindings available for use.
GL.createCapabilities();
// Input
glfwSetCursorPosCallback(window, cursorPosCallback = new MouseInput());
// Create renderer
renderer = new Renderer();
renderer.init();
// To Render:
buttonManager = new ButtonManager();
}
编辑 2:临时解决方案
我能够使用 glfwWindowHint(GLFW_DECORATED, GLFW_FALSE);
从 window 移除整个边框,包括标题栏,这解决了问题。然而现在,我显然没有关闭、最小化等选项,在我的 window 上,尽管我想我可以在必要时自己编写这些选项。如果我发现任何其他解决方案,将会更新。
GLFW 函数通常使用 window 的客户区(内部 window 区域不包括标题栏、滚动条等),因此 glfwSetCursorPosCallback
为您提供预期值.如果您的 OpenGL 帧缓冲区由于某种原因在标题栏后面渲染内容(无论是不正确的设置还是平台特定的细节),您仍然应该能够使用 glfwGetWindowFrameSize:
IntBuffer pLeft = stack.mallocInt(1); // int*
IntBuffer pTop = stack.mallocInt(1); // int*
IntBuffer pRight = stack.mallocInt(1); // int*
IntBuffer pBottom = stack.mallocInt(1); // int*
// Get the window border sizes
glfwGetWindowFrameSize(window, pLeft, pTop, pRight, pBottom);
(免责声明:我只是遵循上面代码中的语法,因为我只熟悉 C++ API。)
标题栏的大小将存储在 top
变量中,然后可以添加到您从 glfwSetCursorPosCallback
和 glfwGetWindowSize
.
float adjustedYpos = ypos + top;
float adjustedHeight = height + top;
float normalizedY = adjustedYpos / adjustedHeight;
float openglY = normalizedY * -2.0f - 1.0f
这个openglY
值应该是根据标题栏大小调整的OpenGL [-1, 1] clip-space坐标。