[
La primera parte ]
Habiendo tratado los conceptos básicos, en esta parte del artículo implementamos efectos como contornos de objetos, floración, SSAO, desenfoque, profundidad de campo, pixelación y otros.
Esquemas
Crear contornos alrededor de la geometría de la escena le da al juego un aspecto único que se asemeja a los cómics o dibujos animados.
Material difuso
El sombreador de contorno necesita una textura de entrada para reconocer y colorear los bordes. Los candidatos para tal textura entrante pueden ser un color difuso de los materiales, colores de texturas difusas, vértices normales o incluso colores de mapas normales.
uniform struct { vec4 diffuse ; } p3d_Material; out vec4 fragColor; void main() { vec3 diffuseColor = p3d_Material.diffuse.rgb; fragColor = vec4(diffuseColor, 1); }
Este es un sombreador de fragmentos pequeños que convierte el color difuso de un material de geometría en una textura de búfer de cuadro. Esta textura de color difusa del búfer de cuadro será la textura de entrada para el sombreador de ruta.
Esta es la textura del color difuso del material del búfer de cuadros, que muestra los colores que configuramos en Blender. El sombreador de contorno reconocerá los bordes de la escena y los coloreará.
Cabe señalar que el color difuso de los materiales no funcionará si ciertas partes de la escena no tienen su propio color difuso del material.
Crear bordes
Crear bordes es similar a usar filtros de reconocimiento de bordes en
GIMP .
Todos los cálculos para esta técnica de sombreado se realizan en un sombreador de fragmentos. Para crear contornos para el sombreador de vértices, es suficiente pasar cuatro vértices de la malla rectangular a la salida para que se ajuste a la pantalla.
Antes de comenzar a reconocer los bordes, debe preparar la textura entrante, con la que trabajaremos. Como la textura tiene un tamaño de pantalla, podemos calcular las coordenadas UV, conociendo las coordenadas del fragmento y el tamaño de la textura entrante.
separation
se puede personalizar para adaptarse a su gusto. Cuanto mayor es la separación, más gruesos son los bordes o líneas.
La técnica de reconocimiento de bordes encuentra cambios en los colores de la textura entrante. Centrándose en el fragmento actual, utiliza la ventana de fragmentos 3x3 para encontrar los colores más brillantes y oscuros de las nueve muestras. Luego resta del brillo de un color al brillo de otro, obteniendo su diferencia.
Esta diferencia se usa en el canal alfa del color de salida. Si no hay diferencia, entonces el borde o la línea no se dibuja. Si hay una diferencia, entonces se dibuja el borde.
Intenta experimentar con el valor umbral. Ahora es cero. Cualquier valor distinto de cero se convierte en un borde; este umbral se puede cambiar. Esto es especialmente útil para texturas entrantes más ruidosas con pequeñas diferencias. En el caso de una textura entrante ruidosa, generalmente necesita crear contornos solo para grandes diferencias.
Código fuente
Niebla
La niebla (o neblina, como se llama en Blender) agrega niebla atmosférica a la escena, creando misteriosas partes salientes suavizadas. Las partes sobresalientes aparecen cuando parte de la geometría cae repentinamente en la pirámide de visibilidad de la cámara.
Panda3D tiene una estructura de datos conveniente que contiene todos los parámetros de niebla, pero puede transferirlos a su sombreador manualmente.
En el ejemplo de código, se usa un modelo lineal para calcular el brillo de la niebla cuando se aleja de la cámara. En cambio, puede usar el modelo exponencial. El brillo de la niebla es cero antes o al comienzo de la niebla. Cuando la posición del vértice se acerca al final de la niebla,
fogIntensity
acerca a la unidad. Para todos los vértices después del final de la niebla,
fogIntensity
limita a 1 desde arriba.
Según el brillo de la niebla, mezclamos el color de la niebla con el color de salida. A medida que
fogIntensity
acerca a la unidad, habrá cada vez menos
outputColor
y más y más color de niebla. Cuando
fogIntensity
alcanza la unidad, solo quedará el color de la niebla.
Niebla en los contornos
El Path Shader aplica niebla a los colores de los bordes para obtener una imagen más holística. Si no hiciera esto, la niebla oscurecería la geometría de los contornos, lo que se vería extraño. Sin embargo, todavía crea contornos en los bordes más exteriores de la geometría de la etapa con el molino, porque los bordes van más allá de la geometría, a donde no hay posiciones de vértices.
positionTexture
es una textura de búfer de cuadro que contiene las posiciones de los vértices del espacio de vista. Aprenderá sobre esto cuando implementemos el sombreador SSAO.
Código fuente
Bloom
Agregar flor a la escena puede crear una ilusión convincente del modelo de iluminación. Los objetos emisores de luz se vuelven más convincentes, y los reflejos de luz reciben una cantidad adicional de brillo.
Puede personalizar esta configuración a su gusto. La separación aumenta el tamaño del desenfoque. Las muestras determinan la intensidad del desenfoque. El umbral determina qué será y no será afectado por este efecto. Cantidad controla la cantidad de salida de floración.
Esta técnica comienza pasando
samples
tamaño de ventana a
samples
centradas en relación con el fragmento actual. Parece una ventana utilizada para crear caminos.
Este código obtiene el color de la textura entrante y convierte los valores de rojo, verde y azul en un valor en escala de grises. Si el valor en escala de grises es inferior al umbral, descarta este color y lo convierte en negro.
Al pasar por todas las muestras dentro de la ventana, acumula todos sus valores como
result
.
Una vez completada la colección de muestras, divide la suma de las muestras de color por el número de muestras tomadas. El resultado es el color medio del fragmento en sí y sus vecinos. Al hacerlo para cada fragmento, obtenemos una imagen borrosa. Este tipo de desenfoque se denomina desenfoque de cuadro.
Aquí puede ver el proceso de ejecución del algoritmo de floración.
Código fuente
Oclusión ambiental de espacio de pantalla (SSAO)
SSAO es uno de esos efectos que no sabes que existen, pero tan pronto como sabes que ya no puedes vivir sin ellos. ¡Puede convertir una escena mediocre en una increíble! En escenas estáticas, la oclusión ambiental se puede hornear en la textura, pero para escenas más dinámicas necesitamos un sombreador. SSAO es una de las técnicas de sombreado más sofisticadas, pero una vez que lo descubras, te convertirás en un sombreador maestro.
Tenga en cuenta que el término "espacio de pantalla" en el título no es del todo correcto, porque no todos los cálculos se realizan en el espacio de pantalla.
Datos entrantes
El sombreador SSAO necesitará la siguiente entrada.
- Vectores de posiciones de vértices en el espacio de visualización.
- Vectores normales a los vértices en el espacio de visualización.
- Vectores de muestra en espacio tangente.
- Vectores de ruido en el espacio tangente.
- La matriz de proyección en la lente de la cámara.
Posición
No es necesario almacenar posiciones de vértices en la textura del búfer de cuadro. Podemos recrearlos desde
el búfer de profundidad de
la cámara . Estoy escribiendo una guía para principiantes, por lo que no utilizaremos esta optimización y nos pondremos manos a la obra de inmediato. En su implementación, puede usar fácilmente el búfer de profundidad.
PT(Texture) depthTexture = new Texture("depthTexture"); depthTexture->set_format(Texture::Format::F_depth_component32); PT(GraphicsOutput) depthBuffer = graphicsOutput->make_texture_buffer("depthBuffer", 0, 0, depthTexture); depthBuffer->set_clear_color(LVecBase4f(0, 0, 0, 0)); NodePath depthCameraNP = window->make_camera(); DCAST(Camera, depthCameraNP.node())->set_lens(window->get_camera(0)->get_lens()); PT(DisplayRegion) depthBufferRegion = depthBuffer->make_display_region(0, 1, 0, 1); depthBufferRegion->set_camera(depthCameraNP);
Si decide usar el búfer de profundidad, así es como puede configurarlo en Panda3D.
in vec4 vertexPosition; out vec4 fragColor; void main() { fragColor = vertexPosition; }
Aquí hay un sombreador simple para representar las posiciones de vértice en el espacio de visualización en una textura de búfer de cuadro. Una tarea más difícil es ajustar la textura de la memoria intermedia de trama para que los componentes del vector de fragmento obtenido por él no se limiten al intervalo
[0, 1]
, y que cada uno tenga una precisión suficientemente alta (un número suficientemente grande de bits). Por ejemplo, si algún tipo de posición de vértice interpolado es
<-139.444444566, 0.00000034343, 2.5>
, entonces no puede guardarlo en la textura como
<0.0, 0.0, 1.0>
.
Aquí hay un código de ejemplo que prepara una textura de búfer de marco para almacenar posiciones de vértice. Necesita 32 bits para rojo, verde, azul y alfa, por lo que desactiva la restricción de valores por el intervalo
[0, 1]
. La llamada a
set_rgba_bits(32, 32, 32, 32)
establece el volumen de bits y deshabilita la restricción.
glTexImage2D ( GL_TEXTURE_2D , 0 , GL_RGB32F , 1200 , 900 , 0 , GL_RGB , GL_FLOAT , nullptr );
Aquí hay una llamada similar en OpenGL.
GL_RGB32F
establece los bits y deshabilita la restricción.
Si el búfer de color tiene una coma fija, entonces los componentes de los valores iniciales y finales, así como los índices de mezcla, antes de calcular la ecuación de mezcla están limitados a [0, 1] o [−1, 1], respectivamente, para los búferes de color normalizados y firmados normalizados sin signo. Si el búfer de color tiene un punto flotante, entonces no se cumple la restricción.
Fuente
Aquí ves las posiciones de los vértices; el eje y está arriba.
Recuerde que Panda3D define el eje z como un vector que apunta hacia arriba, mientras que en OpenGL el eje y mira hacia arriba. El sombreador de posición muestra las posiciones de los vértices con una z hacia arriba, porque en Panda3D
el parámetro
gl-coordinate-system default
está configurado.
Normal
Para la orientación correcta de las muestras obtenidas en el sombreador SSAO, necesitamos las normales a los vértices. El código de muestra genera varios vectores de muestreo distribuidos en el hemisferio, pero puede usar la esfera y resolver completamente el problema de la necesidad de valores normales.
in vec3 vertexNormal; out vec4 fragColor; void main() { vec3 normal = normalize(vertexNormal); fragColor = vec4(normal, 1); }
Al igual que el sombreador de posición, el sombreador normal es muy simple. Recuerde normalizar las normales a los vértices y recuerde que están en el espacio de visualización.
Las normales a los vértices se muestran aquí; el eje y está arriba.
Recuerde que Panda3D considera que el eje z es el vector ascendente y OpenGL al eje y. El sombreador normal muestra las posiciones de vértice con el eje z apuntando hacia arriba, porque el
gl-coordinate-system default
configurado en Panda3D.
Muestras
Para determinar el valor de oclusión ambiental para cualquier fragmento individual, necesitamos muestrear el área circundante.
El código de muestra genera 64 muestras aleatorias distribuidas en un hemisferio. Estos
ssaoSamples
se pasarán al sombreador SSAO.
LVecBase3f sample = LVecBase3f ( randomFloats(generator) * 2.0 - 1.0 , randomFloats(generator) * 2.0 - 1.0 , randomFloats(generator) * 2.0 - 1.0 ).normalized();
Si desea distribuir sus muestras a través de una esfera, cambie el intervalo del componente aleatorio z para que cambie de menos uno a uno.
El ruido
Para cubrir bien el área muestreada, necesitamos generar vectores de ruido. Estos vectores de ruido pueden rotar muestras alrededor de la parte superior de la superficie.
Oclusión ambiental
SSAO cumple su tarea al muestrear el espacio de visualización alrededor del fragmento. Cuantas más muestras debajo de la superficie, más oscuro será el color del fragmento. Estas muestras se encuentran en el fragmento e indican en la dirección general de lo normal al vértice. Cada muestra se utiliza para buscar una posición en la textura de la posición del búfer de cuadros. La posición devuelta se compara con la muestra. Si la muestra está más lejos de la cámara que la posición, entonces la muestra hacia el fragmento está ocluida.
Aquí puede ver el espacio sobre la superficie muestreada para oclusión.
Como algunas otras técnicas, el sombreador SSAO tiene varios parámetros de control que se pueden cambiar para obtener la apariencia deseada. El sesgo se agrega a la distancia de la muestra a la cámara. Este parámetro puede usarse para combatir las manchas. el radio aumenta o disminuye el área de cobertura del espacio muestral. lowerRange y upperRange cambian el rango estándar de la métrica del factor de
[0, 1]
a cualquier valor que seleccione. Al aumentar el rango, puede aumentar el contraste.
Obtenemos la posición, el vector normal y aleatorio para su uso posterior. Recuerde que en el ejemplo del código, se crearon 16 vectores aleatorios. Se selecciona un vector aleatorio en función de la posición de la pantalla de los fragmentos actuales.
Usando un vector aleatorio y un vector normal, recolectamos la matriz de la tangente, binormal y normal. Necesitamos esta matriz para transformar los vectores de muestra del espacio tangente al espacio topográfico.
Al tener una matriz, el sombreador puede recorrer todas las muestras del ciclo, restando el número de muestras sin abrir.
Usando la matriz, coloque la muestra al lado de la posición del vértice / fragmento y escale por el radio.
Usando la posición de la muestra en el espacio de visualización, la transformamos del espacio de visualización al espacio de recorte y luego al espacio UV.
-1 * 0.5 + 0.5 = 0 1 * 0.5 + 0.5 = 1
No olvide que los componentes del espacio de recorte están en el rango de menos uno a uno, y las coordenadas UV están en el rango de cero a uno. Para convertir las coordenadas del espacio de recorte en coordenadas UV, multiplíquelas por un segundo y agregue un segundo.
Usando las coordenadas de desplazamiento UV obtenidas al proyectar la muestra 3D sobre la textura de posición 2D, encontramos el vector de posición correspondiente. Esto nos lleva del espacio de visualización al espacio de recorte al espacio UV y luego de vuelta al espacio de visualización. El sombreador ejecuta este bucle para determinar si hay alguna geometría detrás de la muestra, en la ubicación de la muestra o en frente de la muestra. Si la muestra se encuentra enfrente o en alguna geometría, entonces esta muestra simplemente no se tiene en cuenta en relación con el fragmento superpuesto. Si la muestra está detrás de alguna geometría, entonces esta muestra se tiene en cuenta en relación con el fragmento superpuesto.
Ahora agregue peso a esta posición muestreada según lo lejos que esté dentro o fuera del radio. Luego, reste esta muestra de la métrica de oclusión porque supone que todas las muestras se solaparon antes del bucle.
Divida el número de superposición por el número de muestras para convertir el indicador de oclusión del intervalo
[0, NUM_SAMPLES]
al intervalo
[0, 1]
. Cero significa oclusión completa, unidades significa que no hay oclusión. Ahora asigne la métrica de oclusión al color del fragmento, y eso es todo.
Tenga en cuenta que en el código de ejemplo, al canal alfa se le asigna el valor alfa de la textura de posición del búfer de cuadros para evitar la superposición de fondo.
Desenfoque
La textura del búfer de cuadro SSAO es un poco ruidosa, por lo que debe desenfocarlo para suavizarlo.
El sombreador de desenfoque SSAO es un desenfoque de cuadro normal. Al igual que el sombreador de flores, dibuja una ventana sobre la textura entrante y promedia cada fragmento con los valores de sus vecinos.
Tenga en cuenta que los
parameters.x
es un parámetro de separación.
Color ambiental
El desafío final para SSAO está nuevamente en los cálculos de iluminación. Aquí vemos cómo se encuentra la oclusión en el búfer de textura SSAO de textura y se incluye en el cálculo de la luz ambiental.
Código fuente
Profundidad de campo
La profundidad de campo también es un efecto de este tipo, habiendo aprendido sobre cuál, no puedes vivir sin él. Desde un punto de vista artístico, puede usarlo para atraer la atención del espectador a un objeto específico. Pero en el caso general, la profundidad de campo a costa de un pequeño esfuerzo agrega una gran parte de realismo.
En foco
El primer paso es renderizar la escena completamente en foco. Renderízalo a la textura del buffer de cuadro Será uno de los valores de entrada para el búfer de profundidad de campo.
Fuera de foco
El segundo paso es desenfocar la escena como si estuviera completamente desenfocada. Al igual que con Bloom y SSAO, puede usar el cuadro borroso. Renderice esta escena desenfocada a la textura del búfer de cuadro. Será otro valor de entrada para la profundidad del sombreador de campo.
Tenga en cuenta que los
parameters.x
es un parámetro de separación.
Confusión
Puede personalizar estas opciones a su gusto.
focalLengthSharpness
afecta la
focalLengthSharpness
la escena a la distancia focal. Cuanto más pequeña sea la
focalLengthSharpness
, más desenfocada será la escena a la distancia focal.
blurRate
afecta la velocidad de desenfoque de la escena cuando se aleja de la distancia focal. Cuanto más pequeña sea la
blurRate
, menos borrosa será la escena al alejarse del punto de enfoque.
Necesitaremos colores enfocados y en una imagen desenfocada.
También es posible que necesitemos la posición del vértice en el espacio de visualización. Puede volver a aplicar la textura de las posiciones desde el búfer de trama que se utilizó para SSAO.
Y aquí tiene lugar la confusión. Cuanto más cerca blur
de uno, más usará outOfFocusColor
. Un valor de cero blur
significa que este fragmento está completamente enfocado. Con blur >= 1
este fragmento queda completamente desenfocado.Código fuente
Posterización
La posterización, o muestreo de color, es el proceso de reducir la cantidad de colores únicos en una imagen. Puedes usar este sombreador para darle al juego un aspecto cómico o retro. Si lo combinas con un contorno, obtienes un verdadero estilo de dibujos animados.
Puedes experimentar con este parámetro. Cuanto más grande sea, más flores quedarán como resultado.
Necesitaremos el color entrante.
No he visto tal método de posterización. Después de verificarlo, vi que crea resultados más hermosos en comparación con los métodos convencionales. Para reducir la paleta de colores, primero convierta el color a un valor en escala de grises. Discretamos el color vinculándolo a uno de los niveles. Calculamos la diferencia entre el valor muestreado en escala de grises y el valor no muestreado en escala de grises. Agregue esta diferencia al color de entrada. Esta diferencia es la cantidad por la cual el color debe aumentar / disminuir para lograr un valor discretizado en escala de grises.
No olvide asignar el valor del color de entrada al color del fragmento.Cel shading
La posterización puede dar a una imagen la apariencia de sombreado cel, porque el sombreado cel es el proceso de discretizar colores difusos y difusos en tonos discretos. Queremos utilizar solo colores difusos sólidos sin detalles finos del mapa normal y un valor pequeño levels
.Código fuente
Pixelización
La pixelación de un juego en 3D puede darle una apariencia interesante, o puede ahorrarle tiempo que hubiera requerido la creación manual de todos los píxeles. Combínalo con posterización para crear un verdadero look retro.
Puede ajustar el tamaño del píxel usted mismo. Cuanto más grande sea, más rugosa será la imagen.
Esta técnica une cada fragmento al centro de su ventana de tamaño de píxel no superpuesta más cercana. Estas ventanas se alinean sobre la textura entrante. Los fragmentos en el centro de la ventana determinan el color de otros fragmentos en su ventana.
Después de determinar la coordenada del fragmento deseado para usar, tome su color de la textura entrante y asígnelo al color del fragmento.Código fuente
Afilar
El efecto de nitidez (nitidez) aumenta el contraste en los bordes de la imagen. Resulta útil cuando los gráficos resultan demasiado suaves.
Al cambiar el valor, podemos controlar la magnitud de la nitidez del resultado. Si el valor es cero, la imagen no cambiará. Con valores negativos, la imagen comienza a verse extraña.
Los fragmentos adyacentes se multiplican por amount * -1
. El fragmento actual se multiplica por amount * 4 + 1
.
Los fragmentos vecinos están en la parte superior, inferior, izquierda y derecha. Después de multiplicar los vecinos y el fragmento actual por sus valores, se suma el resultado.
Esta cantidad es el color final del fragmento.Código fuente
Grano de la película
El grano de la película (en pequeñas dosis, y no como en el ejemplo) puede agregar realismo, que es invisible hasta que se elimine este efecto. Por lo general, estas son las imperfecciones que hacen que la imagen generada digitalmente sea más convincente.Tenga en cuenta que el grano de película suele ser el último efecto que se aplica al fotograma antes de mostrarse.Valor
amount
controla la visibilidad del grano de la película. Cuanto mayor sea el valor, más "nieve" en la imagen.Brillo aleatorio
Este código calcula el brillo aleatorio necesario para ajustar el valor. Time Since F1 = 00 01 02 03 04 05 06 07 08 09 10 Frame Number = F1 F3 F4 F5 F6 osg_FrameTime = 00 02 04 07 08
Valor osg_FrameTime
proporcionado por Panda3D. Un tiempo de cuadro es una marca de tiempo con información sobre cuántos segundos han pasado desde el primer cuadro. El código de muestra lo utiliza para animar el grano de la película, que osg_FrameTime
será diferente en cada fotograma.
Para grano estático, las películas deben reemplazarse con un osg_FrameTime
gran número. Para evitar ver patrones, puedes probar diferentes números.
Para crear puntos, o manchas de grano de película, se utilizan ambas coordenadas, y x, e y. Si usa x, solo se mostrarán las líneas verticales, si usa y, solo se mostrarán las líneas horizontales.En el código, una coordenada se multiplica por otra para destruir la simetría diagonal.Por supuesto, puede deshacerse del multiplicador de coordenadas y obtener un efecto de lluvia completamente aceptable.Tenga en cuenta que para animar el efecto de lluvia, multiplique la salida sin
por osg_FrameTime
.Experimente con las coordenadas x e y para cambiar la dirección de la lluvia. Para una ducha descendente, deje solo la coordenada x. input = (gl_FragCoord.x + gl_FragCoord.y * osg_FrameTime) * toRadians frame(10000 * sin(input)) = fract(10000 * sin(6.977777777777778)) = fract(10000 * 0.6400723818964882) =
sin
usado como una función hash. Las coordenadas del fragmento se combinan con valores de salida sin
. Gracias a esto, aparece una propiedad conveniente: sean cuales sean los datos de entrada (grandes o pequeños), el intervalo de salida estará en el rango de menos uno a uno. fract(10000 * sin(6.977777777777778)) = fract(10000 * 0.6400723818964882) = fract(6400.723818964882) = 0.723818964882
sin
en combinación con fract
también se utiliza como generador de números pseudoaleatorios. >>> [floor(fract(4 * sin(x * toRadians)) * 10) for x in range(0, 10)] [0, 0, 1, 2, 2, 3, 4, 4, 5, 6] >>> [floor(fract(10000 * sin(x * toRadians)) * 10) for x in range(0, 10)] [0, 4, 8, 0, 2, 1, 7, 0, 0, 5]
Primero mira la primera fila de números, y luego la segunda. Cada fila es determinista, pero el patrón es menos notable en el segundo que en el segundo. Por lo tanto, a pesar de que el resultado es fract(10000 * sin(...))
determinista, el patrón se reconoce mucho más débil.Aquí vemos cómo el factor sin
es primero 1, luego 10, luego 100 y luego 1000.A medida que aumenta el multiplicador de los valores de salida, el sin
patrón se vuelve menos notable. Por esta razón, el código sin
se multiplica por 10,000.Color del fragmento
Convierta las coordenadas del fragmento en coordenadas UV. Usando estas coordenadas UV, buscamos el color de la textura para el fragmento actual.
Cambie el valor a un brillo aleatorio y agréguelo al color.
Establece el color del fragmento, y eso es todo.Código fuente
Agradecimientos