Esempio minimo eseguibile
glOrtho
: Giochi 2D, oggetti vicini e lontani appaiono della stessa dimensione:
glFrustrum
: più reali come il 3D, oggetti identici più lontani appaiono più piccoli:
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 {
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 a monte .
Compilare:
gcc -ggdb3 -O0 -o main -std=c99 -Wall -Wextra -pedantic main.c -lGL -lGLU -lglut
Corri con glOrtho
:
./main 1
Corri con glFrustrum
:
./main
Testato su Ubuntu 18.10.
Schema
Orto: la fotocamera è un piano, il volume visibile un rettangolo:
Frustrum: la fotocamera è un punto, il volume visibile una fetta di piramide:
Fonte dell'immagine .
Parametri
Cerchiamo sempre da + z a -z con + y verso l'alto:
glOrtho(left, right, bottom, top, near, far)
left
: minimo x
vediamo
right
: massimo x
vediamo
bottom
: minimo y
vediamo
top
: massimo y
vediamo
-near
: minimo z
vediamo. Sì , questo è il -1
momento near
. Quindi un input negativo significa positivo z
.
-far
: massimo z
vediamo. Anche negativo.
Schema:
Fonte dell'immagine .
Come funziona sotto il cofano
Alla fine, OpenGL "usa" sempre:
glOrtho(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0);
Se non usiamo né glOrtho
né glFrustrum
, questo è ciò che otteniamo.
glOrtho
e glFrustrum
sono solo trasformazioni lineari (moltiplicazione di matrici AKA) tali che:
glOrtho
: prende un dato rettangolo 3D nel cubo predefinito
glFrustrum
: prende una data sezione della piramide nel cubo predefinito
Questa trasformazione viene quindi applicata a tutti i vertici. Questo è ciò che intendo in 2D:
Fonte dell'immagine .
Il passaggio finale dopo la trasformazione è semplice:
- rimuovere tutti i punti al di fuori del cubo (culling): assicurati solo che
x
, y
e z
sono dentro[-1, +1]
- ignora il
z
componente e prendi solo x
e y
, che ora può essere messo in uno schermo 2D
Con glOrtho
, z
viene ignorato, quindi potresti anche usare sempre 0
.
Uno dei motivi che potresti voler usare z != 0
è fare in modo che gli sprite nascondano lo sfondo con il buffer di profondità.
Deprecazione
glOrtho
è deprecato a partire da OpenGL 4.5 : il profilo di compatibilità 12.1. "TRASFORMAZIONI VERTICE A FUNZIONE FISSA" è in rosso.
Quindi non usarlo per la produzione. In ogni caso, comprenderlo è un buon modo per ottenere alcune informazioni su OpenGL.
I moderni programmi OpenGL 4 calcolano la matrice di trasformazione (che è piccola) sulla CPU, quindi danno la matrice e tutti i punti da trasformare in OpenGL, che può eseguire migliaia di moltiplicazioni di matrici per diversi punti molto velocemente in parallelo.
Gli shader dei vertici scritti manualmente eseguono quindi la moltiplicazione in modo esplicito, di solito con i comodi tipi di dati vettoriali dell'OpenGL Shading Language.
Poiché scrivi lo shader in modo esplicito, questo ti consente di modificare l'algoritmo in base alle tue esigenze. Tale flessibilità è una delle principali caratteristiche delle GPU più moderne, che a differenza di quelle vecchie che eseguivano un algoritmo fisso con alcuni parametri di input, ora possono eseguire calcoli arbitrari. Vedi anche: https://stackoverflow.com/a/36211337/895245
Con un esplicito GLfloat transform[]
sarebbe simile a questo:
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;
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[] = {
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
};
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 = 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 = 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);
}
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);
}
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);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);
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");
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 a monte .
Compila ed esegui:
gcc -ggdb3 -O0 -o glfw_transform.out -std=c99 -Wall -Wextra -pedantic glfw_transform.c -lGL -lGLU -lglut -lGLEW -lglfw -lm
./glfw_transform.out
Produzione:
La matrice per glOrtho
è davvero semplice, composta solo da ridimensionamento e traslazione:
scalex, 0, 0, translatex,
0, scaley, 0, translatey,
0, 0, scalez, translatez,
0, 0, 0, 1
come menzionato nei documenti di OpenGL 2 .
Anche la glFrustum
matrice non è troppo difficile da calcolare a mano, ma inizia a diventare fastidiosa. Nota come il frustum non può essere composto solo con ridimensionamento e traduzioni come glOrtho
, maggiori informazioni su: https://gamedev.stackexchange.com/a/118848/25171
La libreria matematica GLM OpenGL C ++ è una scelta popolare per il calcolo di tali matrici. http://glm.g-truc.net/0.9.2/api/a00245.html documenti sia una ortho
e frustum
le operazioni.