【Sombreador de Unity】 Mapeo de paralaje más complejo: mapeo de relieve

Prefacio

Hace tiempo que tomamos notas sobre el método clásico de inspección cartográfica.

El sombreador de Unity implementa el mapeo de paralaje

En las soluciones anteriores, era más un método aproximado para resolver UV virtual relativamente preciso basado en viewDir y pos reales.

Se puede ver que cuanto menor es el ángulo entre el ángulo de observación y el plano, más obvio es el efecto de capas discretas del mapeo de paralaje.

Insertar descripción de la imagen aquí

El mapeo en relieve proporciona un método de solución más práctico para garantizar que el UV virtual resuelto sea más preciso.

El principio y la implementación del mapeo en relieve.

Para el mapeo de relieve, nuestro objetivo más importante es resolver puntos UV virtuales más precisos que los métodos anteriores. El punto uv corresponde a la posición de contacto entre viewDir y la superficie del objeto virtual que necesitamos. El valor del mapa de altura muestreado en función de esta posición es el valor que queremos.

Devolvemos el uv virtual resuelto y actualizamos el uv real original para muestrear el mapa de altura.

Para el plano real, podemos obtener el UV correspondiente a su posición de observación real (un cierto punto en el plano negro) a través de una dirección de observación específica (línea azul).
Insertar descripción de la imagen aquí
Para el mapeo de paralaje, necesitamos proyectar el efecto de un plano con diferentes alturas (rojo) en un plano real (en un plano negro).
Insertar descripción de la imagen aquí
Si utilizamos directamente el mapa de altura de muestreo ultravioleta real, el resultado obtenido es incorrecto porque no corresponde al punto de intersección a lo largo de viewDir y el plano virtual.
Insertar descripción de la imagen aquí
Necesitamos encontrar el punto de intersección con el plano virtual (rojo) a lo largo de la dirección de viewDir, es decir, la posición del uv virtual. Usando el uv virtual para muestrear el mapa de altura, podemos obtener el resultado del valor de altura para la posición. en los rayos ultravioleta reales.
Insertar descripción de la imagen aquí

Resolver el punto de intersección de viewDir y plano virtual.

Primero, utilizamos un método similar a la marcha de rayos: cada vez que damos un paso, tomaremos una muestra del valor de su mapa de altura y acumularemos el valor de profundidad:


//最大步进数,避免特殊情况下出现无限迭代
int maxstep= 40;
//当前步进深度
int recentsetp = 0;
float currentLayerDepth = 0;
//
float currentDepthMapValue, lastDepthValue;

//deltaDepth 单位高度偏移,每迈进一步,增加一单位偏移
//deltaTexCoords 单位水平偏移,同上
float deltaDepth =  _height_scale / maxstep;
float2 deltaTexCoords = viewDir.xy * _height_scale/ maxstep;
float2 currentTexCoords = texCoords * _Scale;

//outerUV 记录虚拟平面以外的最后uv值,即最接近uv平面的uv值,每次迭代都会更新
//innerUV 记录虚拟平面以内的第一个uv值
float2 innerUV;
float2 outerUV = currentTexCoords;

//currentDepthMapValue 当前采样深度,随uv变化而变化
currentDepthMapValue = tex2D(_DispTex, currentTexCoords);
while(currentLayerDepth < currentDepthMapValue)
{
    
    
    outerUV = currentTexCoords;
    currentTexCoords -= deltaTexCoords;
    lastDepthValue = currentDepthMapValue;
    currentDepthMapValue = tex2Dlod(_DispTex, float4(currentTexCoords, 0.0, 0.0)).r;
    //currentLayerDepth 记录当前步进深度,逐步累加
    currentLayerDepth += deltaDepth;
    recentsetp += 1;
    if (recentsetp == maxstep) return currentTexCoords;
}

Cuando el valor de profundidad acumulado es menor que el valor de muestreo del mapa de altura, significa que el punto de paso actual todavía está fuera del plano virtual. (Como se muestra en la figura siguiente, s1, s2, s3 son tres pasos)

