如何在OpenGL中使用glOrtho()?


87

我不了解的用法glOrtho。有人可以解释它的用途吗?

是否用于设置xy和z坐标限制的范围?

glOrtho(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0);

这意味着x,y和z范围是-1到1?


1
这部影片对我有很大帮助。
ViniciusArruda

Answers:


153

看看这张照片:图形投影 在此处输入图片说明

glOrtho命令将产生一个“斜线”投影,您可以在底行看到它。无论顶点在z方向上有多远,它们都不会退缩到该距离中。

每当我需要在OpenGL中进行2D图形处理(例如健康栏,菜单等)时,每次调整窗口大小时都使用以下代码来使用glOrtho:

glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0.0f, windowWidth, windowHeight, 0.0f, 0.0f, 1.0f);

这会将OpenGL坐标重新映射为等效的像素值(X从0到windowWidth,Y从0到windowHeight)。请注意,由于OpenGL坐标从窗口的左下角开始,所以我已经翻转了Y值。因此,通过翻转,我从窗口的左上角开始获得了更常规的(0,0)。

请注意,Z值从0剪切到1。因此,当为顶点位置指定Z值时,请注意,如果Z值超出该范围,则会被剪切。否则,如果它在该范围内,则除Z测试外,对位置无影响。


90
哦,天哪,我爱你。您是否知道在线查找/找出这一行代码要花费多长时间?谢谢,我要为此而命名我的第一个孩子
karpathy 2010年

2
注意:(在Android上)即使模型仅具有负z值,最终(远)参数似乎也必须具有正值。我做了一个简单的三角形测试(禁用了剔除功能),顶点为z= -2。如果使用,或glOrtho(.., 0.0f, -4.0f);,则三角形不可见。可见,far参数必须为POSITIVE 2或更大;似乎无关紧要的参数是什么。所有这些工作: ,,,或。..-1.0f, -3.0f)..-3.0f, -1.0f)..0.0f, 2.0f)..-1.0f, 2.0f)..-3.0f, 2.0f)..0.0f, 1000.0f
ToolmakerSteve

9
荒谬的关于OpenGl的大量教程太荒谬了。
basickarl 2014年

1
@Kari,希望此链接可以有所帮助。> learningopengl.com/#!In-Practice/2D-Game/Rendering-Sprites
huahsin68'2

1
@mgouin z范围指定您的Z平面和Z平面的位置。绘制几何图形时,它的Z值必须在两个Z平面内。如果它们落在Z平面之外,则不会渲染您的几何图形。此外,渲染器仅具有一定的深度分辨率。如果您将远平面设置为1000个单位的距离,并且尝试绘制一个彼此相距0.1个单位的小面的微型模型,则OpenGL将无法为您提供所需的深度分辨率,并且会发生Z角化(闪烁)在面孔之间。
Mikepote

54

最小的可运行示例

glOrtho:2D游戏,远近物体的大小相同:

在此处输入图片说明

glFrustrum:像3D这样的更真实的生活,距离较远的相同对象显得更小:

在此处输入图片说明

main.c

#include <stdlib.h>

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>

static int ortho = 0;

