Blending et Unity Terrain: comment se débarrasser des intersections et arrêter de se faire mal aux yeux

Afin d'obtenir un monde réaliste à l'intérieur du jeu, il est nécessaire de prendre en compte l'interaction de différentes formes de relief entre elles et avec d'autres modèles. Et si les lignes d'intersection visibles entre les modèles 3D gâchent l'intégrité de l'image, il convient de réfléchir à la façon de les éliminer. Le cas le plus courant de ces lignes, qui peut être familier à beaucoup, est l'intersection de panneaux d'affichage de particules à géométrie opaque.

image

Un autre exemple est la composition naturelle troublante de l'intersection des roches et de la végétation avec la surface du paysage dans les scènes «en plein air».

image

En plus des diverses méthodes de lissage (SSAA, MSAA, CSAA, FXAA, NFAA, CMAA, DLAA, TAA, etc.), qui atténuent l'apparence provocante de ces lignes d'intersection, mais ne corrigent pas complètement la situation, il existe des techniques plus efficaces. Nous les considérerons.

Fusion en profondeur


Unity a une solution intégrée pour éliminer les intersections visibles entre les particules transparentes et la géométrie opaque appelées particules molles. Les shaders qui soutiennent cet effet améliorent encore la transparence des particules, selon la petite différence entre la profondeur du fragment de particules et la profondeur de la géométrie opaque.

image
Le principe de fonctionnement des particules molles

Evidemment, pour le bon fonctionnement des particules molles, un tampon de profondeur est nécessaire. Dans le cas d'un ombrage différé, le tampon de profondeur est formé au stade du rendu des tampons plein écran, et compte tenu des MRT (Multiple Render Targets, et non Magnetic Resonance Tomography), sa présence ne s'exprime pas en coûts de calcul supplémentaires.

Dans le cas de l'ombrage avant et de l'utilisation du pipeline Unity Legacy, un passage supplémentaire était nécessaire pour rendre la géométrie opaque au tampon de profondeur [1] . Cette passe est activée en affectant la valeur appropriée à la propriété Camera.depthTextureMode. Cette propriété n'est pas disponible dans la fenêtre d'inspection, mais est disponible dans l'API [2] .

Vous pouvez maintenant implémenter votre propre version du pipeline de rendu scriptable avec un ombrage direct, qui avec l'aide de MRT peut rendre simultanément le tampon de profondeur et le tampon de couleur.


Élimination des lignes d'intersection dans les shaders qui supportent les particules molles

En général, il n'y a pas d'obstacles techniques à l'utilisation de la méthode de fusion en profondeur pour éliminer les intersections visibles des modèles 3D avec le paysage:

Afficher le code
// Blending with depth buffer

#include "UnityCG.cginc"

float BlendStart;
float BlendEnd;
UNITY_DECLARE_DEPTH_TEXTURE(_CameraDepthTexture);

struct v2f
{
    // ...

    half4 projPos : TEXCOORD0;
};

v2f vert(appdata v)
{
    v2f o;
    UNITY_INITIALIZE_OUTPUT(v2f,o);

    // ...

    o.projPos = ComputeScreenPos(o.pos);
    COMPUTE_EYEDEPTH(o.projPos.z);

    // ...

    return o;
}

fixed4 frag(v2f i) : COLOR
{     
    fixed4 result = 0;
      
    // ... 

    float depth = SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(i.projPos));
    float sceneZ = LinearEyeDepth(depth);
    float partZ = i.projPos.z;
    float fade = saturate( sceneZ - partZ );
    result.a = smoothstep( BlendStart, BlendEnd, fade );

    // ... 
       
    return result; 
}


Cependant, cette approche présente plusieurs inconvénients.

Le premier inconvénient est lié aux performances. Le mélange en profondeur fonctionne au stade du mélange matériel des tuyaux, c'est-à-dire immédiatement après la pixellisation et le calcul du fragment shader. À ce stade, le résultat de l'exécution du fragment shader est mélangé au résultat enregistré dans le tampon de sortie [3] [4] [5] selon la formule prédéfinie par les appels à l'API [6] [7] [8] [9] .

Il s'agit de la partie la moins évolutive de tout pipeline de matériel en ce sens qu'il fonctionne exactement comme son prédécesseur il y a vingt ans. Le GPU lit la valeur de la mémoire, la mélange avec la valeur du fragment shader et la réécrit en mémoire.

