Terremoto interno: siempre considere alternativas

imagen

El programador Michael Abrash, quien fue invitado por John Carmack para trabajar en el motor del primer Quake a mediados de los 90, escribió una serie de artículos durante el proceso de desarrollo. Esta es la segunda columna de esta serie. La traducción del primero está aquí .

Debo admitir: me cans√© del rock cl√°sico. La √ļltima vez que estuve feliz de escuchar algo de Cars o Boston hace mucho tiempo, hace unos 20 a√Īos. Adem√°s, nunca me sent√≠ particularmente atra√≠do por Bob Seager y Queen, por no mencionar a Elvis, tan poco ha cambiado. Pero me di cuenta de que algo cambi√≥ cuando quise cambiar la radio cuando escuch√© a Allman Brothers, o Steely Dan, o Pink Floyd, o, Dios, perd√≥name, los Beatles (pero solo en cosas como "Hola adi√≥s" y "Voy a En cambio, llora, no Ticket to Ride o A Day in the Life; todav√≠a no he llegado tan lejos). No tard√≥ mucho en encontrar las razones para esto; Escuch√© las mismas canciones durante un cuarto de siglo y me cans√© de ellas.

Lo cuento todo esto porque cuando mi hija y yo salimos del café una noche, la estación de radio "No hay alternativa" se encendió por primera vez en el automóvil.

Estamos hablando de una ni√Īa de diez a√Īos que creci√≥ con una dieta constante de viejos √©xitos. Le gustan las melod√≠as, las canciones pegadizas y los buenos cantantes. No encontrar√° nada de esto cuando escuche una estaci√≥n de rock alternativa. Por lo tanto, no es sorprendente que cuando encend√≠ la radio, ella dijo primero: "¬°Fu!"

Pero esto es lo que me sorprendió: después de escuchar por un tiempo, ella dijo: "Sabes, papá, pero eso es realmente interesante".

Esto no solo me insinu√≥ sobre qu√© tipo de m√ļsica sonar√≠a en la casa cuando se convirtiera en una adolescente. Su r√°pida adopci√≥n del rock alternativo (en comparaci√≥n con mi fascinaci√≥n por la m√ļsica de mi propia juventud durante diez a√Īos) me record√≥ algo que se olvida f√°cilmente cuando envejeces y el estilo de vida se establece. Esto me record√≥ que era necesario mantener una mente abierta y estar preparado, adem√°s, esforzarse, para probar cosas nuevas. Los programadores tienden a apegarse a enfoques familiares y son propensos a usarlos si hacen frente adecuadamente a las tareas. Pero siempre hay alternativas a la programaci√≥n, y creo que a menudo vale la pena explorarlas.

Pero dada la naturaleza siempre cambiante de Quake , realmente no debería necesitar ese recordatorio.

Flujo creativo


En enero, describ√≠ una secuencia creativa que llev√≥ a John Carmack a decidir utilizar pol√≠gonos de conjuntos potencialmente visibles (PVS) precalculados para cada punto de vista posible en Quake (un juego que estamos desarrollando conjuntamente en id Software). El c√°lculo preliminar de PVS significa que, en lugar de pasar mucho tiempo buscando en la base de datos de pol√≠gonos visibles desde el punto de vista actual en la base de datos mundial, simplemente podemos dibujar todos los pol√≠gonos en el PVS de atr√°s hacia adelante (tomando el orden del √°rbol BSP del mundo; discusi√≥n de BSP- vea √°rboles en nuestras columnas para mayo, julio y noviembre de 1995), y obtenga la escena renderizada correctamente por completo sin buscar, permitiendo que el renderizado hacia atr√°s realice el √ļltimo paso de eliminaci√≥n de superficie oculta (HSR). Fue una idea incre√≠ble, pero para la arquitectura Quake, el camino a√ļn no se ha completado.

Dibujando objetos en movimiento


Por ejemplo, todavía ha habido una pregunta sobre cómo ordenar y dibujar correctamente los objetos en movimiento; de hecho, esta pregunta después de la columna de enero se hizo sobre todo, así que le daré tiempo. El principal problema es que un modelo en movimiento puede caer en varias hojas BSP, y cuando el modelo se mueve, estas hojas cambian; junto con la posibilidad de encontrar varios modelos en una hoja, esto significa que no hay una manera fácil de usar el orden BSP para dibujar modelos en un orden ordenado correctamente. Cuando escribí la columna de enero, dibujamos sprites (como explosiones), modelos BSP móviles (como puertas) y modelos poligonales (como monstruos), truncando cada uno de ellos con las hojas que tocan, y luego dibujando las partes correspondientes cuando cada hoja BSP alcanzó tu turno cuando vas de atrás hacia adelante. Sin embargo, esto no resolvió el problema de clasificar varios modelos en movimiento en una hoja uno con respecto al otro, y también dejó problemas desagradables con modelos poligonales complejos.

John resolvi√≥ el problema de clasificaci√≥n de sprites y modelos de pol√≠gonos de una manera sorprendentemente de baja tecnolog√≠a: ahora los escribimos en el z-buffer. (Es decir, antes de renderizar cada p√≠xel, comparamos la distancia al mismo, o z, con el valor z del p√≠xel que ya est√° en la pantalla. Un nuevo p√≠xel se dibuja solo si est√° m√°s cerca que el existente). Primero, dibujamos el mundo principal: paredes, techos, etc. as√≠ En esta etapa, no se utilizan pruebas del z-buffer (como veremos pronto, la definici√≥n de las superficies visibles del mundo se realiza de otra manera); sin embargo, llenamos el b√ļfer z con valores z (en realidad, valores 1 / z, como se describe a continuaci√≥n) para todos los p√≠xeles del mundo. Llenar un b√ļfer Z es un proceso mucho m√°s r√°pido que el b√ļfer z que ser√≠a todo el mundo, porque no hay lectura, no hay comparaciones, solo se escriben valores z. Despu√©s de completar el dibujo y completar el z-buffer del mundo, simplemente podemos dibujar sprites y modelos poligonales usando z-buffering y obtener el tipo perfecto.

Cuando se usa el z-buffer, inevitablemente surgen preguntas: ¬Ņc√≥mo afecta esto a la memoria ocupada y al rendimiento? Con una resoluci√≥n de 320x200, requiere 128 KB de memoria, lo cual no es trivial, pero no tanto para un juego que requiere 8 MB para funcionar. Impacto en el rendimiento: aproximadamente el 10% al llenar el z-buffer del mundo, y aproximadamente el 20% (los indicadores var√≠an mucho) al renderizar sprites y modelos poligonales. A cambio, obtenemos un mundo perfectamente ordenado, as√≠ como la capacidad de crear efectos adicionales, por ejemplo, explosiones y humo de part√≠culas, porque el z-buffer le permite clasificar f√°cilmente estos efectos en el mundo. En general, el uso de z-buffer aument√≥ significativamente la calidad visual y la flexibilidad del motor Quake, as√≠ como tambi√©n simplific√≥ significativamente el c√≥digo, a costa de costos de memoria y rendimiento bastante razonables.

Nivelación y aumento de la productividad


Como dije anteriormente, la arquitectura Quake primero dibuja el mundo mismo, sin leer o comparar el b√ļfer z, simplemente rellenando el b√ļfer z con los valores de los pol√≠gonos del mundo en z. Despu√©s de eso, los objetos en movimiento se dibujan en la cima del mundo utilizando el z-buffering completo. Hasta ahora solo he hablado sobre c√≥mo dibujar objetos en movimiento. En el resto de la columna, hablar√© sobre otra parte de la ecuaci√≥n de representaci√≥n: dibujar el mundo mismo, cuando todo el mundo se almacena como un √°rbol BSP y nunca se mueve.

Como puede recordar en la columna de enero, est√°bamos preocupados por el rendimiento bruto y su promedio. Es decir, quer√≠amos que el c√≥digo de renderizado se ejecutara lo m√°s r√°pido posible, pero al mismo tiempo, para que la diferencia entre la velocidad de renderizado de la escena central y la m√°s lenta en renderizar la escena fuera lo m√°s peque√Īa posible. No hay nada bueno en el promedio de 30 cuadros por segundo si el 10% de las escenas se dibujan a 5 fps, porque la sacudida en tales escenas ser√° extremadamente notable en comparaci√≥n con la escena promedio. Es mejor promediar la frecuencia con 15 cuadros por segundo en el 100% de los casos, a pesar de que la velocidad promedio de renderizado ser√° la mitad.

Los PVS precalculados fueron un paso importante hacia un rendimiento m√°s alto y m√°s equilibrado, porque eliminaron la necesidad de determinar los pol√≠gonos visibles, una etapa bastante lenta que se manifest√≥ peor en las escenas m√°s complejas. Sin embargo, en algunos lugares de niveles de juego reales, los PVS precalculados contienen cinco veces m√°s pol√≠gonos de lo que se ve realmente; junto con la eliminaci√≥n de la superficie de ocultaci√≥n hacia atr√°s (HSR), esto cre√≥ "zonas calientes" en las que la velocidad de fotogramas se redujo notablemente. Cientos de pol√≠gonos fueron dibujados de atr√°s hacia adelante, y la mayor√≠a de ellos fueron redise√Īados inmediatamente por pol√≠gonos m√°s cercanos. El rendimiento bruto en su conjunto tambi√©n disminuy√≥ en un promedio del 50% de la redibujada causada por mostrar todo en el PVS. Por lo tanto, aunque renderizar conjuntos PVS hacia atr√°s funcion√≥ como la √ļltima etapa del HSR y fue una mejora con respecto a la arquitectura anterior, no era ideal. John pens√≥ que probablemente hab√≠a una mejor manera de usar PVS que dibujando de adelante hacia atr√°s.

Y en realidad fue encontrado.

Intervalos ordenados


La √ļltima etapa ideal de HSR para Quake fue descartar todos los pol√≠gonos en el PVS que en realidad resultaron ser invisibles, y dibujar solo los p√≠xeles visibles de los pol√≠gonos restantes sin volver a dibujar. Es decir, cada p√≠xel se dibujar√≠a exactamente una vez y sin p√©rdida de rendimiento, por supuesto. Sin embargo, una soluci√≥n (que requiere costos) es dibujar de adelante hacia atr√°s, guardar el √°rea que describe las partes superpuestas de la pantalla y truncar cada pol√≠gono con los bordes de esta √°rea antes de renderizar. Suena prometedor, pero de hecho recuerda m√°s o menos la soluci√≥n del √°rbol de paquetes que describ√≠ en la columna de enero. Como descubrimos, este enfoque requiere un desperdicio de recursos adicionales y tiene serios problemas con el equilibrio de carga.

Puede hacerlo mucho mejor si mueve el √ļltimo paso de HSR desde el nivel de pol√≠gono al nivel de intervalo y usa la soluci√≥n con intervalos ordenados. En esencia, este enfoque consiste en convertir cada pol√≠gono en un conjunto de intervalos, como se muestra en la Figura 1, seguido de ordenar y truncar los intervalos entre s√≠, hasta que solo las partes visibles de los intervalos visibles permanezcan para renderizar, como se muestra en la Figura 2. Esto puede parecer muy similar al z-buffering (que, como dije anteriormente, es demasiado lento para ser usado en la representaci√≥n del mundo, aunque es adecuado para objetos en movimiento m√°s peque√Īos), pero hay diferencias importantes. A diferencia del almacenamiento en b√ļfer en z, solo las partes visibles de los intervalos visibles se escanean p√≠xel por p√≠xel (aunque todos los bordes de los pol√≠gonos a√ļn deben rasterizarse). A√ļn mejor, la clasificaci√≥n realizada por z-buffering para cada p√≠xel se convierte en una operaci√≥n de intervalo con intervalos ordenados, y dado que una propiedad integral de la lista de intervalos es la conectividad, cada borde se ordena solo en relaci√≥n con algunos intervalos en la misma fila, y se trunca solo por unos pocos intervalos cuando Superposici√≥n horizontal. Aunque las escenas complejas a√ļn tardaron m√°s en procesarse que las escenas simples, los peores casos no fueron tan malos como cuando se usaban √°rboles de vigas o cuando se ordenaba de atr√°s hacia adelante, porque no hay que volver a dibujar y escanear en busca de p√≠xeles ocultos, la complejidad est√° limitada por la resoluci√≥n de p√≠xeles y la conectividad de intervalos limita la clasificaci√≥n de los peores casos en cualquier √°rea de la pantalla. Como beneficio adicional, la salida de los intervalos ordenados tiene exactamente la forma que necesita un rasterizador de bajo nivel: en el formato de un conjunto de descriptores de intervalos, cada uno de los cuales consiste en una coordenada del comienzo y la longitud.



Generación de intervalos

En resumen, la soluci√≥n con intervalos ordenados est√° bastante cerca de nuestros criterios originales; aunque no ahorra costos, todav√≠a no son del todo terribles. Elimina completamente el redise√Īo y escaneo de p√≠xeles de las partes superpuestas de los pol√≠gonos, y es propenso a igualar el rendimiento en los peores casos. No depender√≠amos solo de los intervalos ordenados como mecanismo para eliminar las superficies ocultas, sino que los PVS precalculados reducen el n√ļmero de pol√≠gonos a un nivel que los intervalos ordenados manejan suficientemente bien.

Entonces, hemos encontrado el enfoque que necesitamos; solo queda escribir el c√≥digo y esto ha terminado, ¬Ņverdad? Si y no El enfoque conceptual con intervalos ordenados es simple, pero sorprendentemente dif√≠cil de implementar: debe tomar un par de decisiones de dise√Īo importantes, requiere un poco de matem√°tica y hay trampas astutas. Veamos primero las soluciones de dise√Īo.

Costillas vs intervalos


La primera decisión es elegir qué clasificar: intervalos o aristas (ambos conceptos pertenecen a la categoría general de "intervalos ordenados"). Aunque los resultados en ambos casos serán los mismos (una lista de intervalos que deben dibujarse sin volver a dibujar), las implementaciones y las implicaciones de rendimiento son muy diferentes, ya que la clasificación y el truncamiento se realizan mediante estructuras de datos muy diferentes.

Al ordenar los intervalos, estos intervalos se almacenan en segmentos de memoria ordenados por x listas vinculadas, generalmente un segmento por línea de trama. Cada polígono, a su vez, se rasteriza en intervalos, como se muestra en la Figura 1. Cada intervalo se ordena y trunca en el segmento de memoria de la línea de trama en la que se encuentra el intervalo, como se muestra en la Figura 2, de modo que en cualquier momento cada segmento contenga los intervalos encontrados más cercanos , siempre sin superposiciones. Con este enfoque, es necesario generar todos los intervalos para cada polígono, y cada intervalo se ordena, trunca y agrega de inmediato al segmento de memoria correspondiente.



Figura 2: los intervalos del polígono A de la Figura 1 se ordenan y truncan a intervalos del polígono B, mientras que el polígono A está a una distancia constante de 100 a lo largo del eje Z, y el polígono B está a una distancia constante de 50 a lo largo del eje Z (el polígono B está más cerca de la cámara) )

