OMNIA Engine
OMNIA es un Motor Grafico que renderiza en OpenGL, Vulkan y PS4. El Motor Grafico es multihilo y esta hecho con C/C++ y tiene como lenguaje de scripting Lua.
OMNIA permite al usuario cambiar los valores de las diferentes entidades en el editor, el editor esta hecho con Imgui. Algunos de los valores que se pueden modificar son tales como la posición, la rotación, la escala, además de los valores de cada material como puede ser el color de los objetos, su especular o si son emisivos.
Se pueden ver la jerarquia de carpetas y su contenido además de la gestión del contenido, pudiendo crear, borrar, editar y abrir archivos.
OMNIA permite al usuario cambiar los valores de las diferentes entidades en el editor, el editor esta hecho con Imgui. Algunos de los valores que se pueden modificar son tales como la posición, la rotación, la escala, además de los valores de cada material como puede ser el color de los objetos, su especular o si son emisivos.
Se pueden ver la jerarquia de carpetas y su contenido además de la gestión del contenido, pudiendo crear, borrar, editar y abrir archivos.
Características
- OpenGL
- Vulkan
- PS4
- ECS
- Transform Parent
- Lua Scripting
- Imgui
- Multithread
- Deferred Shading
- Direcctional Light
- Spot Light
- Point Light
- Light Volume
- Shadow Mapping
- Normal Map
- Skybox
- Postprocess Bloom
- Postprocess HDR
- SSAO
- Postprocess Grain
Galeria
Explicación
Las tecnicas implementadas más importantes que se han hecho para el motor son:
Los componentes son objetos que solo almacenan información local, por ejemplo un componente de la transformación local almacena la posición , rotación y escalado. Estos componentes tienen asignado un bit de la entidad para saber si esa entidad contiene o no el componente, por ejemplo un componente de la transformación local tiene asignado el segundo bit para saber si está añadido a la entidad.
Las Entidades son un objeto que almacena una máscara de bits con los bits de los componentes activos, por ejemplo si se quiere añadir a la entidad el componente de la transformación local se pondría a 1 el segundo bit. Con esto se puede saber con una comprobación si esa entidad tiene un componente en concreto. La Entidades también tienen un id para saber dentro del arquetipo donde esta.
El Arquetipo es una combinación de componentes, cada vez que se añade un componente a una entidad se añade el componente a un arquetipo y a la entidad se le dice el id que tiene en ese arquetipo, también tiene una máscara de componentes para saber que componentes tiene el arquetipo. Hay un arquetipo por cada combinación de componentes, así que todas las entidades que tengan los mismos componentes están en el mismo arquetipo. Por ejemplo tienes 3 entidades la primera entidad contiene 2 componentes, el componente de transformación local, y el componente de Render, la segunda entidad tiene solo el componente de transformación local, y la tercera entidad tiene los mismos componentes que la primera entidad. Con esto se habrían creado 2 arquetipos 1 con el componente de transformación local, que aquí estaría el componente de la segunda entidad. También se habría creado otro arquetipo con 2 componentes, uno de transformación local y otro de render, en este arquetipo estarían los componentes de la primera y tercera entidad.
Por último para tener la implementación del ECS correcta faltarían los system. Los system son funciones que recorren los arquetipos en busca de la configuración que le de el usuario. Por ejemplo si quieres pintar los objetos en pantalla hay que hacer un system que recorra todos los arquetipos hasta que encuentre un arquetipo que contenga los componentes de Render,Material y Transformación Local. Una vez el system ha encontrado el arquetipo solo lo tiene que recorrer y no hace falta que pegue saltos en la memoria ya que ese arquetipo tiene todos los componentes juntos. También se le puede indicar si se quiere no acceder a una entidad que tenga un componente en concreto. Para saber si un arquetipo tiene un componente se comprueba la máscara del arquetipo para así de una forma rápida encontrar los arquetipos que cumplen la condición.
Esta técnica funciona haciendo una primera pasada de pintado donde a partir de un RenderTexture se obtienen 3 texturas, una de posciones, otra de normales y otra de color. Como los valores que se guardan son de 3 floats, se puede utilizar el alpha de la textura para almacenar otros valores que ocupen un float, por ejemplo el specular.
Con las texturas del deferred shading podemos obtener la posición, las normales, el color y el especular. Proyectamos las texturas sobre la geometría para así que solo se pinte lo que la geometría abarque, luego se le aplican los cálculos de iluminación poniendo un límite de radio que vaya a proporción del tamaño de la geometría, para que no se pueda salir de ella.
Para pintar las geometrías en el mundo y hacer el calculo de luces bien hay que activar el bullface frontal para pintar las caras de atrás de la geometría y así no desaparezca cuando entres dentro de la geometría. También hay que activar el blend de forma aditiva ONE ONE. Y hay que desactivar el depth para que no se sobrepongan las luces.
La spot light se utiliza una geometría en forma de cono.
La point light se utiliza una geometría en forma de esfera
La directional light se utiliza un quad que abarca toda la pantalla.
Para calcular el especular hay que hacer un producto punto con la dirección de la cámara y el reflejo sobre el ángulo en el que incide la luz.
Con el depth buffer obtenido se le pasa la textura al shader de luces y se calcula si el punto a iluminar esta tapado por otro objeto, para asi no iluminar y hacer las sombras.
Para ver si el objeto a iluminar esta tapado por otro se necesita la textura del depth buffer y la z del vértice a iluminar. Para ello se saca la proyección de la textura de depth y se compara con la z del vértice, si la z del vértice a iluminar es menor que el valor del depth se ilumina este vértice, si es menor eso quiere decir que está tapado por el otro y no se ilumina.
Con esto estaría ya hecha las sombras pero tiene errores de acné, para solucionar esto hay que aplicarle un bias.
Hay 2 tipos de crear sombras, con una cámara ortográfica o una cámara en perspectiva. Con la cámara en perspectiva se simula el efecto de una luz normal donde si el objeto está cerca de la luz la sombra se hace más grande y si el objeto está lejos se hace más pequeña. Con la cámara ortográfica simula la luz del sol ya que el tamaño no cambia dependiendo de la cercanía del objeto a la luz, esto en el sol ocurre ya que el sol está tan lejos que no se aprecia.
En el fragment shader hay que leer la textura cubemap con las coordenadas de la posición de los vértices del cubo ya que el cubo va de -1 a 1 y se acopla la textura a él.
En la primera pasada se calculan las luces y con el cálculo este se saca los píxeles más brillantes y se guardan en una textura para luego aplicarle el glow. Con este RenderTexture se obtienen 2 texturas una de el calculo de luces y otra de lo que brillan los objetos.
Ahora hay que hacerle un gaussian blur a la imagen, para ello se utilizan 2 RenderTexture que se van alternando. Cada RenderTexture hace un blur de la imagen que se le pase, por cada pasada se le hace el blur una vez en horizontal y otra en vertical. Cuando un RenderTexture termina le pasa la textura resultante al otro RenderTexture así se van acumulando el blur y queda más difuminado. Para hacer el blur se coge la textura que recibe el RenderTexture se mezclan los colores de al lado, tantos como se decida implementar, por ejemplo con un offset de 5 se cogerian los 5 de la izquierda y los 5 de la derecha y se mezclarian con una suma. La primera textura que se le pasa a estos 2 RenderTexture es la textura que hemos obtenido con los brillos.
Cuando ya se tiene la textura con el gaussian blur aplicado hay que hacer una última pasada para juntar la textura difuminada con la textura de las luces con una suma. Y por último hay que transformarlo de HDR a LDR para representar los colores en pantalla.
Por último como ejemplo se ha implementado un valor de emisivo para poder hacer que un objeto brille entero sin necesidad de que las luces le afecten. Para ello se le ha añadido una variable de emisivo a los objetos y estos con ese valor rellenan la textura de brillos, para así poder aplicarle el efecto de bloom.
Para hacer el SSAO se necesitan muestras en espacio de tangente para comprobar si los puntos están, estas muestras se obtienen asumiendo que la apunta en z positivo se sacan puntos aleatorios entre -1.0 y 1.0 para la x e y, y entre 0.0 y 1.0 para la z para hacer la semiesfera de muestras.
Para simplificar la cantidad de muestras hay que crear una textura de 4x4 para poner puntos de rotación aleatorios, esta textura hay que hacer que se repita para distribuir estos puntos por toda la pantalla.
Con esta información hay que pasar los puntos del espacio de tangente al view space, para ello se calcula la tangente de la textura 4x4, el bitangente de las normales con la tangente y con la tangente, bitangente y las normales se crea la matriz para pasar del espacio tangente al view space, para ello se multiplica la matriz que hemos obtenido por cada muestra. Luego se saca cada muestra de la semiesfera se le pasa al view space y se le agrega la posición actual del fragment, para asi poder comparar la profundidad, para ello se le suma la muestra en view space a la posición del vértice y se le multiplica la projection para ponerla en el espacio de clipping y asi comprobar los depth. Con esto se saca el sombreado que tienen los objetos por el ambient occlusion.
Cuando ya se tiene el sombreado hay que hacer un blur para suavizar el efecto. Para hacer el blur hay que mezclar los colores de la textura sombreada con un offset, por ejemplo sumando los 9 píxeles que contiene alrededor.
ECS
Para la implementación del ECS se encesitan 3 objetos, las Entidades, los Componentes y los Arquetipos.Los componentes son objetos que solo almacenan información local, por ejemplo un componente de la transformación local almacena la posición , rotación y escalado. Estos componentes tienen asignado un bit de la entidad para saber si esa entidad contiene o no el componente, por ejemplo un componente de la transformación local tiene asignado el segundo bit para saber si está añadido a la entidad.
Las Entidades son un objeto que almacena una máscara de bits con los bits de los componentes activos, por ejemplo si se quiere añadir a la entidad el componente de la transformación local se pondría a 1 el segundo bit. Con esto se puede saber con una comprobación si esa entidad tiene un componente en concreto. La Entidades también tienen un id para saber dentro del arquetipo donde esta.
El Arquetipo es una combinación de componentes, cada vez que se añade un componente a una entidad se añade el componente a un arquetipo y a la entidad se le dice el id que tiene en ese arquetipo, también tiene una máscara de componentes para saber que componentes tiene el arquetipo. Hay un arquetipo por cada combinación de componentes, así que todas las entidades que tengan los mismos componentes están en el mismo arquetipo. Por ejemplo tienes 3 entidades la primera entidad contiene 2 componentes, el componente de transformación local, y el componente de Render, la segunda entidad tiene solo el componente de transformación local, y la tercera entidad tiene los mismos componentes que la primera entidad. Con esto se habrían creado 2 arquetipos 1 con el componente de transformación local, que aquí estaría el componente de la segunda entidad. También se habría creado otro arquetipo con 2 componentes, uno de transformación local y otro de render, en este arquetipo estarían los componentes de la primera y tercera entidad.
Por último para tener la implementación del ECS correcta faltarían los system. Los system son funciones que recorren los arquetipos en busca de la configuración que le de el usuario. Por ejemplo si quieres pintar los objetos en pantalla hay que hacer un system que recorra todos los arquetipos hasta que encuentre un arquetipo que contenga los componentes de Render,Material y Transformación Local. Una vez el system ha encontrado el arquetipo solo lo tiene que recorrer y no hace falta que pegue saltos en la memoria ya que ese arquetipo tiene todos los componentes juntos. También se le puede indicar si se quiere no acceder a una entidad que tenga un componente en concreto. Para saber si un arquetipo tiene un componente se comprueba la máscara del arquetipo para así de una forma rápida encontrar los arquetipos que cumplen la condición.
Deferred Shading
El Deferred Shading consiste en guardar la información en texturas para así luego hacer cálculos sobre ellas. Esta técnica es más eficaz que el forward rendering sobre todo cuando hay muchas luces pequeñas.Esta técnica funciona haciendo una primera pasada de pintado donde a partir de un RenderTexture se obtienen 3 texturas, una de posciones, otra de normales y otra de color. Como los valores que se guardan son de 3 floats, se puede utilizar el alpha de la textura para almacenar otros valores que ocupen un float, por ejemplo el specular.
Light Volume
Para mejorar el cálculo de iluminación junto el deferred shading se le puede poner geometrías a las luces para así comprobar si los objetos están dentro de la geometría y así aplicarle los cálculos de luz.Con las texturas del deferred shading podemos obtener la posición, las normales, el color y el especular. Proyectamos las texturas sobre la geometría para así que solo se pinte lo que la geometría abarque, luego se le aplican los cálculos de iluminación poniendo un límite de radio que vaya a proporción del tamaño de la geometría, para que no se pueda salir de ella.
Para pintar las geometrías en el mundo y hacer el calculo de luces bien hay que activar el bullface frontal para pintar las caras de atrás de la geometría y así no desaparezca cuando entres dentro de la geometría. También hay que activar el blend de forma aditiva ONE ONE. Y hay que desactivar el depth para que no se sobrepongan las luces.
La spot light se utiliza una geometría en forma de cono.
La point light se utiliza una geometría en forma de esfera
La directional light se utiliza un quad que abarca toda la pantalla.
Lights
Hay 3 tipos de luces, las direccionales, las spot y las puntuales. Para el cálculo de las luces hay que comprobar hacia donde miran las normales de los objetos, para ello se multiplican las normales por el modelo para saber hacia donde miran las normales en cada momento. Con eso se hace un producto punto con la dirección de la luz y las normales, para saber si esa parte del objeto está iluminada o sombreada. Además, a la luz hay que añadirle valores externos, como la intensidad del entorno y el especular del objeto, que indica cómo interactúa la luz, si se dispersa o rebota.Para calcular el especular hay que hacer un producto punto con la dirección de la cámara y el reflejo sobre el ángulo en el que incide la luz.
Shadow Mapping
Esta técnica hay que tener una cámara en la posición y rotación de la luz que va a aplicar las sombras, para así utilizar un RenderTexture con esta cámara y sacar el depth buffer.Con el depth buffer obtenido se le pasa la textura al shader de luces y se calcula si el punto a iluminar esta tapado por otro objeto, para asi no iluminar y hacer las sombras.
Para ver si el objeto a iluminar esta tapado por otro se necesita la textura del depth buffer y la z del vértice a iluminar. Para ello se saca la proyección de la textura de depth y se compara con la z del vértice, si la z del vértice a iluminar es menor que el valor del depth se ilumina este vértice, si es menor eso quiere decir que está tapado por el otro y no se ilumina.
Con esto estaría ya hecha las sombras pero tiene errores de acné, para solucionar esto hay que aplicarle un bias.
Hay 2 tipos de crear sombras, con una cámara ortográfica o una cámara en perspectiva. Con la cámara en perspectiva se simula el efecto de una luz normal donde si el objeto está cerca de la luz la sombra se hace más grande y si el objeto está lejos se hace más pequeña. Con la cámara ortográfica simula la luz del sol ya que el tamaño no cambia dependiendo de la cercanía del objeto a la luz, esto en el sol ocurre ya que el sol está tan lejos que no se aprecia.
Skybox
Hay que crear un cubo y en el shader en el cálculo de view projection a la view hay que quitarle la traslación para que el skybox se quede fijo y no lo puedas atravesar.En el fragment shader hay que leer la textura cubemap con las coordenadas de la posición de los vértices del cubo ya que el cubo va de -1 a 1 y se acopla la textura a él.
Bloom
Para implementar esta técnica hay que utilizar los RenderTexture para sacar y almacenar la información.En la primera pasada se calculan las luces y con el cálculo este se saca los píxeles más brillantes y se guardan en una textura para luego aplicarle el glow. Con este RenderTexture se obtienen 2 texturas una de el calculo de luces y otra de lo que brillan los objetos.
Ahora hay que hacerle un gaussian blur a la imagen, para ello se utilizan 2 RenderTexture que se van alternando. Cada RenderTexture hace un blur de la imagen que se le pase, por cada pasada se le hace el blur una vez en horizontal y otra en vertical. Cuando un RenderTexture termina le pasa la textura resultante al otro RenderTexture así se van acumulando el blur y queda más difuminado. Para hacer el blur se coge la textura que recibe el RenderTexture se mezclan los colores de al lado, tantos como se decida implementar, por ejemplo con un offset de 5 se cogerian los 5 de la izquierda y los 5 de la derecha y se mezclarian con una suma. La primera textura que se le pasa a estos 2 RenderTexture es la textura que hemos obtenido con los brillos.
Cuando ya se tiene la textura con el gaussian blur aplicado hay que hacer una última pasada para juntar la textura difuminada con la textura de las luces con una suma. Y por último hay que transformarlo de HDR a LDR para representar los colores en pantalla.
Por último como ejemplo se ha implementado un valor de emisivo para poder hacer que un objeto brille entero sin necesidad de que las luces le afecten. Para ello se le ha añadido una variable de emisivo a los objetos y estos con ese valor rellenan la textura de brillos, para así poder aplicarle el efecto de bloom.
HDR
Los colores en RGB van de 0 a 1 y esto puede ser un problema cuando tienes luces que afectan a zonas muy iluminadas, ya que el color es limitado y el resultado no se puede representar bien. Para ello hay que hacer los cálculos de iluminación en HDR y luego en la textura final hacer la conversión de HDR a LDR para poder representarlos. Para ello debemos dividir los valores de HDR obtenidos por su propio valor más 1, de forma que quede acotado entre 0 y 1. Con esto tenemos unos colores claros más adecuados, ahora hay que aplicar la Corrección Gamma para tener unos colores más estables.SSAO
La técnica a implementar de Ambient Occlusion es SSAO, el primer paso para implementar SSAO hay que hacer una primera pasada obteniendo la posición y las normales con los valores del vértice por la view y la model para poner los objetos en espacio de pantalla.Para hacer el SSAO se necesitan muestras en espacio de tangente para comprobar si los puntos están, estas muestras se obtienen asumiendo que la apunta en z positivo se sacan puntos aleatorios entre -1.0 y 1.0 para la x e y, y entre 0.0 y 1.0 para la z para hacer la semiesfera de muestras.
Para simplificar la cantidad de muestras hay que crear una textura de 4x4 para poner puntos de rotación aleatorios, esta textura hay que hacer que se repita para distribuir estos puntos por toda la pantalla.
Con esta información hay que pasar los puntos del espacio de tangente al view space, para ello se calcula la tangente de la textura 4x4, el bitangente de las normales con la tangente y con la tangente, bitangente y las normales se crea la matriz para pasar del espacio tangente al view space, para ello se multiplica la matriz que hemos obtenido por cada muestra. Luego se saca cada muestra de la semiesfera se le pasa al view space y se le agrega la posición actual del fragment, para asi poder comparar la profundidad, para ello se le suma la muestra en view space a la posición del vértice y se le multiplica la projection para ponerla en el espacio de clipping y asi comprobar los depth. Con esto se saca el sombreado que tienen los objetos por el ambient occlusion.
Cuando ya se tiene el sombreado hay que hacer un blur para suavizar el efecto. Para hacer el blur hay que mezclar los colores de la textura sombreada con un offset, por ejemplo sumando los 9 píxeles que contiene alrededor.