Blending and Unity Terrain: كيفية التخلص من التقاطعات والتوقف عن إيذاء عينيك

من أجل الحصول على عالم واقعي داخل اللعبة ، من الضروري مراعاة تفاعل الأشكال الأرضية المختلفة مع بعضها البعض ومع النماذج الأخرى. وإذا كانت خطوط التقاطع المرئية بين النماذج ثلاثية الأبعاد تفسد تكامل الصورة ، فمن الجدير التفكير في كيفية التخلص منها. الحالة الأكثر شيوعًا لمثل هذه الخطوط ، والتي قد تكون مألوفة للكثيرين ، هي تقاطع لوحات الجسيمات مع الهندسة غير الشفافة.

صورة

مثال آخر هو التكوين الطبيعي المقلق لتقاطع الصخور والنباتات مع سطح المناظر الطبيعية في مشاهد "في الهواء الطلق".

صورة

بالإضافة إلى طرق التنعيم المختلفة (SSAA و MSAA و CSAA و FXAA و NFAA و CMAA و DLAA و TAA وما إلى ذلك) ، والتي وإن كانت تخفف من المظهر المتحدي لخطوط التقاطع هذه ، ولكنها لا تصحح الموقف تمامًا ، فهناك تقنيات أكثر فعالية. سننظر فيها.

مزج العمق


تمتلك الوحدة حلًا مدمجًا للقضاء على التقاطعات المرئية بين الجسيمات الشفافة والهندسة المعتمة التي تسمى الجسيمات اللينة. تعمل التظليل التي تدعم هذا التأثير على تعزيز شفافية الجسيمات ، اعتمادًا على مدى صغر الفرق بين عمق جزء الجسيم وعمق الهندسة غير الشفافة.

صورة
مبدأ تشغيل الجزيئات اللينة من

الواضح أنه من أجل التشغيل الصحيح للجسيمات اللينة ، يلزم وجود مخزن مؤقت للعمق. في حالة التظليل المؤجل ، يتم تكوين المخزن المؤقت للعمق في مرحلة عرض المخازن المؤقتة ملء الشاشة ، ومع مراعاة MRT (أهداف العرض المتعددة ، وليس التصوير المقطعي بالرنين المغناطيسي) ، لا يتم التعبير عن وجودها في تكاليف حسابية إضافية.

في حالة التظليل الأمامي واستخدام خط أنابيب Unity Legacy ، كان هناك حاجة إلى تمرير إضافي لتقديم الهندسة غير الشفافة إلى المخزن المؤقت للعمق [1] . يتم تنشيط هذا التمرير من خلال تعيين القيمة المناسبة لخاصية Camera.depthTextureMode. هذه الخاصية غير متاحة في نافذة المفتش ، ولكنها متاحة في API [2] .

يمكنك الآن تنفيذ نسختك الخاصة من خط أنابيب التقديم Scriptable مع تظليل أمامي ، والذي بمساعدة MRT يمكن أن يعرض كل من المخزن المؤقت للعمق ومخزن الألوان المؤقت في نفس الوقت.


إزالة خطوط التقاطع في التظليل التي تدعم الجزيئات اللينة

بشكل عام ، لا توجد عوائق فنية لاستخدام طريقة مزج العمق لإزالة التقاطعات المرئية للنماذج ثلاثية الأبعاد مع المناظر الطبيعية:

عرض الكود
// 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; 
}


ومع ذلك ، فإن هذا النهج له عيوب عديدة.

العيب الأول يتعلق بالأداء. يعمل مزج العمق في مرحلة مزج أنابيب الأجهزة ، أي مباشرة بعد تنقيط وحساب جهاز تظليل الشظايا. في هذه المرحلة ، يتم خلط نتيجة تنفيذ جهاز تظليل الأجزاء مع النتيجة المسجلة في المخزن المؤقت للإخراج [3] [4] [5] وفقًا للصيغة المحددة مسبقًا بواسطة الاستدعاءات إلى API [6] [7] [8] [9] .

