【Unity shader】Bases du rendu de l'eau - effet de perspective sous-marine

Vient ensuite le dernier article sur les bases du rendu de l'eau, la visualisation des objets sous-marins à travers la surface de l'eau et la démonstration de l'effet de profondeur.

1. Créez une scène de démonstration simple

Montons simplement une petite scène.
Insérer la description de l'image ici
Ajoutez une surface d'eau, donnez un matériau de surface d'eau déformé par les UV et augmentez les paramètres de transparence.

Insérer la description de l'image ici

SubShader
    {
    
    
        Tags {
    
     "RenderType"="Transparent" "Queue" = "Transparent" }
        LOD 100

        Pass
        {
    
    
            //Tags {"LightMode" = "ForwardBase"}

            ZWrite Off
            Blend SrcAlpha OneMinusSrcAlpha
            //.......返回的color结果,添加一个控制透明度的参数
         }
         //注意FallBack也要注释掉
    }

2. Réalisez un effet de profondeur d'eau basé sur l'effet de brouillard

L’eau absorbe la lumière, la vraie eau n’est donc pas complètement transparente. De plus, les plans d’eau ont des taux d’absorption de la lumière différents à différentes fréquences, la lumière bleue étant la moins absorbée.
Par conséquent, plus la profondeur est profonde, plus les objets dans l’eau deviendront bleus.

Bien sûr, nous pouvons appliquer directement un brouillard global, mais ici, il est préférable d'utiliser le calcul de l'effet du brouillard uniquement pour les plans d'eau.

À partir d'ici, nous ajoutons un cginc lié aux calculs sous-marins et une fonction qui renvoie le résultat de couleur du fragment sous-marin.
Lors de la création d'un nouveau fichier cginc, nous devons faire attention à la création d'un fichier txt dans le dossier Windows et veiller à modifier le suffixe du fichier.

#include "LookingThroughWater.cginc"
float3 ColorBelowWater () 
{
    
    
	//目前只返回黑色
    return 0;
}

//返回值乘以ColorBelowWater()的结果,透明度调整为1

Tout d'abord, nous avons défini le calcul de couleur des fragments sous-marins le plus simple et obtenu un fluide semblable au pétrole.
Insérer la description de l'image ici
Pour calculer la profondeur du brouillard, nous avons d'abord besoin d'une carte de profondeur de caméra.

// in xxx.cginc
sampler2D _CameraDepthTexture;

De plus, lors du calcul du shader, nous devons obtenir les coordonnées de l'espace écran correspondantes.

Ajoutez screenPos au shader de surface :

struct Input 
{
    
    
	float2 uv_MainTex;
	float4 screenPos;
};void surf (Input IN, inout SurfaceOutputStandard o) 
{
    
    
	…
	
	o.Albedo = ColorBelowWater(IN.screenPos);
	o.Alpha = 1;
}

Dans le shader éteint, nous pouvons ajouter la sémantique VPOS au fragment shader pour introduire des coordonnées spatiales planes.

Puisque VPOS et SV_POSITION ne peuvent pas exister dans la même structure v2f, nous devons supprimer SV_POSITION dans le v2f d'origine et le faire sortir séparément via la sémantique dans les paramètres du vertex shader.

struct v2f
{
    
    
    float2 uv : TEXCOORD0;
    //......
    // float4 vertex : SV_POSITION;
};

v2f vert (appdata_tan v, out float4 vertex : SV_POSITION)
{
    
    
    v2f o;
    vertex = UnityObjectToClipPos(v.vertex);
    //.......
    
    return o;
}

fixed4 frag (v2f i, UNITY_VPOS_TYPE screenPos : VPOS) : SV_Target
{
    
    
    //....
}

De même, nous pouvons également effectuer des calculs directement, il n'est donc pas nécessaire d'utiliser VPOS ou de supprimer la définition SV_POSITION dans la structure v2f.

v2f vert (appdata_tan v)
{
    
    
    v2f o;
    
    o.vertex = UnityObjectToClipPos(v.vertex);
    o.screenpos = ComputeScreenPos(o.vertex);

    //.......
}

2.1 Obtenir la distance entre le fragment sous-marin et la surface de l'eau

La logique n'est pas difficile, c'est-à-dire qu'à travers la profondeur du fragment au fond de l'eau - la profondeur du fragment à la surface de l'eau, on trouve l'épaisseur du fragment correspondant dans le plan d'eau.

float3 ColorBelowWater (float4 screenPos) 
{
    
    
    float2 uv = screenPos.xy / screenPos.w;
    
    float backgroundDepth = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, uv));
	float surfaceDepth = UNITY_Z_0_FAR_FROM_CLIPSPACE(screenPos.z);

    float depthDifference = backgroundDepth - surfaceDepth;
    
    //除以二十,拉开层次差别,这个20的常量,我们可以理解为最大深度
    //所有常量,最好都根据实际搭的场景深度,进行灵活调整
	return depthDifference/20;
}