Il existe également une différence entre l'utilisation de la fusion en profondeur pour les modèles 3D entièrement transparents ou partiellement transparents. Transparent - par exemple, les panneaux d'affichage de particules - même sans se fondre en profondeur, tout le rendu est transparent. Dans le cas des modèles 3D opaques, une transparence réelle, tangible et visible lors du mélange en profondeur ne sera dotée que d'un très petit nombre de fragments, tandis que la grande majorité d'entre eux restera opaque. Mais ce dernier ne signifie pas du tout que le mélange ne sera pas utilisé pour leur rendu - il fonctionnera simplement au ralenti.

Le deuxième inconvénient est lié à la façon dont la couleur de mélange est sélectionnée. En bref, alors tous les fragments qui sont mélangés dans un pixel d'écran particulier se trouvent sur un rayon émanant de la position mondiale de la caméra et passant par la position mondiale de ce pixel d'écran. Ceci, à son tour, signifie qu'avec tout changement de position ou d'orientation de la caméra, la parallaxe sera observée: les fragments du modèle 3D situés plus près de la caméra se déplaceront plus rapidement que les fragments du paysage situés plus loin de la caméra [10] [11] . Cela est particulièrement visible lorsqu'on le regarde de près avec un déplacement latéral constant de la caméra.


Parallaxe latérale lors du déplacement de la caméra: les fragments du modèle 3D sont décalés à une plus grande distance par rapport aux fragments du paysage


Parallaxe latérale lors du déplacement de la caméra: lors de la fixation de la caméra sur un fragment du paysage, la vitesse de déplacement des fragments du modèle devient sensible.

Lorsque la caméra est tournée, la parallaxe est observée immédiatement le long de deux axes des coordonnées de l'écran. Cependant, en dynamique, cela est moins évident que la parallaxe latérale.


Parallaxe azimutale lorsque la caméra est déplacée: il est plus difficile pour le cerveau de reconnaître le modèle de parallaxe lorsque les fragments sont déplacés sur deux

axes, mais surtout, l'apparence du mélange en profondeur change en fonction de l'angle sous lequel l'observateur regarde la surface du paysage. La zone de fusion devient presque invisible lorsque la direction de la vue est perpendiculaire à la normale à la surface du paysage, mais la taille de cette zone augmente rapidement si vous inclinez la caméra vers le bas.


Changer la largeur de la zone de fusion lors de l'inclinaison de la caméra

en profondeur La fusion peut être une bonne option pour éliminer les lignes d'intersection des modèles 3D avec le paysage, sinon pour l'abondance d'artefacts qui l'accompagnent. Cette méthode convient mieux aux effets de particules qui ne sont pas statiques et, en règle générale, ne contiennent pas de textures très détaillées, par conséquent, les effets de parallaxe ne sont pas observés dans leur cas.


Mélange de cartes de hauteur


Une autre option pour implémenter le mélange de paysages consiste à utiliser une carte de hauteur, à laquelle Unity donne accès via l'API TerrainData [12] .

Connaissant la position de l'objet Terrain et les dimensions du terrain indiquées dans TerrainData, et ayant une "carte de hauteur" à portée de main, vous pouvez calculer la hauteur du terrain à tout point spécifié en coordonnées mondiales.


Paramètres de terrain requis pour échantillonner la carte des hauteurs

// Setting up a heightmap and uniforms to use with shaders... 

Shader.SetGlobalTexture(Uniforms.TerrainHeightmap, terrain.terrainData.heightmapTexture);
Shader.SetGlobalVector(Uniforms.HeightmapScale, terrain.terrainData.heightmapScale);
Shader.SetGlobalVector(Uniforms.TerrainSize, terrain.terrainData.size);
Shader.SetGlobalVector(Uniforms.TerrainPos, terrain.transform.position);

Eh bien, maintenant, après avoir calculé la hauteur du paysage, vous pouvez également calculer les coordonnées uv dans le shader pour échantillonner la carte des hauteurs du paysage en coordonnées mondiales.

// Computes UV for sampling terrain heightmap... 

float2 TerrainUV(float3 worldPos)
{
    return (worldPos.xz - TerrainPos.xz) / TerrainSize.xz;
}

Afin de pouvoir utiliser le même code dans les shaders de fragment et de vertex, la fonction tex2Dlod est utilisée pour l'échantillonnage. De plus, la carte de hauteur n'a pas de niveau de mip, donc l'échantillonner avec la fonction tex2D, qui calcule automatiquement le niveau de mip, n'a pratiquement aucun sens.

