จะใช้ glOrtho () ใน OpenGL ได้อย่างไร?


89

ฉันไม่เข้าใจการใช้งานของglOrtho. ใครช่วยอธิบายว่ามันใช้ทำอะไร?

ใช้กำหนดช่วงพิกัด xy และ z หรือไม่?

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

หมายความว่าช่วง x, y และ z อยู่ระหว่าง -1 ถึง 1?


1
วิดีโอนี้ช่วยฉันได้มาก
ViniciusArruda

คำตอบ:


156

ดูภาพนี้: การฉายภาพ ใส่คำอธิบายภาพที่นี่

glOrthoคำสั่งผลิต "เอียง" ฉายที่คุณเห็นในแถวด้านล่าง ไม่ว่าจุดยอดจะอยู่ห่างออกไปในทิศทาง z แค่ไหนก็จะไม่ถอยกลับไปในระยะทาง

ฉันใช้ glOrtho ทุกครั้งที่ต้องทำกราฟิก 2 มิติใน OpenGL (เช่นแถบสุขภาพเมนู ฯลฯ ) โดยใช้รหัสต่อไปนี้ทุกครั้งที่ปรับขนาดหน้าต่าง:

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

สิ่งนี้จะทำการแมปพิกัด OpenGL ให้เป็นค่าพิกเซลที่เท่ากัน (X จาก 0 เป็น windowWidth และ Y จาก 0 เป็น windowHeight) โปรดทราบว่าฉันได้พลิกค่า Y เนื่องจากพิกัด OpenGL เริ่มจากมุมล่างซ้ายของหน้าต่าง ดังนั้นโดยการพลิกฉันจะได้รับแบบธรรมดามากขึ้น (0,0) โดยเริ่มที่มุมซ้ายบนของหน้าต่าง

โปรดทราบว่าค่า Z ถูกตัดจาก 0 ถึง 1 ดังนั้นโปรดใช้ความระมัดระวังเมื่อคุณระบุค่า Z สำหรับตำแหน่งจุดยอดของคุณค่านั้นจะถูกตัดออกหากอยู่นอกช่วงนั้น มิฉะนั้นหากอยู่ในช่วงดังกล่าวดูเหมือนว่าจะไม่มีผลกับตำแหน่งยกเว้นการทดสอบ Z


91
โอ้พระเจ้าฉันรักคุณ คุณทราบหรือไม่ว่าต้องใช้เวลานานแค่ไหนในการค้นหา / ค้นหาโค้ดบรรทัดเดียวทางออนไลน์ ขอบคุณฉันจะตั้งชื่อลูกคนแรกของฉันตามคุณสำหรับสิ่งนี้
karpathy

2
หมายเหตุ: (บน Android) แม้ว่าโมเดลจะมีค่า z ที่เป็นลบเท่านั้น แต่ดูเหมือนว่าจำเป็นต้องมีค่าบวกสำหรับพารามิเตอร์สุดท้าย (ไกล) ฉันไม่ได้ทดสอบอย่างง่ายสามเหลี่ยม (มีการเลือกสรรคนพิการ) z= -2กับจุดที่ สามเหลี่ยมมองไม่เห็นถ้าผมใช้glOrtho(.., 0.0f, -4.0f);, หรือ..-1.0f, -3.0f) ..-3.0f, -1.0f)เพื่อให้มองเห็นได้พารามิเตอร์ far จะต้องเป็น POSITIVE 2 ขึ้นไป ดูเหมือนจะไม่สำคัญว่าพารามิเตอร์ใกล้คืออะไร ใด ๆ เหล่านี้ทำงาน: ..0.0f, 2.0f), ..-1.0f, 2.0f), หรือ..-3.0f, 2.0f) ..0.0f, 1000.0f
ToolmakerSteve

10
มันไร้สาระที่มีบทช่วยสอนที่ไม่ดีใน OpenGl
basickarl

1
@Kari หวังว่าลิงค์นี้จะช่วยได้ > learnopengl.com/#!In-Practice/2D-Game/Rendering-Sprites
huahsin68