Notez que la valeur de retour du fragment shader est ici remplacée par le résultat de ColorBelowWater() pur.
Insérer la description de l'image ici
Si nous obtenons un résultat inversé en noir et blanc à ce moment-là, il se peut que la coordonnée v de la carte de profondeur soit calculée de haut en bas. Dans ce cas, nous devons nier le uv dans la dimension v.

//in xxxx.cginc
flaot4 _CameraDepthTexture_TexelSize;

float3 ColorBelowWater (float4 screenPos) 
{
    
    
    float2 uv = screenPos.xy / screenPos.w;
    #if UNITY_UV_STARTS_AT_TOP
		if (_CameraDepthTexture_TexelSize.y < 0) {
    
    
			uv.y = 1 - uv.y;
		}
	#endif
    
    //.........
}

2.2 Obtenir le tampon de trame de rendu sous-marin

Après avoir résolu le calcul des informations de profondeur, un nouveau problème se pose : si nous multiplions directement les informations de profondeur en calculant les résultats existants, cela ne pourra pas refléter correctement les informations de couleur sous-marine.

//in frag shader
return fixed4((col * _BaseColor + diffuse + specular)* ColorBelowWater(i.screenpos), _AlphaScale);

Insérer la description de l'image ici
Bien sûr, nous pouvons être astucieux et ajuster la valeur alpha pour diluer l'effet noir, mais cela détruira directement l'effet de profondeur.
Insérer la description de l'image ici
Par conséquent, nous devons mélanger différemment les résultats de couleur du rendu sous-marin d'origine et les résultats de règlement en profondeur.

Étant donné que le water shader d'origine calcule uniquement le résultat de la couleur de la surface de l'eau, il nous est définitivement impossible de terminer le mélange en un seul passage.

Nous ajoutons un GrabPass séparément pour stocker à l'avance les résultats de rendu d'autres objets. Étant donné que l'ordre de rendu des objets transparents est après les objets non transparents, si vous souhaitez obtenir GrabPass pour les objets non transparents, vous devez faire attention au problème de l'ordre de rendu.

Selon la description de la documentation Unity , grabpass ne peut capturer que les informations du tampon de trame et dispose de deux méthodes d'appel. Lorsque la texture cible n'est pas fournie, le résultat sera stocké dans _GrabTexture ; lorsque l'utilisateur doit spécifier la texture temporaire en sortie par grabpass, elle doit être indiquée entre guillemets doubles.
Insérer la description de l'image ici

SubShader
    {
    
    
        Tags {
    
     "RenderType"="Transparent" "Queue" = "Transparent" }
        LOD 100
		
		//增加一个GrabPass,将背景物体渲染的结果预先存储到_WaterBackground中,供后续颜色插值混合使用
        GrabPass {
    
    "_WaterBackground"}

        Pass
        {
    
    
            //水体渲染pass
        }
    }

//in xxx.cginc

float3 ColorBelowWater (float4 screenPos) 
{
    
    
    float2 uv = screenPos.xy / screenPos.w;
    #if UNITY_UV_STARTS_AT_TOP
		if (_CameraDepthTexture_TexelSize.y < 0) {
    
    
			uv.y = 1 - uv.y;
		}
	#endif
    
    float3 backgroundColor = tex2D(_WaterBackground, uv).rgb;

    return backgroundColor;
}

Vous pouvez constater que lorsque l’alpha est plein, il y a aussi un effet de perspective.
Insérer la description de l'image ici

2.3 Rendu complet de l'arrière-plan et mélange d'interpolation de profondeur d'eau

Deux nouveaux paramètres liés à l'effet de brouillard sont ajoutés aux propriétés :

_WaterFogColor ("Water Fog Color", Color) = (0, 0, 0, 0)
_WaterFogDensity ("Water Fog Density", Range(0, 2)) = 0.1

Selon la couleur du fond de l'eau (couleur du brouillard), la couleur de fond est mélangée différemment et le facteur d'interpolation est une combinaison de concentration et de profondeur du brouillard.

//in xxx.cginc
// update ColorBelowWater( )
float3 backgroundColor = tex2D(_WaterBackground, uv).rgb;
float fogFactor = exp2(-_WaterFogDensity * depthDifference);

return lerp(_WaterFogColor, backgroundColor, fogFactor);

Insérer la description de l'image ici
Désormais, nous pouvons utiliser _WaterFogDensity pour contrôler l'effet de diffusion de l'eau afin d'obtenir des différences de profondeur dans le plan d'eau.
Insérer la description de l'image ici
Ensuite, ajustez la valeur de l'échelle alpha. Évidemment, nous n'avons plus besoin d'un paramètre pour contrôler la transparence de la couleur de sortie, mais d'un paramètre pour contrôler si le plan d'eau est rendu en profondeur transparente.

L'alphascale d'origine est principalement utilisée pour contrôler la transparence des résultats du calcul des couleurs du fragment shader.
Insérer la description de l'image ici
Maintenant, nous l'ajustons pour qu'il affecte le résultat final du calcul de fogFactor et fixe la valeur w renvoyée par le fragment shader à 1.

//in .cginc ColorBelowWater
return lerp(_WaterFogColor, backgroundColor, fogFactor * _AlphaScale);