static void display(void) {
    glClear(GL_COLOR_BUFFER_BIT);
    glLoadIdentity();
    if (ortho) {
    } else {
        /* This only rotates and translates the world around to look like the camera moved. */
        gluLookAt(0.0, 0.0, -3.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
    }
    glColor3f(1.0f, 1.0f, 1.0f);
    glutWireCube(2);
    glFlush();
}

static void reshape(int w, int h) {
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    if (ortho) {
        glOrtho(-2.0, 2.0, -2.0, 2.0, -1.5, 1.5);
    } else {
        glFrustum(-1.0, 1.0, -1.0, 1.0, 1.5, 20.0);
    }
    glMatrixMode(GL_MODELVIEW);
}

int main(int argc, char** argv) {
    glutInit(&argc, argv);
    if (argc > 1) {
        ortho = 1;
    }
    glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
    glutInitWindowSize(500, 500);
    glutInitWindowPosition(100, 100);
    glutCreateWindow(argv[0]);
    glClearColor(0.0, 0.0, 0.0, 0.0);
    glShadeModel(GL_FLAT);
    glutDisplayFunc(display);
    glutReshapeFunc(reshape);
    glutMainLoop();
    return EXIT_SUCCESS;
}

GitHub上游

编译:

gcc -ggdb3 -O0 -o main -std=c99 -Wall -Wextra -pedantic main.c -lGL -lGLU -lglut

运行glOrtho

./main 1

运行glFrustrum

./main

在Ubuntu 18.10上测试。

架构图

正交:相机是一个平面,可见体积是一个矩形:

在此处输入图片说明

截锥体:相机是一个点,可见体积是一块金字塔:

在此处输入图片说明

图片来源

参量

我们一直在寻找从+ z到-z且+ y向上的位置:

glOrtho(left, right, bottom, top, near, far)
  • left:最低限度x我们看到
  • rightx我们看到的最大值
  • bottom:最低限度y我们看到
  • topy我们看到的最大值
  • -near:最低限度,z我们看到了。是的,这是-1时代near。因此,负输入表示正z
  • -farz我们看到的最大值。也是负面的。

架构:

图片来源

它是如何工作的

最后,OpenGL总是“使用”:

glOrtho(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0);

如果我们既不使用glOrtho也不使用glFrustrum,那就是我们得到的。

glOrtho并且glFrustrum仅仅是线性变换(AKA矩阵乘法),使得:

  • glOrtho:将给定的3D矩形放入默认立方体
  • glFrustrum:将给定的金字塔截面放入默认的多维数据集

然后,此转换将应用于所有顶点。这就是我在二维中的意思:

图片来源

转换后的最后一步很简单:

  • 删除点立方体外(剔除):只是保证xyz[-1, +1]
  • 忽略该z组件,仅使用xy,现在可以将其放到2D屏幕中

使用glOrtho,会z被忽略,因此您最好还是使用0

您可能要使用的一个原因z != 0是使精灵使用深度缓冲区隐藏背景。

弃用

glOrthoOpenGL 4.5开始不推荐使用:兼容性配置文件12.1。“固定功能顶点转换”为红色。

因此,请勿将其用于生产。无论如何,了解它是获得OpenGL见识的好方法。

现代OpenGL 4程序在CPU上计算转换矩阵(很小),然后将矩阵和所有要转换的点提供给OpenGL,Op​​enGL可以真正并行地对不同的点进行数千次矩阵乘法。

然后,手动编写的顶点着色器通常使用OpenGL着色语言的便捷矢量数据类型来显式进行乘法。

由于您明确编写了着色器,因此您可以根据需要调整算法。这种灵活性是更现代的GPU的一个主要功能,与旧的GPU(使用某些输入参数执行固定算法的旧有的GPU)不同,现在可以执行任意计算。另请参阅:https : //stackoverflow.com/a/36211337/895245

显式的GLfloat transform[]看起来像这样:

#include <math.h>
#include <stdio.h>
#include <stdlib.h>

#define GLEW_STATIC
#include <GL/glew.h>

#include <GLFW/glfw3.h>

#include "common.h"

static const GLuint WIDTH = 800;
static const GLuint HEIGHT = 600;
/* ourColor is passed on to the fragment shader. */
static const GLchar* vertex_shader_source =
    "#version 330 core\n"
    "layout (location = 0) in vec3 position;\n"
    "layout (location = 1) in vec3 color;\n"
    "out vec3 ourColor;\n"
    "uniform mat4 transform;\n"
    "void main() {\n"
    "    gl_Position = transform * vec4(position, 1.0f);\n"
    "    ourColor = color;\n"
    "}\n";
static const GLchar* fragment_shader_source =
    "#version 330 core\n"
    "in vec3 ourColor;\n"
    "out vec4 color;\n"
    "void main() {\n"
    "    color = vec4(ourColor, 1.0f);\n"
    "}\n";
static GLfloat vertices[] = {
/*   Positions          Colors */
     0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f,
    -0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f,
     0.0f,  0.5f, 0.0f, 0.0f, 0.0f, 1.0f
};

int main(void) {
    GLint shader_program;
    GLint transform_location;
    GLuint vbo;
    GLuint vao;
    GLFWwindow* window;
    double time;

    glfwInit();
    window = glfwCreateWindow(WIDTH, HEIGHT, __FILE__, NULL, NULL);
    glfwMakeContextCurrent(window);
    glewExperimental = GL_TRUE;
    glewInit();
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glViewport(0, 0, WIDTH, HEIGHT);

    shader_program = common_get_shader_program(vertex_shader_source, fragment_shader_source);

    glGenVertexArrays(1, &vao);
    glGenBuffers(1, &vbo);
    glBindVertexArray(vao);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    /* Position attribute */
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)0);
    glEnableVertexAttribArray(0);
    /* Color attribute */
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
    glEnableVertexAttribArray(1);
    glBindVertexArray(0);

    while (!glfwWindowShouldClose(window)) {
        glfwPollEvents();
        glClear(GL_COLOR_BUFFER_BIT);

        glUseProgram(shader_program);
        transform_location = glGetUniformLocation(shader_program, "transform");
        /* THIS is just a dummy transform. */
        GLfloat transform[] = {
            0.0f, 0.0f, 0.0f, 0.0f,
            0.0f, 0.0f, 0.0f, 0.0f,
            0.0f, 0.0f, 1.0f, 0.0f,
            0.0f, 0.0f, 0.0f, 1.0f,
        };
        time = glfwGetTime();
        transform[0] = 2.0f * sin(time);
        transform[5] = 2.0f * cos(time);
        glUniformMatrix4fv(transform_location, 1, GL_FALSE, transform);

        glBindVertexArray(vao);
        glDrawArrays(GL_TRIANGLES, 0, 3);
        glBindVertexArray(0);
        glfwSwapBuffers(window);
    }
    glDeleteVertexArrays(1, &vao);
    glDeleteBuffers(1, &vbo);
    glfwTerminate();
    return EXIT_SUCCESS;
}