1
@mgouin ช่วง z ระบุว่าเครื่องบิน Z-near และเครื่องบิน Z-far ของคุณอยู่ที่ไหน เมื่อคุณวาดรูปทรงเรขาคณิตค่า Z จะต้องอยู่ในระนาบ Z ทั้งสอง หากพวกมันตกนอกระนาบ Z รูปทรงเรขาคณิตของคุณจะไม่แสดงผล นอกจากนี้ตัวแสดงผลของคุณยังมีความละเอียดเฉพาะสำหรับความลึกเท่านั้น หากคุณตั้งค่าระนาบไกลของคุณไว้ที่ 1,000 หน่วยและคุณลองวาดแบบจำลองขนาดเล็กที่มีใบหน้าน้อย ๆ ห่างจากกัน 0.1 หน่วย OpenGL จะไม่สามารถให้ความละเอียดเชิงลึกที่คุณต้องการได้และคุณจะได้รับการต่อสู้แบบ Z (การกะพริบ) ระหว่างใบหน้า
Mikepote

56

ตัวอย่างที่รันได้น้อยที่สุด

glOrtho: เกม 2 มิติวัตถุที่อยู่ใกล้และไกลมีขนาดเท่ากัน:

ใส่คำอธิบายภาพที่นี่

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

สคีมา

Ortho: กล้องเป็นระนาบปริมาตรที่มองเห็นได้เป็นรูปสี่เหลี่ยมผืนผ้า:

ใส่คำอธิบายภาพที่นี่

Frustrum: กล้องเป็นจุดปริมาตรที่มองเห็นได้ชิ้นหนึ่งของปิรามิด:

ใส่คำอธิบายภาพที่นี่

ที่มาของภาพ

พารามิเตอร์

เรามักจะมองจาก + z ถึง -z ด้วย + y ขึ้นไป:

glOrtho(left, right, bottom, top, near, far)
  • left: ขั้นต่ำที่xเราเห็น
  • right: สูงสุดที่xเราเห็น
  • bottom: ขั้นต่ำที่yเราเห็น
  • top: สูงสุดที่yเราเห็น
  • -near: ขั้นต่ำที่zเราเห็น ใช่นี้เป็นครั้ง-1 ดังนั้นลบอินพุตหมายในเชิงบวกnearz
  • -far: สูงสุดที่zเราเห็น ลบด้วย

Schema:

ที่มาของภาพ

วิธีการทำงานภายใต้ประทุน

ในท้ายที่สุด OpenGL จะ "ใช้" เสมอ:

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

ถ้าเราใช้ค่าglOrthoมิได้glFrustrumว่าเป็นสิ่งที่เราได้รับ

glOrthoและglFrustrumเป็นเพียงการแปลงเชิงเส้น (การคูณเมทริกซ์ AKA) เช่นนั้น:

  • glOrtho: นำสี่เหลี่ยมผืนผ้า 3 มิติที่กำหนดมาไว้ในคิวบ์เริ่มต้น
  • glFrustrum: นำส่วนปิรามิดที่กำหนดมาไว้ในคิวบ์เริ่มต้น

จากนั้นการเปลี่ยนแปลงนี้จะถูกนำไปใช้กับจุดยอดทั้งหมด นี่คือสิ่งที่ฉันหมายถึงใน 2D:

ที่มาของภาพ

ขั้นตอนสุดท้ายหลังการเปลี่ยนแปลงนั้นง่ายมาก:

  • ลบใด ๆ นอกจุดที่ก้อน (คัด): เพียงแค่ให้มั่นใจว่าx, yและzอยู่ใน[-1, +1]
  • ละเว้นzส่วนประกอบและรับเฉพาะxและyซึ่งตอนนี้สามารถใส่ลงในหน้าจอ 2D ได้

ด้วยglOrtho, จะถูกละเว้นดังนั้นคุณอาจรวมทั้งการใช้งานเสมอz0

เหตุผลหนึ่งที่คุณอาจต้องการใช้z != 0คือทำให้สไปรต์ซ่อนพื้นหลังด้วยบัฟเฟอร์ความลึก

การเลิกใช้งาน

