Como posso deslocar / encolher um polígono triangular no GLSL?

8

Preciso compensar todos os triângulos (azuis), cada um independentemente dos outros, usando o vertex-shader. Para manipular o triângulo como um todo, criei atributos personalizados (vec3) para cada vértice (vermelho) que representa os vértices vizinhos à esquerda (roxa) e à direita (verde). A partir disso, preciso derivar o ponto laranja, equidistante (no espaço da tela ) de ambas as bordas adjacentes. Com três desses pontos laranja derivados de cada triângulo, o triângulo processado (laranja) é passado para o sombreador do fragmento.

por operação de vértice triângulos de deslocamento

Idealmente, o triângulo será selecionado (como em backfacing / não renderizado) se as compensações negarem qualquer espaço disponível dentro do triângulo, como no segundo triângulo da segunda foto.

Estou usando THREE.BufferGeometry () como minha estrutura de dados.

Aqui está uma captura de tela do efeito que estou buscando:

insira a descrição da imagem aqui

Jackalope
fonte
Você poderia adicionar um pouco mais sobre o contexto mais amplo? Os triângulos de deslocamento devem permanecer anexados como na malha original? "Selecionado" significa que o triângulo original é descartado ou apenas que a compensação foi abandonada, deixando o triângulo em seu tamanho original?
Trichoplax #
11
Então ... como isso funciona com malhas? Porque em uma malha, um vértice tem mais de 2 vizinhos. Ou isso é apenas para triângulos individuais?
Nicol Bolas
Minha implementação é tal que todos os triângulos são dispostos em um buffer contínuo: [P1.x, P1.y, P1.z, P2.x, P2.y, P2.z ... Pn.x, Pn.y, Pn.z] com pontos vizinhos também dispostos explicitamente em atributos. Dessa forma, cada vértice de cada face pode ser calculado e manipulado sem afetar as faces vizinhas. Nicol Bolas, sim, lidando com cada triângulo separadamente.
Jackalope
trichoplax - "Abatido" significa jogado fora, não renderizado, como em um primitivo de face única e voltado para trás.
Jackalope
11
@ Jackalope: " Vocês parecem sugerir que a GPU vê rostos" amarrados "a outros rostos. " Isso porque, de um modo geral, isso é verdade. A maioria das malhas não possui apenas triângulos vizinhos que usam "atributos idênticos"; eles reutilizam os mesmos vértices . Isso pode ser feito através de listas de triângulos que usam o mesmo índice várias vezes, ou através de tiras de triângulos ou qualquer outra coisa. Mas de um modo geral, as malhas reutilizam os vértices vizinhos. Suas malhas não, mas o seu caso específico não altera o caso geral. Por isso pedi esclarecimentos.
Nicol Bolas

Respostas:

9

Dado o triângulo ▲ ABC, dividimos o ângulo ACBAC com a linha AD, derivada do Teorema do Bisetor de Ângulos :

BA / BD = CA / CD O Diagrama de inserção de triângulo ponto E representa nossa posição refinada objetiva no triângulo de inserção resultante desejado. Como se encontra na bissetriz de ângulo AD, é equidistante dos lados BA e CA, formando triângulos retângulos idênticos ▲ AFE e ▲ AGE. Agora podemos usar Seno para Triângulos Retos para encontrar o comprimento de AE:

EA = EG / Sin (AGEAG)

Isso é tudo o que precisamos, então vamos preparar um GLSL!

Começamos com todos os atributos típicos: matrizes de posição, normal e de transformação, mas como o sombreador de vértice funciona apenas em um único vértice, precisamos adicionar os vértices vizinhos como atributos adicionais. Dessa forma, cada vértice encontrará seu próprio "ponto E", criando o triângulo inserido resultante. (Nota: não os chamo de "B" e "C" aqui, porque eles ainda não estão no espaço da tela .)

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

Por falar em espaço na tela, também incluo a proporção da tela (e a uniformidade, caso a janela seja redimensionada).

Depois de preparar variáveis ​​normais para o shader de fragmento e transformar a face em espaço de recorte, podemos começar a aplicar a matemática acima:

        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 fornece os resultados abaixo.

Captura de tela

Nota, há alguns borda casos que têm a ver com quase triângulos abatidos-Backface sendo capotou por este processo, e eu comecei a abordar isso no código, mas decidiu simplesmente evitar estes casos por agora. Talvez eu o revise quando concluir esse projeto.

Jackalope
fonte
11
Bom trabalho para descobrir isso! Realmente gosto da descrição matemática no começo.
User1118321
0

Isso pode ser alcançado sem funções trigonométricas, reduzindo o círculo do triângulo.

incircle()calcula o incirculo do triângulo formado pelos vértices A,B,C, retorna centro e raio como vec4. Os vértices X=A,B,Csão então movidos para dentro pela fração de sua distância ao centro do círculo ( Q-X), que é igual à razão da margem desejada para o raio do 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ão
fonte
Muito interessante, Adam! Eu não tinha ouvido falar sobre essa função.
Jackalope