Nie mogę zrozumieć użycia glOrtho
. Czy ktoś może wyjaśnić, do czego służy?
Czy służy do ustawiania zakresu limitu współrzędnych xy i z?
glOrtho(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0);
Oznacza to, że zakres x, yiz wynosi od -1 do 1?
Odpowiedzi:
Spójrz na ten obrazek: Projekcje graficzne
glOrtho
Komenda wywołuje projekcję „ukryte”, które można zobaczyć w dolnym rzędzie. Bez względu na to, jak daleko znajdują się wierzchołki w kierunku z, nie cofną się one w dal.
Używam glOrtho za każdym razem, gdy potrzebuję zrobić grafikę 2D w OpenGL (takie jak paski zdrowia, menu itp.), Używając następującego kodu za każdym razem, gdy zmienia się rozmiar okna:
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0.0f, windowWidth, windowHeight, 0.0f, 0.0f, 1.0f);
Spowoduje to przemapowanie współrzędnych OpenGL na równoważne wartości pikseli (X od 0 do windowWidth, a Y od 0 do windowHeight). Zwróć uwagę, że odwróciłem wartości Y, ponieważ współrzędne OpenGL zaczynają się od lewego dolnego rogu okna. Tak więc, odwracając, otrzymuję bardziej konwencjonalny (0,0), zaczynając raczej od lewego górnego rogu okna.
Zwróć uwagę, że wartości Z są obcinane od 0 do 1. Dlatego uważaj, kiedy określasz wartość Z dla położenia wierzchołka, zostanie on obcięty, jeśli wykracza poza ten zakres. W przeciwnym razie, jeśli znajduje się w tym zakresie, wydaje się, że nie ma wpływu na pozycję, z wyjątkiem testów Z.
z= -2
. Trójkąt był niewidoczny jeśli użyłem glOrtho(.., 0.0f, -4.0f);
, ..-1.0f, -3.0f)
albo ..-3.0f, -1.0f)
. Aby był widoczny, parametr far musiał być DODATNI 2 lub większy; wydawało się, że nie ma znaczenia, jaki był parametr w pobliżu. Każda z nich pracował: ..0.0f, 2.0f)
, ..-1.0f, 2.0f)
, ..-3.0f, 2.0f)
, lub ..0.0f, 1000.0f
.
Minimalny działający przykład
glOrtho
: Gry 2D, obiekty bliskie i dalekie mają ten sam rozmiar:
glFrustrum
: bardziej realistyczne, jak 3D, identyczne obiekty dalej wydają się mniejsze:
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;
}
Skompilować:
gcc -ggdb3 -O0 -o main -std=c99 -Wall -Wextra -pedantic main.c -lGL -lGLU -lglut
Uruchom z glOrtho
:
./main 1
Uruchom z glFrustrum
:
./main
Testowane na Ubuntu 18.10.
Schemat
Orto: kamera to płaszczyzna, widoczna objętość to prostokąt:
Frustrum: kamera to punkt, widoczna objętość kawałek piramidy:
Parametry
Zawsze patrzymy od + z do -z z + y w górę:
glOrtho(left, right, bottom, top, near, far)
left
: minimum x
widzimyright
: maksimum, x
które widzimybottom
: minimum y
widzimytop
: maksimum, y
które widzimy-near
: minimum z
widzimy. Tak , to -1
czasy near
. Zatem ujemne wejście oznacza pozytywne z
.-far
: maksimum, z
które widzimy. Również negatywne.Schemat:
Jak to działa pod maską
Ostatecznie OpenGL zawsze „używa”:
glOrtho(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0);
Jeśli nie użyjemy ani glOrtho
nie glFrustrum
, otrzymamy to.
glOrtho
i glFrustrum
są po prostu przekształceniami liniowymi (mnożenie macierzy AKA), takimi, że:
glOrtho
: pobiera dany prostokąt 3D do domyślnej kostkiglFrustrum
: przenosi daną sekcję piramidy do domyślnej kostkiTa transformacja jest następnie stosowana do wszystkich wierzchołków. To właśnie mam na myśli w 2D:
Ostatni krok po transformacji jest prosty:
x
, y
i z
są w[-1, +1]
z
komponent i weź tylko x
i y
, które teraz można umieścić na ekranie 2DZ glOrtho
, z
jest ignorowane, więc równie dobrze możesz zawsze używać 0
.
Jednym z powodów, dla których możesz chcieć użyć, z != 0
jest sprawienie, by duszki ukrywały tło za pomocą bufora głębi.
Dezaprobata
glOrtho
jest przestarzałe od OpenGL 4.5 : profil zgodności 12.1. „TRANSFORMACJE VERTEX O STAŁEJ FUNKCJI” jest zaznaczone na czerwono.
Więc nie używaj go do produkcji. W każdym razie zrozumienie tego jest dobrym sposobem na uzyskanie wglądu w OpenGL.
Nowoczesne programy OpenGL 4 obliczają macierz transformacji (która jest mała) na CPU, a następnie przekazują macierz i wszystkie punkty do przekształcenia do OpenGL, który może wykonywać tysiące mnożenia macierzy dla różnych punktów naprawdę szybko równolegle.
Następnie ręcznie napisane moduły cieniujące wierzchołków wykonują mnożenie w sposób jawny, zwykle z wygodnymi typami danych wektorowych języka OpenGL Shading Language.
Ponieważ moduł cieniujący piszesz jawnie, pozwala to dostosować algorytm do własnych potrzeb. Taka elastyczność jest główną cechą nowocześniejszych procesorów graficznych, które w przeciwieństwie do starych, które stosowały stały algorytm z niektórymi parametrami wejściowymi, mogą teraz wykonywać dowolne obliczenia. Zobacz też: https://stackoverflow.com/a/36211337/895245
Z wyraźnym GLfloat transform[]
wyglądem wyglądałoby to mniej więcej tak:
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;
}
Skompiluj i uruchom:
gcc -ggdb3 -O0 -o glfw_transform.out -std=c99 -Wall -Wextra -pedantic glfw_transform.c -lGL -lGLU -lglut -lGLEW -lglfw -lm
./glfw_transform.out
Wynik:
Matryca dla glOrtho
jest naprawdę prosta, składa się tylko ze skalowania i tłumaczenia:
scalex, 0, 0, translatex,
0, scaley, 0, translatey,
0, 0, scalez, translatez,
0, 0, 0, 1
jak wspomniano w dokumentacji OpenGL 2 .
glFrustum
Matryca nie jest zbyt trudne do obliczenia przez strony albo, ale zaczyna się denerwujące. Zwróć uwagę, że frustum nie można nadrobić tylko ze skalowania i tłumaczeń glOrtho
, więcej informacji na: https://gamedev.stackexchange.com/a/118848/25171
Biblioteka matematyczna GLM OpenGL C ++ jest popularnym wyborem do obliczania takich macierzy. http://glm.g-truc.net/0.9.2/api/a00245.html dokumentuje zarówno an, jak ortho
i frustum
operacje.
common.h:19:23: error: ‘TIME_UTC’ undeclared (first use in this function) timespec_get(&ts, TIME_UTC);
glOrtho opisuje transformację, która tworzy równoległą projekcję. Bieżąca macierz (patrz glMatrixMode) jest mnożona przez tę macierz, a wynik zastępuje aktualną macierz, tak jakby glMultMatrix zostało wywołane z następującą macierzą jako argumentem:
Dokumentacja OpenGL (moja pogrubienie)
Liczby określają położenie płaszczyzn tnących (lewa, prawa, dół, góra, blisko i daleko).
„Normalna” projekcja to rzut perspektywiczny, który zapewnia iluzję głębi. Wikipedia definiuje równoległe odwzorowanie jako:
Rzuty równoległe mają linie rzutowania, które są równoległe zarówno w rzeczywistości, jak i na płaszczyźnie rzutowania.
Rzutowanie równoległe odpowiada rzutowi perspektywicznemu z hipotetycznym punktem widzenia - np. Takim, w którym kamera znajduje się w nieskończonej odległości od obiektu i ma nieskończoną ogniskową, czyli „zoom”.