Girando um vetor por outro vetor no shader

8

Eu tenho uma superfície de terreno com um normal para cada ponto no terreno.

Eu tenho um segundo mapa normal de detalhes a ser aplicado ao terreno.

  • Essas normais estão em 3 espaços.

  • O valor Y de ambos os normais é sempre positivo.

  • Os valores X, Z de ambos os normais podem ser positivos / negativos / zero.

  • O primeiro vetor normal (azul) pelo qual estamos girando o segundo vetor (laranja) pode ser quase horizontal.

Eu estou bem com uma solução aproximada, se torna mais fácil / rápido calcular. Imagem da superfície azul normal, mapa normal laranja normal e resultado verde desejado normal.

Na imagem acima, você vê a superfície azul normal (no 1º mapa normal), o mapa normal laranja (no segundo mapa normal) e o resultado desejado verde normal.

A quantidade em que o vetor laranja é rotacionado deve ser aproximadamente (ou se possível) exatamente igual ao ângulo que o vetor normal azul forma com o plano XZ (onde Y está ativo, como o sistema de coordenadas do DirectX).

Aqui está um segundo cenário:

A superfície azul normal é quase horizontal, portanto o 2º mapa normal está sendo aplicado a uma superfície quase vertical, portanto o vetor normal laranja do mapa é girado ainda mais

Na imagem acima, a superfície azul normal é quase horizontal; portanto, o 2º mapa normal está sendo aplicado a uma superfície quase vertical, assim o vetor laranja normal do mapa é girado ainda mais.

A rotação está sendo implementada em um shader HLSL.

Como eu giro a 1ª normal de laranja com base na direção da 2ª normal de azul?

Edit: Talvez eu precise de uma tangente e bitangente, bem como o normal?

Aqui está como eu obtenho o normal:

float4 ComputeNormals(VS_OUTPUT input) : COLOR
{
    float2 uv = input.TexCoord;

    // top left, left, bottom left, top, bottom, top right, right, bottom right
    float tl = abs(tex2D(HeightSampler, uv + TexelSize * float2(-1, -1)).x);
    float  l = abs(tex2D(HeightSampler, uv + TexelSize * float2(-1,  0)).x);
    float bl = abs(tex2D(HeightSampler, uv + TexelSize * float2(-1,  1)).x);
    float  t = abs(tex2D(HeightSampler, uv + TexelSize * float2( 0, -1)).x);
    float  b = abs(tex2D(HeightSampler, uv + TexelSize * float2( 0,  1)).x);
    float tr = abs(tex2D(HeightSampler, uv + TexelSize * float2( 1, -1)).x);
    float  r = abs(tex2D(HeightSampler, uv + TexelSize * float2( 1,  0)).x);
    float br = abs(tex2D(HeightSampler, uv + TexelSize * float2( 1,  1)).x);

    // Compute dx using Sobel filter.
    //           -1  0  1 
    //           -2  0  2
    //           -1  0  1
    float dX = tr + 2*r + br - tl - 2*l - bl;

    // Compute dy using Sobel filter.
    //           -1 -2 -1 
    //            0  0  0
    //            1  2  1
    float dY = bl + 2*b + br - tl - 2*t - tr;

    // Compute cross-product and renormalize
    float3 N = normalize(float3(-dX, NormalStrength, -dY));    

    // Map [-1.0 , 1.0] to [0.0 , 1.0];
    return float4(N * 0.5f + 0.5f, 1.0f);
}

Como posso obter os vetores tangente e bitangente?

É suficiente pegar o produto cruzado do normal com o vetor unitário do eixo Z para encontrar o vetor tangente? (Como o normal.Y é sempre positivo, onde Y está ativo e Z está apontando na tela).

E então pegue esse vetor tangente e cruze-o com o normal para obter o bitangente?

E então pegue o normal, tangente e bitangente juntos para formar uma matriz de rotação para girar o vetor de mapa normal laranja?

Mesmo que isso funcione, parece muita computação para um pixel shader. Isso funciona e existe uma maneira mais eficiente?

Editar:

Esta imagem pode ajudá-lo a entender o que estou tentando fazer: Mapeamento normal.

Se você sabe o que é o mapeamento normal, acho que isso deve ser direto.