Insertar descripción de la imagen aquí

Cuando el valor de profundidad acumulada es mayor que el valor de muestreo del mapa de altura, significa que el punto de paso actual ha ingresado al plano virtual.
Insertar descripción de la imagen aquí
Por ahora, la idea de paso a paso no es muy diferente del mapeo de oclusión de paralaje, la gran diferencia es que después de resolver dos puntos dentro y fuera del plano virtual, se determina el punto de intersección.

El mapeo en relieve utiliza una idea similar al método de búsqueda binaria.
La búsqueda binaria es un algoritmo de búsqueda muy clásico. Si eres estudiante de arte, puedes buscar directamente en Baidu para encontrar muchos casos relacionados, por lo que no entraré en detalles aquí.

Insertar descripción de la imagen aquí
En general, se trata de actualizar alternativa e iterativamente los valores de izquierda, media y derecha de acuerdo con diferentes relaciones de valores de profundidad y, finalmente, recuperar las coordenadas uv que cumplen con los requisitos.

float2 midUV = 0.5 * (outerUV + innerUV);
float midDepthValue = tex2D(_DispTex, midUV).r;
while ( 0.5 * (lastDepthValue + currentDepthMapValue) !=  midDepthValue)
{
    
    
	//divideTimes 迭代次数限制
    divideTimes -= 1;
    //即中值uv的采样深度在中值深度以下,说明交叉点交叉点位于左区间
    if (0.5 * (lastDepthValue + currentDepthMapValue) <  midDepthValue)
    {
    
    
        outerUV = midUV;
        lastDepthValue = 0.5 * (lastDepthValue + currentDepthMapValue);

        midUV = 0.5 * (outerUV + innerUV);
        midDepthValue = tex2Dlod(_DispTex, float4(midUV, 0.0, 0.0)).r;
    }
    //即中值uv的采样深度在中值深度以上,说明交叉点交叉点位于右区间
    else
    {
    
    
        innerUV = midUV;
        currentDepthMapValue = 0.5 * (lastDepthValue + currentDepthMapValue);
        
        midUV = 0.5 * (outerUV + innerUV);
        midDepthValue = tex2Dlod(_DispTex, float4(midUV, 0.0, 0.0)).r;
    }
    if(divideTimes == 0) break;
}

Punto azul, es decir, el caso donde el punto de intersección está a la derecha del punto medio:

0.5 * (lastDepthValue + currentDepthMapValue) <  midDepthValue

Insertar descripción de la imagen aquí
Punto azul, es decir, el caso donde el punto de intersección está a la izquierda del punto medio:

0.5 * (lastDepthValue + currentDepthMapValue) >  midDepthValue

Insertar descripción de la imagen aquí
Así que aquí se ha implementado el mapeo básico del relieve.

La siguiente imagen es una comparación del mapeo de relieve (izquierda) y el mapeo de ParallaxOcclusion (derecha).
Se puede ver que cuando el tamaño de profundidad total es grande y el ángulo de observación es grande (es decir, relativamente plano con respecto al plano), la sensación de paralaje de altura en capas representada por el mapeo en relieve es más débil.

Insertar descripción de la imagen aquí
En casos más extremos, el mapeo del relieve tiene ventajas más obvias.
Insertar descripción de la imagen aquí

Implementar distancia de paso dinámica

Pero al acercarnos lo suficiente, todavía podemos encontrar fallas en capas en el mapeo del relieve.

Esto se debe a la distancia de paso relativamente fija, lo que da como resultado una precisión de muestreo deficiente. Tiene una sensación de capas como un pastel en capas como el mapeo de Parallax empinado.
Insertar descripción de la imagen aquí
Cuando está lejos de la intersección, aunque el paso todavía se realiza, el cálculo del paso en este momento es básicamente un cálculo inútil en la etapa inicial porque no se puede obtener la intersección.
En la etapa posterior, cuando el paso alcanza una posición cercana al punto de intersección, la amplitud del paso es demasiado grande, lo que resulta en una precisión insuficiente.

