Reflexiones cáusticas realistas


La mayoría de los artistas técnicos en algún momento de sus carreras intentan crear reflejos plausibles de cáusticos. Si eres un desarrollador de juegos, entonces una de las principales razones para leer Twitter es el flujo interminable de inspiración que puedes obtener de él. Hace unos días, Florian Gelzenlichter ( kolyaTQ en Twitter) publicó un GIF del efecto cáustico creado en Unity usando sombreadores. La publicación (presentada a continuación) ganó rápidamente 1,5 mil me gusta, lo que muestra un sincero interés en este tipo de contenido.


Aunque generalmente me atraen más las series de artículos más largas y técnicamente complejas (por ejemplo, sobre la dispersión volumétrica de la luz atmosférica [ traducción en Habré] y la cinemática inversa [ primera y segunda parte de la traducción en Habré], no pude resistir la tentación de escribir un tutorial corto y lindo sobre los efectos de Florian .

Al final de este artículo hay un enlace para descargar el paquete de Unity y todos los recursos necesarios.

¿Qué es cáustico?


Es posible que no conozca el concepto de cáusticos , aunque encuentre este efecto a diario. Los cáusticos son reflejos de luz causados ​​por superficies curvas. En el caso general, cualquier superficie curva puede comportarse como una lente, enfocando la luz en algunos puntos y dispersándola en otros. Los medios más comunes que proporcionan tal efecto son el vidrio y el agua, que genera las llamadas ondas cáusticas (ver más abajo).


Los cáusticos pueden tomar otras formas. Un arco iris, por ejemplo, es un fenómeno óptico que ocurre cuando la luz se refracta en las gotas de lluvia. Por lo tanto, estrictamente hablando, es cáustico.

Anatomía del efecto


Una característica reconocible de las ondas cáusticas es la forma en que se mueve; lo más probable es que lo hayas visto si alguna vez miraste el fondo de la piscina. La recreación de un cáustico real es muy costosa porque requiere la simulación de muchos rayos de luz.

Florian logró crear un efecto plausible, comenzando con una textura cáustica única. Para crear mi tutorial, utilicé la textura que se muestra a continuación, tomada de OpenGameArt .


Una propiedad importante que permite realizar este efecto es que el patrón cáustico que se muestra arriba es perfecto . Esto significa que puede colocar dos imágenes una al lado de la otra y no habrá una costura notable entre ellas. Como queremos usar este efecto en grandes superficies, es importante que tengamos la oportunidad de estirar esta textura sin rasgaduras que puedan destruir la ilusión.

Habiendo recibido la textura, Florian sugiere seguir tres pasos:

  • Aplique un patrón cáustico dos veces a la superficie del modelo, cada vez con diferentes tamaños y velocidades.
  • Mezcle dos patrones con el operador min
  • Canales RGB separados al muestrear.

Veamos cómo puede implementar cada uno de los pasos en Unity.

Creación de sombreadores


El primer paso es crear un nuevo sombreador. Dado que es probable que este efecto se use en un juego 3D que también tiene iluminación real, es mejor comenzar con un sombreador de superficie . Los sombreadores de superficie son uno de los muchos tipos de sombreadores compatibles con Unity (como sombreadores de vértices y fragmentos para materiales no iluminados, sombreadores de pantalla para efectos de procesamiento posterior y sombreadores computacionales para simulaciones fuera de pantalla).

El nuevo sombreador de superficie tiene solo algunas características. Para crear este efecto, necesitamos transferir información al sombreador. El primero es la textura cáustica. En segundo lugar, este es el parámetro utilizado para escalarlo y compensarlo.

Creemos dos propiedades de sombreador :

 Properties { ... [Header(Caustics)] _CausticsTex("Caustics (RGB)", 2D) = "white" {} // Tiling X, Tiling Y, Offset X, Offset Y _Caustics_ST("Caustics ST", Vector) = (1,1,0,0) } 

y las correspondientes variables de Cg :

 sampler2D _CausticsTex; float4 _Caustics_ST; 

Las propiedades del sombreador corresponden a los campos que se muestran en el Inspector de materiales de Unity. Las variables Cg correspondientes son los valores mismos, que se pueden usar en el código del sombreador.

Como puede ver en el código anterior, _Caustics_ST es float4 , es decir, contiene cuatro valores. Los utilizaremos para controlar el muestreo de la textura cáustica. A saber:

  • _Caustics_ST.x : escala de la textura cáustica a lo largo del eje X;
  • _Caustics_ST.y : escala de la textura cáustica a lo largo del eje Y;
  • _Caustics_ST.z : desplazamiento de la textura cáustica a lo largo del eje X;
  • _Caustics_ST.w : desplazamiento de la textura cáustica a lo largo del eje Y;

¿Por qué la variable se llama _Caustics_ST?
Si ya tiene un poco de experiencia trabajando con sombreadores, ya ha visto otras propiedades que terminan con el sufijo _ST . En Unity, _ST puede usarse para agregar información adicional sobre cómo se muestrea la textura.

Por ejemplo, si crea la variable Cg _MainTex_ST , puede usarla para establecer el tamaño y el desplazamiento al aplicar textura al modelo.

Por _ST general, _ST variables _ST no necesitan propiedades porque se muestran automáticamente en el inspector. Sin embargo, en este caso particular, no podemos confiar en esto porque necesitamos muestrear la textura dos veces, cada vez con una escala y un desplazamiento diferentes. En el futuro, necesitamos duplicar esta variable en dos variables diferentes.

Textura de muestreo


Cada sombreador de superficie contiene una función, comúnmente llamada surf , que se utiliza para determinar el color de cada píxel renderizado. La función de surf "estándar" se ve así:

 void surf (Input IN, inout SurfaceOutputStandard o) { // Albedo comes from a texture tinted by color fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color; o.Albedo = c.rgb; // Metallic and smoothness come from slider variables o.Metallic = _Metallic; o.Smoothness = _Glossiness; o.Alpha = ca; } 

El color final está determinado por la cantidad de campos que el sombreador debe inicializar y devolver en una estructura llamada SurfaceOutputStandard . Necesitamos cambiar Albedo , que coincide aproximadamente con el color del objeto iluminado por la luz blanca.

En el sombreador de superficie recién creado, el albedo se toma de una textura llamada _MainTex . Dado que el efecto cáustico se superpone sobre la textura existente, tendremos que realizar un muestreo adicional de la textura en _CausticsTex .

Una técnica llamada superposición UV le permite comprender qué parte de la textura necesita ser muestreada dependiendo de qué parte de la geometría necesita ser renderizada. Esto se hace usando uv_MainTex , la variable float2 , almacenada en cada vértice del modelo 3D e indicando la coordenada de la textura.

Nuestra idea es usar _Caustics_ST para escalar y compensar uv_MainTex para estirar y mover la textura cáustica a través del modelo.

 void surf (Input IN, inout SurfaceOutputStandard o) { // Albedo comes from a texture tinted by color fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color; o.Albedo = c.rgb; // Caustics sampling fixed2 uv = IN.uv_MainTex * _Caustics_ST.xy + _Caustics_ST.zw; fixed3 caustics = tex2D(_CausticsTex, uv).rgb; // Add o.Albedo.rgb += caustics; // Metallic and smoothness come from slider variables o.Metallic = _Metallic; o.Smoothness = _Glossiness; o.Alpha = ca; } 

¿Qué pasa si Albedo excede 1?
En el código anterior, agregamos dos texturas. El color suele estar entre 0antes 1Sin embargo, no hay garantía de que, como resultado, algunos valores no excedan este intervalo.

En sombreadores más antiguos, esto podría causar un problema. Aquí es en realidad una característica . Si el valor del color del píxel excede la unidad, esto significa que su influencia debería "extenderse" más allá de sus bordes y afectar a los píxeles vecinos.

Esto es exactamente lo que sucede cuando se obtienen reflejos especulares muy brillantes. Sin embargo, este efecto no debe ser creado solo por un sombreador de superficie. Para que el efecto funcione, la cámara debe tener HDR activado. Esta propiedad significa alto rango dinámico ; permite que los valores de color excedan 1. Además, para desenfocar una cantidad excesiva de colores en píxeles vecinos, se requiere un efecto de procesamiento posterior.

Unity tiene su propia pila de procesamiento posterior, que tiene un filtro de floración que hace exactamente eso. Puede leer más sobre esto en el blog de Unity: PostFX v2 - Imágenes increíbles, actualizadas .

Los resultados preliminares se muestran a continuación:


Cáusticos animados


Una de las características más importantes de los cáusticos es cómo se mueve. Por el momento, simplemente se proyectan estáticamente en la superficie del modelo como una segunda textura.

La animación de materiales en sombreadores se puede implementar utilizando la propiedad Unity _Time . Se puede usar para acceder al tiempo de juego actual, es decir, agregar tiempo a las ecuaciones.

La forma más fácil es simplemente compensar la textura en función de la hora actual.

 // Caustics UV fixed2 uv = IN.uv_MainTex * _Caustics_ST.xy + _Caustics_ST.zw; uv += _CausticsSpeed * _Time.y; // Sampling fixed3 caustics = tex2D(_CausticsTex, uv).rgb; // Add o.Albedo.rgb += caustics; 

El campo _Time.y contiene el tiempo de reproducción actual en segundos . Si el reflejo se mueve demasiado rápido, puede multiplicarlo por un factor. Para esto, la variable _CausticsSpeed de tipo float2 usa en el código presentado anteriormente.

Es posible que necesite hacer vibrar la textura cáustica en una sinusoide para sus propósitos. Es importante entender aquí que no hay una forma estándar de realizar el efecto. Dependiendo de sus necesidades, puede hacer que los reflejos cáusticos se muevan de manera completamente diferente.

Los resultados que se muestran a continuación siguen siendo bastante mediocres. Esto es normal: todavía tenemos mucho por hacer para que los reflejos se vean hermosos.


Muestreo múltiple


El efecto cobra vida si muestras la textura cáustica no una, sino dos veces. Si los coloca uno encima del otro y los mueve a diferentes velocidades, el resultado será completamente diferente.

Primero, _CausticsSpeed propiedades _Caustics_ST y _CausticsSpeed para que las muestras de las dos texturas tengan diferentes escalas, desplazamientos y velocidades:

 [Header(Caustics)] _CausticsTex("Caustics (RGB)", 2D) = "white" {} // Tiling X, Tiling Y, Offset X, Offset Y _Caustics1_ST("Caustics 1 ST", Vector) = (1,1,0,0) _Caustics2_ST("Caustics 1 ST", Vector) = (1,1,0,0) // Speed X, Speed Y _Caustics1_Speed("Caustics 1 Speed", Vector) = (1, 1, 0 ,0) _Caustics2_Speed("Caustics 2 Speed", Vector) = (1, 1, 0 ,0) 

Ahora que tenemos dos muestras cáusticas, se pueden mezclar usando el operador min . Si solo toma el valor promedio, el resultado no será muy bueno.

 // Caustics samplings fixed3 caustics1 = ... fixed3 caustics2 = ... // Blend o.Albedo.rgb += min(caustics1, caustics2); 

Un cambio tan pequeño hace una gran diferencia:


Para mantener el código hermoso, también puede ajustar el código de muestreo cáustico en su propia función:

 // Caustics fixed3 c1 = causticsSample(_CausticsTex, IN.uv_MainTex, _Caustics1_ST, _Caustics1_Speed); fixed3 c2 = causticsSample(_CausticsTex, IN.uv_MainTex, _Caustics2_ST, _Caustics2_Speed); o.Albedo.rgb += min(c1, c2); 

Separación RGB


Para que los reflejos cáusticos se vean bien, debes hacer el último truco. Al pasar a través de un corte, la luz de diferentes longitudes de onda se refracta de manera diferente. Esto significa que cuando se mueve a través del agua, la luz puede "dividirse" en diferentes colores.

Para simular este efecto, podemos dividir cada muestra cáustica en tres, una para cada canal de color. Al muestrear los canales rojo, verde y azul con un ligero sesgo, obtenemos una falta de coincidencia de color.

Comencemos agregando la propiedad _SplitRGB , que indica la fuerza del efecto de _SplitRGB - _SplitRGB :

 // Caustics UV fixed2 uv = IN.uv_MainTex * _Caustics_ST.xy + _Caustics_ST.zw; uv += _CausticsSpeed * _Time.y; // RGB split fixed s = _SplitRGB; fixed r = tex2D(tex, uv + fixed2(+s, +s)).r; fixed g = tex2D(tex, uv + fixed2(+s, -s)).g; fixed b = tex2D(tex, uv + fixed2(-s, -s)).b; fixed3 caustics = fixed3(r, g, b); 

La cantidad de desplazamiento de los canales RGB se puede seleccionar arbitrariamente, pero incluso con este desplazamiento simple, se obtiene una imagen muy convincente:


Conclusión y descargas


Si está interesado en aprender cómo crear texturas cáusticas sin costura, entonces debería leer el interesante artículo Texturas cáusticas periódicas .

Mientras tanto, Florian continúa trabajando en su sombreador cáustico y ha realizado algunas mejoras bastante interesantes que se pueden ver.


Un paquete completo para este tutorial está disponible en Patreon, incluye todos los recursos necesarios para recrear esta técnica. El paquete se exportó desde Unity 2019.2 y requiere la pila de posprocesamiento v2.

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


All Articles