Al ordenar los bordes, estos bordes se almacenan en segmentos de memoria ordenados por x listas vinculadas de acuerdo con su línea ráster inicial. Cada polígono, a su vez, se divide en bordes, juntos creando una lista de todos los bordes de la escena. Cuando todos los bordes de todos los polígonos en la pirámide de visibilidad se agregan a la lista de bordes, la lista completa se escanea en una pasada de arriba a abajo, de izquierda a derecha. Se guarda una lista de bordes activos (AEL). En cada paso hacia una nueva línea ráster, los bordes que aparecen en esta línea ráster se eliminan del AEL, los bordes activos van a sus nuevas coordenadas x, los bordes que comienzan desde la nueva línea ráster se agregan al AEL y los bordes se ordenan por la coordenada x actual.

Para cada línea ráster, se almacena una lista de polígonos activos (APL) ordenada por z. Va en orden ordenado por x AEL. Cuando se encuentra con cada borde nuevo (es decir, cuando cada polígono comienza o termina cuando se mueve de izquierda a derecha), el polígono asociado con él se activa y clasifica en APL (en el caso de un borde inicial), como se muestra en la Figura 3, o se desactiva y elimina de APL ( en el caso de un borde posterior), como se muestra en la Figura 4. Si el polígono más cercano ha cambiado (es decir, el más cercano es un nuevo polígono o el polígono más cercano ha terminado), para el polígono que acaba de dejar de ser el más cercano, se crea un intervalo a partir del punto donde el polígono no está vym porque son los más cercanos y terminando x coordenadas de los bordes y la corriente de la coordenada x se registra en el vertedero, que ahora es el más cercano. Esta coordenada almacenada se usa más tarde como el comienzo del intervalo creado cuando el nuevo polígono más cercano deja de estar al frente.