هذا هو الجزء الأقل تقدميًا من أي خط أنابيب للأجهزة ، بمعنى أنه يعمل تمامًا كما عمل سلفه قبل عشرين عامًا. يقرأ GPU القيمة من الذاكرة ، ويمزجها مع قيمة جهاز تظليل الأجزاء ، ويعيدها إلى الذاكرة.

هناك أيضًا اختلاف في استخدام المزج العميق للنماذج ثلاثية الأبعاد الشفافة تمامًا أو الشفافة جزئيًا. شفاف - على سبيل المثال ، لوحات الجسيمات - حتى بدون المزج في العمق ، يكون التجسيد بأكمله شفافًا. في حالة النماذج ثلاثية الأبعاد غير الشفافة ، ستمنح الشفافية الحقيقية والملموسة والمرئية عند المزج في العمق عددًا صغيرًا جدًا من الأجزاء ، في حين ستظل الغالبية العظمى منها غير شفافة. لكن هذا الأخير لا يعني على الإطلاق أن المزج لن يستخدم في عرضه - بل سيعمل ببساطة.

يتعلق العيب الثاني بكيفية تحديد لون المزج. باختصار ، جميع الأجزاء التي يتم خلطها في بكسل شاشة معين تقع على شعاع واحد ينبعث من الموقع العالمي للكاميرا ويمر من خلال الموقع العالمي لهذه البكسل. وهذا بدوره يعني أنه مع أي تغيير في موضع الكاميرا أو اتجاهها ، سيتم ملاحظة اختلاف المنظر: ستتحرك أجزاء من النموذج ثلاثي الأبعاد الموجود بالقرب من الكاميرا بشكل أسرع من أجزاء المشهد الموجودة بعيدًا عن الكاميرا [10] [11] . هذا ملحوظ بشكل خاص عند النظر إليه من مسافة قريبة مع الإزاحة الجانبية المستمرة للكاميرا.


المنظر الجانبي عند تحريك الكاميرا: يتم تحويل أجزاء من النموذج ثلاثي الأبعاد إلى مسافة أكبر مقارنة بأجزاء المشهد


المنظر الجانبي عند تحريك الكاميرا: عند تثبيت الكاميرا على جزء من المشهد ، يصبح ملحوظًا مدى سرعة تحرك أجزاء النموذج.

عند تدوير الكاميرا ، يتم ملاحظة المنظر على الفور على طول محورين لإحداثيات الشاشة. ومع ذلك ، في الديناميات هذا أقل وضوحا من المنظر الجانبي.


اختلاف المنظر عند تحريك الكاميرا: من الصعب على الدماغ التعرف على نمط اختلاف المنظر عندما يتم تحويل الأجزاء على طول محورين ،

ولكن بشكل ملحوظ ، يتغير مظهر المزج في العمق اعتمادًا على الزاوية التي ينظر فيها المراقب إلى سطح المشهد. تصبح منطقة المزج غير مرئية تقريبًا عندما يكون اتجاه الرؤية متعامدًا مع السطح الطبيعي على السطح الأفقي ، لكن حجم هذه المنطقة يزداد بسرعة إذا قمت بإمالة الكاميرا لأسفل.


تغيير عرض منطقة المزج أثناء إمالة الكاميرا

بعمق قد يكون المزج خيارًا جيدًا لإزالة خطوط التقاطع للنماذج ثلاثية الأبعاد مع المناظر الطبيعية ، إن لم يكن لوفرة القطع المصاحبة لها. هذه الطريقة أكثر ملاءمة لتأثيرات الجسيمات غير الثابتة ، وكقاعدة عامة ، لا تحتوي على مواد مفصلة للغاية ، وبالتالي ، لا يتم ملاحظة تأثيرات المنظر في حالتهم.


مزج خريطة الارتفاع


