【Sombreador de unidad】 Conceptos básicos del material de flujo direccional de renderizado de agua

En el capítulo anterior, implementamos un material de flujo básico basado en compensación UV.
[Unity Shader] Conceptos básicos de la representación del agua: material de flujo de fluido basado en la distorsión de la textura.
Pero obviamente, cuando el cuerpo de agua necesita fluir de manera direccional, la solución anterior se vuelve menos realista.
Insertar descripción de la imagen aquí

1. Implementar compensación direccional UV básica

Simplemente tómese el tiempo directamente, escriba una función para compensar la dirección UV y utilice el mapa de muestreo UV compensado.

float2 DirectionalFlowUV(float2 uv, float2 flowVector, float tilling, float time)
{
    
    
	//实例的flowVector为(0,1)
    uv.y -= time;
    return uv * tilling;
}

Insertar descripción de la imagen aquí
Si desea ajustar diferentes ángulos, ajuste el flowVector correspondiente.

float2 DirectionalFlowUV(float2 uv, float2 flowVector, float tilling, float time)
{
    
    
    uv -= flowVector * time;
    return uv * tilling;
}

Insertar descripción de la imagen aquí

1.1 Implementar rotación ultravioleta

Por supuesto, es solo un simple desplazamiento a lo largo de la dirección xy de la uv. No importa cómo combine diferentes vectores de flujo, solo puede lograr la traducción de la uv en diferentes direcciones. El efecto general de la uv permanece paralelo a la dirección x. .
Si queremos que los UV se compensen en su conjunto, será mejor que movamos la textura en Photoshop o SD,
será mejor que cambiemos el método de cálculo de los UV.
Insertar descripción de la imagen aquí
Para el punto de muestreo p en un mapa, ya sea que uv se cambie en las direcciones xey (para obtener p1, p2) o que XOR se mezcle con un cambio lineal (para obtener p3), el resultado final es simplemente lograr En lugar de una traducción lineal, todos los UV se traducirán según la misma lógica y, finalmente, se convertirán en la traducción de toda la textura.
Insertar descripción de la imagen aquí
Entonces, la lógica que debemos implementar es encontrar las coordenadas uv después de rotar en un ángulo específico con el origen como el centro del círculo mientras se mantiene la distancia al origen (es decir, el radio).

De esta manera, todas las coordenadas pueden rotar con diferentes desplazamientos según sus diferentes radios, logrando así la rotación del mapa general.
Insertar descripción de la imagen aquí
Para mostrar la situación general, aquí eliminamos la textura que bloquea la línea de visión, de modo que el punto p en sí tenga un cierto ángulo en lugar de estar ubicado directamente en el eje x.

Ahora necesitamos encontrar las coordenadas s, t del punto p' que gira b grados en el ángulo de rotación predeterminado de un grado.

Por supuesto, las longitudes de OP y OP' son las mismas, ambas r.
Insertar descripción de la imagen aquí
Aquí debes usar la fórmula de división de funciones trigonométricas de la escuela secundaria, la publicaré directamente.
Insertar descripción de la imagen aquí

对于s,t来说,其值为
s = r * cos(a + b)
t = r * sin(a + b)
对于x, y来说,其值为
x = r * cosa
y = r * sina

对s,t进行拆分
s = r * (cosa * cosb - sina * sinb)
t = r * (sina * cosb + cosa * sinb)

直接用x,y的值带入即可
s = x * cosb - y * sinb
t = y * cosb + x * sinb

Entonces resolvimos la relación correspondiente entre las coordenadas después de la rotación, las coordenadas antes de la rotación y los ángulos.

Cuando se gira 45 grados, podemos encontrar la relación correspondiente como

s = x *2/2 - y *2/2
t = x *2/2 + y *2/2

我们直接将其化归,就能够得到对应的float2x2矩阵
(1,  1)
(-1, 1)

Utilice la matriz correspondiente para multiplicar el uv original y obtener el valor uv después de girar 45 grados.

Tenga en cuenta que el parámetro de función trigonométrica predeterminado de Unity aquí no es un ángulo, sino un radian, que debe convertirse.