Figura 3: activación del polígono cuando se detecta un borde inicial en AEL.



Figura 4: desactivación del polígono cuando se detecta un borde posterior en AEL.

No se preocupe si no comprende completamente lo anterior; Esta es solo una descripción general rápida de la clasificación de bordes para que el resto de la columna sea más claro. Una explicación detallada estará en la siguiente columna.

Los intervalos generados al ordenar los bordes parecen ser exactamente los mismos intervalos que resultarían de la clasificación de los intervalos; La diferencia está en las estructuras de datos intermedios utilizados para ordenar los intervalos en la escena. Al ordenar las aristas, los intervalos se almacenan dentro de las aristas hasta que se genera el conjunto final de intervalos visibles, por lo que la ordenación, el recorte y la creación de intervalos se realizan cuando cada arista agrega o elimina un polígono, en función del estado del intervalo definido por la arista y el conjunto de polígonos activos. Al ordenar intervalos, los intervalos se vuelven aparentes instantáneamente cuando cada polígono se rasteriza, y estos intervalos intermedios se ordenan y truncan en relación con los intervalos en la línea de trama para crear los intervalos finales; por lo tanto, los estados de los intervalos se establecen constantemente de manera explícita, y todo el trabajo se realiza directamente a intervalos.

Tanto la clasificación por intervalos como la clasificación por bordes funcionan bien; se han utilizado con éxito en proyectos comerciales. Para Quake, elegimos la clasificación de bordes, en parte porque parece ser más eficiente y tiene una excelente conectividad horizontal que proporciona el tiempo de clasificación mínimo, en contraste con la clasificación potencialmente costosa en listas vinculadas, que puede ser necesaria al ordenar los intervalos.Sin embargo, una razón más importante fue que al ordenar los bordes, podemos dividir los bordes entre polígonos adyacentes, y esto reduce la clasificación, el recorte y la rasterización de los bordes a la mitad, y también reduce significativamente la base de datos mundial debido al hecho de que los bordes se vuelven comunes.

