Este artículo completa una serie de publicaciones del estudio Krasnodar Plarium sobre varios aspectos del trabajo con modelos 3D en Unity. Artículos anteriores:
"Características de trabajar con Mesh in Unity" ,
"Unity: edición de procedimientos de Mesh" ,
"Importación de modelos 3D en Unity y escollos" ,
"Muesca de píxeles en el escaneo de texturas" .
Hace casi 2 años escribimos
un artículo en el que hablamos sobre la opción de optimizar la geometría 3D en una escena con restricciones en el ángulo de la cámara y la rotación de los objetos correspondientes. No ha fluido mucho desde entonces, pero la oportunidad de mejorar la solución, considerar diferentes enfoques y espiar a otros está obsesionando las mentes de los desarrolladores. En este artículo, describiremos una versión mejorada del algoritmo basado en pintar polígonos, y hablaremos sobre tratar de transferir parte de este trabajo a un paquete 3D.

Recortar en la escena
Ya consideramos el principio básico de este algoritmo en el artículo anterior: extinguimos todos los efectos y objetos transparentes, pintamos polígonos no procesados en un color y procesamos los de diferentes colores, renderizamos y extraemos el resultado. En la versión anterior, pintaban de modo que todo lo negro fuera redundante, y solo un triángulo estaba marcado en rojo.
En los comentarios a ese artículo, uno de los lectores señaló la posibilidad de optimizar el algoritmo estableciendo una correspondencia uno a uno entre el conjunto de polígonos y algún conjunto de números únicos. Entonces será posible procesar más de un triángulo de la misma manera. Considera esta opción.
En este caso, así como en la última vez, se supone que algunos entrenamientos previos están relacionados con la desactivación de todos los objetos silbantes en el escenario y los objetos que se garantiza que no afectarán la visibilidad del modelo objetivo. Las vistas de la cámara se procesan casi de forma independiente; están conectadas solo por un búfer de índice común de polígonos visibles. Además, se realiza un preprocesamiento de geometría para cada ángulo, durante el cual se giran los polígonos que se vuelven a la cámara (
cara posterior ). Esto se hace porque en una determinada etapa del algoritmo se crea una malla temporal con un número significativamente mayor de vértices que la original. Este número puede superar fácilmente el umbral de 65.535, lo que requerirá gestos adicionales en los cálculos y reducirá el rendimiento. En cualquier caso, estos polígonos se eliminarán, ya que su color no caerá en el marco. Sin embargo, debido al hecho de que cada triángulo potencialmente da lugar a tres vértices de basura, la eliminación anticipada de polígonos innecesarios facilita la etapa principal del algoritmo y reduce los costos de memoria.
Que haya algún modelo 3D, cuya geometría esté representada por una malla. Para pintar un polígono específico en un color único, debe pintar todos sus vértices en este color. Como en el caso general un vértice puede pertenecer a diferentes polígonos, no será posible resolver el problema de frente. No importa cómo coloreamos cualquier vértice, al renderizar, su color se arrastrará sobre todos los triángulos que lo poseen, de acuerdo con el algoritmo de interpolación en el lado de la tarjeta de video.
Un ejemplo de interpolación de color al mostrar polígonos con vértices comunesPor lo tanto, es necesario dividir de alguna manera la malla en polígonos independientes separados, mientras se preserva la topología y la geometría del objeto. Dictum factum. Transformamos las matrices de triángulos y vértices de tal manera que para cada triángulo se crearán 3 vértices únicos, cuya posición está determinada por los vértices correspondientes de la malla original. Vale la pena señalar que, en el caso general, dicha malla tendrá un número significativamente mayor de vértices en comparación con el original. Y si este número excedió 65 535, al crear la malla, debe especificar el formato de indexación apropiado.
Convierta la malla original en una malla con vértices únicos para cada polígonoprivate static Mesh GetNotSmoothMesh(Mesh origin) { var oVertices = origin.vertices; var oTriangles = origin.triangles; var vertices = new Vector3[oTriangles.Length]; var triangles = new int[oTriangles.Length]; for (int i = 0; i < triangles.Length; i++) { vertices[i] = oVertices[oTriangles[i]]; triangles[i] = i; } return new Mesh() { indexFormat = vertices.Length > 65535 ? IndexFormat.UInt32 : IndexFormat.UInt16, vertices = vertices, triangles = triangles }; }
Ahora debe designar los polígonos de esta malla para que después de la operación de renderizado sea posible determinar cuál apareció en la pantalla. Como ya se mencionó, generamos colores únicos para los polígonos y pintamos cada uno de los tres vértices en el color correspondiente. El resultado es una nueva malla, que llamamos
Byte-Colored Mesh .
Malla de color byteColor de malla en el que cada vértice pertenece a un solo polígono private static void ColorizePolygons(Mesh mesh) { var pColors = ColorsOfPolygons(mesh); var colors = new Color[mesh.vertexCount]; for (int i = 0; i < colors.Length; i++) { colors[i] = pColors[i / 3]; } mesh.colors = colors; } private static Color[] GetColorsOfPolygons(Mesh mesh) { var colors = new Color[mesh.triangles.Length / 3]; for (int i = 0; i < colors.Length; i++) { var color = Int2Color(i);
Recuerda el color. Es hora de rendir. Realizamos renderizado 3D para todos los ángulos de cámara y, al procesar cada uno de ellos, reponemos el búfer de índices de polígono únicos cuyos colores se detectaron en el marco. Para el momento de los cálculos de la cámara, debe desactivar el suavizado para evitar la aparición de nuevos colores debido a la interpolación de píxeles vecinos.
Lectura y almacenamiento de colores desde diferentes ángulos de cámara. Vale la pena mencionar que debido a la discretización, es posible que algunos triángulos no se muestren debido al tamaño especialmente pequeño de su proyección en la pantalla, y no porque algo se superponga o se gire del lado equivocado. Hemos implementado una versión conservadora del algoritmo. En este caso, se calcula el
AABB de la proyección del triángulo en la pantalla, y si al menos uno de sus lados es menor que el lado de texel en la imagen, dicho polígono se marca como visible. Este enfoque protege contra los artefactos cuando se ejecuta el algoritmo con una resolución inferior a la resolución de pantalla del dispositivo de destino. Si ignora los polígonos pequeños, el resultado también será aceptable siempre que la resolución de la textura renderizada utilizada sea mayor que la resolución de las pantallas de los dispositivos previstos.
Implementamos este algoritmo de recorte en
Unity y lo usamos para optimizar objetos estáticos cuyos modelos se encuentran en la escena más de una vez en varias posiciones. Este es principalmente el escenario: piedras, árboles, estatuas, jarrones, etc., que se refiere al prefabricado de uso frecuente. Nos gustaría optimizar dichos objetos antes, en la etapa de creación en un paquete 3D, pero quién sabe en qué pose fantasmagórica el diseñador de niveles quiere poner su candelabro favorito.
Recortar el conjunto de objetos del mismo tipo con una herramienta de este tipo reduce el tamaño de la escena, ya que durante
el procesamiento por lotes estático, los datos de la malla
prefabricada común se copian en la etapa de construcción tantas veces como los objetos dibujados activos con esta malla se representan en la escena. Nuestro método también libera espacio en atlas de texturas, como el
mapa de luz . Utilizamos el espacio guardado para aumentar el detalle de las partes de los modelos que sobrevivieron a la limpieza.
Recorte 3D
Sin embargo, es mejor si el artista puede cortar todo lo innecesario en su editor, reduciendo así el número de etapas de preparación de contenido. Esto se justifica cuando el modelo se usa en una escena con solo una rotación predeterminada en relación con la cámara. Anteriormente, los objetos que serían dirigidos exactamente al usuario por un lado a menudo se simplificaban manualmente antes de la integración en el proyecto. Es importante tener en cuenta que implementar dicha simplificación mediante programación en
Unity es mucho más difícil debido a la complejidad del desarrollo de los paquetes
UV , por lo que la automatización en la etapa de un paquete 3D a veces facilita la vida de un artista.
Una de las herramientas para trabajar con modelos 3D en nuestra empresa es
Blender . Nos subimos a ella. Parece que un software tan "adulto", como
Blender , debería tener una funcionalidad similar. Sin embargo, resultó que no debería. Tuve que ver mi propia bicicleta.
La primera idea era usar la herramienta de selección familiar: básicamente, repetir parte del trabajo manual del artista para un ángulo de cámara: seleccionar polígonos visibles, invertir selección, eliminar. El plan era este: mover la cámara, determinar la proyección
AABB del modelo en cada posición, luego solicitar el resultado de seleccionar los polígonos del área correspondiente al
AABB , obtener la unión del conjunto de polígonos de la vista actual con los anteriores y eliminar los polígonos no seleccionados al final.
Sin embargo, durante la implementación del script, se encontró un inconveniente significativo en términos de la tarea. Las herramientas de selección en
Blender (selección de rectángulo, selección de círculo) pierden precisión al aumentar el número de elementos seleccionados por unidad de área de la pantalla (algunos polígonos permanecen sin seleccionar), lo que hace que su uso en nuestras herramientas de automatización sea imposible. Dato interesante: en el mismo
3ds Max no se observa este problema.
Destacando desde lejos en Blender
Resultado de seleccionEl siguiente intento tenía como objetivo resolver el problema en la frente: enviamos rayos desde la cámara a través de cada píxel de la ventana y observamos qué polígonos fueron los primeros en cruzarse con al menos un rayo. No esperábamos resultados precisos con este enfoque, pero valió la pena intentarlo. El resultado es obvio: muy baja productividad al procesar en la
CPU o los mismos agujeros con un pequeño número de rayos.
Sin embargo, establecimos un punto de apoyo para la implementación de un enfoque más avanzado. La idea era seleccionar un cierto número de puntos aleatorios en cada polígono y luego enviar rayos desde la cámara en su dirección. Este enfoque funcionó bien, pero tuvimos algunos casos límite: también se cortaron los polígonos, en los que el ángulo entre el haz y su normalidad era aproximadamente igual a π / 2. Por lo tanto, cuando la cámara hace zoom debido a distorsiones de perspectiva, se pueden abrir áreas recortadas.
Este método fue, en opinión de los artistas, demasiado agresivo, por lo que decidimos centrarnos en
recortar solo las
caras posteriores .
Conclusión
No es ningún secreto que una actitud cuidadosa con los recursos del dispositivo al crear juegos es el factor más importante que afecta la calidad del producto final. Esto es especialmente cierto para las plataformas móviles, de mal humor para el uso activo de RAM. Reducir el número de polígonos le permite llenar de manera más efectiva el espacio de los atlas de textura y reduce ligeramente la carga computacional.
Además, no olvide el costo de las horas hombre y el costo de los errores al usar las herramientas descritas anteriormente, y similares. El enfoque propuesto supone una tubería que funcione bien para el trabajo del departamento de arte, especialmente para los empleados involucrados en la integración de modelos en el proyecto.
Por lo tanto, teniendo las condiciones y herramientas discutidas en este artículo, nos adherimos a las siguientes reglas. Si se supone que el modelo creado siempre será volteado por un lado para el usuario, y también si desde estos ángulos la superposición de algunas partes del modelo por otras es bastante pequeña, entonces el artista usa nuestra
herramienta de recorte de la
cara posterior en el editor 3D, verifica la corrección y continúa con el empaquetado de desarrollo
UV . Si el modelo se usa a menudo en diferentes posiciones o tiene una geometría más compleja, luego de importarlo al proyecto, ejecutamos el algoritmo descrito en la primera parte del artículo, procesando todos los objetos estáticos de la escena con él.