Skewed: uma câmera rotativa em um simples raycaster / raytracer voxel baseado em CPU

7

TL; DR - no meu primeiro software simples voxel raycaster, não consigo fazer as rotações da câmera funcionarem, apesar das matrizes aparentemente corretas. O resultado é distorcido: como uma renderização plana, girada corretamente, porém distorcida e sem profundidade. (Enquanto alinhados ao eixo, ou seja, sem rotação, profundidade e paralaxe são os esperados.)

Estou tentando escrever um simples voxel raycaster como um exercício de aprendizado. Isso é puramente baseado em CPU por enquanto, até que eu descubra como as coisas funcionam exatamente - agora, o OpenGL é apenas (ab) usado para mesclar o bitmap gerado na tela o mais rápido possível.

Agora cheguei ao ponto em que uma câmera de projeção em perspectiva pode se mover pelo mundo e posso renderizar (principalmente, menos alguns artefatos que precisam de investigação) vistas tridimensionais corretas em perspectiva do "mundo", que está basicamente vazio, mas contém um cubo de voxel do Stanford Bunny.

Então, eu tenho uma câmera que eu posso mover para cima e para baixo, esticar para a esquerda e para a direita e "andar para frente / para trás" - todos alinhados por eixos até agora, sem rotações da câmera. Aqui reside o meu problema.

Screenshot # 1: profundidade correta quando a câmera ainda está estritamente alinhada ao eixo, ou seja. sem rotação.

insira a descrição da imagem aqui

Agora, há alguns dias, tenho tentado fazer a rotação funcionar. A lógica e a teoria básicas por trás das matrizes e rotações 3D, em teoria, são muito claras para mim. No entanto, só consegui uma "renderização 2,5" quando a câmera gira ... olho de peixe, como no Google Streetview: embora eu tenha uma representação volumétrica do mundo, parece - não importa o que eu tente - como faria primeiro crie uma renderização a partir da "vista frontal" e gire essa renderização plana de acordo com a rotação da câmera. Escusado será dizer que agora estou ciente de que os raios rotativos não são particularmente necessários e propensos a erros.

Ainda assim, na minha configuração mais recente, com o algoritmo de posição e direção dos raios mais simplificado possível de raycast, minha rotação ainda produz a mesma aparência de estilo com rotação de renderização plana:

Captura de tela 2: câmera "girada para a direita em 39 graus" - observe como o lado esquerdo do cubo com sombra azul da tela nº 2 não está visível nesta rotação, mas agora "realmente deveria"!

insira a descrição da imagem aqui

Agora, é claro, estou ciente disso: em uma configuração simples de alinhamento de eixo sem rotação, como eu fiz no início, o raio simplesmente atravessa em pequenos passos a direção z positiva, divergindo para a esquerda ou direita e para cima. ou inferior apenas dependendo da posição do pixel e da matriz de projeção. Quando eu "giro a câmera para a direita ou esquerda" - ou seja, eu a giro em torno do eixo Y - esses mesmos passos devem ser simplesmente transformados pela matriz de rotação adequada, certo? Portanto, para a marcha à frente, o passo Z fica um pouco menor quanto mais a came gira, compensado por um "aumento" no passo X. No entanto, para a divergência horizontal + vertical baseada na posição do pixel, frações crescentes da etapa x precisam ser "adicionadas" à etapa z. De alguma forma, nenhuma das minhas muitas matrizes com as quais experimentei,

Aqui está o meu algoritmo básico de pré-passagem por raio - sintaxe no Go, mas use-o como pseudocódigo:

  • fx e fy : posições em pixels x e y
  • rayPos : vec3 para a posição inicial do raio no espaço mundial (calculado como abaixo)
  • rayDir : vec3 para as etapas xyz serem adicionadas à rayPos em cada etapa durante a passagem de raio
  • rayStep : um vec3 temporário
  • camPos : vec3 para a posição da câmera no espaço mundial
  • camRad : vec3 para rotação da câmera em radianos
  • pmat : matriz de projeção em perspectiva típica

O algoritmo / pseudocódigo:

// 1: rayPos is for now "this pixel, as a vector on the view plane in 3d, at The Origin"
rayPos.X, rayPos.Y, rayPos.Z = ((fx / width) - 0.5), ((fy / height) - 0.5), 0

// 2: rotate around Y axis depending on cam rotation. No prob since view plane still at Origin 0,0,0
rayPos.MultMat(num.NewDmat4RotationY(camRad.Y))

// 3: a temp vec3. planeDist is -0.15 or some such — fov-based dist of view plane from eye and also the non-normalized, "in axis-aligned world" traversal step size "forward into the screen"
rayStep.X, rayStep.Y, rayStep.Z = 0, 0, planeDist

// 4: rotate this too — 0,zstep should become some meaningful xzstep,xzstep
rayStep.MultMat(num.NewDmat4RotationY(CamRad.Y))

// set up direction vector from still-origin-based-ray-position-off-rotated-view-plane plus rotated-zstep-vector
rayDir.X, rayDir.Y, rayDir.Z = -rayPos.X - me.rayStep.X, -rayPos.Y, rayPos.Z + rayStep.Z

// perspective projection
rayDir.Normalize()
rayDir.MultMat(pmat)

// before traversal, the ray starting position has to be transformed from origin-relative to campos-relative
rayPos.Add(camPos)

Estou pulando as partes transversais e de amostragem - conforme a captura de tela nº 1, essas são "basicamente basicamente corretas" (embora não sejam bonitas) - quando alinhadas / não rotacionadas por eixo.

voxelizr
fonte
Interessante ... Tenho a impressão de que você omitiu uma captura de tela entre os nºs 1 e 2, é isso?
Usuário não encontrado

Respostas:

2

Seu pseudo-código na linguagem Go é um grande mistério para mim, então tentarei descrever minha abordagem :)

Antes de tudo, você não precisa de nenhuma matriz projetiva em perspectiva. Tudo o que você precisa para construir o raio de visão atual é:

  • posição da câmera
  • camera up vector
  • olhar de câmera no vetor
  • pixel atual na tela

Vamos supor que nossa câmera gratuita funcione corretamente. Agora pegamos nossos vetores e executamos o processo de Gram – Schmidt . Isso nos dá uma base de câmera ortogonal. No GLSL, esta etapa é mais ou menos assim:

vec3 cameraW = normalize(cameraPosition - cameraLookAt);
vec3 cameraU = normalize(cross(cameraUp, cameraW));
vec3 cameraV = cross(cameraW, cameraU);

Ok, nós temos base. Essa base é comum para todos os pixels na tela. Ainda mais, é invariável até movermos ou girarmos a câmera. Para calcular o raio de visualização do pixel atual, é necessário:

vec3 rayOrigin = cameraPosition;
vec3 rayDirection = normalize(p.x * cameraU + p.y * cameraV - cameraViewPlaneDistance * cameraW);

where p- é coord do seu pixel atual.

Depois disso, você tem o raio de visão. Já é hora de fazer algumas coisas de raytracing ou raymarching.

Vamos voltar para a câmera gratuita. Para avançar ou retroceder, simplesmente mudamos a posição da câmera na direção do vetor alvo. Para mover para a esquerda ou direita, extraímos o vetor direito da matriz de rotação da câmera e o adicionamos aos vetores de destino e posição.

Para girar a câmera, usamos a matriz de rotação da câmera. A primeira matriz de rotação da câmera é a matriz de identidade. Temos três carros alegóricos: guinada, pitch and roll. Quando o usuário move o mouse, esse flutuador acumula ângulos de rotação. Na atualização, devemos aplicar essas rotações à nossa matriz de rotação da câmera. Vamos guiar, o que corresponde às rotações em torno do eixo superior. Primeiro, extraímos o vetor da matriz de rotação e depois construímos uma nova matriz de rotação. Afinal, multiplicamos a matriz de rotação da câmera e a nova. Aqui estão alguns códigos C ++ que usam a biblioteca glm :

vec3 up(cameraRotation[0][1], cameraRotation[1][1], cameraRotation[2][1]);
cameraRotation *= glm::rotate(mat4(1.0), yaw, up);

Espero que isto ajude..

iodiota
fonte