خيار آخر لتطبيق المزج الأفقي هو استخدام خريطة الارتفاع ، التي توفر Unity الوصول إليها من خلال TerrainData API [12] .

بمعرفة موضع كائن التضاريس وأبعاد التضاريس المشار إليها في TerrainData ، ولديها "خريطة ارتفاع" في متناول اليد ، يمكنك حساب ارتفاع التضاريس في أي نقطة محددة في إحداثيات العالم.


معلمات التضاريس المطلوبة لأخذ عينات من خريطة الارتفاع

// 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);

حسنًا ، الآن ، بعد حساب ارتفاع المشهد ، يمكنك أيضًا حساب إحداثيات الأشعة فوق البنفسجية في التظليل لتجربة خريطة ارتفاعات المناظر الطبيعية في إحداثيات العالم.

// Computes UV for sampling terrain heightmap... 

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

من أجل أن تكون قادرًا على استخدام نفس الرمز في تظليل الشظية والرأس ، يتم استخدام وظيفة tex2Dlod لأخذ العينات. بالإضافة إلى ذلك ، لا تحتوي خريطة الارتفاع على مستويات mip ، لذا فإن أخذ عينات منها باستخدام وظيفة tex2D ، التي تحسب مستوى mip تلقائيًا ، لا معنى له في الأساس.

// 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;
}

يمكنك محاولة إعادة إنتاج إزالة التقاطعات من خلال الشفافية دون استخدام مخزن عمق. هذا لا يحل المشاكل الأخرى المرتبطة بهذه الطريقة ، ولكنه يجعل من الممكن التحقق من قابلية تشغيل المزج باستخدام خريطة الارتفاع.

عرض الكود
// 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; 
}





مزج العمق ومزج الارتفاع. يختلف عرض منطقة المزج مع نفس معلمات التظليل ،

وتستخدم الرسوم التوضيحية معلمات مزج متطابقة لكلا الطريقتين. يختلف عرض مناطق المزج بصريًا ، لأن المزج بخريطة الارتفاع لا يعتمد على الزاوية بين نظرة المراقب والعادي إلى المناظر الطبيعية.

يعتبر المزج بخريطة الارتفاع أفضل من المزج من العمق على الأقل من ناحية: فهو يصحح اعتماد المزج المرئي بالعين المجردة على الزاوية التي تنظر بها الكاميرا إلى المناظر الطبيعية. لسوء الحظ ، سوف يستمر ملاحظة تأثير المنظر.


مزج إعادة بناء المناظر الطبيعية


للتخلص من اختلاف المنظر ، تحتاج إلى مزج جزء من النموذج ثلاثي الأبعاد مع جزء من المشهد الأفقي أسفله (لا يعتمد اختيار اللون للخلط في هذه الحالة على موضع الكاميرا واتجاهها).


كيفية إصلاح المنظر: اختيار جزء المناظر الطبيعية للمزج

بالطبع ، نحن نتحدث هنا أكثر عن جزء المناظر الطبيعية الافتراضية. اعتمادًا على موضع الكاميرا ، يكون الوضع ممكنًا عندما لا يقع جزء من المشهد ، والذي من الضروري مزج جزء من نموذج ثلاثي الأبعاد ، في مجال رؤية الكاميرا. توجد مشكلة مماثلة في عرض الانعكاسات المحلية في مساحة الشاشة (SSLR). وهو يتألف من حقيقة أنه من المستحيل تقديم انعكاس لجزء غير موجود على الشاشة [13] .

في حالة المناظر الطبيعية ، يمكن إعادة بناء لون الجزء الافتراضي بدقة عالية باستخدام مواد مساعدة مقدمة من Unity API: الخريطة العادية [14] ، والخريطة الخفيفة [15] ، والقوام الموزون لطبقات المزج [16] ، والقوام المتضمن في تكوين الطبقات [17] .


إعادة بناء جزء من المناظر