glOrthoเลิกใช้แล้วเมื่อOpenGL 4.5 : โปรไฟล์ความเข้ากันได้ 12.1 "FIXED-FUNCTION VERTEX TRANSFORMATIONS" เป็นสีแดง

ดังนั้นอย่าใช้เพื่อการผลิต ไม่ว่าในกรณีใดการทำความเข้าใจเป็นวิธีที่ดีในการรับข้อมูลเชิงลึกของ OpenGL

โปรแกรม OpenGL 4 สมัยใหม่จะคำนวณเมทริกซ์การแปลง (ซึ่งมีขนาดเล็ก) บน CPU จากนั้นให้เมทริกซ์และจุดทั้งหมดที่จะเปลี่ยนเป็น OpenGL ซึ่งสามารถทำการคูณเมทริกซ์หลายพันรายการสำหรับจุดต่างๆได้อย่างรวดเร็วแบบขนาน

เฉดสีจุดยอดที่เขียนด้วยตนเองจากนั้นทำการคูณอย่างชัดเจนโดยปกติจะใช้ชนิดข้อมูลเวกเตอร์ที่สะดวกของ OpenGL Shading Language

เนื่องจากคุณเขียน shader อย่างชัดเจนจึงช่วยให้คุณปรับแต่งอัลกอริทึมตามความต้องการของคุณได้ ความยืดหยุ่นดังกล่าวเป็นคุณสมบัติหลักของ GPU ที่ทันสมัยกว่าซึ่งแตกต่างจากรุ่นเก่าที่ทำอัลกอริธึมคงที่พร้อมพารามิเตอร์อินพุตบางตัวตอนนี้สามารถคำนวณได้ตามอำเภอใจ ดูเพิ่มเติม: https://stackoverflow.com/a/36211337/895245

ด้วยความชัดเจนGLfloat transform[]มันจะมีลักษณะดังนี้:

glfw_transform.c

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

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

#include <GLFW/glfw3.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
};

/* Build and compile shader program, return its ID. */
GLuint common_get_shader_program(
    const char *vertex_shader_source,
    const char *fragment_shader_source
) {
    GLchar *log = NULL;
    GLint log_length, success;
    GLuint fragment_shader, program, vertex_shader;

    /* Vertex shader */
    vertex_shader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertex_shader, 1, &vertex_shader_source, NULL);
    glCompileShader(vertex_shader);
    glGetShaderiv(vertex_shader, GL_COMPILE_STATUS, &success);
    glGetShaderiv(vertex_shader, GL_INFO_LOG_LENGTH, &log_length);
    log = malloc(log_length);
    if (log_length > 0) {
        glGetShaderInfoLog(vertex_shader, log_length, NULL, log);
        printf("vertex shader log:\n\n%s\n", log);
    }
    if (!success) {
        printf("vertex shader compile error\n");
        exit(EXIT_FAILURE);
    }

    /* Fragment shader */
    fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragment_shader, 1, &fragment_shader_source, NULL);
    glCompileShader(fragment_shader);
    glGetShaderiv(fragment_shader, GL_COMPILE_STATUS, &success);
    glGetShaderiv(fragment_shader, GL_INFO_LOG_LENGTH, &log_length);
    if (log_length > 0) {
        log = realloc(log, log_length);
        glGetShaderInfoLog(fragment_shader, log_length, NULL, log);
        printf("fragment shader log:\n\n%s\n", log);
    }
    if (!success) {
        printf("fragment shader compile error\n");
        exit(EXIT_FAILURE);
    }

    /* Link shaders */
    program = glCreateProgram();
    glAttachShader(program, vertex_shader);
    glAttachShader(program, fragment_shader);
    glLinkProgram(program);
    glGetProgramiv(program, GL_LINK_STATUS, &success);
    glGetProgramiv(program, GL_INFO_LOG_LENGTH, &log_length);
    if (log_length > 0) {
        log = realloc(log, log_length);
        glGetProgramInfoLog(program, log_length, NULL, log);
        printf("shader link log:\n\n%s\n", log);
    }
    if (!success) {
        printf("shader link error");
        exit(EXIT_FAILURE);
    }

    /* Cleanup. */
    free(log);
    glDeleteShader(vertex_shader);
    glDeleteShader(fragment_shader);
    return program;
}

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 อัปสตรี