// Returns the height of terrain at a given position in world space... 

float TerrainHeight(float2 terrainUV)
{
    float heightmapSample = tex2Dlod(TerrainHeightmap, float4(terrainUV,0,0));
    return TerrainPos.y + UnpackHeightmap(heightmapSample) * HeightmapScale.y * 2;
}

Vous pouvez essayer de reproduire l'élimination des intersections par transparence sans utiliser de tampon de profondeur. Cela ne résout pas les autres problèmes associés à cette méthode, mais permet de vérifier l'opérabilité du mélange à l'aide d'une carte de hauteur.

Afficher le code
// Blending with terrain heightmap

#include "UnityCG.cginc"

float BlendStart;
float BlendEnd;

sampler2D_float TerrainHeightmap; 
float4 HeightmapScale;
float4 TerrainSize;
float4 TerrainPos;        

struct v2f
{
   // ...

   float3 worldPos : TEXCOORD0;
   float2 heightMapUV : TEXCOORD1;

   // ...
};


v2f vert(appdata v)
{
    v2f o;
    UNITY_INITIALIZE_OUTPUT(v2f,o);
   
    // ...
    
    o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
    o.heightMapUV = TerrainUV(o.worldPos);

    // ...

    return o;
}

fixed4 frag(v2f i) : COLOR
{
    fixed4 result = 0;

    // ... 

    half height = TerrainHeight(i.heightMapUV);
    half deltaHeight = i.worldPos.y - height;
    result.a = smoothstep( BlendStart, BlendEnd, deltaHeight );

    // ... 
       
    return result; 
}





Mélange de profondeur et d'élévation. La largeur de la zone de fusion diffère avec les mêmes paramètres de shader.

Les illustrations utilisent des paramètres de fusion identiques pour les deux méthodes. La largeur des zones de fusion est visuellement différente, car la fusion avec une carte de hauteur ne dépend pas de l'angle entre le regard de l'observateur et la normale au paysage.

Le mélange avec une carte en hauteur est au moins à un égard meilleur que le mélange en profondeur: il corrige la dépendance du mélange qui est visible à l'œil nu de l'angle sous lequel la caméra regarde le paysage. Malheureusement, l'effet de parallaxe sera toujours observé.


Mélange de reconstruction d'aménagement paysager