Y la √ļltima ventaja de ordenar los bordes es que no distingue entre pol√≠gonos convexos y c√≥ncavos. Para la mayor√≠a de los motores gr√°ficos, este no es un aspecto muy importante, pero en Quake, recortar, transformar, proyectar y clasificar los bordes se ha convertido en el principal cuello de botella, por lo que estamos haciendo todo lo posible para reducir la cantidad de pol√≠gonos y bordes, y los pol√≠gonos c√≥ncavos son muy √ļtiles a este respecto. Aunque los pol√≠gonos c√≥ncavos tambi√©n se pueden procesar mediante intervalos de clasificaci√≥n, esto implica una disminuci√≥n significativa en el rendimiento.

Sin embargo, no hay una respuesta definitiva sobre el mejor enfoque. Al final, los intervalos de clasificaci√≥n y los bordes de clasificaci√≥n cumplen una funci√≥n, y elegir entre ellos es una cuesti√≥n de usabilidad. En la siguiente columna, hablar√© m√°s sobre c√≥mo ordenar los bordes con su implementaci√≥n completa. En el resto de esta columna, sentar√© las bases para la pr√≥xima hablando sobre las claves de clasificaci√≥n y el c√°lculo de 1 / z. En el proceso, har√© varias referencias a los aspectos de clasificaci√≥n de bordes, que a√ļn no se han discutido en detalle; Pido disculpas, pero esto es inevitable, y todo se aclarar√° solo al final de la pr√≥xima columna.