รวบรวมและเรียกใช้:

gcc -ggdb3 -O0 -o glfw_transform.out -std=c99 -Wall -Wextra -pedantic glfw_transform.c -lGL -lGLU -lglut -lGLEW -lglfw -lm
./glfw_transform.out

เอาท์พุต:

ใส่คำอธิบายภาพที่นี่

เมทริกซ์สำหรับ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เอกสารทั้ง an orthoและfrustumoperation


1
“ จะใช้อะไรแทนดี?” - สร้างเมทริกซ์ของคุณเองและกำหนดโดยตรง
Kromster

ฉันมีช่วงเวลาที่ยากลำบากในการรวบรวมตัวอย่างโค้ดสุดท้ายของคุณ (การเปลี่ยนรูปสามเหลี่ยม) ฉันได้โคลนที่เก็บ แต่ฉันเพิ่งได้รับข้อผิดพลาดcommon.h:19:23: error: ‘TIME_UTC’ undeclared (first use in this function) timespec_get(&ts, TIME_UTC);
Ivanzinho

1
@Ivanzinho ฉันไม่สามารถทำซ้ำบน Ubuntu 20.04 ได้ซึ่งน่าจะเกิดขึ้นเพราะอยู่ใน C11 ซึ่ง GCC ของคุณยังไม่ได้ใช้ แต่ตอนนี้ฉันย่อตัวอย่างของคำตอบนี้โดยไม่ใช้ร่วมกัน h เท่าที่ฉันควรจะทำมาก่อนดังนั้นมันควรจะใช้งานได้ :-)
Ciro Santilli 郝海东冠状病六四事件法轮功

4

glOrtho อธิบายการเปลี่ยนแปลงที่ก่อให้เกิดการฉายภาพคู่ขนาน เมทริกซ์ปัจจุบัน (ดู glMatrixMode) คูณด้วยเมทริกซ์นี้และผลลัพธ์จะแทนที่เมทริกซ์ปัจจุบันราวกับว่า glMultMatrix ถูกเรียกด้วยเมทริกซ์ต่อไปนี้เป็นอาร์กิวเมนต์:

เอกสาร OpenGL (ตัวหนาของฉัน)

ตัวเลขกำหนดตำแหน่งของระนาบการตัด (ซ้ายขวาล่างบนสุดใกล้และไกล)

การฉายภาพ "ปกติ" คือการฉายภาพมุมมองที่ให้ภาพลวงตาของความลึก Wikipediaให้คำจำกัดความของการฉายภาพคู่ขนานว่า:

เส้นโครงร่างขนานมีเส้นฉายที่ขนานกันทั้งในความเป็นจริงและในระนาบการฉาย

การฉายภาพคู่ขนานสอดคล้องกับการฉายภาพมุมมองที่มีมุมมองสมมุติเช่นจุดที่กล้องอยู่ห่างจากวัตถุเป็นระยะทางไม่สิ้นสุดและมีทางยาวโฟกัสไม่สิ้นสุดหรือ "ซูม"


สวัสดีขอบคุณสำหรับข้อมูล ฉันไม่ค่อยเข้าใจความแตกต่างระหว่างการฉายภาพแบบขนานและมุมมอง ฉัน googled เล็กน้อยและพบคำตอบในwiki.answers.com/Q/…
ufk

6
น่าเสียดายที่ข้อมูลที่คุณได้รับจาก answer.com นั้นค่อนข้างไร้ค่า ตัวอย่างเช่นมุมมองภาพสามมิติเป็น 3 มิติมาก แต่เป็นการฉายภาพคู่ขนานที่ไม่มีมุมมอง ดูที่นี่และยังมีลิงก์ไปยังตัวอย่างการคาดการณ์อื่น ๆ อีกมากมาย: en.wikipedia.org/wiki/Isometric_projection
Ben Voigt
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.