float2 DirectionalFlowUV(float2 uv, float angle, float tilling, float time)
{
    
    
    float2 uv2;
    uv2.x = uv.x * cos(angle) - uv.y * sin(angle);
    uv2.y = uv.x * sin(angle) + uv.y * cos(angle);
    uv2 += time;

    return uv2 * tilling;
}

Insertar descripción de la imagen aquí
A continuación, debemos resolver el problema de las normales incorrectas después de la rotación.

Generamos directamente las normales como colores y podemos ver que las normales se muestran normalmente sin rotación.
Insertar descripción de la imagen aquí
Sin embargo, después de girar 90 grados, la dirección de la normal sigue siendo hacia el eje y (es decir, la parte azul en la imagen de arriba sigue siendo azul en la imagen de abajo), lo que significa que después de girar 90 grados, la normal es todavía mirando hacia el eje z. y la rotación no se completa.
Insertar descripción de la imagen aquí
Necesitamos usar la matriz rotada y también rotar los resultados muestreados.

float2 dh2;
dh2.x = dh.x * cos(_Rotate) - dh.y * sin(_Rotate);
dh2.y = dh.x * sin(_Rotate) + dh.y * cos(_Rotate);

float3 worldNormal = normalize(float3(-dh2.x, 1, -dh2.y));

Insertar descripción de la imagen aquí
Insertar descripción de la imagen aquí

1.2 Mapa de flujo de muestreo

Aunque es más divertido controlarlo directamente usando ángulos, para usar la dirección proporcionada por el mapa de flujo, todavía tenemos que modificar las partes correspondientes.

float2 DirectionalFlowUV(float2 uv,float3 flowVectorAndSpeed, float tilling, float time)
{
    
    
    float2 uv2;
    float2 dir = normalize(flowVectorAndSpeed.xy);
    uv2.x = uv.x * dir.x - uv.y * dir.y;
    uv2.y = uv.x * dir.y + uv.y * dir.x;
    uv2 += time * flowVectorAndSpeed;

    return uv2 * tilling;
}

Sin embargo, los resultados del muestreo directo son obviamente muy confusos y los ángulos de rotación de cada coordenada son inconsistentes. Debido a la introducción de cambios no lineales, el efecto es completamente inconsistente.
Insertar descripción de la imagen aquí

2. Implementar rotación + flujo en mosaico

En nuestro plan para lidiar con la distorsión UV traslacional, utilizamos principalmente dos UV con diferencias de tiempo para muestrear y luego mezclar los resultados del muestreo. Pero esto se debe a que la función frac en sí tiene un período de reinicio y los cambios de rotación no pueden resolver el problema de la distorsión excesiva simplemente mediante el procesamiento del tiempo.
existir
Como era imposible manejar todo el avión de manera global, dividimos el avión en múltiples áreas, de modo que la dirección del flujo en cada área fuera la misma, y ​​las áreas se mezclaron para lograr continuidad. Este método es el algoritmo Tiled Directional Flow propuesto por Frans van Hoesel.

2.1 Implementar muestreo en escalera

Agregue una función de paso basada en el parámetro de configuración _GridResolution para realizar el cambio uv correspondiente en pasos.

Insertar descripción de la imagen aquí

float2 tiledUV = floor(i.uv * _GridResolution) / _GridResolution;
float3 flow = tex2D(_FlowMap, tiledUV);

Dado que los UV en la misma escalera son consistentes, podemos obtener un plano dividido en cuadrados y los UV en un solo cuadrado cambian en una dirección.
Insertar descripción de la imagen aquí
Insertar descripción de la imagen aquí

2.2 Mezclar múltiples cuadrados

Primero, encapsulamos los cálculos de rotación y muestreo de escalera existentes en métodos que son independientes de fs.

float3 FlowCell(float2 uv, float time)
 {
    
    
     float2 uvTiled = floor(uv * _GridResolution) / _GridResolution;
     float3 flow = tex2D(_FlowMap, uvTiled);
     flow.xy = flow.xy * 2 -1;
     float2 uvFlow = DirectionalFlowUV(uv, flow, _Tiling, time);
     float3 dh = UnpackDerivativeHeight(tex2D(_MainTex, uvFlow));

     float3 dh2;
     float2 dir = normalize(flow.xy);
     dh2.x = dh.x * dir.x - dh.y * dir.y;
     dh2.y = dh.x * dir.y + dh.y * dir.x;
     dh2.z = dh.z;

     return dh2;
 }