Teclas de clasificación de costillas


Ahora que sabemos que seleccionaremos la clasificaci√≥n de los bordes y la usaremos para crear los intervalos de los pol√≠gonos m√°s cercanos al espectador, surge la pregunta: ¬Ņc√≥mo saber si estos pol√≠gonos son los m√°s cercanos? Idealmente, simplemente almacenar√≠amos la clave de clasificaci√≥n de cada pol√≠gono, y cuando aparezca un nuevo borde, comparar√≠amos la clave de su superficie con las claves de otros pol√≠gonos activos actuales para determinar f√°cilmente cu√°l de los pol√≠gonos est√° m√°s cerca.

Eso suena demasiado bien, pero es posible. Si, por ejemplo, su base de datos mundial se almacena como un √°rbol BSP, y todos los pol√≠gonos se truncan en hojas BSP, entonces el orden transversal de BSP ser√° el orden de representaci√≥n correcto. Por lo tanto, por ejemplo, si recorre BSP de adelante hacia atr√°s, asignando a cada pol√≠gono un valor de clave incrementalmente mayor cuando lo alcanza, entonces los pol√≠gonos con valores de clave m√°s altos estar√≠an garantizados frente a pol√≠gonos con claves m√°s peque√Īas. Este enfoque se ha utilizado en Quake durante alg√ļn tiempo, pero ahora se est√° aplicando una soluci√≥n diferente por razones que explicar√© en breve.