Estou tentando pegar o mapa normal, que contém vários normais, e aplicá-los a uma superfície. A superfície tem seus próprios normais. O mapa normal conterá muito mais normais do que a superfície; portanto, vários normais normais do mapa são amostrados em uma única parte da superfície que possui apenas um único normal.

Olhovsky
fonte
Sua pergunta não faz sentido. Você não pode girar um vetor por um vetor (você pode girar em torno de um vetor e, de fato, deve especificar um eixo de rotação em três espaços). Você pode encontrar o ângulo que um vetor forma com um plano. Mas, no caso de uma superfície normal, é definida como 90 graus. Além disso, girar um vetor em 90 graus não faz sentido, a menos que você especifique, como mencionei, o eixo de rotação.
Andrew Russell
A superfície normal está a 90 graus da superfície e em algum outro ângulo do plano XZ (com Y sendo levantado e Z voltado para a tela, como no Direct X). Eu quero girar o vetor pelo ângulo que o normal se forma com o plano XZ.
Olhovsky
O que estou tentando fazer é aplicar um mapa normal a uma superfície do terreno. Eu acho que uma maneira é encontrar o vetor tangente e bitangente e depois usar aqueles com a superfície normal para formar uma matriz, que eu multiplico pelo mapa normal laranja normal, para obter o resultado verde normal. Não sei como encontrar a tangente e tangente, ou se existe uma maneira mais fácil. Atualizei minha pergunta para mostrar como estou conseguindo o normal do terreno.
Olhovsky
Adicionei outra imagem à minha pergunta para deixar mais claro o que estou tentando fazer. Nada realmente sofisticado, apenas mapeamento normal.
Olhovsky
Seu último diagrama faz muito mais sentido.
22711 Andrew Russell

Respostas:

4

Acho que recebi sua pergunta, embora o título e as 2 primeiras fotos sejam confusos.

O problema é que existe um mapa normal para ser mapeado em um plano xz (o vetor laranja). Agora, o plano real descrito pelo normal azul não está necessariamente no plano xz; portanto, é necessário encontrar uma rotação do plano xz para o plano real, a fim de girar o vetor laranja para mapeá-lo no plano real.

Agora, como podemos fazer isso? Bacialmente, devemos encontrar a rotação, que gira o plano xz para o plano descrito pela normal azul.

  1. Encontre o eixo de rotação usando o produto cruzado do normal azul e do normal no plano xz {0, 1, 0}, o vetor laranja deve ser rotacionado em torno desse eixo.

    axis = N_xz cross N_blue
  2. Encontre o ângulo de rotação tomando arccosina do produto escalar das duas normais

    angle = acos(N_xz dot N_blue)
  3. Agora crie uma matriz de rotação a partir desses valores e transforme o normal laranja com essa matriz.

Maik Semder
fonte
O que você descreve pode funcionar, mas estou cansado de fazer tudo isso em um shader, 920000 vezes por quadro (ou seja, uma vez por pixel). Posso apenas formar uma matriz do normal, cruz (normal, float3 (1,0,0)) e cruz (normal, float3 (0,0,1))? Estou tentando isso agora, embora não pareça funcionar.
Olhovsky
Adicionei uma imagem à minha pergunta para deixar mais claro o que estou tentando fazer. Nada realmente sofisticado, apenas mapeamento normal.
Olhovsky
Você pode calcular essa matriz uma vez por plano no shader de vértice e passá-la ao pixel-shader ou na CPU por plano e passar essa matriz ao shader. Mas isso é otimizar, primeiro fazê-lo funcionar e entender como fazê-lo, em seguida, otimizá-lo;)
Maik Semder
Ok, tentando agora.
Olhovsky
Isso funciona, +1. Obrigado. No entanto, é um pouco lento. Estou tentando descobrir como armazenar um quaternion para essa matriz de rotação em vez de armazenar o normal azul e depois construir o normal a partir desse quaternion. Você sabe como eu posso fazer isso?
precisa saber é o seguinte
2

Eu acredito que você acabou pensando no seu problema.

