¿Cómo puedo compensar / reducir un polígono triangular en GLSL?

8

Necesito compensar todos los triángulos (azules), cada uno independientemente de los demás, usando el sombreador de vértices. Para manipular el triángulo como un todo, he creado atributos personalizados (vec3) para cada vértice (rojo) que representa los vértices vecinos hacia la izquierda (púrpura) y hacia la derecha (verde). A partir de esto, necesito derivar el punto naranja, equidistante (en el espacio de la pantalla ) de ambos bordes adyacentes. Con tres de estos puntos naranjas derivados de cada triángulo, el triángulo procesado (naranja) se pasa al sombreador de fragmentos.

por operación de vértice triángulos compensados

Idealmente, el triángulo se eliminará (como en el retroceso / no renderizado) si los desplazamientos niegan cualquier espacio disponible dentro del triángulo, como en el segundo triángulo en la segunda imagen.

Estoy usando THREE.BufferGeometry () como mi estructura de datos.

Aquí hay una captura de pantalla del efecto al que apunto:

ingrese la descripción de la imagen aquí

Jackalope
fuente
¿Podría agregar un poco más sobre el contexto más amplio? ¿Los triángulos desplazados permanecerán unidos como en la malla original? ¿"Descartado" significa que el triángulo original se descarta, o simplemente que se abandona la compensación, dejando el triángulo en su tamaño original?
trichoplax
1
Entonces ... ¿cómo funciona esto con mallas? Porque en una malla, un vértice tiene más de 2 vecinos. ¿O es solo para triángulos individuales?
Nicol Bolas
Mi implementación es tal que todos los triángulos se presentan en un búfer continuo: [P1.x, P1.y, P1.z, P2.x, P2.y, P2.z ... Pn.x, Pn.y, Pn.z] con puntos vecinos también establecidos explícitamente en los atributos. De esta manera, cada vértice de cada cara se puede calcular y manipular sin afectar a las caras vecinas. Nicol Bolas, sí, lidiando con cada triángulo por separado.
Jackalope
trichoplax - "Culled" significa tirado, no renderizado, como en un primitivo de un solo lado orientado hacia atrás.
Jackalope
1
@Jackalope: " Ambos parecen sugerir que la GPU ve las caras como" atadas "a otras caras " . Eso es porque, en términos generales, esto es cierto. La mayoría de las mallas no solo tienen triángulos vecinos que usan "atributos idénticos"; reutilizan los mismos vértices . Esto podría ser a través de listas de triángulos que usan el mismo índice varias veces, o a través de tiras de triángulos, o lo que sea. Pero, en general, las mallas reutilizan los vértices vecinos. Sus mallas no lo hacen, pero su caso específico no cambia el caso general. Por eso pedí una aclaración.
Nicol Bolas

Respuestas:

9

Dado el triángulo ▲ ABC, bisecamos el ángulo ∠BAC con la línea AD, derivada con el teorema de la bisectriz de ángulo :

BA / BD = CA / CD El Diagrama de inserción triangular punto E representa nuestra posición objetiva refinada en el triángulo de inserción resultante deseado. Como yace sobre la bisectriz de ángulo AD, es equidistante de los lados BA y CA, formando triángulos rectángulos idénticos ▲ AFE y ▲ EDAD. Ahora podemos usar Seno para triángulos rectángulos para encontrar la longitud de AE:

AE = EG / Sin (∠EAG)

¡Esas son todas las matemáticas que necesitamos, así que cocinemos un poco de GLSL!

Comenzamos con todos los atributos típicos: posición, normal y matrices de transformación, pero dado que el sombreador de vértices solo funciona en un solo vértice, necesitamos agregar los vértices vecinos como atributos adicionales. De esta manera, cada vértice encontrará su propio "punto E", creando el triángulo insertado resultante. (Nota: no los llamo "B" y "C" aquí, porque todavía no están en el espacio de la pantalla ).

    attribute vec3 left; //vertex to the left of this vertex
    attribute vec3 right; //vertex to the right of this vertex

Hablando del espacio de la pantalla, también incluyo la relación de aspecto de la pantalla (y la hago uniforme, en caso de que la ventana cambie de tamaño).

Después de preparar diferentes valores normales para el sombreador de fragmentos y de transformar la cara en un espacio de recorte, podemos dedicarnos al negocio de aplicar las matemáticas anteriores:

        attribute vec3 left; //vertex to the left of this vertex
        attribute vec3 right; //vertex to the right of this vertex
        uniform float aspect;
        varying vec3 vNormal;
        varying vec2 vUv;

        void main() {
            vNormal = normal;
            vUv = uv;

            mat4 xform= projectionMatrix * modelViewMatrix;
            vec4 A = xform * vec4( position, 1.0 );
            vec4 B = xform * vec4( left, 1.0 );
            vec4 C = xform * vec4( right, 1.0 );

            vec3 CB = C.xyz - B.xyz;
            vec2 BA = B.xy - A.xy;
            vec2 CA = C.xy - A.xy;
            float lengthBA = length(BA);
            float lengthCA = length(CA);
            float ratio = lengthBA / ( lengthBA + lengthCA );
            vec3 D = B.xyz + ratio * CB.xyz;
            vec3 AD = D - A.xyz;
            vec3 bisect = normalize(AD);

            float theta = acos( dot(BA, CA) / (lengthBA * lengthCA) ) / 2.0;
            float AE = 1.0/(sin(theta)*aspect);
            newPos.z += AE/length(AD) * (D.z - A.z);
            newPos.x += bisect.x*AE;
            newPos.y += bisect.y*AE;

            gl_Position = newPos;
        }

Este código nos da los resultados a continuación.

Captura de pantalla

Tenga en cuenta que hay algunos casos extremos que tienen que ver con triángulos casi sin fondo que se voltean por este proceso, y comencé a abordar esto en código, pero decidí simplemente evitar estos casos por ahora. Quizás lo vuelva a visitar cuando termine este proyecto.

Jackalope
fuente
1
¡Buen trabajo resolviendo esto! Realmente me gusta la descripción matemática al principio.
user1118321
0

Esto se puede lograr sin funciones trigonométricas reduciendo la escala del triángulo.

incircle()calcula el círculo del triángulo formado por los vértices A,B,C, devuelve centro y radio como vec4. Los vértices X=A,B,Cse mueven hacia adentro por la fracción de su distancia al centro del círculo ( Q-X), que es igual a la proporción del margen deseado al radio del círculo ( m/Q.w).

vec4 incircle(vec3 A, vec3 B, vec3 C) {
    float a = length(B - C), b = length(C - A), c = length(A - B);
    float abc = a + b + c;
    // http://mathworld.wolfram.com/Incenter.html
    vec3 I = (a * A + b * B + c * C) / abc;
    // http://mathworld.wolfram.com/Inradius.html
    float r = 0.5
            * sqrt((-a + b + c) * (a - b + c) * (a + b - c) / abc);
    return vec4(I, r);
}

vec3 A,B,C; // vertices
float m; // margin
vec4 Q = incircle(A,B,C);
A += clamp(m / Q.w, 0.0, 1.0) * (Q.xyz - A);
B += clamp(m / Q.w, 0.0, 1.0) * (Q.xyz - B);
C += clamp(m / Q.w, 0.0, 1.0) * (Q.xyz - C);
Adán
fuente
Muy interesante, Adam! No había escuchado sobre esta función.
Jackalope