fixed4 frag (v2f i) : SV_Target
 {
    
    
     float flowTime =  _Time.y;
     
     float3 dh = FlowCell(i.uv, flowTime);

     float3 worldNormal = normalize(float3(-dh.x, 1, -dh.y));
     
     fixed3 col = dh.z * dh.z * _Color;

     fixed3 diffuse = _LightColor0 * dot(_WorldSpaceLightPos0, worldNormal);
     
     return fixed4(col.xyz + diffuse, 1.0);
 }

La implementación del muestreo de compensación también es muy simple: agregue un desplazamiento de número de punto flotante bidimensional como parámetro y agréguelo durante el muestreo de pasos para recopilar información sobre los pasos adyacentes.

float3 FlowCell(float2 uv, float2 offset, float time)
{
    
    
    float2 uvTiled = floor(uv * _GridResolution + offset) / _GridResolution;
    //..........
}

Insertar descripción de la imagen aquí

Insertar descripción de la imagen aquí
Hacemos directamente un muestreo doble y luego nos referimos a mezclar.

//in fragment shader
float3 dhA = FlowCell(i.uv, float2(0, 0), flowTime);
float3 dhB = FlowCell(i.uv, float2(1, 0), flowTime);

float3 dh = 0.5 * dhA + 0.5 * dhB;

Se puede ver que en la dirección u(x), el problema de la discontinuidad obviamente ha mejorado algo.
Insertar descripción de la imagen aquí
Para garantizar el efecto de mezcla, utilizamos un método de mezcla de peso dinámico, utilizando la función decimal frac para obtener los cambios de UV en una sola celda y asignamos el peso de mezcla de acuerdo con los diferentes valores de u.
Insertar descripción de la imagen aquí

float t = frac(i.uv.x * _GridResolution);
float wA = 1 - t;
float wB = t;

float3 dh = wA * dhA + wB * dhB;

Insertar descripción de la imagen aquí
Pero ahora puedes ver que todavía hay espacios en el medio de cada celda, lo cual es causado por el salto de t.

Por un lado, comprimimos el desplazamiento para que una sola celda pueda desplazarse dos veces.

float3 FlowCell(float2 uv, float2 offset, float time)
{
    
    
    offset *= 0.5;
    //.............
}

Insertar descripción de la imagen aquí

Por otro lado, para el cálculo de pesos también se debe duplicar la amplitud de cambio para igualar los dos saltos de cada cuadrado.

float t = abs(2 * frac(i.uv.x * _GridResolution) - 1);
// float t = frac(i.uv.x * _GridResolution);
float wA = 1 - t;
float wB = t;

Insertar descripción de la imagen aquí
A continuación hacemos un muestreo compensado en la dirección v.

Tenga en cuenta que aquí estamos mezclando muestras para cuatro bloques diferentes, en lugar de simplemente mezclar muestras para los bloques directamente adyacentes del bloque 0,0.

float3 dhA = FlowCell(i.uv, float2(0, 0), flowTime);
float3 dhB = FlowCell(i.uv, float2(1, 0), flowTime);
float3 dhC = FlowCell(i.uv, float2(0, 1), flowTime);
float3 dhD = FlowCell(i.uv, float2(1, 1), flowTime);

float2 t = abs(2 * frac(i.uv * _GridResolution) - 1);
// float t = frac(i.uv.x * _GridResolution);
float wA = (1 - t.x) * (1 - t.y);
float wB = t.x * (1 - t.y);
float wC = (1 - t.x) * t.y;
float wD = t.x * t.y;

float3 dh = wA * dhA + wB * dhB + wC * dhC + wD * dhD;

Insertar descripción de la imagen aquí
En este punto, se ha logrado la fusión por rotación UV, pero si miras de cerca, todavía hay una vaga sensación de cuadrícula, especialmente en la dirección horizontal.