Pour se débarrasser de la parallaxe, vous devez mélanger un fragment du modèle 3D avec un fragment du paysage qui est verticalement en dessous (la sélection des couleurs pour le mélange dans ce cas ne dépend pas de la position et de l'orientation de la caméra).


Comment corriger la parallaxe: choisir un fragment de paysage à mélanger

Bien sûr, nous parlons ici plus d'un fragment de paysage virtuel. Selon la position de la caméra, une situation est possible lorsqu'un fragment du paysage, avec lequel il est nécessaire de mélanger un fragment d'un modèle 3D, ne tombe même pas dans le champ de vision de la caméra. Un problème similaire existe dans le rendu des réflexions locales dans l'espace écran (SSLR). Elle consiste dans le fait qu'il est impossible de restituer le reflet d'un fragment qui n'est pas sur l'écran [13] .

Dans le cas du paysage, la couleur du fragment virtuel peut être reconstruite avec une grande précision à l'aide de textures auxiliaires fournies par l'API Unity: carte normale [14] , carte claire [15] , textures pondérées pour mélanger les couches [16] et textures incluses dans composition des couches [17] .


Reconstitution d'un fragment du paysage

Toutes les textures qui composent le paysage sont échantillonnées selon le même UV que la carte des hauteurs. Dans le cas des couches, les coordonnées d'échantillonnage sont ajustées par les paramètres de mosaïque spécifiés pour une couche particulière [18] [19] .

Afficher le code
// Blending with reconstructed terrain fragments

#include "UnityCG.cginc"

float BlendStart;
float BlendEnd;

sampler2D_float TerrainHeightmapTexture;
sampler2D_float TerrainNormalTexture;
sampler2D TerrainAlphaMap;

float4 HeightmapScale;
float4 TerrainSize;
float4 TerrainPos;
Float4 TerrainLightmap_ST;

UNITY_DECLARE_TEX2D(TerrainSplatMap0);
UNITY_DECLARE_TEX2D_NOSAMPLER(TerrainNormalMap0);
half4 TerrainSplatMap0_ST;

UNITY_DECLARE_TEX2D(TerrainSplatMap1);
UNITY_DECLARE_TEX2D_NOSAMPLER(TerrainNormalMap1);
half4 TerrainSplatMap1_ST;

UNITY_DECLARE_TEX2D(TerrainSplatMap2);
UNITY_DECLARE_TEX2D_NOSAMPLER(TerrainNormalMap2);
half4 TerrainSplatMap2_ST;

UNITY_DECLARE_TEX2D(TerrainSplatMap3);
UNITY_DECLARE_TEX2D_NOSAMPLER(TerrainNormalMap3);
half4 TerrainSplatMap3_ST;

struct v2f
{
   // ...

   float3 worldPos : TEXCOORD0;
   float2 heightMapUV : TEXCOORD1;
#if defined(LIGHTMAP_ON)
   float2 modelLightMapUV : TEXCOORD2;
   float2 terrainLightMapUV : TEXCOORD3;
#endif

   // ...
};


v2f vert(appdata v)
{
    v2f o;
    UNITY_INITIALIZE_OUTPUT(v2f,o);
   
    // ...
    
    o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
    o.heightMapUV = TerrainUV(o.worldPos);

#if defined(LIGHTMAP_ON)
    o.modelLightMapUV = v.texcoord1.xy * unity_LightmapST.xy + unity_LightmapST.zw;
    o.terrainLightMapUV = o.heightMapUV * TerrainLightmap_ST.xy + TerrainLightmap_ST.zw;
#endif

    // ...

    return o;
}
half3 TerrainNormal(float2 terrainUV)
{
    return tex2Dlod( TerrainNormalTexture, float4(terrainUV,0,0) ).xyz * 2.0 - 1.0;
}

half4 TerrainSplatMap(float2 uv0, float2 uv1, float2 uv2, float2 uv3, half4 control)
{
    half4 splat0 = UNITY_SAMPLE_TEX2D_SAMPLER(TerrainSplatMap0, TerrainSplatMap0, uv0);
    half4 splat1 = UNITY_SAMPLE_TEX2D_SAMPLER(TerrainSplatMap1, TerrainSplatMap1, uv1);
    half4 splat2 = UNITY_SAMPLE_TEX2D_SAMPLER(TerrainSplatMap2, TerrainSplatMap2, uv2);
    half4 splat3 = UNITY_SAMPLE_TEX2D_SAMPLER(TerrainSplatMap3, TerrainSplatMap3, uv3);         
    half4 result = splat0 * control.r + 
                   splat1 * control.g + 
                   splat2 * control.b + 
                   splat3 * control.a;
    return result;
}

half3 TerrainNormalMap(float2 uv0, float2 uv1, float2 uv2, float2 uv3, half4 control)
{
    half4 n0 = UNITY_SAMPLE_TEX2D_SAMPLER(TerrainNormalMap0, TerrainSplatMap0, uv0);
    half4 n1 = UNITY_SAMPLE_TEX2D_SAMPLER(TerrainNormalMap1, TerrainSplatMap1, uv1);
    half4 n2 = UNITY_SAMPLE_TEX2D_SAMPLER(TerrainNormalMap2, TerrainSplatMap2, uv2);
    half4 n3 = UNITY_SAMPLE_TEX2D_SAMPLER(TerrainNormalMap3, TerrainSplatMap3, uv3);
    half3 result = UnpackNormalWithScale(n0, 1.0) * control.r +
                   UnpackNormalWithScale(n1, 1.0) * control.g +
                   UnpackNormalWithScale(n2, 1.0) * control.b +
                   UnpackNormalWithScale(n3, 1.0) * control.a;
    result.z += 1e-5;
    return result;
}

half3 TerrainLightmap(float2 uv, half3 normal)
{
#if defined(LIGHTMAP_ON)
#if defined(DIRLIGHTMAP_COMBINED)
    half4 lm = UNITY_SAMPLE_TEX2D(unity_Lightmap, uv);
    half4 lmd = UNITY_SAMPLE_TEX2D_SAMPLER(unity_LightmapInd, unity_Lightmap, uv);
    half3 result = DecodeLightmapRGBM(lm, unity_Lightmap_HDR);
    result = DecodeDirectionalLightmap(result, lmd, normal);
#else
    half4 lm = UNITY_SAMPLE_TEX2D(unity_Lightmap, uv);
    half3 result = DecodeLightmapRGBM(lm, unity_Lightmap_HDR);
#endif                
#else
    half3 result = UNITY_LIGHTMODEL_AMBIENT.rgb;
#endif
    return result;
}

fixed4 frag(v2f i) : COLOR
{
    fixed4 result = 0;

    // ...

    // compute model color and put it to the result

    // ... 

    // reconstruction of terrain fragment

    float2 splatUV0 = TRANSFORM_TEX(i.heightMapUV, TerrainSplatMap0);
    float2 splatUV1 = TRANSFORM_TEX(i.heightMapUV, TerrainSplatMap1);
    float2 splatUV2 = TRANSFORM_TEX(i.heightMapUV, TerrainSplatMap2);
    float2 splatUV3 = TRANSFORM_TEX(i.heightMapUV, TerrainSplatMap3);

    half4 control = tex2D(_TerrainAlphaMap, i.heightMapUV);
    half4 terrainColor = TerrainSplatMap(splatUV0, splatUV1, splatUV2, splatUV3, control);

    half3 terrainSurfaceNormal = TerrainNormal(i.heightMapUV);
    half3 terrainSurfaceTangent = cross(terrainSurfaceNormal, float3(0,0,1));
    half3 terrainSurfaceBitangent = cross(terrainSurfaceTangent, terrainSurfaceNormal);

    half3 terrainNormal = TerrainNormalMap(splatUV0, splatUV1, splatUV2, splatUV3, control);
    terrainNormal = terrainNormal.x * terrainSurfaceTangent + 
                    terrainNormal.y * terrainSurfaceBitangent + 
                    terrainNormal.z * terrainSurfaceNormal;
    
    half3 terrainLightmapColor = TerrainLightmap(i.heightMapUV, terrainNormal);
    terrainColor *= terrainLightmapColor;

    // blend model color & terrain color

    half height = TerrainHeight(i.heightMapUV);
    half deltaHeight = i.worldPos.y - height;
    half blendingWeight = smoothstep(BlendStart, BlendEnd, deltaHeight);

    result.rgb = lerp(result.rgb, terrainColor, blendingFactor);
       
    return result; 
}


Ainsi, le mélange avec la reconstruction de fragments de paysage corrige tous les problèmes typiques du mélange en profondeur et du mélange avec une carte de hauteur, y compris la parallaxe.



Mélange de reconstruction d'aménagement paysager


Performances de reconstruction des fragments de terrain


À ce stade, il est temps de se demander quelle est la valeur de ce type de compromis? À première vue, l'intensité des ressources de la reconstruction de fragments de paysage dépasse de loin l'intensité des ressources du mélange alpha. Pour la reconstruction, il est nécessaire d'effectuer une douzaine d'opérations de lecture supplémentaires à partir de la mémoire. Pour le mélange alpha, vous n'avez besoin que d'une opération de lecture de la mémoire et d'une opération d'écriture dans la mémoire.

En réalité, tout dépendra des fonctionnalités de la plateforme matérielle. La reconstruction des fragments est prise en charge par la compression de texture, le mip-mapping, la puissance de traitement du cœur du processeur graphique et des optimisations spécifiques du pipeline matériel (rejet en profondeur précoce). Et contre l'alpha-blending, le fait déjà mentionné ci-dessus jouera que c'est la partie la moins progressive de tout GPU.

Cependant, il y a toujours place à l'optimisation. Par exemple, dans le cas de la reconstruction de la couleur du paysage, la nécessité de cette reconstruction n'est que pour une étroite bande de fragments du modèle 3D située à une hauteur au-dessus d'une certaine hauteur au-dessus de la surface du paysage.

La ramification dynamique dans les shaders peut donner des résultats de performance peu prévisibles, mais il y a deux points à prendre en compte:

  1. Il convient de sauter les calculs inutiles dans le branchement par une condition si cette condition n'est pas satisfaite dans une partie importante des cas.
  2. . , ( , ), GPU. ― (branch granularity), , , , , . , , . , GPU , , . , GPU, , 1 (PowerVR SGX).


Visualisation de différents degrés de cohérence

Dans le cas de la reconstruction de fragments, ces deux points sont pris en compte: la condition de branchement permettra dans la plupart des cas de couper la mise en œuvre d'opérations gourmandes en ressources pour reconstruire la couleur du paysage, et cette condition est cohérente, à l'exception d'un très petit nombre de fragments (dans l'illustration, ce sont des fragments qui se trouvent) à la frontière entre les zones «rouge» et «verte»).


