OpenGL: escala y luego traducir? y cómo?


Tengo algo de geometría 2D. Quiero tomar un recto delimitador alrededor de mi geometría, y luego renderizar una versión más pequeña de ella en otro lugar del plano. Aquí está más o menos el código que tengo que hacer escala y traducción:

// source and dest are arbitrary rectangles.
float scaleX = dest.width / source.width;
float scaleY = dest.height / source.height;
float translateX = dest.x - source.x;
float translateY = dest.y - source.y;

glScalef(scaleX, scaleY, 0.0);
glTranslatef(translateX, translateY, 0.0);
// Draw geometry in question with its normal verts.

Esto funciona exactamente como se espera para una dimensión dada cuando el origen dest es 0. Pero si el origen de, por ejemplo, x, es distinto de cero, el resultado todavía se escala correctamente, pero se ve como (?) se traduce a algo cerca de cero en ese eje de todos modos turns resulta no es exactamente lo mismo que si Dest.x eran cero.

¿Puede alguien señalar algo obvio que me estoy perdiendo?

Gracias!

ACTUALIZACIÓN FINAL Según las respuestas de Bahbar y Marcus a continuación, hice algo más de experimentación y resolví esto. El comentario de Adam Bowen fue el soplo apagado. Me faltaban dos hechos críticos:

  1. Necesitaba escalar alrededor del centro de la geometría que me importaba.
  2. Necesitaba aplicar las transformadas en el orden opuesto de la intuición (para mí).

El primero es bastante obvio en retrospectiva. Pero para este último, para otros buenos programadores / malos matemáticos como yo: Resulta que mi intuición estaba operando en lo que el Libro Rojo llama un "Gran Sistema de Coordenadas Fijas", en el que hay un plano absoluto, y su geometría se mueve alrededor de ese plano usando transformaciones. Esto está bien, pero dada la naturaleza de las matemáticas detrás de apilar múltiples transformaciones en una matriz, es lo contrario de cómo las cosas realmente funciona (consulte las respuestas a continuación o el Libro Rojo para obtener más información). Básicamente, las transformaciones se "aplican" en "orden inverso" a cómo aparecen en el código. Aquí está la solución de trabajo final:

// source and dest are arbitrary rectangles.
float scaleX = dest.width / source.width;
float scaleY = dest.height / source.height;
Point sourceCenter = centerPointOfRect(source);
Point destCenter = centerPointOfRect(dest);

glTranslatef(destCenter.x, destCenter.y, 0.0);
glScalef(scaleX, scaleY, 0.0);
glTranslatef(sourceCenter.x * -1.0, sourceCenter.y * -1.0, 0.0);
// Draw geometry in question with its normal verts.
Author: genpfault, 2010-02-13

3 answers

En OpenGL, las matrices que especifique se multiplican a la derecha de la matriz existente, y el vértice está en el extremo derecho de la expresión.

Por lo tanto, la última operación que especifique está en el sistema de coordenadas de la geometría misma. (El primero suele ser la transformación de la vista, es decir, la inversa de la transformación de la cámara en el mundo.)

Bahbar hace un buen punto que necesita considerar el punto central para escalar. (o el punto de pivote para rotaciones.) Por lo general se traduce allí, rotar / escalar, luego traducir hacia atrás. (o en general, aplicar transformada de base, la operación, luego la inversa). Esto se llama Cambio de Base , que tal vez quieras leer.

De todos modos, para obtener alguna intuición sobre cómo funciona, intente con algunos valores simples (cero, etc.) luego alterarlos ligeramente (tal vez una animación) y ver qué sucede con la salida. Entonces es mucho más fácil ver lo que sus transformaciones realmente están haciendo a su geometría.

Update

Que el orden está "invertido" w.r.t. intuition es bastante común entre los programadores OpenGL principiantes. He estado dando clases particulares a un curso de gráficos por computadora y muchos reaccionan de una manera similar. Se vuelve más fácil pensar en cómo OpenGL lo hace si considera el uso de pushmatrix / popmatrix mientras renderiza un árbol (scene-graph) de transformaciones y geometrías. Entonces el orden actual de las cosas se vuelve bastante natural, y lo contrario haría que sea bastante difícil obtener cualquier cosa útil hecha.

 17
Author: Macke,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-02-14 17:40:04

La escala, al igual que Rotar, opera desde el origen. así que si escala por la mitad de un objeto que abarca el segmento [10:20] (en el eje X, por ejemplo), se obtiene [5: 10]. Por lo tanto, el objeto se escaló y se acercó al origen. Exactamente lo que observaste.

Esta es la razón por la que se aplica primero la escala en general (porque los objetos tienden a definirse alrededor de 0).

Así que si desea escalar un objeto alrededor del centro del punto, puede traducir el objeto del centro al origen, escalar allí y vuelve a traducir.

Nota al margen, si traduce primero y luego escala, entonces su escala se aplica a la traducción anterior, por lo que probablemente tuvo problemas con este método.

 6
Author: Bahbar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-02-13 20:37:44

No he jugado con OpenGL ES, solo un poco con OpenGL.

Parece que quieres transformar desde una posición diferente a la del origen, no estoy seguro, pero ¿puedes intentar hacer las transformaciones y dibujar ese bit dentro de glPushMatrix() y glPopMatrix()?

e.g.

// source and dest are arbitrary rectangles.
float scaleX = dest.width / source.width;
float scaleY = dest.height / source.height;
float translateX = dest.x - source.x;
float translateY = dest.y - source.y;

glPushMatrix();
  glScalef(scaleX, scaleY, 0.0);
  glTranslatef(translateX, translateY, 0.0);
  // Draw geometry in question with its normal verts.
  //as if it were drawn from 0,0
glPopMatrix();

Aquí hay un boceto de procesamiento simple que escribí para ilustrar el punto:

import processing.opengl.*;
import javax.media.opengl.*;


void setup() {
  size(500, 400, OPENGL);
}

void draw() {
  background(255);
  PGraphicsOpenGL pgl = (PGraphicsOpenGL) g;
  GL gl = pgl.beginGL();  


  gl.glPushMatrix();
    //transform the 'pivot'
    gl.glTranslatef(100,100,0);
    gl.glScalef(10,10,10);
    //draw something from the 'pivot'
    gl.glColor3f(0, 0.77, 0);
    drawTriangle(gl);
  gl.glPopMatrix();
  //matrix poped, we're back to orginin(0,0,0), continue as normal
  gl.glColor3f(0.77, 0, 0);
  drawTriangle(gl);
  pgl.endGL();
}

void drawTriangle(GL gl){
  gl.glBegin(GL.GL_TRIANGLES);
  gl.glVertex2i(10, 0);
  gl.glVertex2i(0, 20);
  gl.glVertex2i(20, 20);
  gl.glEnd();
}

Aquí hay una imagen del croquis corriendo, se dibuja el mismo triángulo verde, con traducción y escala aplicada, luego el rojo, supera el 'bloque' push/pop, por lo que no se ve afectado por la transformación:

texto alt

HTH, George

 1
Author: George Profenza,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-02-08 14:21:05