الطبيعية يتم أخذ عينات من جميع القوام الذي يتكون من المناظر الطبيعية وفقًا لنفس الأشعة فوق البنفسجية مثل خريطة الارتفاع. في حالة الطبقات ، يتم ضبط إحداثيات أخذ العينات من خلال معلمات التجانب المحددة لطبقة معينة [18] [19] .

عرض الكود
// 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; 
}


لذا ، فإن المزج مع إعادة بناء أجزاء المناظر الطبيعية يعمل على إصلاح جميع المشاكل النموذجية لمزج العمق والمزج بخريطة الارتفاع ، بما في ذلك اختلاف المنظر.



مزج إعادة بناء المناظر الطبيعية


أداء إعادة بناء جزء التضاريس


في هذه المرحلة ، حان الوقت للسؤال ، ما قيمة هذا النوع من التسوية؟ للوهلة الأولى ، تتجاوز كثافة الموارد لإعادة بناء أجزاء المناظر الطبيعية كثافة الموارد لمزج ألفا. لإعادة البناء ، من الضروري القيام بعشرات عمليات القراءة الإضافية من الذاكرة. لمزج ألفا ، تحتاج فقط إلى عملية قراءة واحدة من الذاكرة وعملية كتابة واحدة إلى الذاكرة.

في الواقع ، سيعتمد كل شيء على ميزات النظام الأساسي للأجهزة. يتم دعم إعادة بناء الأجزاء من خلال ضغط النسيج ، وتعيين الخرائط ، وقوة المعالجة الأساسية لوحدة معالجة الرسومات ، وتحسينات محددة لخط أنابيب الأجهزة (الرفض المبكر للعمق). وعلى عكس مزج ألفا ، فإن الحقيقة المذكورة أعلاه ستلعب أنها أقل جزء تقدمي من أي GPU.

ومع ذلك ، هناك دائمًا مجال للتحسين. على سبيل المثال ، في حالة إعادة بناء لون المناظر الطبيعية ، فإن الحاجة إلى إعادة البناء هذه هي فقط لشريط ضيق من أجزاء النموذج ثلاثي الأبعاد التي لا تزيد عن ارتفاع معين فوق سطح المناظر الطبيعية.

يمكن أن يعطي التفرع الديناميكي في التظليل نتائج أداء يمكن التنبؤ بها بشكل سيئ ، ولكن هناك نقطتان يجب مراعاتهما:

  1. يجب تخطي الحسابات غير الضرورية في التفرع بواسطة شرط إذا لم يتم استيفاء هذا الشرط في جزء كبير من الحالات.
  2. . , ( , ), GPU. ― (branch granularity), , , , , . , , . , GPU , , . , GPU, , 1 (PowerVR SGX).


تصور درجات مختلفة من التماسك

في حالة إعادة بناء الأجزاء ، تؤخذ هاتان النقطتان في الاعتبار: تسمح حالة التفرع في معظم الحالات بقطع تنفيذ العمليات كثيفة الموارد لإعادة بناء اللون الطبيعي ، وهذا الشرط متماسك ، باستثناء عدد قليل جدًا من الأجزاء (في الرسم التوضيحي ، هذه شظايا تكمن على الحدود بين المنطقتين "الحمراء" و "الخضراء").


تماسك إعادة بناء أجزاء المناظر الطبيعية

يبقى إضافة بعض التعليقات بخصوص طريقة المزج هذه:

  1. توفر الوحدة جميع القوام الضروري فقط إذا كان الوضع الأفقي ممكّنًا وضع Draw Instanced [20] ، وإلا فلن تكون الخريطة العادية متاحة ، والتي بدورها لن تسمح لك بإعادة بناء إضاءة المناظر الطبيعية بشكل صحيح للمزج.
  2. Unity API , (base map) . - .
  3. , API (, Metal 16 ).
  4. 3D- , Terrain, SRP.
  5. 3D- , 3D- .
  6. , «» , «» . , «» , . «» .