La idea central del paso dinámico es acelerar el paso y mejorar la eficiencia del cálculo cuando el algoritmo está lejos de la intersección en la etapa inicial. En las etapas posteriores, cuando se acerca al punto de intersección, la zancada se reduce para mejorar la precisión.

Insertar descripción de la imagen aquí

currentDepthMapValue = tex2D(_DispTex, currentTexCoords);
while(currentLayerDepth < currentDepthMapValue)
{
    
    
	//这里我们把累计深度和采样深度的插值,作为判断当前步进点离交叉点距离的依据
	//动态调整moveScale,以动态控制步幅
    moveScale = max(0.1, 3 * (currentDepthMapValue - currentLayerDepth));
    
    outerUV = currentTexCoords;
    currentTexCoords -= deltaTexCoords * moveScale;
    lastDepthValue = currentDepthMapValue;
    currentDepthMapValue = tex2Dlod(_DispTex, float4(currentTexCoords, 0.0, 0.0)).r;
    currentLayerDepth += deltaDepth * moveScale;
    recentsetp += 1;
    if (recentsetp == maxstep) return currentTexCoords;
}

Zancada fija versus zancada dinámica
Insertar descripción de la imagen aquí

Insertar descripción de la imagen aquí
El código fuente completo de Relief Mapping es el siguiente:

float2 ReliefMapping(float2 texCoords, fixed3 viewDir)
{
    
    
    int divideTimes = 2;
    int maxstep= 40;
    float currentLayerDepth = 0;
    float currentDepthMapValue, lastDepthValue;
    float deltaDepth =  _height_scale / maxstep;
    float2 deltaTexCoords = viewDir.xy * _height_scale/ maxstep;
    float2 currentTexCoords = texCoords * _Scale;
    float2 innerUV;
    float2 outerUV = currentTexCoords;
    int recentsetp = 0;
    float moveScale;

    currentDepthMapValue = tex2D(_DispTex, currentTexCoords);
    while(currentLayerDepth < currentDepthMapValue)
    {
    
    
        moveScale = max(0.1, 3 * (currentDepthMapValue - currentLayerDepth));
        // moveScale = 1;
        outerUV = currentTexCoords;
        currentTexCoords -= deltaTexCoords * moveScale;
        lastDepthValue = currentDepthMapValue;
        currentDepthMapValue = tex2Dlod(_DispTex, float4(currentTexCoords, 0.0, 0.0)).r;
        currentLayerDepth += deltaDepth * moveScale;
        recentsetp += 1;
        if (recentsetp == maxstep) return currentTexCoords;
    }

    innerUV = currentTexCoords;

    float2 midUV = 0.5 * (outerUV + innerUV);
    float midDepthValue = tex2D(_DispTex, midUV).r;
    while ( 0.5 * (lastDepthValue + currentDepthMapValue) !=  midDepthValue)
    {
    
    
        divideTimes -= 1;
        if (0.5 * (lastDepthValue + currentDepthMapValue) <  midDepthValue)
        {
    
    
            outerUV = midUV;
            lastDepthValue = 0.5 * (lastDepthValue + currentDepthMapValue);

            midUV = 0.5 * (outerUV + innerUV);
            midDepthValue = tex2Dlod(_DispTex, float4(midUV, 0.0, 0.0)).r;
        }
        else
        {
    
    
            innerUV = midUV;
            currentDepthMapValue = 0.5 * (lastDepthValue + currentDepthMapValue);
            
            midUV = 0.5 * (outerUV + innerUV);
            midDepthValue = tex2Dlod(_DispTex, float4(midUV, 0.0, 0.0)).r;
        }
        if(divideTimes == 0) break;
    }

    return midUV;
}

Supongo que te gusta

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