Cohérence de reconstruction des fragments de paysage

Reste à ajouter quelques commentaires concernant cette méthode de fusion:

  1. Unity fournit toutes les textures nécessaires uniquement si le paysage a le mode Draw Instanced activé [20] , sinon la carte normale ne sera pas disponible, ce qui, à son tour, ne vous permettra pas de reconstruire correctement l'éclairage du paysage pour le mélange.
  2. Unity API , (base map) . - .
  3. , API (, Metal 16 ).
  4. 3D- , Terrain, SRP.
  5. 3D- , 3D- .
  6. , «» , «» . , «» , . «» .






Lors de la conception de modèles 3D, il est impossible de prendre en compte la variété des reliefs de terrain avec lesquels ces modèles sont censés être utilisés. Souvent, les modèles 3D doivent être profondément «enfoncés» dans le paysage ou pivotés afin de cacher les parties saillantes, ou vice versa - pour montrer celles cachées qui devraient être visibles. Les modèles «chauffants» limitent leur applicabilité, et si les modèles 3D sont rendus plus tôt que le paysage, cela conduit également à un effet de sur-dessin. Le virage, à son tour, est également loin d'être adapté à tous les modèles 3D (par exemple, pas pour les maisons et les arbres).


Pour masquer les éléments saillants du modèle 3D, il doit être «noyé» dans le paysage