عند تصميم النماذج ثلاثية الأبعاد ، من المستحيل مراعاة تنوع التضاريس الأرضية التي يُفترض أن تستخدم بها هذه النماذج. في كثير من الأحيان ، يجب أن تكون النماذج ثلاثية الأبعاد "غارقة" في المناظر الطبيعية أو يتم تدويرها لإخفاء الأجزاء البارزة ، أو العكس - لإظهار الأجزاء المخفية التي يجب أن تكون مرئية. تحد نماذج "الاحترار" من قابليتها للتطبيق ، وإذا تم عرض النماذج ثلاثية الأبعاد في وقت أبكر من المناظر الطبيعية ، فإنها تؤدي أيضًا إلى تأثير زائد. بدوره ، بدوره ، بعيد أيضًا عن كونه مناسبًا لجميع النماذج ثلاثية الأبعاد (على سبيل المثال ، ليس للمنازل والأشجار).


لإخفاء العناصر البارزة للنموذج ثلاثي الأبعاد ، يجب أن "يغرق" في المشهد

العض هو مصطلح مألوف لمستخدمي المحررين الرسومية. هذه وظيفة تسمح لنقاط التحكم "بالالتصاق" بعقد الشبكة المكانية ، وفي المحررات ثلاثية الأبعاد لوجوه وأسطح الأشياء الأخرى. يمكن أن يؤدي الالتقاط إلى خريطة ارتفاعات المناظر الطبيعية في تظليل الرأس إلى تبسيط تصميم المشاهد إلى حد كبير.


نموذج ثلاثي الأبعاد بدون التقاط. نموذج ثلاثي الأبعاد مع التقاط الرأس. نموذج ثلاثي الأبعاد مع الالتقاط والمزج. نموذج ثلاثي الأبعاد مع التقاط الرأس ، والمزج ، والإضاءة الثابتة

تتمثل الصعوبة الرئيسية في تنفيذ الالتقاط في أنك تحتاج إلى معرفة رؤوس النموذج ثلاثي الأبعاد التي تحتاج إلى التقاطها على خريطة الارتفاع ، وأيها لا تستحق ذلك. تحتوي الرؤوس على معلومات فقط حول الطبيعة المحلية للسطح (وهي ليست كافية) ولا تحتوي على أي معلومات حول هيكلها (وهو ضروري).

كما هو الحال في حالات التطبيق الأخرى ، تكون هذه المشكلة أسهل لحلها في مرحلة النمذجة من خلال التنفيذ المباشر للمعلمات الضرورية في القمم. كمثل هذه المعلمة ، يجب عليك اختيار سمة بديهية - على سبيل المثال ، عامل ترجيح العض (وليس المسافة إلى حدود سطح مفتوح ، كما نرغب في المرونة).


ترميز الترجيح لالتقاط الصور

عرض الكود
// 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;
}


إن قابلية انطباق قمة الرأس محدودة بالمراسلة العامة بين التضاريس وسطح النموذج ثلاثي الأبعاد. للتعويض عن اختلافاتهم الكبيرة ، من الضروري استخدام طرق أخرى أكثر كثافة في استخدام الموارد - على سبيل المثال ، استخدام نماذج ثلاثية الأبعاد مع القشرة.


استنتاج


الفكرة الرئيسية التي يجب أخذها من المقالة: أي جهاز تظليل معقد بدرجة كافية وقابل للتطوير يحتاج إلى بيانات أساسية. ومهمة المطور هي فهم كيفية تشغيل النظام الرسومي: ما هي البيانات التي يوفرها ، وكيف يمكن دمجها مع بعضها البعض وكيفية استخدامها في التظليل.

في الحالة العامة ، يمكننا أن نستنتج أن الخيار الوحيد للتغلب على الإطار الذي من خلاله تكون إمكانات التأثيرات الرسومية محدودة هو الجمع بين نتائج الظلال المختلفة.


المراجع



All Articles