Para voltar ao mapeamento normal padrão, você normalmente possui 3 vetores: a superfície normal, um vetor tangente perpendicular a esse e um cotangente perpendicular a ambos. Juntos, eles formam o espaço tangente no qual o mapa normal descreve uma direção. Com essa configuração, basta multiplicar a leitura de cada componente do mapa normal pelo respectivo eixo do espaço tangente e somar os resultados.

Então você tem um terreno e um mapa normal que gostaria de aplicar a ele. Você já descobriu como obter a altura delta e gerar uma superfície normal. Agora você está procurando a tangente e cotangente.

Então, o que é uma tangente? Bem, a partir da descrição acima, na verdade, é a direção 3D descrita por um dos eixos UV no seu mapeamento de texturas. Afinal, é para isso que é usado, certo? Então, como você encontra essa direção? Bem, dada uma malha arbitrária, é um pouco complicado; mas, dado um terreno de grade regular, é evidente: se você tem uma grade regular com UVs regulares, as direções tangente e cotangente são apenas as duas arestas alinhadas pelo eixo, deixando seu vértice.

Então, você sabe quais são as alturas de pontos ao seu redor. Você pode passar a que distância esses pontos estão no avião. Subtraia-os do ponto em que você está, normalize e pronto: espaço tangente instantâneo.

Chris Subagio
fonte
+1 porque esta é uma contribuição útil. Eu sei que você pode formar uma matriz básica que nos transforma em um espaço tangente (que é o que você está sugerindo, eu acho). Não sei como formar. Sua resposta meio que faz alusão a como começar, mas não mostra realmente como fazê-lo. Por exemplo: "Você pode indicar a que distância esses pontos estão no plano. Subtraia-os do ponto em que está, normalize e pronto: espaço tangente instantâneo". é realmente vago. Atualmente, meus mapas normais não estão no espaço tangente. São 4 da manhã aqui, então vou revisitar isso amanhã.
precisa saber é o seguinte
0

Se você também tiver a tangente, poderá usar esta função para gerar a normalidade que você procura. Caso contrário, sinta-se livre para plagiar no futuro!

//------------------------------------------------------------------------------
// Transforms a normal map sample to world space.
//------------------------------------------------------------------------------


  float3 NormalSampleToWorldSpace(float3 normalMapSample, float3 unitNormalW, float3 tangentW)
    {
        // Uncompress each component from [0,1] to [-1,1].
        float3 normalT = 2.0f * normalMapSample - 1.0f;

        // Build orthonormal basis.
        float3 N = unitNormalW;
        float3 T = normalize(tangentW - dot(tangentW, N) * N);
        float3 B = cross(N, T);

        float3x3 TBN = float3x3(T, B, N);

        // Transform from tangent space to world space.
        float3 bumpedNormalW = mul(normalT, TBN);

        return bumpedNormalW;
    }
ErnieDingo
fonte
-1

Você pode usar a função LERP, que irá interpolar linearmente entre os dois vetores. Dando-lhe um bom normal "média".

http://msdn.microsoft.com/en-us/library/bb509618(VS.85).aspx

(É claro que isso não é realmente rotativo, mas, considerando o vetor laranja e azul, você obterá o vetor verde)

Roy T.
fonte
Obrigado. No entanto, o vetor azul pode ser cerca de 30 graus elevado do plano XZ, caso em que a leitura não funcionará (a menos que esteja faltando alguma coisa?). Eu reconheço sua foto, você escreveu um exemplo de pesquisa A * para XNA.
precisa saber é o seguinte
Ei, sim, sou eu :). Sobre sua pergunta, acho que o lerping ainda funcionará; talvez seja necessário renormalizar o vetor. Se não funcionar, você pode fornecer um pequeno caso de teste (não precisa estar no HLSL, o Vector da XNA também possui Lerp) com dois vetores, um resultado esperado e um resultado de retorno, talvez eu possa lhe dizer mais .
Roy T.
Adicionei uma segunda imagem para ilustrar por que o lerping não funciona. O mapa normal laranja normal deve ser girado para que se aplique à superfície. Por exemplo, se você tiver uma superfície normal igual ao mapa normal laranja normal, mas ambas forem giradas, a leitura de qualquer quantidade fornecerá o mesmo vetor e nenhuma rotação ocorrerá.
Olhovsky