La capture est un terme familier aux utilisateurs des éditeurs graphiques. Il s'agit d'une fonction qui permet aux points de contrôle de «coller» aux nœuds de la grille spatiale et, dans les éditeurs 3D, aux faces et surfaces d'autres objets. La capture sur la carte des hauteurs du paysage dans le vertex shader peut grandement simplifier la conception des scènes.


Modèle 3D sans accrochage. Modèle 3D avec accrochage au sommet. Modèle 3D avec accrochage et fusion de sommets. Modèle 3D avec accrochage au sommet, mélange et éclairage statique

La principale difficulté dans l'implémentation de l'accrochage est que vous devez déterminer quels sommets du modèle 3D vous devez accrocher à la carte de hauteur et ceux qui n'en valent pas la peine. Les sommets ne contiennent que des informations sur la nature locale de la surface (ce qui n'est pas suffisant) et ne contiennent aucune information sur sa topologie (qui est nécessaire).

Comme dans d'autres cas d'application, ce problème est plus facile à résoudre au stade de la modélisation en implémentant directement les paramètres nécessaires dans les sommets. En tant que tel paramètre, vous devez choisir un attribut intuitif - par exemple, le facteur de pondération pour l'accrochage (et non la distance au bord d'une surface ouverte, comme nous le souhaiterions pour la flexibilité).


Codage de pondération pour la capture

Afficher le code
// Per-vertex snapping with terrain heightmap

#include "UnityCG.cginc"

sampler2D_float TerrainHeightmapTexture;

float4 HeightmapScale;
float4 TerrainSize;
float4 TerrainPos;

struct v2f
{

   // ...

   float3 worldPos : TEXCOORD0;
   float2 heightMapUV : TEXCOORD1;

   // ...

};

v2f vert(appdata v)
{
    v2f o;
    UNITY_INITIALIZE_OUTPUT(v2f,o);
   
    // ...
    
    o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
    o.heightMapUV = TerrainUV(o.worldPos);
    float snappingWeight = v.color.r;                
    half height = TerrainHeight( o.heightMapUV );                
    o.worldPos.y = lerp( o.worldPos.y, height, snappingWeight );
    o.pos = UnityWorldToClipPos( half4( o.worldPos, 1 ) );

    // ...

    return o;
}


L'applicabilité du vertex snapping est limitée par la correspondance générale entre le terrain et la surface du modèle 3D. Pour compenser leurs différences importantes, il est nécessaire d'utiliser d'autres méthodes, plus gourmandes en ressources - par exemple, utiliser des modèles 3D avec skinning.


Conclusion


L'idée principale qui devrait être retirée de l'article: tout shader suffisamment complexe et potentiellement évolutif a besoin de données de base. Et la tâche du développeur est de comprendre comment le système graphique peut fonctionner: quelles données il fournit, comment il peut être combiné et comment l'utiliser dans les shaders.

Dans le cas général, nous pouvons conclure que la seule option pour surmonter le cadre par lequel les possibilités d'effets graphiques sont limitées est de combiner les résultats de divers shaders.


Références



All Articles