[أجزاء السابقة من التحليل: الأول و الثاني و الثالث .]الجزء 1. سحابة سيروس
عندما تجري اللعبة في مساحات مفتوحة ، فإن أحد العوامل التي تحدد مصداقية العالم هي السماء. فكر في الأمر - معظم الوقت تأخذ السماء حرفيا حوالي 40-50 ٪ من الشاشة بأكملها. السماء هي أكثر بكثير من مجرد تدرج جميل. لديها النجوم والشمس والقمر ، وأخيرا الغيوم.على الرغم من أن الاتجاهات الحالية يبدو أنها تتكون من تجسيد حجمي للسحب باستخدام Raymarching (انظر هذه المقالة ) ، فإن الغيوم في The Witcher 3 تعتمد بالكامل على الملمس. لقد قمت بفحصهم بالفعل من قبل ، ولكن اتضح أن كل شيء معهم أكثر تعقيدًا مما توقعت في الأصل. إذا اتبعت سلسلة مقالاتي ، فأنت تعلم أن هناك فرقًا بين Blood and Wine DLC وبقية اللعبة. وكما قد تتوقع ، هناك بعض التغييرات في العمل مع السحب في DLC.يحتوي Witcher 3 على عدة طبقات من الغيوم. اعتمادًا على الطقس ، يمكن أن يكون فقط غيوم مقلوبة ، غيوم تراكمية عالية ، ربما بعض الغيوم من عائلة السحب ذات الطبقات (على سبيل المثال ، أثناء العاصفة). في النهاية ، قد لا تكون هناك غيوم على الإطلاق.تختلف بعض الطبقات من حيث القوام والتظليل المستخدم لتقديمها. من الواضح أن هذا يؤثر على مدى تعقيد وطول كود المجمّع لتظليل البكسل.على الرغم من كل هذا التنوع ، هناك بعض الأنماط الشائعة التي يمكن ملاحظتها عند عرض الغيوم في Witcher 3. أولاً ، يتم عرضها جميعًا في مسار استباقي ، وهذا هو الخيار المثالي. كلهم يستخدمون الخلط (انظر أدناه). هذا يجعل من السهل التحكم في كيفية تغطية طبقة منفصلة للسماء - ويتأثر ذلك بقيمة ألفا من تظليل البكسل.والأكثر إثارة للاهتمام أن بعض الطبقات يتم تقديمها مرتين بنفس المعلمات.بعد النظر في الشفرة ، اخترت أقصر تظليل من أجل (1) على الأرجح تنفيذ هندستها العكسية الكاملة ، (2) معرفة جميع جوانبها.لقد ألقيت نظرة فاحصة على سحابة السرخس من Witcher 3: Blood and Wine.هنا إطار مثال:قبل التقديمبعد مرور التقديم الأولبعد مرور التجسيد الثانيفي هذا الإطار بالذات ، تكون غيوم السيروت هي الطبقة الأولى في التجسيد. كما ترى ، يتم تقديمه مرتين ، مما يزيد من سطوعه.تظليل هندسي ورأسي
قبل تظليل البكسل ، سنتحدث بإيجاز عن تظليل الهندسي والرأس المستخدم. تشبه شبكة عرض السحب قليلاً قبة السماء العادية:جميع القمم في الفاصل الزمني [0-1] ، لذلك لتوسيط الشبكة على النقطة (0،0،0) ، يتم استخدام القياس والانحراف قبل التحويل إلى worldViewProj (نحن نعلم بالفعل هذا النمط من الأجزاء السابقة من السلسلة). في حالة السحب ، تمتد الشبكة بقوة على طول المستوى XY (يشير المحور Z للأعلى) لتغطية مساحة أكبر من هرم الرؤية. والنتيجة هي على النحو التالي:بالإضافة إلى ذلك ، تحتوي الشبكة على ناقلات طبيعية ومماس. يحسب جهاز تظليل الرأس أيضًا ناقل ثنائي الظل بواسطة المنتج المتجه - يتم عرض الثلاثة في شكل عادي. هناك أيضًا حساب علوي للضباب (لونه وسطوعه).بكسل تظليل
يبدو رمز تجميع تظليل البكسل كما يلي: ps_5_0
dcl_globalFlags refactoringAllowed
dcl_constantbuffer cb0[10], immediateIndexed
dcl_constantbuffer cb1[9], immediateIndexed
dcl_constantbuffer cb12[238], immediateIndexed
dcl_constantbuffer cb4[13], immediateIndexed
dcl_sampler s0, mode_default
dcl_resource_texture2d (float,float,float,float) t0
dcl_resource_texture2d (float,float,float,float) t1
dcl_input_ps linear v0.xyzw
dcl_input_ps linear v1.xyzw
dcl_input_ps linear v2.w
dcl_input_ps linear v3.xyzw
dcl_input_ps linear v4.xyz
dcl_input_ps linear v5.xyz
dcl_output o0.xyzw
dcl_temps 4
0: mul r0.xyz, cb0[9].xyzx, l(1.000000, 1.000000, -1.000000, 0.000000)
1: dp3 r0.w, r0.xyzx, r0.xyzx
2: rsq r0.w, r0.w
3: mul r0.xyz, r0.wwww, r0.xyzx
4: mul r1.xy, cb0[0].xxxx, cb4[5].xyxx
5: mad r1.xy, v1.xyxx, cb4[4].xyxx, r1.xyxx
6: sample_indexable(texture2d)(float,float,float,float) r1.xyzw, r1.xyxx, t0.xyzw, s0
7: add r1.xyz, r1.xyzx, l(-0.500000, -0.500000, -0.500000, 0.000000)
8: add r1.xyz, r1.xyzx, r1.xyzx
9: dp3 r0.w, r1.xyzx, r1.xyzx
10: rsq r0.w, r0.w
11: mul r1.xyz, r0.wwww, r1.xyzx
12: mul r2.xyz, r1.yyyy, v3.xyzx
13: mad r2.xyz, v5.xyzx, r1.xxxx, r2.xyzx
14: mov r3.xy, v1.zwzz
15: mov r3.z, v3.w
16: mad r1.xyz, r3.xyzx, r1.zzzz, r2.xyzx
17: dp3_sat r0.x, r0.xyzx, r1.xyzx
18: add r0.y, -cb4[2].x, cb4[3].x
19: mad r0.x, r0.x, r0.y, cb4[2].x
20: dp2 r0.y, -cb0[9].xyxx, -cb0[9].xyxx
21: rsq r0.y, r0.y
22: mul r0.yz, r0.yyyy, -cb0[9].xxyx
23: add r1.xyz, -v4.xyzx, cb1[8].xyzx
24: dp3 r0.w, r1.xyzx, r1.xyzx
25: rsq r1.z, r0.w
26: sqrt r0.w, r0.w
27: add r0.w, r0.w, -cb4[7].x
28: mul r1.xy, r1.zzzz, r1.xyxx
29: dp2_sat r0.y, r0.yzyy, r1.xyxx
30: add r0.y, r0.y, r0.y
31: min r0.y, r0.y, l(1.000000)
32: add r0.z, -cb4[0].x, cb4[1].x
33: mad r0.z, r0.y, r0.z, cb4[0].x
34: mul r0.x, r0.x, r0.z
35: log r0.x, r0.x
36: mul r0.x, r0.x, l(2.200000)
37: exp r0.x, r0.x
38: add r1.xyz, cb12[236].xyzx, -cb12[237].xyzx
39: mad r1.xyz, r0.yyyy, r1.xyzx, cb12[237].xyzx
40: mul r2.xyz, r0.xxxx, r1.xyzx
41: mad r0.xyz, -r1.xyzx, r0.xxxx, v0.xyzx
42: mad r0.xyz, v0.wwww, r0.xyzx, r2.xyzx
43: add r1.x, -cb4[7].x, cb4[8].x
44: div_sat r0.w, r0.w, r1.x
45: mul r1.x, r1.w, cb4[9].x
46: mad r1.y, -cb4[9].x, r1.w, r1.w
47: mad r0.w, r0.w, r1.y, r1.x
48: mul r1.xy, cb0[0].xxxx, cb4[11].xyxx
49: mad r1.xy, v1.xyxx, cb4[10].xyxx, r1.xyxx
50: sample_indexable(texture2d)(float,float,float,float) r1.x, r1.xyxx, t1.xyzw, s0
51: mad r1.x, r1.x, cb4[12].x, -cb4[12].x
52: mad_sat r1.x, cb4[12].x, v2.w, r1.x
53: mul r0.w, r0.w, r1.x
54: mul_sat r0.w, r0.w, cb4[6].x
55: mul o0.xyz, r0.wwww, r0.xyzx
56: mov o0.w, r0.w
57: ret
يتم إدخال قوامين سلسين. يحتوي أحدها على خريطة عادية (قنوات xyz ) وشكل سحابي (القناة a ). والثاني هو الضوضاء لتشويه الشكل.خريطة عادية ، خاصية CD Projekt Redشكل سحابة ، قرص مضغوط الملكية Projekt الأحمرنسيج الضوضاء ، خاصية CD Projekt Redالمخزن المؤقت الرئيسي للثوابت مع معلمات السحابة هو cb4. لهذا الإطار ، له المعاني التالية:بالإضافة إلى ذلك ، يتم استخدام قيم أخرى من الأصفاد الأخرى. لا تقلق ، سننظر فيها أيضًا.عكس اتجاه ضوء الشمس
أول شيء يحدث في التظليل هو حساب الاتجاه الطبيعي لضوء الشمس المقلوب على طول المحور Z: 0: mul r0.xyz, cb0[9].xyzx, l(1.000000, 1.000000, -1.000000, 0.000000)
1: dp3 r0.w, r0.xyzx, r0.xyzx
2: rsq r0.w, r0.w
3: mul r0.xyz, r0.wwww, r0.xyzx
float3 invertedSunlightDir = normalize(lightDir * float3(1, 1, -1) );
كما ذكرنا سابقًا ، يتم توجيه المحور Z إلى الأعلى ، و cb0 [9] هو اتجاه ضوء الشمس. وتهدف هذه النواقل في الشمس - من المهم! يمكنك التحقق من ذلك عن طريق كتابة تظليل حسابي بسيط يقوم بتشغيل NdotL بسيط وإدراجه في تمرير تظليل مؤجل.سحابة أخذ العينات
والخطوة التالية هي حساب texcoords لأخذ عينات من نسيج السحابة ، وتفريغ الناقل العادي وتطبيعه. 4: mul r1.xy, cb0[0].xxxx, cb4[5].xyxx
5: mad r1.xy, v1.xyxx, cb4[4].xyxx, r1.xyxx
6: sample_indexable(texture2d)(float,float,float,float) r1.xyzw, r1.xyxx, t0.xyzw, s0
7: add r1.xyz, r1.xyzx, l(-0.500000, -0.500000, -0.500000, 0.000000)
8: add r1.xyz, r1.xyzx, r1.xyzx
9: dp3 r0.w, r1.xyzx, r1.xyzx
10: rsq r0.w, r0.w
float2 cloudTextureUV = Texcoords * textureScale + elapsedTime * speedFactors;
float4 cloudTextureValue = texture0.Sample( sampler0, cloudTextureUV ).rgba;
float3 normalMap = cloudTextureValue.xyz;
float cloudShape = cloudTextureValue.a;
float3 unpackedNormal = (normalMap - 0.5) * 2.0;
unpackedNormal = normalize(unpackedNormal);
دعونا نتعامل معها تدريجياً.للحصول على حركة السحب ، نحتاج إلى الوقت المنقضي بالثواني ( cb [0] .x ) مضروبًا في معامل السرعة ، مما يؤثر على مدى سرعة تحرك السحب عبر السماء ( cb4 [5] .xy ).كما قلت سابقًا ، يتم تمديد الأشعة فوق البنفسجية على طول هندسة قبة السماء ، ونحتاج أيضًا إلى عوامل تحجيم النسيج التي تؤثر على حجم الغيوم ( cb4 [4] .xy ).الصيغة النهائية هي:samplingUV = Input.TextureUV * textureScale + time * speedMultiplier;
بعد أخذ عينات من جميع القنوات الأربع ، لدينا خريطة عادية (قنوات RGB) وشكل سحابي (القناة أ).لتفريغ الخريطة العادية من الفاصل الزمني [0؛ 1] في الفاصل الزمني [-1 ؛ 1] نستخدم الصيغة التالية:unpackedNormal = (packedNormal - 0.5) * 2.0;
يمكنك أيضًا استخدام هذا:unpackedNormal = packedNormal * 2.0 - 1.0;
أخيرًا ، نقوم بتطبيع المتجه العادي غير المغلف.تراكب عادي
عند وجود المتجهات العادية ، وناقلات الظل والمماس الثنائي من تظليل الرأس ، والمتجه الطبيعي من الخريطة العادية ، فإننا عادة ما نرسم المعايير الطبيعية. 11: mul r1.xyz, r0.wwww, r1.xyzx
12: mul r2.xyz, r1.yyyy, v3.xyzx
13: mad r2.xyz, v5.xyzx, r1.xxxx, r2.xyzx
14: mov r3.xy, v1.zwzz
15: mov r3.z, v3.w
16: mad r1.xyz, r3.xyzx, r1.zzzz, r2.xyzx
float3 SkyTangent = Input.Tangent;
float3 SkyNormal = (float3( Input.Texcoords.zw, Input.param3.w ));
float3 SkyBitangent = Input.param3.xyz;
float3x3 TBN = float3x3(SkyTangent, SkyBitangent, SkyNormal);
float3 finalNormal = (float3)mul( unpackedNormal, (TBN) );
سطوع (1)
في الخطوة التالية ، يتم تطبيق حساب NdotL وهذا يؤثر على مقدار إضاءة بكسل معين.خذ بعين الاعتبار كود المجمع التالي: 17: dp3_sat r0.x, r0.xyzx, r1.xyzx
18: add r0.y, -cb4[2].x, cb4[3].x
19: mad r0.x, r0.x, r0.y, cb4[2].x
فيما يلي تصور NdotL على الإطار المعني:يتم استخدام هذا المنتج العددي (مع التشبع) للاستيفاء بين minIntensity و maxIntensity. وبفضل هذا ، ستكون أجزاء الغيوم المضاءة بأشعة الشمس أكثر إشراقًا.
float NdotL = saturate( dot(invertedSunlightDir, finalNormal) );
float intensity1 = lerp( param1Min, param1Max, NdotL );
سطوع (2)
هناك عامل آخر يؤثر على سطوع الغيوم.يجب أن تكون الغيوم الموجودة في ذلك الجزء من السماء حيث توجد الشمس أكثر إبرازًا. للقيام بذلك ، نحسب التدرج بناءً على المستوى XY.يستخدم هذا التدرج لحساب الاستكمال الخطي بين قيم min / max ، على غرار ما يحدث في الجزء (1).أي أنه من الناحية النظرية ، يمكننا أن نطلب تغميق السحب الموجودة على الجانب الآخر من الشمس ، ولكن هذا لا يحدث في هذا الإطار بالذات ، لأن param2Min و param2Max ( cb4 [0] .x و cb4 [1] .x ) يتم تعيينهما على 1.0f. 20: dp2 r0.y, -cb0[9].xyxx, -cb0[9].xyxx
21: rsq r0.y, r0.y
22: mul r0.yz, r0.yyyy, -cb0[9].xxyx
23: add r1.xyz, -v4.xyzx, cb1[8].xyzx
24: dp3 r0.w, r1.xyzx, r1.xyzx
25: rsq r1.z, r0.w
26: sqrt r0.w, r0.w
27: add r0.w, r0.w, -cb4[7].x
28: mul r1.xy, r1.zzzz, r1.xyxx
29: dp2_sat r0.y, r0.yzyy, r1.xyxx
30: add r0.y, r0.y, r0.y
31: min r0.y, r0.y, l(1.000000)
32: add r0.z, -cb4[0].x, cb4[1].x
33: mad r0.z, r0.y, r0.z, cb4[0].x
34: mul r0.x, r0.x, r0.z
35: log r0.x, r0.x
36: mul r0.x, r0.x, l(2.200000)
37: exp r0.x, r0.x
float2 lightDirXY = normalize( -lightDir.xy );
float3 vWorldToCamera = ( CameraPos - WorldPos );
float worldToCamera_distance = length(vWorldToCamera);
vWorldToCamera = normalize( vWorldToCamera );
float LdotV = saturate( dot(lightDirXY, vWorldToCamera.xy) );
float highlightedSkySection = saturate( 2*LdotV );
float intensity2 = lerp( param2Min, param2Max, highlightedSkySection );
float finalIntensity = pow( intensity2 *intensity1, 2.2);
في النهاية ، نضرب السطوع ونرفع النتيجة إلى قوة 2.2.لون الغيوم
يبدأ حساب لون الغيوم بالحصول على ثوابت من الثوابت العازلة تشير إلى لون الغيوم بجوار الشمس والغيوم على الجانب الآخر من السماء. فيما بينها ، يتم إجراء الاستكمال الخطي بناءً على highlightSkySection .ثم يتم ضرب النتيجة في كثافة النهائي .وفي النهاية ، يتم خلط النتيجة مع الضباب (لأسباب الأداء ، تم حسابها بواسطة جهاز تظليل الرأس). 38: add r1.xyz, cb12[236].xyzx, -cb12[237].xyzx
39: mad r1.xyz, r0.yyyy, r1.xyzx, cb12[237].xyzx
40: mul r2.xyz, r0.xxxx, r1.xyzx
41: mad r0.xyz, -r1.xyzx, r0.xxxx, v0.xyzx
42: mad r0.xyz, v0.wwww, r0.xyzx, r2.xyzx
float3 cloudsColor = lerp( cloudsColorBack, cloudsColorFront, highlightedSunSection );
cloudsColor *= finalIntensity;
cloudsColor = lerp( cloudsColor, FogColor, FogAmount );
جعل السحب الرقيقة أكثر وضوحا في الأفق
هذا ليس ملحوظًا جدًا على الإطار ، ولكن في الواقع هذه الطبقة أكثر وضوحًا بالقرب من الأفق أكثر من رأس Geralt. هيريس كيفية القيام بذلك.هل يمكن أن تلاحظ أن عند حساب سطوع الثاني، حسبنا طول ناقلات worldToCamera : 23: add r1.xyz, -v4.xyzx, cb1[8].xyzx
24: dp3 r0.w, r1.xyzx, r1.xyzx
25: rsq r1.z, r0.w
26: sqrt r0.w, r0.w
دعونا نجد التكرارات التالية لهذا الطول في الكود: 26: sqrt r0.w, r0.w
27: add r0.w, r0.w, -cb4[7].x
...
43: add r1.x, -cb4[7].x, cb4[8].x
44: div_sat r0.w, r0.w, r1.x
واو ، ما الأمر معنا؟cb [7] .x و cb [8] .x لها قيم 2000.0 و 7000.0.اتضح أن هذا هو نتيجة استخدام وظيفة linstep .تتلقى ثلاث معلمات: الحد الأدنى / الأقصى - الفاصل الزمني والقيمة v .يعمل هذا على النحو التالي: إذا كانت v في الفاصل الزمني [ min - max ] ، فستُرجع الدالة الاستكمال الداخلي الخطي في الفاصل الزمني [0.0 - 1.0]. من ناحية أخرى ، إذا كانت v خارج النطاق ، فإن linstep تُرجع 0.0 أو 1.0.مثال بسيط:linstep( 1000.0, 2000.0, 999.0) = 0.0
linstep( 1000.0, 2000.0, 1500.0) = 0.5
linstep( 1000.0, 2000.0, 2000.0) = 1.0
أي أنها تشبه إلى حد كبير الخطوة السلس من HLSL ، باستثناء أنه في هذه الحالة ، بدلاً من الاستيفاء الهرميتي ، يتم تنفيذ خطي.Linstep ليست ميزة في HLSL ، ولكنها مفيدة جدًا. يجدر وجوده في مجموعة الأدوات الخاصة بك.
float linstep( float min, float max, float v )
{
return saturate( (v - min) / (max - min) );
}
دعنا نعود إلى Witcher 3: بعد حساب هذا المؤشر ، والإبلاغ عن مدى مسافة جزء معين من السماء من Geralt ، نستخدمه لإضعاف سطوع السحب: 45: mul r1.x, r1.w, cb4[9].x
46: mad r1.y, -cb4[9].x, r1.w, r1.w
47: mad r0.w, r0.w, r1.y, r1.x
float distanceAttenuation = linstep( fadeDistanceStart, fadeDistanceEnd, worldToCamera_distance );
float fadedCloudShape = closeCloudsHidingFactor * cloudShape;
cloudShape = lerp( fadedCloudShape, cloudShape, distanceAttenuation );
cloudShape هي قناة .a من النسيج الأول ، و closeCloudsHidingFactor هي قيمة عازلة ثابتة تتحكم في رؤية الغيوم فوق رأس Geralt. في جميع الإطارات التي اختبرتها ، كانت تساوي 0.0 ، وهو ما يعادل غياب السحب. كما distanceAttenuation النهج 1.0 (المسافة من الكاميرا إلى قبة السماء الزيادات)، الغيوم تصبح أكثر وضوحا.أخذ عينات نسيج الضوضاء
حساب إحداثيات أخذ العينات الملمس الضوضاء حسابات مماثلة لنسيج من الغيوم، إلا أنه يمكنك استخدام مجموعة مختلفة من textureScale و speedMultiplier .بالطبع ، يتم استخدام أداة عينات مع تمكين وضع عنوان الالتفاف لأخذ عينات من جميع هذه المواد . 48: mul r1.xy, cb0[0].xxxx, cb4[11].xyxx
49: mad r1.xy, v1.xyxx, cb4[10].xyxx, r1.xyxx
50: sample_indexable(texture2d)(float,float,float,float) r1.x, r1.xyxx, t1.xyzw, s0
float2 noiseTextureUV = Texcoords * textureScaleNoise + elapsedTime * speedFactorsNoise;
float noiseTextureValue = texture1.Sample( sampler0, noiseTextureUV ).x;
ضع كل شيء معا
بعد استلام قيمة الضوضاء ، يجب أن ندمجها مع cloudShape.واجهت بعض المشاكل في فهم هذه السطور ، حيث توجد معلمة 2.w (والتي تكون دائمًا 1.0) وضوضاء متعددة (لها قيمة 5.0 ، مأخوذة من المخزن المؤقت الثابت).ومع ذلك ، فإن أهم شيء هنا هو القيمة النهائية لـ GeneralCloudsVisibility ، التي تؤثر على رؤية الغيوم.ألق نظرة أيضًا على القيمة النهائية للضوضاء. يتم ضرب اللون الناتج للسحباللون بالضجيج النهائي ، والذي يتم إنتاجه أيضًا لقناة ألفا. 51: mad r1.x, r1.x, cb4[12].x, -cb4[12].x
52: mad_sat r1.x, cb4[12].x, v2.w, r1.x
53: mul r0.w, r0.w, r1.x
54: mul_sat r0.w, r0.w, cb4[6].x
55: mul o0.xyz, r0.wwww, r0.xyzx
56: mov o0.w, r0.w
57: ret
float noiseTextureValue = texture1.Sample( sampler0, noiseTextureUV ).x;
noiseTextureValue = noiseTextureValue * noiseMult - noiseMult;
float noiseValue = saturate( noiseMult * Input.param2.w + noiseTextureValue);
noiseValue *= cloudShape;
float finalNoise = saturate( noiseValue * generalCloudsVisibility);
return float4( cloudsColor*finalNoise, finalNoise );
مجموع
تبدو النتيجة النهائية قابلة للتصديق للغاية.يمكنك المقارنة. الصورة الأولى هي تظليل بلدي ، والثانية هي تظليل اللعبة:إذا كنت فضوليًا ، فإن جهاز تظليل متاح هنا .الجزء 2. الضباب
يمكن تنفيذ الضباب بطرق مختلفة. ومع ذلك ، كانت الأوقات التي كان بإمكاننا فيها تطبيق ضباب بسيط يعتمد على المسافة والتخلص منه إلى الأبد في الماضي (على الأرجح). إن العيش في عالم تظليل قابل للبرمجة قد فتح الباب أمام حلول جنونية جديدة ، ولكن الأهم من ذلك ، دقيقة جسديًا وواقعية بصريًا.تستند الاتجاهات الحالية في تقديم الضباب إلى تظليل حسابي (لمزيد من التفاصيل ، انظر هذا العرض التقديمي بارت بارتونسكي).على الرغم من حقيقة أن هذا العرض التقديمي ظهر في عام 2014 ، وتم إصدار The Witcher 3 في 2015/2016 ، فإن الضباب في الجزء الأخير من مغامرات Geralt يعتمد تمامًا على الشاشة ويتم تنفيذه كمعالجة نموذجية.قبل أن نبدأ جلسة الهندسة العكسية التالية ، يجب أن أقول أنه خلال العام الماضي حاولت اكتشاف ضباب Witcher 3 خمس مرات على الأقل ، وفي كل مرة أخفق فيها. إن رمز المجمع ، كما سترى قريبًا ، معقد للغاية ، وهذا يجعل عملية إنشاء تظليل الضباب القابل للقراءة على HLSL شبه مستحيل.ومع ذلك ، تمكنت من العثور على أداة تظليل الضباب على الإنترنت والتي جذبت انتباهي على الفور بسبب تشابهها مع ضباب The Witcher 3 من حيث الأسماء المتغيرة والترتيب العام للتعليمات. لم يكن هذا التظليل هو نفسه تمامًا كما هو الحال في اللعبة ، لذلك كان علي إعادة صياغة الأمر قليلاً. أريد أن أقول هذا أن الجزء الرئيسي من كود HLSL الذي سترونه هنا ، باستثناء اثنين ، لم أقم بإنشائه / تحليله. تذكر هذا.فيما يلي كود التجميع الخاص بتظليل بكسل الضباب - تجدر الإشارة إلى أنه هو نفسه للعبة بأكملها (الجزء الرئيسي من عام 2015 وكلا من DLC): ps_5_0
dcl_globalFlags refactoringAllowed
dcl_constantbuffer cb3[2], immediateIndexed
dcl_constantbuffer cb12[214], immediateIndexed
dcl_resource_texture2d (float,float,float,float) t0
dcl_resource_texture2d (float,float,float,float) t1
dcl_resource_texture2d (float,float,float,float) t2
dcl_input_ps_siv v0.xy, position
dcl_output o0.xyzw
dcl_temps 7
0: ftou r0.xy, v0.xyxx
1: mov r0.zw, l(0, 0, 0, 0)
2: ld_indexable(texture2d)(float,float,float,float) r1.x, r0.xyww, t0.xyzw
3: mad r1.y, r1.x, cb12[22].x, cb12[22].y
4: lt r1.y, r1.y, l(1.000000)
5: if_nz r1.y
6: utof r1.yz, r0.xxyx
7: mul r2.xyzw, r1.zzzz, cb12[211].xyzw
8: mad r2.xyzw, cb12[210].xyzw, r1.yyyy, r2.xyzw
9: mad r1.xyzw, cb12[212].xyzw, r1.xxxx, r2.xyzw
10: add r1.xyzw, r1.xyzw, cb12[213].xyzw
11: div r1.xyz, r1.xyzx, r1.wwww
12: ld_indexable(texture2d)(float,float,float,float) r2.xyz, r0.xyww, t1.xyzw
13: ld_indexable(texture2d)(float,float,float,float) r0.x, r0.xyzw, t2.xyzw
14: max r0.x, r0.x, cb3[1].x
15: add r0.yzw, r1.xxyz, -cb12[0].xxyz
16: dp3 r1.x, r0.yzwy, r0.yzwy
17: sqrt r1.x, r1.x
18: add r1.y, r1.x, -cb3[0].x
19: add r1.zw, -cb3[0].xxxz, cb3[0].yyyw
20: div_sat r1.y, r1.y, r1.z
21: mad r1.y, r1.y, r1.w, cb3[0].z
22: add r0.x, r0.x, l(-1.000000)
23: mad r0.x, r1.y, r0.x, l(1.000000)
24: div r0.yzw, r0.yyzw, r1.xxxx
25: mad r1.y, r0.w, cb12[22].z, cb12[0].z
26: add r1.x, r1.x, -cb12[22].z
27: max r1.x, r1.x, l(0)
28: min r1.x, r1.x, cb12[42].z
29: mul r1.z, r0.w, r1.x
30: mul r1.w, r1.x, cb12[43].x
31: mul r1.zw, r1.zzzw, l(0.000000, 0.000000, 0.062500, 0.062500)
32: dp3 r0.y, cb12[38].xyzx, r0.yzwy
33: add r0.z, r0.y, cb12[42].x
34: add r0.w, cb12[42].x, l(1.000000)
35: div_sat r0.z, r0.z, r0.w
36: add r0.w, -cb12[43].z, cb12[43].y
37: mad r0.z, r0.z, r0.w, cb12[43].z
38: mul r0.w, abs(r0.y), abs(r0.y)
39: mad_sat r2.w, r1.x, l(0.002000), l(-0.300000)
40: mul r0.w, r0.w, r2.w
41: lt r0.y, l(0), r0.y
42: movc r3.xyz, r0.yyyy, cb12[39].xyzx, cb12[41].xyzx
43: add r3.xyz, r3.xyzx, -cb12[40].xyzx
44: mad r3.xyz, r0.wwww, r3.xyzx, cb12[40].xyzx
45: movc r4.xyz, r0.yyyy, cb12[45].xyzx, cb12[47].xyzx
46: add r4.xyz, r4.xyzx, -cb12[46].xyzx
47: mad r4.xyz, r0.wwww, r4.xyzx, cb12[46].xyzx
48: ge r0.y, r1.x, cb12[48].y
49: if_nz r0.y
50: add r0.y, r1.y, cb12[42].y
51: mul r0.w, r0.z, r0.y
52: mul r1.y, r0.z, r1.z
53: mad r5.xyzw, r1.yyyy, l(16.000000, 15.000000, 14.000000, 13.000000), r0.wwww
54: max r5.xyzw, r5.xyzw, l(0, 0, 0, 0)
55: add r5.xyzw, r5.xyzw, l(1.000000, 1.000000, 1.000000, 1.000000)
56: div_sat r5.xyzw, r1.wwww, r5.xyzw
57: add r5.xyzw, -r5.xyzw, l(1.000000, 1.000000, 1.000000, 1.000000)
58: mul r1.z, r5.y, r5.x
59: mul r1.z, r5.z, r1.z
60: mul r1.z, r5.w, r1.z
61: mad r5.xyzw, r1.yyyy, l(12.000000, 11.000000, 10.000000, 9.000000), r0.wwww
62: max r5.xyzw, r5.xyzw, l(0, 0, 0, 0)
63: add r5.xyzw, r5.xyzw, l(1.000000, 1.000000, 1.000000, 1.000000)
64: div_sat r5.xyzw, r1.wwww, r5.xyzw
65: add r5.xyzw, -r5.xyzw, l(1.000000, 1.000000, 1.000000, 1.000000)
66: mul r1.z, r1.z, r5.x
67: mul r1.z, r5.y, r1.z
68: mul r1.z, r5.z, r1.z
69: mul r1.z, r5.w, r1.z
70: mad r5.xyzw, r1.yyyy, l(8.000000, 7.000000, 6.000000, 5.000000), r0.wwww
71: max r5.xyzw, r5.xyzw, l(0, 0, 0, 0)
72: add r5.xyzw, r5.xyzw, l(1.000000, 1.000000, 1.000000, 1.000000)
73: div_sat r5.xyzw, r1.wwww, r5.xyzw
74: add r5.xyzw, -r5.xyzw, l(1.000000, 1.000000, 1.000000, 1.000000)
75: mul r1.z, r1.z, r5.x
76: mul r1.z, r5.y, r1.z
77: mul r1.z, r5.z, r1.z
78: mul r1.z, r5.w, r1.z
79: mad r5.xy, r1.yyyy, l(4.000000, 3.000000, 0.000000, 0.000000), r0.wwww
80: max r5.xy, r5.xyxx, l(0, 0, 0, 0)
81: add r5.xy, r5.xyxx, l(1.000000, 1.000000, 0.000000, 0.000000)
82: div_sat r5.xy, r1.wwww, r5.xyxx
83: add r5.xy, -r5.xyxx, l(1.000000, 1.000000, 0.000000, 0.000000)
84: mul r1.z, r1.z, r5.x
85: mul r1.z, r5.y, r1.z
86: mad r0.w, r1.y, l(2.000000), r0.w
87: max r0.w, r0.w, l(0)
88: add r0.w, r0.w, l(1.000000)
89: div_sat r0.w, r1.w, r0.w
90: add r0.w, -r0.w, l(1.000000)
91: mul r0.w, r0.w, r1.z
92: mad r0.y, r0.y, r0.z, r1.y
93: max r0.y, r0.y, l(0)
94: add r0.y, r0.y, l(1.000000)
95: div_sat r0.y, r1.w, r0.y
96: add r0.y, -r0.y, l(1.000000)
97: mad r0.y, -r0.w, r0.y, l(1.000000)
98: add r0.z, r1.x, -cb12[48].y
99: mul_sat r0.z, r0.z, cb12[48].z
100: else
101: mov r0.yz, l(0.000000, 1.000000, 0.000000, 0.000000)
102: endif
103: log r0.y, r0.y
104: mul r0.w, r0.y, cb12[42].w
105: exp r0.w, r0.w
106: mul r0.y, r0.y, cb12[48].x
107: exp r0.y, r0.y
108: mul r0.yw, r0.yyyw, r0.zzzz
109: mad_sat r1.xy, r0.wwww, cb12[189].xzxx, cb12[189].ywyy
110: add r5.xyz, -r3.xyzx, cb12[188].xyzx
111: mad r5.xyz, r1.xxxx, r5.xyzx, r3.xyzx
112: add r0.z, cb12[188].w, l(-1.000000)
113: mad r0.z, r1.y, r0.z, l(1.000000)
114: mul_sat r5.w, r0.z, r0.w
115: lt r0.z, l(0), cb12[192].x
116: if_nz r0.z
117: mad_sat r1.xy, r0.wwww, cb12[191].xzxx, cb12[191].ywyy
118: add r6.xyz, -r3.xyzx, cb12[190].xyzx
119: mad r3.xyz, r1.xxxx, r6.xyzx, r3.xyzx
120: add r0.z, cb12[190].w, l(-1.000000)
121: mad r0.z, r1.y, r0.z, l(1.000000)
122: mul_sat r3.w, r0.z, r0.w
123: add r1.xyzw, -r5.xyzw, r3.xyzw
124: mad r5.xyzw, cb12[192].xxxx, r1.xyzw, r5.xyzw
125: endif
126: mul r0.z, r0.x, r5.w
127: mul r0.x, r0.x, r0.y
128: dp3 r0.y, l(0.333000, 0.555000, 0.222000, 0.000000), r2.xyzx
129: mad r1.xyz, r0.yyyy, r4.xyzx, -r2.xyzx
130: mad r0.xyw, r0.xxxx, r1.xyxz, r2.xyxz
131: add r1.xyz, -r0.xywx, r5.xyzx
132: mad r0.xyz, r0.zzzz, r1.xyzx, r0.xywx
133: else
134: mov r0.xyz, l(0, 0, 0, 0)
135: endif
136: mov o0.xyz, r0.xyzx
137: mov o0.w, l(1.000000)
138: ret
بصراحة ، التظليل طويل جدًا. ربما تكون طويلة جدًا لإجراء عملية عكسية فعالة.فيما يلي مثال لمشهد غروب الشمس مع الضباب:دعنا نلقي نظرة على المدخلات:أما بالنسبة إلى الأنسجة ، فلدينا مخزن مؤقت للعمق ، وضمير المحيط ، ومخزن ألوان HDR.المخزن المؤقت لعمق الواردانسداد المحيط الوارديبدو المخزن المؤقت للون HDR الوارد... ونتيجة تطبيق تظليل الضباب في هذا المشهد كما يلي:نسيج HDR بعد تطبيق الضباب.يستخدم المخزن المؤقت للعمق لإعادة إنشاء الموضع في العالم. هذا هو النمط القياسي لتظليل Witcher 3.الحصول على بيانات حجب المحيط (في حالة تمكينه) يتيح لنا إخفاء الضباب. فكرة ذكية للغاية ، ربما فكرة واضحة ، لكني لم أفكر بها بهذه الطريقة. سأعود إلى هذا الجانب في وقت لاحق.يبدأ جهاز تظليل بتحديد ما إذا كانت البكسل في السماء. في حالة وجود البكسل في السماء (العمق == 1.0) ، فإن التظليل يعود باللون الأسود. إذا كانت البكسل في المشهد (العمق <1.0) ، فإننا نعيد إنشاء الموضع في العالم باستخدام المخزن المؤقت للعمق (السطور 7-11) ونستمر في حساب الضباب.يحدث مرور الضباب بعد فترة وجيزة من عملية التظليل المتأخرة. قد تلاحظ أن بعض العناصر المتعلقة بالتشغيل الأمامي ليست متاحة بعد. في هذا المشهد بالذات ، تم تطبيق أحجام إضاءة مؤجلة ، وبعد ذلك قدمنا شعر / وجه / عيون Geralt.أول شيء تحتاج إلى معرفته عن الضباب في "The Witcher 3": يتكون من جزأين - "لون الضباب" و "لون الغلاف الجوي". struct FogResult
{
float4 paramsFog;
float4 paramsAerial;
};
لكل جزء ثلاثة ألوان: أمامي ومتوسط وخلفي. أي أنه في المخزن المؤقت المستمر توجد بيانات مثل "FogColorFront" و "FogColorMiddle" و "AerialColorBack" وما إلى ذلك ... لنلق نظرة على البيانات الواردة:
float3 FogSunDir = cb12_v38.xyz;
float3 FogColorFront = cb12_v39.xyz;
float3 FogColorMiddle = cb12_v40.xyz;
float3 FogColorBack = cb12_v41.xyz;
float4 FogBaseParams = cb12_v42;
float4 FogDensityParamsScene = cb12_v43;
float4 FogDensityParamsSky = cb12_v44;
float3 AerialColorFront = cb12_v45.xyz;
float3 AerialColorMiddle = cb12_v46.xyz;
float3 AerialColorBack = cb12_v47.xyz;
float4 AerialParams = cb12_v48;
قبل حساب الألوان النهائية ، نحتاج إلى حساب المتجهات ومنتجات العددية. يمكن للتظليل الوصول إلى موضع البكسل في العالم ، وموضع الكاميرا (cb12 [0] .xyz) واتجاه الضباب / الإضاءة (cb12 [38] .xyz). هذا يسمح لنا بحساب الناتج العددي لناقل شكل الضباب واتجاهه. float3 frag_vec = fragPosWorldSpace.xyz - customCameraPos.xyz;
float frag_dist = length(frag_vec);
float3 frag_dir = frag_vec / frag_dist;
float dot_fragDirSunDir = dot(GlobalLightDirection.xyz, frag_dir);
لحساب تدرج المزج ، تحتاج إلى استخدام مربع منتج العدد المطلق ، ثم ضرب النتيجة مرة أخرى في بعض المعلمات التي تعتمد على المسافة: float3 curr_col_fog;
float3 curr_col_aerial;
{
float _dot = dot_fragDirSunDir;
float _dd = _dot;
{
const float _distOffset = -150;
const float _distRange = 500;
const float _mul = 1.0 / _distRange;
const float _bias = _distOffset * _mul;
_dd = abs(_dd);
_dd *= _dd;
_dd *= saturate( frag_dist * _mul + _bias );
}
curr_col_fog = lerp( FogColorMiddle.xyz, (_dot>0.0f ? FogColorFront.xyz : FogColorBack.xyz), _dd );
curr_col_aerial = lerp( AerialColorMiddle.xyz, (_dot>0.0f ? AerialColorFront.xyz : AerialColorBack.xyz), _dd );
}
يوضح هذا الكود البرمجي لنا من أين جاءت هذه 0.002 و -0.300. كما نرى ، فإن المنتج القياسي بين نواقل الرؤية والإضاءة مسؤول عن الاختيار بين الألوان "الأمامية" و "الخلفية". ذكي!هنا تصور التدرج النهائي الناتج (_dd).ومع ذلك ، فإن حساب تأثير الغلاف الجوي / الضباب أكثر تعقيدًا. كما ترون ، لدينا خيارات أكثر بكثير من ألوان RGB فقط. وهي تشمل ، على سبيل المثال ، كثافة المشهد. نستخدم Raymarching (16 خطوة ، وهذا هو السبب في أنه يمكن توسيع الدورة) لتحديد حجم الضباب وعامل المقياس: عندوجود ناقل [camera ---> world] ، يمكننا تقسيم جميع مكوناته إلى 16 - ستكون هذه خطوة واحدة. كما نرى أدناه ، فإن مكون .z (الارتفاع) فقط ( curr_pos_z_step ) هو الذي يشارك في الحسابات .اقرأ المزيد عن الضباب الذي يتم تطبيقه بواسطة raymarching ، على سبيل المثال ، هنا . float fog_amount = 1;
float fog_amount_scale = 0;
[branch]
if ( frag_dist >= AerialParams.y )
{
float curr_pos_z_base = (customCameraPos.z + FogBaseParams.y) * density_factor;
float curr_pos_z_step = frag_step.z * density_factor;
[unroll]
for ( int i=16; i>0; --i )
{
fog_amount *= 1 - saturate( density_sample_scale / (1 + max( 0.0, curr_pos_z_base + (i) * curr_pos_z_step ) ) );
}
fog_amount = 1 - fog_amount;
fog_amount_scale = saturate( (frag_dist - AerialParams.y) * AerialParams.z );
}
FogResult ret;
ret.paramsFog = float4 ( curr_col_fog, fog_amount_scale * pow( abs(fog_amount), final_exp_fog ) );
ret.paramsAerial = float4 ( curr_col_aerial, fog_amount_scale * pow( abs(fog_amount), final_exp_aerial ) );
من الواضح أن كمية الضباب تعتمد على الارتفاع (المكونات. z) ، وفي النهاية يتم رفع كمية الضباب إلى درجة الضباب / الغلاف الجوي. يتم أخذfinal_exp_fog و final_exp_aerial من المخزن المؤقت الثابت ؛ تسمح لك بالتحكم في كيفية تأثير ألوان الضباب والجو على العالم بارتفاع متزايد.تجاوز الضباب
التظليل الذي وجدته ليس لديه جزء رمز التجميع التالي: 109: mad_sat r1.xy, r0.wwww, cb12[189].xzxx, cb12[189].ywyy
110: add r5.xyz, -r3.xyzx, cb12[188].xyzx
111: mad r5.xyz, r1.xxxx, r5.xyzx, r3.xyzx
112: add r0.z, l(-1.000000), cb12[188].w
113: mad r0.z, r1.y, r0.z, l(1.000000)
114: mul_sat r5.w, r0.w, r0.z
115: lt r0.z, l(0.000000), cb12[192].x
116: if_nz r0.z
117: mad_sat r1.xy, r0.wwww, cb12[191].xzxx, cb12[191].ywyy
118: add r6.xyz, -r3.xyzx, cb12[190].xyzx
119: mad r3.xyz, r1.xxxx, r6.xyzx, r3.xyzx
120: add r0.z, l(-1.000000), cb12[190].w
121: mad r0.z, r1.y, r0.z, l(1.000000)
122: mul_sat r3.w, r0.w, r0.z
123: add r1.xyzw, -r5.xyzw, r3.xyzw
124: mad r5.xyzw, cb12[192].xxxx, r1.xyzw, r5.xyzw
125: endif
إذا حكمنا بما تمكنت من فهمه ، فهذا يشبه إعادة تعريف اللون وتأثير الضباب: فيمعظم الوقت ، يتم إجراء إعادة تعريف واحدة فقط (cb12_v192.x تساوي 0.0) ، ولكن في هذه الحالة بالذات تكون قيمتها 0.22 ، لذلك نقوم بالتخطي الثاني.
#ifdef OVERRIDE_FOG
float fog_influence = ret.paramsFog.w;
float override1ColorScale = cb12_v189.x;
float override1ColorBias = cb12_v189.y;
float3 override1Color = cb12_v188.rgb;
float override1InfluenceScale = cb12_v189.z;
float override1InfluenceBias = cb12_v189.w;
float override1Influence = cb12_v188.w;
float override1ColorAmount = saturate(fog_influence * override1ColorScale + override1ColorBias);
float override1InfluenceAmount = saturate(fog_influence * override1InfluenceScale + override1InfluenceBias);
float4 paramsFogOverride;
paramsFogOverride.rgb = lerp(curr_col_fog, override1Color, override1ColorAmount );
float param1 = lerp(1.0, override1Influence, override1InfluenceAmount);
paramsFogOverride.w = saturate(param1 * fog_influence );
const float extraFogOverride = cb12_v192.x;
[branch]
if (extraFogOverride > 0.0)
{
float override2ColorScale = cb12_v191.x;
float override2ColorBias = cb12_v191.y;
float3 override2Color = cb12_v190.rgb;
float override2InfluenceScale = cb12_v191.z;
float override2InfluenceBias = cb12_v191.w;
float override2Influence = cb12_v190.w;
float override2ColorAmount = saturate(fog_influence * override2ColorScale + override2ColorBias);
float override2InfluenceAmount = saturate(fog_influence * override2InfluenceScale + override2InfluenceBias);
float4 paramsFogOverride2;
paramsFogOverride2.rgb = lerp(curr_col_fog, override2Color, override2ColorAmount);
float ov_param1 = lerp(1.0, override2Influence, override2InfluenceAmount);
paramsFogOverride2.w = saturate(ov_param1 * fog_influence);
paramsFogOverride = lerp(paramsFogOverride, paramsFogOverride2, extraFogOverride);
}
ret.paramsFog = paramsFogOverride;
#endif
هذا هو السعر النهائي بدون إعادة تعريف الضباب (الصورة الأولى) ، مع إعادة تعريف واحدة (الصورة الثانية) وإعادة تعريف مزدوجة (الصورة الثالثة ، النتيجة النهائية):تنظيم الانسداد المحيط
التظليل الذي وجدته أيضًا لم يستخدم الانسداد المحيط على الإطلاق. دعونا نلقي نظرة على نسيج AO مرة أخرى والرمز الذي يهمنا: 13: ld_indexable(texture2d)(float,float,float,float) r0.x, r0.xyzw, t2.xyzw
14: max r0.x, r0.x, cb3[1].x
15: add r0.yzw, r1.xxyz, -cb12[0].xxyz
16: dp3 r1.x, r0.yzwy, r0.yzwy
17: sqrt r1.x, r1.x
18: add r1.y, r1.x, -cb3[0].x
19: add r1.zw, -cb3[0].xxxz, cb3[0].yyyw
20: div_sat r1.y, r1.y, r1.z
21: mad r1.y, r1.y, r1.w, cb3[0].z
22: add r0.x, r0.x, l(-1.000000)
23: mad r0.x, r1.y, r0.x, l(1.000000)
ربما هذا المشهد ليس أفضل مثال ، لأننا لا نرى التفاصيل في جزيرة بعيدة. ومع ذلك ، دعنا نلقي نظرة على المخزن المؤقت الثابت ، والذي يُستخدم لتعيين قيمة الغلق المحيط:نبدأ بتحميل AO من النسيج ، ثم تنفيذ التعليمات القصوى. في هذا المشهد ، cb3_v1.x مرتفع جدًا (0.96888) ، مما يجعل AO ضعيفًا جدًا.يحسب الجزء التالي من الرمز المسافة بين مواضع الكاميرا والبكسل في العالم.أعتقد أن الشفرة تتحدث أحيانًا عن نفسها ، لذلك دعونا نلقي نظرة على HLSL ، الذي يقوم بمعظم هذا الإعداد: float AdjustAmbientOcclusion(in float inputAO, in float worldToCameraDistance)
{
const float aoDistanceStart = cb3_v0.x;
const float aoDistanceEnd = cb3_v0.y;
const float aoStrengthStart = cb3_v0.z;
const float aoStrengthEnd = cb3_v0.w;
float aoDistanceIntensity = linstep( aoDistanceStart, aoDistanceEnd, worldToCameraDistance );
float aoStrength = lerp(aoStrengthStart, aoStrengthEnd, aoDistanceIntensity);
float adjustedAO = lerp(1.0, inputAO, aoStrength);
return adjustedAO;
}
يتم استخدام المسافة المحسوبة بين الكاميرا والعالم لوظيفة لينستيب. نحن نعلم بالفعل هذه الوظيفة ، ظهرت في تظليل سحابة cirrus.كما ترى ، في المخزن المؤقت الثابت لدينا قيم مسافة البدء / النهاية AO. يؤثر ناتج linstep على قوة AO (وكذلك من cbuffer) ، وتؤثر القوة على إخراج AO.مثال موجز: البكسل بعيد ، على سبيل المثال ، المسافة 500.ترجع linstep 1.0؛aoStrength يساوي aoStrengthEnd ؛ينتج عن هذا إرجاع AO ، وهو ما يقرب من 77٪ (القوة النهائية) من قيمة الإدخال.تعرض AO الوارد لهذه الوظيفة مسبقًا إلى الحد الأقصى للتشغيل.ضع كل شيء معا
بعد تلقي لون وتأثير لون الضباب ولون الغلاف الجوي ، يمكنك في النهاية الجمع بينهما.نبدأ بتخفيف التأثير مع AO الناتج: ...
FogResult fog = CalculateFog( worldPos, CameraPosition, fogStart, ao, false );
fog.paramsFog.w *= ao;
fog.paramsAerial.w *= ao;
outColor = ApplyFog(fog, colorHDR);
كل السحر يحدث في وظيفة ApplyFog : float3 ApplyFog(FogResult fog, float3 color)
{
const float3 LuminanceFactors = float3(0.333f, 0.555f, 0.222f);
float3 aerialColor = dot(LuminanceFactors, color) * fog.paramsAerial.xyz;
color = lerp(color, aerialColor, fog.paramsAerial.w);
color = lerp(color, fog.paramsFog.xyz, fog.paramsFog.w);
return color.xyz;
}
أولاً ، نحسب لمعان البكسل:ثم نضربها بلون الغلاف الجوي:ثم نقوم بدمج لون HDR مع لون الغلاف الجوي:الخطوة الأخيرة هي دمج النتيجة المتوسطة مع لون الضباب:هذا كل شئ!بعض لقطات شاشة التصحيح
التأثير الجويلون الغلاف الجويتأثير الضبابلون الضبابمشهد منتهي بدون ضبابمشهد جاهز مع الضباب فقطالمشهد النهائي هو الضباب الرئيسيمشهد جاهز مرة أخرى مع كل الضباب لسهولة المقارنةمجموع
أعتقد أنه يمكنك فهم الكثير مما سبق ، إذا نظرت إلى التظليل ، فهو هنا .أستطيع أن أقول بسرور أن هذا التظليل هو نفس الشيء الأصلي تمامًا - إنه يجعلني سعيدًا جدًا.بشكل عام ، تعتمد النتيجة النهائية بشكل كبير على القيم التي يتم تمريرها إلى التظليل. هذا ليس حلاً "سحريًا" يعطي ألوانًا مثالية في الإخراج ؛ فهو يتطلب الكثير من التكرارات والفنانين لجعل النتيجة النهائية تبدو لائقة. أعتقد أنها يمكن أن تكون عملية طويلة ، ولكن بعد إكمالها ، ستكون النتيجة مقنعة للغاية ، تمامًا مثل مشهد غروب الشمس هذا.تستخدم Witcher 3 Sky Shader أيضًا حسابات الضباب لإنشاء انتقال سلس للألوان بالقرب من الأفق. ومع ذلك ، يتم تمرير مجموعة مختلفة من معاملات الكثافة إلى تظليل السماء.دعني أذكرك - لم يتم إنشاء / تحليل معظم هذا التظليل من قبلي. يجب إرسال جميع الإقرارات إلى CD PROJEKT RED. ادعمهم ، يقومون بعمل ممتاز.الجزء 3. نجوم الرماية
في The Witcher 3 هناك تفاصيل صغيرة ولكنها غريبة - إطلاق النار على النجوم. ومن المثير للاهتمام أنها لا تبدو في الدم والنبيذ DLC.في الفيديو يمكنك أن ترى كيف تبدو:دعونا نرى كيف تمكنا من الحصول على هذا التأثير.كما ترون ، فإن جسم نجم الرماية أكثر إشراقًا من الذيل. هذه خاصية مهمة سنستخدمها لاحقًا.أجندتنا مألوفة تمامًا: أولاً سأصف الخصائص العامة ، ثم سأتحدث عن الموضوعات المتعلقة بالهندسة ، وفي النهاية سننتقل إلى تظليل البكسل ، حيث تحدث الأشياء الأكثر إثارة للاهتمام.1. نظرة عامة
صف بإيجاز ما يحدث.يتم رسم نجوم الرماية في ممر استباقي ، مباشرة بعد قبة السماء والسماء والقمر:DrawIndexed (720) - قبة السماء ،DrawIndexed (2160) - المجال للسماء / القمر ،DrawIndexed (36) - غير ذي صلة ، يبدو وكأنه موازٍ لانسداد الشمس (؟)DrawIndexed (12) - نجم الرمايةDrawIndexInstanced (1116 ، 1) - السحب الرقيقةمثل السحب الرقيقة ، يتم رسم كل نجم إطلاق نار مرتين على التوالي.قبل إجراء المكالمة الأولىنتيجة مكالمة السحب الأولنتيجة استدعاء السحب الثانيبالإضافة إلى ذلك ، كما هو الحال في العديد من عناصر التمرير الاستباقي لهذه اللعبة ، يتم استخدام حالة الخلط التالية:2. الهندسة
فيما يتعلق بالهندسة ، فإن أول شيء يجب ذكره هو أن كل نجم رماية يتم تمثيله برباع رفيع مع أغطية: 4 رؤوس ، 6 مؤشرات. هذا هو أبسط رباعية ممكنة.التقريب رباعي لنجم الرماية.
الأقرب هو الرباعي التقريبي لنجم الرماية. يمكنك أن ترى عرض الإطار السلكي لخط يشير إلى مثلثين.انتظر دقيقة ، ولكن يوجد DrawIndexed (12) ! هل هذا يعني أننا نرسم نجمتين للرماية في نفس الوقت؟نعم.في هذا الإطار ، يقع أحد نجوم الرماية خارج هرم الرؤية تمامًا.دعونا نلقي نظرة على رمز المجمع الخاص بظل تظليل الرأس: vs_5_0
dcl_globalFlags refactoringAllowed
dcl_constantbuffer cb1[9], immediateIndexed
dcl_constantbuffer cb2[3], immediateIndexed
dcl_constantbuffer cb12[193], immediateIndexed
dcl_input v0.xyz
dcl_input v1.xyzw
dcl_input v2.xy
dcl_input v3.xy
dcl_input v4.xy
dcl_input v5.xyz
dcl_input v6.x
dcl_input v7.x
dcl_output o0.xyzw
dcl_output o1.xyzw
dcl_output o2.xy
dcl_output o3.xyzw
dcl_output_siv o4.xyzw, position
dcl_temps 5
0: mov r0.xyz, v0.xyzx
1: mov r0.w, l(1.000000)
2: dp4 r1.x, r0.xyzw, cb2[0].xyzw
3: dp4 r1.y, r0.xyzw, cb2[1].xyzw
4: dp4 r1.z, r0.xyzw, cb2[2].xyzw
5: add r0.x, v2.x, v2.y
6: add r0.y, -v2.y, v2.x
7: add r2.xyz, -r1.zxyz, cb1[8].zxyz
8: dp3 r0.z, r2.xyzx, r2.xyzx
9: rsq r0.z, r0.z
10: mul r2.xyz, r0.zzzz, r2.xyzx
11: dp3 r0.z, v5.xyzx, v5.xyzx
12: rsq r0.z, r0.z
13: mul r3.xyz, r0.zzzz, v5.xyzx
14: mul r4.xyz, r2.xyzx, r3.yzxy
15: mad r2.xyz, r2.zxyz, r3.zxyz, -r4.xyzx
16: dp3 r0.z, r2.xyzx, r2.xyzx
17: rsq r0.z, r0.z
18: mul r2.xyz, r0.zzzz, r2.xyzx
19: mad r0.z, v7.x, v6.x, l(1.000000)
20: mul r3.xyz, r0.zzzz, r3.xyzx
21: mul r3.xyz, r3.xyzx, v3.xxxx
22: mul r2.xyz, r2.xyzx, v3.yyyy
23: mad r0.xzw, r3.xxyz, r0.xxxx, r1.xxyz
24: mad r0.xyz, r2.xyzx, r0.yyyy, r0.xzwx
25: mov r0.w, l(1.000000)
26: dp4 o4.x, r0.xyzw, cb1[0].xyzw
27: dp4 o4.y, r0.xyzw, cb1[1].xyzw
28: dp4 o4.z, r0.xyzw, cb1[2].xyzw
29: dp4 o4.w, r0.xyzw, cb1[3].xyzw
30: add r0.xyz, r0.xyzx, -cb12[0].xyzx
31: dp3 r0.w, r0.xyzx, r0.xyzx
32: sqrt r0.w, r0.w
33: div r0.xyz, r0.xyzx, r0.wwww
34: add r0.w, r0.w, -cb12[22].z
35: max r0.w, r0.w, l(0)
36: min r0.w, r0.w, cb12[42].z
37: dp3 r0.x, cb12[38].xyzx, r0.xyzx
38: mul r0.y, abs(r0.x), abs(r0.x)
39: mad_sat r1.x, r0.w, l(0.002000), l(-0.300000)
40: mul r0.y, r0.y, r1.x
41: lt r1.x, l(0), r0.x
42: movc r1.yzw, r1.xxxx, cb12[39].xxyz, cb12[41].xxyz
43: add r1.yzw, r1.yyzw, -cb12[40].xxyz
44: mad r1.yzw, r0.yyyy, r1.yyzw, cb12[40].xxyz
45: movc r2.xyz, r1.xxxx, cb12[45].xyzx, cb12[47].xyzx
46: add r2.xyz, r2.xyzx, -cb12[46].xyzx
47: mad o0.xyz, r0.yyyy, r2.xyzx, cb12[46].xyzx
48: ge r0.y, r0.w, cb12[48].y
49: if_nz r0.y
50: mad r0.y, r0.z, cb12[22].z, cb12[0].z
51: mul r0.z, r0.w, r0.z
52: mul r0.z, r0.z, l(0.062500)
53: mul r1.x, r0.w, cb12[43].x
54: mul r1.x, r1.x, l(0.062500)
55: add r0.x, r0.x, cb12[42].x
56: add r2.x, cb12[42].x, l(1.000000)
57: div_sat r0.x, r0.x, r2.x
58: add r2.x, -cb12[43].z, cb12[43].y
59: mad r0.x, r0.x, r2.x, cb12[43].z
60: add r0.y, r0.y, cb12[42].y
61: mul r2.x, r0.x, r0.y
62: mul r0.z, r0.x, r0.z
63: mad r3.xyzw, r0.zzzz, l(16.000000, 15.000000, 14.000000, 13.000000), r2.xxxx
64: max r3.xyzw, r3.xyzw, l(0, 0, 0, 0)
65: add r3.xyzw, r3.xyzw, l(1.000000, 1.000000, 1.000000, 1.000000)
66: div_sat r3.xyzw, r1.xxxx, r3.xyzw
67: add r3.xyzw, -r3.xyzw, l(1.000000, 1.000000, 1.000000, 1.000000)
68: mul r2.y, r3.y, r3.x
69: mul r2.y, r3.z, r2.y
70: mul r2.y, r3.w, r2.y
71: mad r3.xyzw, r0.zzzz, l(12.000000, 11.000000, 10.000000, 9.000000), r2.xxxx
72: max r3.xyzw, r3.xyzw, l(0, 0, 0, 0)
73: add r3.xyzw, r3.xyzw, l(1.000000, 1.000000, 1.000000, 1.000000)
74: div_sat r3.xyzw, r1.xxxx, r3.xyzw
75: add r3.xyzw, -r3.xyzw, l(1.000000, 1.000000, 1.000000, 1.000000)
76: mul r2.y, r2.y, r3.x
77: mul r2.y, r3.y, r2.y
78: mul r2.y, r3.z, r2.y
79: mul r2.y, r3.w, r2.y
80: mad r3.xyzw, r0.zzzz, l(8.000000, 7.000000, 6.000000, 5.000000), r2.xxxx
81: max r3.xyzw, r3.xyzw, l(0, 0, 0, 0)
82: add r3.xyzw, r3.xyzw, l(1.000000, 1.000000, 1.000000, 1.000000)
83: div_sat r3.xyzw, r1.xxxx, r3.xyzw
84: add r3.xyzw, -r3.xyzw, l(1.000000, 1.000000, 1.000000, 1.000000)
85: mul r2.y, r2.y, r3.x
86: mul r2.y, r3.y, r2.y
87: mul r2.y, r3.z, r2.y
88: mul r2.y, r3.w, r2.y
89: mad r2.zw, r0.zzzz, l(0.000000, 0.000000, 4.000000, 3.000000), r2.xxxx
90: max r2.zw, r2.zzzw, l(0, 0, 0, 0)
91: add r2.zw, r2.zzzw, l(0.000000, 0.000000, 1.000000, 1.000000)
92: div_sat r2.zw, r1.xxxx, r2.zzzw
93: add r2.zw, -r2.zzzw, l(0.000000, 0.000000, 1.000000, 1.000000)
94: mul r2.y, r2.z, r2.y
95: mul r2.y, r2.w, r2.y
96: mad r2.x, r0.z, l(2.000000), r2.x
97: max r2.x, r2.x, l(0)
98: add r2.x, r2.x, l(1.000000)
99: div_sat r2.x, r1.x, r2.x
100: add r2.x, -r2.x, l(1.000000)
101: mul r2.x, r2.x, r2.y
102: mad r0.x, r0.y, r0.x, r0.z
103: max r0.x, r0.x, l(0)
104: add r0.x, r0.x, l(1.000000)
105: div_sat r0.x, r1.x, r0.x
106: add r0.x, -r0.x, l(1.000000)
107: mad r0.x, -r2.x, r0.x, l(1.000000)
108: add r0.y, r0.w, -cb12[48].y
109: mul_sat r0.y, r0.y, cb12[48].z
110: else
111: mov r0.xy, l(1.000000, 0.000000, 0.000000, 0.000000)
112: endif
113: log r0.x, r0.x
114: mul r0.z, r0.x, cb12[42].w
115: exp r0.z, r0.z
116: mul r0.z, r0.z, r0.y
117: mul r0.x, r0.x, cb12[48].x
118: exp r0.x, r0.x
119: mul o0.w, r0.x, r0.y
120: mad_sat r0.xy, r0.zzzz, cb12[189].xzxx, cb12[189].ywyy
121: add r2.xyz, -r1.yzwy, cb12[188].xyzx
122: mad r2.xyz, r0.xxxx, r2.xyzx, r1.yzwy
123: add r0.x, cb12[188].w, l(-1.000000)
124: mad r0.x, r0.y, r0.x, l(1.000000)
125: mul_sat r2.w, r0.x, r0.z
126: lt r0.x, l(0), cb12[192].x
127: if_nz r0.x
128: mad_sat r0.xy, r0.zzzz, cb12[191].xzxx, cb12[191].ywyy
129: add r3.xyz, -r1.yzwy, cb12[190].xyzx
130: mad r1.xyz, r0.xxxx, r3.xyzx, r1.yzwy
131: add r0.x, cb12[190].w, l(-1.000000)
132: mad r0.x, r0.y, r0.x, l(1.000000)
133: mul_sat r1.w, r0.x, r0.z
134: add r0.xyzw, -r2.xyzw, r1.xyzw
135: mad o1.xyzw, cb12[192].xxxx, r0.xyzw, r2.xyzw
136: else
137: mov o1.xyzw, r2.xyzw
138: endif
139: mov o3.xyzw, v1.xyzw
140: mov o2.xy, v4.yxyy
141: ret
هنا ، يمكن لحساب الضباب أن يجذب الانتباه على الفور (السطور 30-138). حساب قمة الضباب منطقي لأسباب الأداء. بالإضافة إلى ذلك ، نحن لسنا بحاجة إلى مثل هذه الدقة من الضباب - عادة ما تطير النيازك فوق رأس Geralt ولا تصل إلى الأفق.يتم تخزين معلمات الغلاف الجوي (rgb = color ، a = تأثير) في o0.xyzw ، ومعلمات الضباب في o1.xyzw.o2.xy (السطر 140) عبارة عن تكسورد فقط.o3.xyzw (السطر 139) غير ذي صلة.الآن دعنا نقول بضع كلمات حول حساب مركز في العالم. تظليل Vertex يؤدي لوحة الإعلانات . أولاً وقبل كل شيء ، تأتي البيانات الواردة للوحات الإعلانات من ذاكرة التخزين المؤقت للرأس - دعنا نلقي نظرة عليها.البيانات الأولى هي الوظيفة:كما ذكرنا أعلاه ، لدينا هنا 2 رؤوس رباعية: 8 رؤوس ، 12 مؤشرًا.ولكن لماذا هو الموقف نفسه لكل رباعية؟ بسيط للغاية - هذا هو موقف مركز الرباعي.علاوة على ذلك ، يكون لكل قمة إزاحة من المركز إلى حافة الرباعي:هذا يعني أن كل نجم إطلاق نار له حجم (400 ، 3) وحدة في الفضاء العالمي. (على المستوى XY ، في Witcher 3 ، يتم توجيه المحور Z لأعلى)العنصر الأخير الذي يحتويه كل قمة هو ناقل اتجاه الوحدة في الفضاء العالمي الذي يتحكم في حركة نجم الرماية:نظرًا لأن البيانات تأتي من وحدة المعالجة المركزية ، فمن الصعب فهم كيفية حسابها.الآن دعنا ننتقل إلى رمز لوحة الإعلانات. الفكرة بسيطة للغاية - أولاً تحصل على ناقل وحدة من مركز الرباعي إلى الكاميرا: 7: add r2.xyz, -r1.zxyz, cb1[8].zxyz
8: dp3 r0.z, r2.xyzx, r2.xyzx
9: rsq r0.z, r0.z
10: mul r2.xyz, r0.zzzz, r2.xyzx
ثم نحصل على متجه ظلام واحد يتحكم في حركة نجم الرماية.بالنظر إلى أن هذا المتجه تم تطبيعه بالفعل على جانب وحدة المعالجة المركزية ، فإن هذا التطبيع غير ضروري. 11: dp3 r0.z, v5.xyzx, v5.xyzx
12: rsq r0.z, r0.z
13: mul r3.xyz, r0.zzzz, v5.xyzx
إذا كان هناك متجهان ، فسيتم استخدام منتج متجه لتحديد متجه المماس الثنائي المتعامد مع كلا المتجهين القادمين. 14: mul r4.xyz, r2.xyzx, r3.yzxy
15: mad r2.xyz, r2.zxyz, r3.zxyz, -r4.xyzx
16: dp3 r0.z, r2.xyzx, r2.xyzx
17: rsq r0.z, r0.z
18: mul r2.xyz, r0.zzzz, r2.xyzx
الآن لدينا تطبيع المماس المماس (r3.xyz) و bitangent (r2.xyz).دعنا نقدم Xsize و Ysize المطابقين للعنصر الوارد TEXCOORD1 ، على سبيل المثال (-200 ، 1.50).يتم الحساب النهائي للموضع في الفضاء العالمي على النحو التالي: 19: mad r0.z, v7.x, v6.x, l(1.000000)
20: mul r3.xyz, r0.zzzz, r3.xyzx
21: mul r3.xyz, r3.xyzx, v3.xxxx
22: mul r2.xyz, r2.xyzx, v3.yyyy
23: mad r0.xzw, r3.xxyz, r0.xxxx, r1.xxyz
24: mad r0.xyz, r2.xyzx, r0.yyyy, r0.xzwx
25: mov r0.w, l(1.000000)
بالنظر إلى أن r0.x و r0.y و r0.z تساوي 1.0 ، يتم تبسيط الحساب النهائي:worldSpacePosition = quadCenter + tangent * Xsize + bitangent * Ysize
الجزء الأخير هو ضرب بسيط لموضع في الفضاء العالمي بواسطة مصفوفة عرض الإسقاط للحصول على SV_Position: 26: dp4 o4.x, r0.xyzw, cb1[0].xyzw
27: dp4 o4.y, r0.xyzw, cb1[1].xyzw
28: dp4 o4.z, r0.xyzw, cb1[2].xyzw
29: dp4 o4.w, r0.xyzw, cb1[3].xyzw
3. بيكسل شادر
كما هو موضح في قسم النظرة العامة ، يتم استخدام حالة المزج التالية: حيث SrcColor و SrcAlpha هما المكونان .rgb و .a من تظليل البكسل ، على التوالي ، و DestColor هو لون .rgb حاليًا في rendertarget. المؤشر الرئيسي الذي يتحكم في الشفافية هو SrcAlpha . يحسبها العديد من أجهزة تظليل الألعاب الاستباقية على أنها عتامة وتطبقها في النهاية على النحو التالي: لم يكن ظل النجوم الساقط استثناءً. باتباع هذا النمط ، نعتبر ثلاث حالات تكون فيها العتامة 1.0 و 0.1 و 0.0.FinalColor = SrcColor * One + DestColor * (1.0 - SrcAlpha) =
FinalColor = SrcColor + DestColor * (1.0 - SrcAlpha)
return float4( color * opacity, opacity )
a) opacity = 1.0
FinalColor = color * opacity + DestColor * (1.0 - opacity) =
FinalColor = color = SrcColor
b) opacity = 0.1
FinalColor = color * opacity + DestColor * (1.0 - opacity) =
FinalColor = 0.1 * color + 0.9 * DestColor
c) opacity = 0.0
FinalColor = color * opacity + DestColor * (1.0 - opacity) =
FinalColor = DestColor
الفكرة الأساسية لهذا التظليل هي تصميم واستخدام عتامة وظيفة العتامة (x) ، التي تتحكم في عتامة البكسل على طول نجم التصوير. الشرط الرئيسي هو أن العتامة يجب أن تصل إلى القيم القصوى في نهاية النجم ("جسمه") وتتلاشى بسلاسة إلى 0.0 (إلى "ذيله").عندما نبدأ في فهم رمز المجمّع لتظليل البكسل ، يصبح هذا واضحًا: ps_5_0
dcl_globalFlags refactoringAllowed
dcl_constantbuffer cb0[10], immediateIndexed
dcl_constantbuffer cb2[3], immediateIndexed
dcl_constantbuffer cb4[2], immediateIndexed
dcl_input_ps linear v0.xyzw
dcl_input_ps linear v1.xyzw
dcl_input_ps linear v2.y
dcl_input_ps linear v3.w
dcl_output o0.xyzw
dcl_temps 4
0: mov_sat r0.x, v2.y
1: ge r0.y, r0.x, l(0.052579)
2: ge r0.z, l(0.965679), r0.x
3: and r0.y, r0.z, r0.y
4: if_nz r0.y
5: ge r0.y, l(0.878136), r0.x
6: add r0.z, r0.x, l(-0.052579)
7: mul r1.w, r0.z, l(1.211303)
8: mov_sat r0.z, r1.w
9: mad r0.w, r0.z, l(-2.000000), l(3.000000)
10: mul r0.z, r0.z, r0.z
11: mul r0.z, r0.z, r0.w
12: mul r2.x, r0.z, l(0.084642)
13: mov r1.yz, l(0.000000, 0.000000, 0.084642, 0.000000)
14: movc r2.yzw, r0.yyyy, r1.yyzw, l(0.000000, 0.000000, 0.000000, 0.500000)
15: not r0.z, r0.y
16: if_z r0.y
17: ge r0.y, l(0.924339), r0.x
18: add r0.w, r0.x, l(-0.878136)
19: mul r1.w, r0.w, l(21.643608)
20: mov_sat r0.w, r1.w
21: mad r3.x, r0.w, l(-2.000000), l(3.000000)
22: mul r0.w, r0.w, r0.w
23: mul r0.w, r0.w, r3.x
24: mad r1.x, r0.w, l(0.889658), l(0.084642)
25: mov r1.yz, l(0.000000, 0.084642, 0.974300, 0.000000)
26: movc r2.xyzw, r0.yyyy, r1.xyzw, r2.xyzw
27: else
28: mov r2.y, l(0)
29: mov r0.y, l(-1)
30: endif
31: not r0.w, r0.y
32: and r0.z, r0.w, r0.z
33: if_nz r0.z
34: ge r0.y, r0.x, l(0.924339)
35: add r0.x, r0.x, l(-0.924339)
36: mul r1.w, r0.x, l(24.189651)
37: mov_sat r0.x, r1.w
38: mad r0.z, r0.x, l(-2.000000), l(3.000000)
39: mul r0.x, r0.x, r0.x
40: mul r0.x, r0.x, r0.z
41: mad r1.x, r0.x, l(-0.974300), l(0.974300)
42: mov r1.yz, l(0.000000, 0.974300, 0.000000, 0.000000)
43: movc r2.xyzw, r0.yyyy, r1.xyzw, r2.xyzw
44: endif
45: else
46: mov r2.yzw, l(0.000000, 0.000000, 0.000000, 0.500000)
47: mov r0.y, l(0)
48: endif
49: mov_sat r2.w, r2.w
50: mad r0.x, r2.w, l(-2.000000), l(3.000000)
51: mul r0.z, r2.w, r2.w
52: mul r0.x, r0.z, r0.x
53: add r0.z, -r2.y, r2.z
54: mad r0.x, r0.x, r0.z, r2.y
55: movc r0.x, r0.y, r2.x, r0.x
56: mad r0.y, cb4[1].x, -cb0[9].w, l(1.000000)
57: mul_sat r0.y, r0.y, v3.w
58: mul r0.x, r0.y, r0.x
59: mul r0.yzw, cb2[2].xxyz, cb4[0].xxxx
60: mul r0.x, r0.x, cb2[2].w
61: dp3 r1.x, l(0.333000, 0.555000, 0.222000, 0.000000), r0.yzwy
62: mad r1.xyz, r1.xxxx, v0.xyzx, -r0.yzwy
63: mad r0.yzw, v0.wwww, r1.xxyz, r0.yyzw
64: add r1.xyz, -r0.yzwy, v1.xyzx
65: mad r0.yzw, v1.wwww, r1.xxyz, r0.yyzw
66: mul o0.xyz, r0.xxxx, r0.yzwy
67: mov o0.w, r0.x
68: ret
بشكل عام ، التظليل أكثر تعقيدًا قليلاً وكان من الصعب بالنسبة لي معرفة ما يجري فيه. على سبيل المثال ، من أين جاءت جميع القيم مثل 1.211303 و 21.643608 و 24.189651؟إذا كنا نتحدث عن دالة التعتيم ، فإننا بحاجة إلى قيمة إدخال واحدة. هذا أمر بسيط للغاية - texcoord في النطاق من [0،1] (السطر 0) سيكون مفيدًا هنا ، حتى نتمكن من تطبيق الوظيفة على طول النيزك بالكامل.تحتوي وظيفة التعتيم على ثلاثة أجزاء / فواصل محددة بواسطة أربع نقاط تحكم:
const float controlPoint0 = 0.052579;
const float controlPoint1 = 0.878136;
const float controlPoint2 = 0.924339;
const float controlPoint3 = 0.965679;
ليس لدي أي فكرة عن كيفية اختيارهم / حسابهم.كما يمكننا أن نرى من كود المجمع ، الشرط الأول هو التحقق فقط ما إذا كانت قيمة الإدخال في النطاق [controlPoint0 - controlPoint3]. إذا لم يكن كذلك ، فإن التعتيم هو 0.0 فقط.
float y = saturate(Input.Texcoords.y);
float opacity = 0.0;
[branch]
if (y >= controlPoint0 && y <= controlPoint3)
{
...
يعد فك شفرة رمز المجمع أدناه ضروريًا إذا أردنا أن نفهم كيف تعمل وظيفة التعتيم: 6: add r0.z, r0.x, l(-0.052579)
7: mul r1.w, r0.z, l(1.211303)
8: mov_sat r0.z, r1.w
9: mad r0.w, r0.z, l(-2.000000), l(3.000000)
10: mul r0.z, r0.z, r0.z
11: mul r0.z, r0.z, r0.w
12: mul r2.x, r0.z, l(0.084642)
يحتوي الخط 9 على المعاملين "-2.0" و "3.0" ، مما يلمح إلى استخدام وظيفة السلس . نعم ، هذا تخمين جيد.وظيفة HLSL السلس مع النموذج الأولي: خطوة السلس (min ، max ، x) تحدد دائمًا x إلى [ min-max ]. من وجهة نظر المجمّع ، يطرح هذا الحد الأدنى من قيمة الإدخال (أي من r0.z في السطر 9) ، ولكن لا يوجد شيء مثل ذلك في التعليمات البرمجية. بالنسبة إلى الحد الأقصى ، فهذا يعني ضمناً مضاعفة قيمة الإدخال ، ولكن لا يوجد شيء مثل 'mul_sat' في التعليمات البرمجية. بدلاً من ذلك ، هناك "mov_sat". هذا يخبرنا أن دقيقة و الحد الأقصى لل وظائف smoothstep هي 0 و 1.نحن نعرف الآن أن سيجب أن يكون في الفاصل الزمني [0 ، 1]. كما ذكر أعلاه ، هناك ثلاثة أجزاء في دالة التعتيم. يشير هذا بوضوح إلى أن الشفرة تبحث عن مكاننا في الفاصل الزمني [sectionStart-sectionEnd].الجواب هو وظيفة Linstep! float linstep(float min, float max, float v)
{
return ( (v-min) / (max-min) );
}
على سبيل المثال ، لنأخذ الجزء الأول: [0.052579 - 0.878136]. يكون الطرح في السطر 6. إذا استبدلنا القسمة بالضرب -> 1.0 / (0.878136 - 0.052579) = 1.0 / 0.825557 = ~ 1.211303.نتيجة السلاسة في النطاق [0 ، 1]. الضرب على الخط 12 هو وزن القطعة. كل قطعة لها وزنها الخاص ، مما يسمح لك بالتحكم في العتامة القصوى لهذه القطعة المعينة.هذا يعني أنه بالنسبة للمقطع الأول [0.052579 - 0.878136] ، تكون التعتيم في النطاق [0 - 0.084642].يمكن كتابة دالة HLSL التي تحسب العتامة لمقطع عشوائي على النحو التالي: float getOpacityFunctionValue(float x, float cpLeft, float cpRight, float weight)
{
float val = smoothstep( 0, 1, linstep(cpLeft, cpRight, x) );
return val * weight;
}
لذا ، فإن الهدف كله هو ببساطة استدعاء هذه الوظيفة للمقطع المقابل.ألق نظرة على الأوزان: const float weight0 = 0.084642;
const float weight1 = 0.889658;
const float weight2 = 0.974300;
وفقًا لكود المجمع ، يتم حساب دالة التعتيم (x) على النحو التالي: float opacity = 0.0;
[branch]
if (y >= controlPoint0 && y <= controlPoint3)
{
float v = getOpacityFunctionValue(y, controlPoint0, controlPoint1, weight0);
opacity = v;
[branch]
if ( y >= controlPoint1 )
{
float v = getOpacityFunctionValue(y, controlPoint1, controlPoint2, weight1);
opacity = weight0 + v;
[branch]
if (y >= controlPoint2)
{
float v = getOpacityFunctionValue(y, controlPoint2, controlPoint3, weight2);
opacity = weight2 - v;
}
}
}
فيما يلي رسم بياني لوظيفة التعتيم. يمكنك بسهولة رؤية زيادة حادة في التعتيم ، مما يشير إلى بداية جسم نجم الرماية:دالة عتامة الرسم البياني.
القناة الحمراء - قيمة التعتيم - القناة
الخضراء - نقاط التحكم - القناة
الزرقاء - الأوزان.بعد حساب التعتيم ، كل شيء آخر هو مجرد اللمسات الأخيرة. ثم هناك مضاعفات إضافية: عتامة النجوم ولون النجم الرامي وتأثير الضباب. كما هو معتاد في تظليل TW3 ، يمكنك أيضًا العثور على مضاعفات زائدة عن طريق 1.0 هنا:
float starsOpacity = 1.0 - cb0_v9.w * cb4_v1.x;
opacity *= starsOpacity;
float3 color = cb2_v2.rgb * cb4_v0.x;
opacity *= cb2_v2.w;
FogResult fr = { Input.FogParams, Input.AerialParams };
color = ApplyFog(fr, color);
return float4( color*opacity, opacity);
}
4. ملخص
تكمن الصعوبة الرئيسية في الجزء مع وظيفة التعتيم. بعد فك تشفيرها ، يصبح كل شيء آخر سهل الفهم.قلت أعلاه أن تظليل البكسل معقد قليلاً قليلاً. في الواقع ، نحن نهتم فقط بقيمة قيمة التعتيم (x) ، التي يتم تخزينها في r2.x (بدءًا من السطر 49). ومع ذلك ، فإن وظيفة التعتيم في كود التجميع تنشئ ثلاثة متغيرات إضافية: minRange (r2.y) ، maxRange (r2.z) والقيمة (r2.w). كلها معلمات تستخدم لحساب التعتيم عند عدم استخدام التعتيم (x) :lerp( minRange, maxRange, smoothstep(0, 1, value) );
في الواقع ، يتم الحصول على قيمة التعتيم النهائية في الفرع الشرطي في السطر 55 - إذا كانت قيمة الإدخال سفي النطاق [controlPoint0 - controlPoint3] ، هذا يعني أنه يتم استخدام دالة التعتيم ، لذلك يتم تحديد r2.x. خلاف ذلك ، عندما تكون x خارج الفاصل الزمني ، يتم حساب التعتيم من r0.x ، أي وفقًا للمعادلة أعلاه.لقد قمت بتصحيح عدد قليل من وحدات البكسل خارج الفاصل الزمني [controlPoint0 - controlPoint3] ، وتبين دائمًا أن التعتيم النهائي هو صفر.هذا كل شيء لهذا اليوم. و كالمعتاد، شكرا على القراءه.