Si no tiene un BSP o estructura de datos similar, o si tiene muchos polígonos en movimiento (BSP no procesa polígonos en movimiento de manera muy eficiente), entonces otra forma de lograr objetivos es ordenar todos los polígonos entre sí antes de renderizar la escena y asignar las teclas correspondientes de acuerdo con su espacio relaciones en la ventana gráfica. Desafortunadamente, en el caso general, esta es una tarea extremadamente lenta, porque cada polígono necesita ser comparado entre sí. Existen técnicas para mejorar el rendimiento de la clasificación de polígonos, pero no conozco a nadie que realice una clasificación general de polígonos en una PC en tiempo real.

Una alternativa es ordenar por distancia z desde el visor en el espacio de la pantalla; Esta solución encaja bien con la conectividad espacial superior de los bordes de clasificación. Al reunirse con cada nueva arista en la línea de trama, puede calcular la distancia z del polígono correspondiente y compararla con las distancias de otros polígonos, después de lo cual el polígono se puede guardar en APL.

Sin embargo, obtener distancias a lo largo de z puede ser una tarea dif√≠cil. No olvide que necesitamos poder calcular z en cualquier punto arbitrario del pol√≠gono, porque puede ocurrir un borde y hacer que el pol√≠gono se clasifique en el APL en cualquier lugar de la pantalla. Podemos calcular z directamente desde las coordenadas de pantalla x e y y las ecuaciones del plano del pol√≠gono, pero, desafortunadamente, esto no se puede hacer muy r√°pidamente, porque z para el plano no cambia linealmente en el espacio de la pantalla; sin embargo, 1 / z var√≠a linealmente, por lo que usamos este valor. (Para una discusi√≥n sobre linealidad en el espacio de pantalla y gradientes para 1 / z, vea la serie de Chris Hecker sobre mapeo de texturas en la revista Game Developer el a√Īo pasado.) Otra ventaja de usar 1 / z es que la resoluci√≥n aumenta al disminuir la distancia, es decir, con 1 / z siempre tendremos la mejor resoluci√≥n de profundidad para objetos cercanos que son m√°s importantes.

La forma obvia de obtener el valor 1 / z en cualquier punto arbitrario del polígono es calcular 1 / z en los vértices, interpolarlos sobre ambos bordes del polígono e interpolar entre los bordes para obtener el valor en el punto deseado. Desafortunadamente, esto requiere mucho trabajo a lo largo de cada costilla; peor, esto requiere división para calcular el paso 1 / z por píxel en cada intervalo.

Sería mejor calcular 1 / z directamente a partir de la ecuación del plano y la pantalla x e y del píxel que nos interesa. La ecuación tiene la siguiente forma:

1/z=(a/d)x‚Ä≤‚ąí(b/d)y‚Ä≤+c/d


donde z es la coordenada en el espacio z del punto en el plano que se proyecta en las coordenadas de la pantalla (x ', y') (el origen de las coordenadas para estos cálculos es el centro de proyección, el punto en la pantalla justo en frente del punto de vista), [abc] es normal al plano en la ventana gráfica, y d es la distancia desde el origen de la ventana gráfica al plano a lo largo de la normal. La división se realiza solo una vez para cada plano, porque a, b, cyd son constantes para los planos.

Un cálculo completo de 1 / z requiere dos multiplicaciones y dos adiciones, y cada operación debe realizarse con un punto flotante para evitar errores de rango. Tal volumen de cálculos de coma flotante parece costoso, pero en realidad no lo es, especialmente en los procesadores Pentium, donde el valor del plano 1 / z en cualquier punto se puede calcular en lenguaje ensamblador en solo seis ciclos.

Si está interesado, aquí hay una derivación rápida de la ecuación 1 / z. La ecuación del plano para el plano tiene la siguiente forma:

ax+by+cz‚ąíd=0


donde x e y son las coordenadas del espacio de vista, a, b, c, d y z se definen arriba. Si hacemos la sustitución

Source: https://habr.com/ru/post/439830/


All Articles