Contrairement à l'effet de l'ajustement de _WaterFogDensity, l'ajustement de _AlphaScale affecte principalement le calcul de l'effet de fusion en profondeur.
Insérer la description de l'image ici

3. Réaliser la distorsion des objets sous-marins

Les amis ayant l'expérience de la vie savent tous que les objets sous-marins seront déformés en cas de vagues d'eau. Reportez-vous au bord du poisson sous-marin dans l'image ci-dessous, qui apparaît dans une certaine mesure avec les vagues d'eau.
Insérer la description de l'image ici
La logique de mise en œuvre n'est pas compliquée, c'est-à-dire que les UV échantillonnés de la partie sous-marine sont décalés le long de la direction normale de la vague d'eau (direction x, z).

//额外新增了参数_RefractionStrength,用于控制采样水下颜色结果的uv的偏移程度
float3 ColorBelowWater (float4 screenPos, float3 worldNormal) 
{
    
    
    float2 uvoffset = worldNormal.xz * _RefractionStrength;
    float2 uv = (screenPos.xy + uvoffset) / screenPos.w;
    //.........
}

Comparaison du phénomène de distorsion sous-marine à partir de zéro
Insérer la description de l'image ici

3.1 Corriger le faux décalage des objets sur l'eau

En regardant le code ci-dessus, vous pouvez voir que, puisque le décalage UV est un calcul global, de nombreux objets qui ne sont pas sous l'eau verront leurs surfaces d'eau correspondantes déformées en couleur.
Insérer la description de l'image ici
La méthode de correction est très simple, c'est-à-dire qu'après jugement, nous effectuons uniquement le décalage UV pour les fragments sous-marins. Pour les UV au-dessus de l'eau, nous utilisons l'UV d'origine et prêtons attention à la nécessité de recalculer la différence de profondeur.

float3 ColorBelowWater (float4 screenPos, float3 worldNormal) 
{
    
    
    //......新增一个originUV,用于存储偏移前的uv

    if(depthDifference < 0)
    {
    
    
        uv = originUV;
        #if UNITY_UV_STARTS_AT_TOP
		if (_CameraDepthTexture_TexelSize.y < 0) {
    
    
			uv.y = 1 - uv.y;
		}
	    #endif
	    //使用偏移前的uv采样颜色缓冲,会导致偏移后的uv采样的深度差,与颜色不匹配
	    //这里同时重采样backgroundDepth,再算一次depthDifference 
        backgroundDepth = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, uv));
        depthDifference = backgroundDepth - surfaceDepth;
    }

    float3 backgroundColor = tex2D(_WaterBackground, uv).rgb;
    float fogFactor = exp2(-_WaterFogDensity * depthDifference);

    return lerp(_WaterFogColor, backgroundColor, fogFactor * _AlphaScale);
}

Insérer la description de l'image ici

4. Réaliser les fluctuations de la surface de l'eau

Après avoir ajusté l'effet de réfraction sous-marine, on a toujours l'impression que l'effet de la surface de l'eau est évidemment si agité, mais la surface horizontale est toujours aussi calme qu'un miroir, ce qui n'est évidemment pas conforme au bon sens.
Insérer la description de l'image ici
Enfin, sur la base du rendu précédent, nous échantillonnons le flowmap dans le vertex shader, résolvons sa longueur de vecteur et l'utilisons pour la position (direction y) du sommet trop haut.

Bien sûr, un plan 100x100 est utilisé ici. De plus, tex2D ne peut pas être utilisé pour l'échantillonnage de texture dans le vertex shader. Nous devons utiliser tex2Dlod pour échantillonner lowmap et fournir un uv à virgule flottante à quatre bits .

Nous donnons les troisième et quatrième positions de uv 0.

Insérer la description de l'image ici

v2f vert (appdata_tan v)
{
    
    
    v2f o;
    o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
    float2 flowVec;
    flowVec = tex2Dlod(_FlowMap, float4(o.uv + _Time.y * _Speed, 0.0, 0.0)).rg;
    flowVec = flowVec * 2 -1;

    o.vertex = UnityObjectToClipPos(v.vertex + float3(0.0, length(flowVec) * _HeightScale, 0.0));
    o.screenpos = ComputeScreenPos(o.vertex);

    float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
    float3 worldNormal = UnityObjectToWorldNormal(v.normal);
    float3 worldTangent = UnityObjectToWorldDir(v.tangent);
    float3 worldBiTangent = cross(worldNormal, worldTangent) * v.tangent.w;

    o.t2w_0 = float4(worldTangent.x,worldBiTangent.x,worldNormal.x, worldPos.x);
    o.t2w_1 = float4(worldTangent.y,worldBiTangent.y,worldNormal.y, worldPos.y);
    o.t2w_2 = float4(worldTangent.z,worldBiTangent.z,worldNormal.z, worldPos.z);
    
    return o;
}

De cette façon, l’interaction entre le plan d’eau et le bord de l’objet immergé dans le plan d’eau fluctuera au fil du temps, ce qui la rendra plus réaliste.
Insérer la description de l'image ici

Je suppose que tu aimes

Origine blog.csdn.net/misaka12807/article/details/132594033
conseillé
Classement