GitHub上游

输出:

在此处输入图片说明

的矩阵glOrtho真的很简单,仅由缩放和转换组成:

scalex, 0,      0,      translatex,
0,      scaley, 0,      translatey,
0,      0,      scalez, translatez,
0,      0,      0,      1

OpenGL 2文档所述

glFrustum矩阵是不是太难手工计算下去,但开始变得讨厌。请注意如何仅用缩放和转换(例如)无法弥补平截头体glOrtho,更多信息,访问:https : //gamedev.stackexchange.com/a/118848/25171

GLM OpenGL C ++数学库是计算此类矩阵的常用选择。http://glm.g-truc.net/0.9.2/api/a00245.html记录了orthofrustum操作。


1
“应该用什么代替?” -构建自己的矩阵并直接分配。
克罗姆斯特

4

glOrtho描述了产生平行投影的变换。当前矩阵(请参阅glMatrixMode)乘以该矩阵,结果将替换当前矩阵,就像使用以下矩阵作为参数调用glMultMatrix一样:

OpenGL文档(我的粗体)

数字定义剪切平面的位置(左,右,底部,顶部,附近和远处)。

“正常”投影是提供深度幻觉的透视投影。维基百科将平行投影定义为:

平行投影的投影线在现实中和投影平面中都平行。

平行投影对应于具有假设视点的透视投影,例如,相机距离物体无穷远且具有无限焦距或“变焦”的视点投影。


嗨,谢谢你的信息。我不太了解平行投影和透视投影之间的区别。我在Google上搜索了一下,并在wiki.answers.com/Q/…中
ufk 2010年

6
不幸的是,您从Answers.com获得的信息是毫无价值的。例如,等轴测图是非常3D的,但它是没有透视图的平行投影。参见此处,还有指向许多其他投影示例的链接:en.wikipedia.org/wiki/Isometric_projection
Ben Voigt 2010年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.