Necesitamos mover el resultado del piso para que esté en el medio de cada celda.

float3 FlowCell(float2 uv, float2 offset, float time)
{
    
    
    float shift = 1 - offset;
    offset *= 0.5;
    shift *= 0.5;

    float2 uvTiled = (floor(uv * _GridResolution + offset) + shift) / _GridResolution;
    //..........
}

Insertar descripción de la imagen aquí
Podemos ajustar el cultivo y _gridResolution para obtener diferentes efectos, pero tenga en cuenta que _gridResolution no se puede ajustar demasiado.
Insertar descripción de la imagen aquí

2.3 Complementar otros parámetros de control

Ahora agregue también los otros parámetros.

Ajustar dinámicamente la fuerza normal (altura de ola)

//in FlowCell function
dh2 *= flow.z * _HeightScaleModulated + _HeightScale;

Insertar descripción de la imagen aquí
Agregue ruido del mapa de flujo a la labranza

//in FlowCell function
float tilling = flow.z * _TilingModulated + _Tiling;

float2 uvFlow = DirectionalFlowUV(uv, flow, tilling, time);

Insertar descripción de la imagen aquí
Podemos agregar un parámetro que controle el escalado de uso. Cuando el rango de muestreo se controla dentro de un rango más pequeño del mapa de flujo, el flujo general mostrará un flujo relativamente direccional.

_UvTilingScale("uv tiling scale", Range(0.1, 1)) = 1

//in FlowCell function
float3 flow = tex2D(_FlowMap, uvTiled * _UvTilingScale);

Insertar descripción de la imagen aquí
Personalmente prefiero la sensación de _UvTilingScale más bajo.
Insertar descripción de la imagen aquí
Cuando _gridResolution es demasiado pequeño, aún se producirán defectos.
Insertar descripción de la imagen aquí

2.4 Implementar mezcla de baja _gridResolution

Entonces la pregunta es, el lado derecho está borroso, pero los resultados del cálculo en el lado izquierdo son más claros, ¿qué debemos hacer?

La respuesta también es muy simple: compensar el resultado de la izquierda y mezclarlo con el resultado de la derecha puede aliviar el problema hasta cierto punto.

En consecuencia, es agregar 1/4 de compensación al uv (antes ya estaba compensado a 1/2). Al mismo tiempo, separamos los cuatro métodos de mezcla de bloques.

float3 FlowCell(float2 uv, float2 offset, float time, bool gridOffsetTag)
{
    
    
    float shift = 1 - offset;
    offset *= 0.5;
    shift *= 0.5;

    if(gridOffsetTag)
    {
    
    
        offset += 0.25;
        shift -= 0.25;
    }

    //........
}
 

float3 FlowGrid(float2 uv, float flowTime, bool gridOffsetTag)
{
    
    
    float3 dhA = FlowCell(uv, float2(0, 0), flowTime, gridOffsetTag);
    float3 dhB = FlowCell(uv, float2(1, 0), flowTime, gridOffsetTag);
    float3 dhC = FlowCell(uv, float2(0, 1), flowTime, gridOffsetTag);
    float3 dhD = FlowCell(uv, float2(1, 1), flowTime, gridOffsetTag);

    float2 t = uv * _GridResolution;
    if(gridOffsetTag)
    {
    
    
        t += 0.25;
    }
    t = abs(2 * frac(t) - 1);
    // float t = frac(i.uv.x * _GridResolution);
    float wA = (1 - t.x) * (1 - t.y);
    float wB = t.x * (1 - t.y);
    float wC = (1 - t.x) * t.y;
    float wD = t.x * t.y;

    float3 dh = wA * dhA + wB * dhB + wC * dhC + wD * dhD;

    return dh;
}

//in fs
float3 dh = FlowGrid(i.uv, flowTime, false);
dh = (dh + FlowGrid(i.uv, flowTime, true)) * 0.5;

Antes de mezclar (izquierda) Después de mezclar (derecha)
Insertar descripción de la imagen aquí

Supongo que te gusta

Origin blog.csdn.net/misaka12807/article/details/132239695
Recomendado
Clasificación