The Witcher 3 का प्रतिपादन कैसे लागू किया गया है: बिजली, चुड़ैल की स्वभाव और अन्य प्रभाव

छवि

भाग 1. जिपर्स


इस भाग में, हम Witcher 3: Wild Hunt में बिजली प्रदान करने की प्रक्रिया को देखेंगे।

बारिश के पर्दे के प्रभाव की तुलना में थोड़ी देर बाद बिजली प्रदान की जाती है, लेकिन फिर भी प्रत्यक्ष प्रतिपादन पास में होता है। इस वीडियो में बिजली देखी जा सकती है:


वे बहुत तेज़ी से गायब हो जाते हैं, इसलिए 0.25 की गति से वीडियो देखना बेहतर है।

आप देख सकते हैं कि ये स्थिर चित्र नहीं हैं; समय के साथ, उनकी चमक थोड़ी बदल जाती है।

बारीकियों के प्रतिपादन के संदर्भ में, दूरी में एक वर्षा पर्दा खींचने के साथ बहुत सारी समानताएं हैं, उदाहरण के लिए, समान सम्मिश्रण राज्य (योज्य सम्मिश्रण) और गहराई (जाँच सक्षम है, गहराई रिकॉर्डिंग नहीं की गई है)।


बिजली के बिना दृश्य


बिजली का दृश्य

बिजली की ज्यामिति के संदर्भ में, द विचर 3 एक पेड़ जैसा जाल है। बिजली का यह उदाहरण निम्नलिखित जाल द्वारा दर्शाया गया है:


इसमें यूवी निर्देशांक और सामान्य वैक्टर हैं। यह सब वर्टिकल शेडर स्टेज पर काम आता है।

वर्टेक्स shader


आइए एक नज़र डालते हैं असेंबल्ड वर्कट शेडर कोड पर:

vs_5_0 dcl_globalFlags refactoringAllowed dcl_constantbuffer cb1[9], immediateIndexed dcl_constantbuffer cb2[6], immediateIndexed dcl_input v0.xyz dcl_input v1.xy dcl_input v2.xyz dcl_input v4.xyzw dcl_input v5.xyzw dcl_input v6.xyzw dcl_input v7.xyzw dcl_output o0.xy dcl_output o1.xyzw dcl_output_siv o2.xyzw, position dcl_temps 3 0: mov o0.xy, v1.xyxx 1: mov o1.xyzw, v7.xyzw 2: mul r0.xyzw, v5.xyzw, cb1[0].yyyy 3: mad r0.xyzw, v4.xyzw, cb1[0].xxxx, r0.xyzw 4: mad r0.xyzw, v6.xyzw, cb1[0].zzzz, r0.xyzw 5: mad r0.xyzw, cb1[0].wwww, l(0.000000, 0.000000, 0.000000, 1.000000), r0.xyzw 6: mov r1.w, l(1.000000) 7: mad r1.xyz, v0.xyzx, cb2[4].xyzx, cb2[5].xyzx 8: dp4 r2.x, r1.xyzw, v4.xyzw 9: dp4 r2.y, r1.xyzw, v5.xyzw 10: dp4 r2.z, r1.xyzw, v6.xyzw 11: add r2.xyz, r2.xyzx, -cb1[8].xyzx 12: dp3 r1.w, r2.xyzx, r2.xyzx 13: rsq r1.w, r1.w 14: div r1.w, l(1.000000, 1.000000, 1.000000, 1.000000), r1.w 15: mul r1.w, r1.w, l(0.000001) 16: mad r2.xyz, v2.xyzx, l(2.000000, 2.000000, 2.000000, 0.000000), l(-1.000000, -1.000000, -1.000000, 0.000000) 17: mad r1.xyz, r2.xyzx, r1.wwww, r1.xyzx 18: mov r1.w, l(1.000000) 19: dp4 o2.x, r1.xyzw, r0.xyzw 20: mul r0.xyzw, v5.xyzw, cb1[1].yyyy 21: mad r0.xyzw, v4.xyzw, cb1[1].xxxx, r0.xyzw 22: mad r0.xyzw, v6.xyzw, cb1[1].zzzz, r0.xyzw 23: mad r0.xyzw, cb1[1].wwww, l(0.000000, 0.000000, 0.000000, 1.000000), r0.xyzw 24: dp4 o2.y, r1.xyzw, r0.xyzw 25: mul r0.xyzw, v5.xyzw, cb1[2].yyyy 26: mad r0.xyzw, v4.xyzw, cb1[2].xxxx, r0.xyzw 27: mad r0.xyzw, v6.xyzw, cb1[2].zzzz, r0.xyzw 28: mad r0.xyzw, cb1[2].wwww, l(0.000000, 0.000000, 0.000000, 1.000000), r0.xyzw 29: dp4 o2.z, r1.xyzw, r0.xyzw 30: mul r0.xyzw, v5.xyzw, cb1[3].yyyy 31: mad r0.xyzw, v4.xyzw, cb1[3].xxxx, r0.xyzw 32: mad r0.xyzw, v6.xyzw, cb1[3].zzzz, r0.xyzw 33: mad r0.xyzw, cb1[3].wwww, l(0.000000, 0.000000, 0.000000, 1.000000), r0.xyzw 34: dp4 o2.w, r1.xyzw, r0.xyzw 35: ret 

वर्टेकर शेडर वर्षा पर्दे के साथ कई समानताएं हैं, इसलिए मैं दोहराता नहीं हूं। मैं आपको उन महत्वपूर्ण अंतर को दिखाना चाहता हूं जो 11-18 लाइनों में हैं:

  11: add r2.xyz, r2.xyzx, -cb1[8].xyzx 12: dp3 r1.w, r2.xyzx, r2.xyzx 13: rsq r1.w, r1.w 14: div r1.w, l(1.000000, 1.000000, 1.000000, 1.000000), r1.w 15: mul r1.w, r1.w, l(0.000001) 16: mad r2.xyz, v2.xyzx, l(2.000000, 2.000000, 2.000000, 0.000000), l(-1.000000, -1.000000, -1.000000, 0.000000) 17: mad r1.xyz, r2.xyzx, r1.wwww, r1.xyzx 18: mov r1.w, l(1.000000) 19: dp4 o2.x, r1.xyzw, r0.xyzw 

सबसे पहले, cb1 [8] .xyz कैमरे की स्थिति है, और r2.xyz विश्व अंतरिक्ष में स्थिति है, अर्थात, लाइन 11 कैमरे से वेक्टर की दुनिया में स्थिति की गणना करता है। फिर 12-15 पंक्तियों की लंबाई (विश्वपद - कैमरापोस) * 0.000001।

v2.xyz आने वाली ज्यामिति का सामान्य वेक्टर है। लाइन 16 इसे अंतराल [0-1] से अंतराल [-1; 1] तक फैलाती है।

तब दुनिया में अंतिम स्थिति की गणना की जाती है:

फ़ाइनलवर्ल्डपोस = वर्ल्डपोस + लेंथ (वर्ल्डपोस - कैमरापोस) * 0.000001 * नॉर्मलवेक्टर
इस ऑपरेशन के लिए एचएलएसएल कोड स्निपेट कुछ इस तरह होगा:

  ... // final world-space position float3 vNormal = Input.NormalW * 2.0 - 1.0; float lencameratoworld = length( PositionL - g_cameraPos.xyz) * 0.000001; PositionL += vNormal*lencameratoworld; // SV_Posiiton float4x4 matModelViewProjection = mul(g_viewProjMatrix, matInstanceWorld ); Output.PositionH = mul( float4(PositionL, 1.0), transpose(matModelViewProjection) ); return Output; 

यह ऑपरेशन मेष के एक छोटे से "विस्फोट" की ओर जाता है (सामान्य वेक्टर की दिशा में)। मैंने कई अन्य मूल्यों के साथ 0.000001 को प्रतिस्थापित करके प्रयोग किया। यहाँ परिणाम हैं:


0.000002


0.000005


0.00001


0.000025

पिक्सेल shader


ठीक है, हमने वर्टेकर शेडर का पता लगाया, अब पिक्सेल शेडर के लिए कोडांतरक कोड तक पहुंचने का समय आ गया है!

  ps_5_0 dcl_globalFlags refactoringAllowed dcl_constantbuffer cb0[1], immediateIndexed dcl_constantbuffer cb2[3], immediateIndexed dcl_constantbuffer cb4[5], immediateIndexed dcl_input_ps linear v0.x dcl_input_ps linear v1.w dcl_output o0.xyzw dcl_temps 1 0: mad r0.x, cb0[0].x, cb4[4].x, v0.x 1: add r0.y, r0.x, l(-1.000000) 2: round_ni r0.y, r0.y 3: ishr r0.z, r0.y, l(13) 4: xor r0.y, r0.y, r0.z 5: imul null, r0.z, r0.y, r0.y 6: imad r0.z, r0.z, l(0x0000ec4d), l(0.0000000000000000000000000000000000001) 7: imad r0.y, r0.y, r0.z, l(146956042240.000000) 8: and r0.y, r0.y, l(0x7fffffff) 9: round_ni r0.z, r0.x 10: frc r0.x, r0.x 11: add r0.x, -r0.x, l(1.000000) 12: ishr r0.w, r0.z, l(13) 13: xor r0.z, r0.z, r0.w 14: imul null, r0.w, r0.z, r0.z 15: imad r0.w, r0.w, l(0x0000ec4d), l(0.0000000000000000000000000000000000001) 16: imad r0.z, r0.z, r0.w, l(146956042240.000000) 17: and r0.z, r0.z, l(0x7fffffff) 18: itof r0.yz, r0.yyzy 19: mul r0.z, r0.z, l(0.000000001) 20: mad r0.y, r0.y, l(0.000000001), -r0.z 21: mul r0.w, r0.x, r0.x 22: mul r0.x, r0.x, r0.w 23: mul r0.w, r0.w, l(3.000000) 24: mad r0.x, r0.x, l(-2.000000), r0.w 25: mad r0.x, r0.x, r0.y, r0.z 26: add r0.y, -cb4[2].x, cb4[3].x 27: mad_sat r0.x, r0.x, r0.y, cb4[2].x 28: mul r0.x, r0.x, v1.w 29: mul r0.yzw, cb4[0].xxxx, cb4[1].xxyz 30: mul r0.xyzw, r0.xyzw, cb2[2].wxyz 31: mul o0.xyz, r0.xxxx, r0.yzwy 32: mov o0.w, r0.x 33: ret 

अच्छी खबर: कोड इतना लंबा नहीं है।

बुरी खबर:

  3: ishr r0.z, r0.y, l(13) 4: xor r0.y, r0.y, r0.z 5: imul null, r0.z, r0.y, r0.y 6: imad r0.z, r0.z, l(0x0000ec4d), l(0.0000000000000000000000000000000000001) 7: imad r0.y, r0.y, r0.z, l(146956042240.000000) 8: and r0.y, r0.y, l(0x7fffffff) 

... यह सब क्या है?

ईमानदारी से, यह पहली बार नहीं है जब मैंने इस तरह के एक टुकड़े को देखा है ... इस Witcher 3 shaders में कोडांतरक कोड। लेकिन जब मैं उनसे पहली बार मिला, तो मैंने सोचा: "यह क्या है?"

कुछ अन्य TW3 शेड्स में भी कुछ ऐसा ही पाया जा सकता है। मैं इस टुकड़े के साथ अपने कारनामों का वर्णन नहीं करूंगा, और सिर्फ इतना कहूंगा कि इसका उत्तर पूर्णांक शोर में है

  // For more details see: http://libnoise.sourceforge.net/noisegen/ float integerNoise( int n ) { n = (n >> 13) ^ n; int nn = (n * (n * n * 60493 + 19990303) + 1376312589) & 0x7fffffff; return ((float)nn / 1073741824.0); } 

जैसा कि आप देख सकते हैं, पिक्सेल shader में इसे दो बार कहा जाता है। इस वेबसाइट से गाइड का उपयोग करके, हम समझ सकते हैं कि कैसे शोर को सही ढंग से लागू किया जाता है। मैं एक मिनट में इस पर वापस आऊंगा।

पंक्ति ० देखें - यहाँ हम निम्नलिखित सूत्र के आधार पर एनिमेशन कर रहे हैं:

एनीमेशन = elapsedTime * एनीमेशनस्पीड + टेक्सचरयूवी.एक्स
भविष्य में निचले पक्ष ( तल ) (निर्देश राउंड_नी ) पर चक्कर लगाने के बाद ये मान पूर्णांक शोर के इनपुट बिंदु बन जाते हैं। आमतौर पर हम दो पूर्णांकों के लिए शोर मूल्य की गणना करते हैं, और फिर हम उनके बीच अंतिम, प्रक्षेपित मूल्य की गणना करते हैं (विवरण के लिए कामेच्छा वेबसाइट देखें)।

खैर, यह एक पूर्णांक शोर है, लेकिन आखिरकार, सभी पहले से उल्लेखित मूल्य (नीचे भी गोल) फ्लोट हैं!

ध्यान दें कि यहां कोई फ़ॉटी निर्देश नहीं हैं । मुझे लगता है कि सीडी प्रॉजेक्ट रेड के प्रोग्रामर्स ने यहां एचएलएसएल एसिंट आंतरिक फ़ंक्शन का उपयोग किया है , जो फ्लोटिंग पॉइंट वैल्यूज़ को " रिइंटरप्रिट_कास्ट " करता है और उन्हें पूर्णांक पैटर्न के रूप में मानता है।

दो मूल्यों के लिए प्रक्षेप वजन 10-11 लाइनों में गणना की जाती है।

इंटरपोलेशन वाइट = 1.0 - फ़्रेक (एनीमेशन);
यह दृष्टिकोण हमें समय के साथ मूल्यों के बीच अंतर करने की अनुमति देता है।

निर्बाध शोर पैदा करने के लिए, यह प्रक्षेपक SCurve फ़ंक्शन में जाता है:

  float s_curve( float x ) { float x2 = x * x; float x3 = x2 * x; // -2x^3 + 3x^2 return -2.0*x3 + 3.0*x2; } 


स्मूथस्टेप फंक्शन [libnoise.sourceforge.net]

इस सुविधा को "स्मूथस्टेप" के रूप में जाना जाता है। लेकिन जैसा कि आप कोडांतरक कोड से देख सकते हैं, यह HLSL से आंतरिक स्मूथस्टेप फ़ंक्शन नहीं है। एक आंतरिक फ़ंक्शन प्रतिबंध लागू करता है ताकि मान सत्य हो। लेकिन जब से हम जानते हैं कि इंटरपोलेशन वाइट हमेशा रेंज [0-1] में रहेगा, इन चेकों को सुरक्षित रूप से छोड़ दिया जा सकता है।

अंतिम मूल्य की गणना करते समय, कई गुणा संचालन का उपयोग किया जाता है। देखें कि अंतिम अल्फा आउटपुट शोर मूल्य के आधार पर कैसे बदल सकता है। यह सुविधाजनक है क्योंकि यह वास्तविक जीवन की तरह, प्रस्तुत बिजली की अस्पष्टता को प्रभावित करेगा।

तैयार पिक्सेल shader:

  cbuffer cbPerFrame : register (b0) { float4 cb0_v0; float4 cb0_v1; float4 cb0_v2; float4 cb0_v3; } cbuffer cbPerFrame : register (b2) { float4 cb2_v0; float4 cb2_v1; float4 cb2_v2; float4 cb2_v3; } cbuffer cbPerFrame : register (b4) { float4 cb4_v0; float4 cb4_v1; float4 cb4_v2; float4 cb4_v3; float4 cb4_v4; } struct VS_OUTPUT { float2 Texcoords : Texcoord0; float4 InstanceLODParams : INSTANCE_LOD_PARAMS; float4 PositionH : SV_Position; }; // Shaders in TW3 use integer noise. // For more details see: http://libnoise.sourceforge.net/noisegen/ float integerNoise( int n ) { n = (n >> 13) ^ n; int nn = (n * (n * n * 60493 + 19990303) + 1376312589) & 0x7fffffff; return ((float)nn / 1073741824.0); } float s_curve( float x ) { float x2 = x * x; float x3 = x2 * x; // -2x^3 + 3x^2 return -2.0*x3 + 3.0*x2; } float4 Lightning_TW3_PS( in VS_OUTPUT Input ) : SV_Target { // * Inputs float elapsedTime = cb0_v0.x; float animationSpeed = cb4_v4.x; float minAmount = cb4_v2.x; float maxAmount = cb4_v3.x; float colorMultiplier = cb4_v0.x; float3 colorFilter = cb4_v1.xyz; float3 lightningColorRGB = cb2_v2.rgb; // Animation using time and X texcoord float animation = elapsedTime * animationSpeed + Input.Texcoords.x; // Input parameters for Integer Noise. // They are floored and please note there are using asint. // That might be an optimization to avoid "ftoi" instructions. int intX0 = asint( floor(animation) ); int intX1 = asint( floor(animation-1.0) ); float n0 = integerNoise( intX0 ); float n1 = integerNoise( intX1 ); // We interpolate "backwards" here. float weight = 1.0 - frac(animation); // Following the instructions from libnoise, we perform // smooth interpolation here with cubic s-curve function. float noise = lerp( n0, n1, s_curve(weight) ); // Make sure we are in [0.0 - 1.0] range. float lightningAmount = saturate( lerp(minAmount, maxAmount, noise) ); lightningAmount *= Input.InstanceLODParams.w; // 1.0 lightningAmount *= cb2_v2.w; // 1.0 // Calculate final lightning color float3 lightningColor = colorMultiplier * colorFilter; lightningColor *= lighntingColorRGB; float3 finalLightningColor = lightningColor * lightningAmount; return float4( finalLightningColor, lightningAmount ); } 

संक्षेप में देना


इस भाग में, मैंने द विचर 3 में बिजली प्रस्तुत करने का एक तरीका बताया।

मुझे बहुत खुशी है कि कोडर कोड जो मेरी shader से निकला है, मूल से पूरी तरह मेल खाता है!


भाग 2. सिली स्काई ट्रिक्स


यह हिस्सा पिछले वाले से थोड़ा अलग होगा। इसमें, मैं आपको आकाश shader Witcher 3 के कुछ पहलुओं को दिखाना चाहता हूं।

क्यों "मूर्खतापूर्ण चालें" और पूरे shader नहीं? वैसे इसके कई कारण हैं। सबसे पहले, इस Witcher 3 आकाश shader एक बल्कि जटिल जानवर है। 2015 संस्करण के पिक्सेल शेडर में कोडांतरक कोड की 267 लाइनें हैं, और ब्लड और वाइन डीएलसी से शेडर में 385 लाइनें हैं।

इसके अलावा, उन्हें बहुत सारे इनपुट मिलते हैं, जो इंजीनियरिंग को पूर्ण (और पठनीय!) एचएलएसएल कोड को उलटने के लिए बहुत अनुकूल नहीं है।

इसलिए, मैंने इन शेड्स से केवल ट्रिक्स का हिस्सा दिखाने का फैसला किया। यदि मुझे कुछ नया मिलता है, तो मैं पोस्ट को पूरक करूंगा।

2015 संस्करण और डीएलसी (2016) के बीच अंतर बहुत ध्यान देने योग्य हैं। विशेष रूप से, वे सितारों और उनकी झिलमिलाहट की गणना में अंतर शामिल करते हैं, सूर्य को प्रतिपादन करने के लिए एक अलग दृष्टिकोण ... रक्त और शराब shader भी रात में मिल्की वे की गणना करता है।

मैं मूल बातों से शुरू करता हूं और फिर बेवकूफाना चाल के बारे में बात करता हूं।

मूल बातें


अधिकांश आधुनिक खेलों की तरह, डायन 3 आकाश को मॉडल बनाने के लिए स्काइडोम का उपयोग करता है। इस के लिए इस्तेमाल होने वाले गोलार्ध को देखो, विचर 3 (2015) में। नोट: इस मामले में, इस जाल का बाउंडिंग बॉक्स [0,0,0] से लेकर [1,1,1] तक है (Z यह धुरी की ओर इशारा करता है) और आसानी से UVs वितरित करता है। बाद में हम उनका उपयोग करते हैं।


स्काईडोम के पीछे विचार स्काईबॉक्स के विचार के समान है (केवल अंतर उपयोग की गई जाली है)। शीर्ष पर स्थित शेडर स्टेज पर, हम स्काईडोम को पर्यवेक्षक के सापेक्ष बदल देते हैं (आमतौर पर कैमरा स्थिति के अनुसार), जो भ्रम पैदा करता है कि आकाश वास्तव में बहुत दूर है - हम इसे कभी नहीं प्राप्त करेंगे।

यदि आप लेखों की इस श्रृंखला के पिछले भागों को पढ़ते हैं, तो आप जानते हैं कि "The Witcher 3" व्युत्क्रम गहराई का उपयोग करता है, अर्थात्, दूर का विमान 0.0f है, और निकटतम 1.0f है। सुदूर तल पर स्काईडोम उत्पादन को पूरा करने के लिए, ब्राउज़ विंडो के मापदंडों में हम मिनडेप को उसी मान के रूप में सेट करते हैं :


ब्राउज़ विंडो के रूपांतरण के दौरान MinDepth और MaxDepth फ़ील्ड का उपयोग कैसे किया जाता है, यह जानने के लिए, यहां क्लिक करें (docs.microsoft.com)।

वर्टेक्स शेडर


आइए हम शीर्ष शैंडर से शुरू करते हैं। विचर 3 (2015) में, कोडांतरक शेडर कोड निम्नानुसार है:

  vs_5_0 dcl_globalFlags refactoringAllowed dcl_constantbuffer cb1[4], immediateIndexed dcl_constantbuffer cb2[6], immediateIndexed dcl_input v0.xyz dcl_input v1.xy dcl_output o0.xy dcl_output o1.xyz dcl_output_siv o2.xyzw, position dcl_temps 2 0: mov o0.xy, v1.xyxx 1: mad r0.xyz, v0.xyzx, cb2[4].xyzx, cb2[5].xyzx 2: mov r0.w, l(1.000000) 3: dp4 o1.x, r0.xyzw, cb2[0].xyzw 4: dp4 o1.y, r0.xyzw, cb2[1].xyzw 5: dp4 o1.z, r0.xyzw, cb2[2].xyzw 6: mul r1.xyzw, cb1[0].yyyy, cb2[1].xyzw 7: mad r1.xyzw, cb2[0].xyzw, cb1[0].xxxx, r1.xyzw 8: mad r1.xyzw, cb2[2].xyzw, cb1[0].zzzz, r1.xyzw 9: mad r1.xyzw, cb1[0].wwww, l(0.000000, 0.000000, 0.000000, 1.000000), r1.xyzw 10: dp4 o2.x, r0.xyzw, r1.xyzw 11: mul r1.xyzw, cb1[1].yyyy, cb2[1].xyzw 12: mad r1.xyzw, cb2[0].xyzw, cb1[1].xxxx, r1.xyzw 13: mad r1.xyzw, cb2[2].xyzw, cb1[1].zzzz, r1.xyzw 14: mad r1.xyzw, cb1[1].wwww, l(0.000000, 0.000000, 0.000000, 1.000000), r1.xyzw 15: dp4 o2.y, r0.xyzw, r1.xyzw 16: mul r1.xyzw, cb1[2].yyyy, cb2[1].xyzw 17: mad r1.xyzw, cb2[0].xyzw, cb1[2].xxxx, r1.xyzw 18: mad r1.xyzw, cb2[2].xyzw, cb1[2].zzzz, r1.xyzw 19: mad r1.xyzw, cb1[2].wwww, l(0.000000, 0.000000, 0.000000, 1.000000), r1.xyzw 20: dp4 o2.z, r0.xyzw, r1.xyzw 21: mul r1.xyzw, cb1[3].yyyy, cb2[1].xyzw 22: mad r1.xyzw, cb2[0].xyzw, cb1[3].xxxx, r1.xyzw 23: mad r1.xyzw, cb2[2].xyzw, cb1[3].zzzz, r1.xyzw 24: mad r1.xyzw, cb1[3].wwww, l(0.000000, 0.000000, 0.000000, 1.000000), r1.xyzw 25: dp4 o2.w, r0.xyzw, r1.xyzw 26: ret 

इस मामले में, वर्टेक्स शेडर केवल टेक्सकोर्ड्स को स्थानांतरित करता है और आउटपुट के लिए विश्व अंतरिक्ष में एक स्थिति है। रक्त और शराब में, वह एक सामान्य सामान्य वेक्टर भी प्रदर्शित करता है। मैं 2015 संस्करण पर विचार करूंगा क्योंकि यह सरल है।

Cb2 के रूप में निर्दिष्ट निरंतर बफर को देखें :


यहां हमारे पास दुनिया का एक मैट्रिक्स है (100 तक समान स्केलिंग और कैमरा स्थिति के सापेक्ष स्थानांतरण)। कुछ भी जटिल नहीं है। cb2_v4 और cb2_v5 स्केल / विचलन कारक हैं जिनका उपयोग अंतराल के पदों को अंतराल [0-1] से अंतराल [-1; 1] में बदलने के लिए किया जाता है। लेकिन यहाँ, ये गुणांक Z अक्ष (ऊपर की ओर) को "संपीड़ित" करते हैं।


श्रृंखला के पिछले भागों में, हमारे पास समान शीर्ष वाले शेड थे। सामान्य एल्गोरिथ्म टेक्सकोर्ड्स को आगे स्थानांतरित करने के लिए है, फिर स्थिति की गणना पैमाने / विचलन गुणांक को ध्यान में रखते हुए की जाती है , फिर स्थिति स्पेस की गणना विश्व अंतरिक्ष में की जाती है, फिर क्लिपिंग स्पेस की अंतिम स्थिति की गणना मैटवर्ल्ड और मैट्रोप्रोज़ को गुणा करके की जाती है -> अंतिम एसवी_पोज़िशन प्राप्त करने के लिए उनके उत्पाद को स्थिति से गुणा करने के लिए उपयोग किया जाता है। ।

इसलिए, इस शीर्ष shader का HLSL कुछ इस प्रकार होना चाहिए:

  struct InputStruct { float3 param0 : POSITION; float2 param1 : TEXCOORD; float3 param2 : NORMAL; float4 param3 : TANGENT; }; struct OutputStruct { float2 param0 : TEXCOORD0; float3 param1 : TEXCOORD1; float4 param2 : SV_Position; }; OutputStruct EditedShaderVS(in InputStruct IN) { OutputStruct OUT = (OutputStruct)0; // Simple texcoords passing OUT.param0 = IN.param1; // * Manually construct world and viewProj martices from float4s: row_major matrix matWorld = matrix(cb2_v0, cb2_v1, cb2_v2, float4(0,0,0,1) ); matrix matViewProj = matrix(cb1_v0, cb1_v1, cb1_v2, cb1_v3); // * Some optional fun with worldMatrix // a) Scale //matWorld._11 = matWorld._22 = matWorld._33 = 0.225f; // b) Translate // XYZ //matWorld._14 = 520.0997; //matWorld._24 = 74.4226; //matWorld._34 = 113.9; // Local space - note the scale+bias here! //float3 meshScale = float3(2.0, 2.0, 2.0); //float3 meshBias = float3(-1.0, -1.0, -0.4); float3 meshScale = cb2_v4.xyz; float3 meshBias = cb2_v5.xyz; float3 Position = IN.param0 * meshScale + meshBias; // World space float4 PositionW = mul(float4(Position, 1.0), transpose(matWorld) ); OUT.param1 = PositionW.xyz; // Clip space - original approach from The Witcher 3 matrix matWorldViewProj = mul(matViewProj, matWorld); OUT.param2 = mul( float4(Position, 1.0), transpose(matWorldViewProj) ); return OUT; } 

मेरे shader की तुलना (बाएं) और मूल (दाएं):


रेंडरडॉक की एक उत्कृष्ट संपत्ति यह है कि यह हमें मूल के बजाय अपने स्वयं के शेडर को इंजेक्ट करने की अनुमति देता है, और ये परिवर्तन पाइपलाइन को फ्रेम के बहुत अंत तक प्रभावित करेंगे। जैसा कि आप एचएलएसएल कोड से देख सकते हैं, मैंने अंतिम ज्यामिति को ज़ूम करने और बदलने के लिए कई विकल्प प्रदान किए हैं। आप उनके साथ प्रयोग कर सकते हैं और बहुत मज़ेदार परिणाम प्राप्त कर सकते हैं:


वर्टेक्स शेडर ऑप्टिमाइज़ेशन


क्या आपने मूल वर्टिशर शेडर की समस्या पर ध्यान दिया है? एक मैट्रिक्स द्वारा एक मैट्रिक्स का शीर्ष गुणन पूरी तरह से बेमानी है! मुझे यह कम से कम कुछ शीर्ष रंगों में मिला (उदाहरण के लिए, छाया में दूरी में बारिश का एक पर्दा )। हम MatViewProj द्वारा तुरंत स्थिति डब्ल्यू को गुणा करके इसे अनुकूलित कर सकते हैं!

इसलिए, हम इस कोड को HLSL से बदल सकते हैं:

  // Clip space - original approach from The Witcher 3 matrix matWorldViewProj = mul(matViewProj, matWorld); OUT.param2 = mul( float4(Position, 1.0), transpose(matWorldViewProj) ); 

निम्नानुसार है:

  // Clip space - optimized version OUT.param2 = mul( matViewProj, PositionW ); 

अनुकूलित संस्करण हमें निम्नलिखित विधानसभा कोड देता है:

  vs_5_0 dcl_globalFlags refactoringAllowed dcl_constantbuffer CB1[4], immediateIndexed dcl_constantbuffer CB2[6], immediateIndexed dcl_input v0.xyz dcl_input v1.xy dcl_output o0.xy dcl_output o1.xyz dcl_output_siv o2.xyzw, position dcl_temps 2 0: mov o0.xy, v1.xyxx 1: mad r0.xyz, v0.xyzx, cb2[4].xyzx, cb2[5].xyzx 2: mov r0.w, l(1.000000) 3: dp4 r1.x, r0.xyzw, cb2[0].xyzw 4: dp4 r1.y, r0.xyzw, cb2[1].xyzw 5: dp4 r1.z, r0.xyzw, cb2[2].xyzw 6: mov o1.xyz, r1.xyzx 7: mov r1.w, l(1.000000) 8: dp4 o2.x, cb1[0].xyzw, r1.xyzw 9: dp4 o2.y, cb1[1].xyzw, r1.xyzw 10: dp4 o2.z, cb1[2].xyzw, r1.xyzw 11: dp4 o2.w, cb1[3].xyzw, r1.xyzw 12: ret 

जैसा कि आप देख सकते हैं, हमने निर्देशों की संख्या 26 से घटाकर 12 कर दी है - एक महत्वपूर्ण बदलाव। मुझे नहीं पता कि खेल में यह समस्या कितनी व्यापक है, लेकिन भगवान के लिए, सीडी प्रॉजेक्ट रेड, शायद एक पैच जारी कर सकता है? :)

और मैं मजाक नहीं कर रहा हूं। आप मूल रेंडरडॉक के बजाय मेरे अनुकूलित शेडर को सम्मिलित कर सकते हैं और आप देखेंगे कि यह अनुकूलन किसी भी चीज़ को प्रभावित नहीं करता है। ईमानदारी से, मुझे समझ में नहीं आता है कि सीडी प्रॉजेक्ट रेड ने एक मैट्रिक्स द्वारा एक मैट्रिक्स के वर्टेक्स गुणा करने का निर्णय क्यों लिया ...

सूरज


द विचर 3 (2015) में, वायुमंडलीय प्रकीर्णन और सूर्य की गणना में दो अलग-अलग ड्राइंग कॉल शामिल हैं:


विचर 3 (2015) - जब तक


चुड़ैल 3 (2015) - आकाश के साथ


चुड़ैल 3 (2015) - आकाश + सूर्य के साथ

2015 संस्करण में सूर्य का प्रतिपादन ज्यामिति और मिश्रण / गहराई की स्थिति के अनुसार चंद्रमा के प्रतिपादन के समान है।

दूसरी ओर, "रक्त और शराब" में सूर्य के साथ आकाश को एक पास में प्रस्तुत किया गया है:


द विचर 3: ब्लड एंड वाइन (2016) - टू हेवेन


द विचर 3: ब्लड एंड वाइन (2016) - स्वर्ग और सूर्य के साथ

कोई फर्क नहीं पड़ता कि आप सूर्य को कैसे प्रस्तुत करते हैं, कुछ चरण में आपको अभी भी सूर्य के प्रकाश की (सामान्यीकृत) दिशा की आवश्यकता होती है। इस वेक्टर को प्राप्त करने का सबसे तार्किक तरीका गोलाकार निर्देशांक का उपयोग करना है । वास्तव में, हमें केवल दो मूल्यों की आवश्यकता है जो दो कोणों (रेडियन में!) का संकेत देते हैं: फ़ि और थीटा । उन्हें प्राप्त करने के बाद, हम मान सकते हैं कि आर = 1 , इस प्रकार इसे कम करना। फिर कार्टेशियन के लिए Y अक्ष को इंगित करने वाले निर्देशांक के साथ, आप एचएलएसएल में निम्नलिखित कोड लिख सकते हैं:

  float3 vSunDir; vSunDir.x = sin(fTheta)*cos(fPhi); vSunDir.y = sin(fTheta)*sin(fPhi); vSunDir.z = cos(fTheta); vSunDir = normalize(vSunDir); 

आमतौर पर, सूर्य के प्रकाश की दिशा की गणना अनुप्रयोग में की जाती है, और फिर भविष्य के उपयोग के लिए निरंतर बफर को पारित किया जाता है।

सूर्य के प्रकाश की दिशा प्राप्त करने के बाद, हम "ब्लड एंड वाइन" पिक्सेल शेडर के कोडर कोड में अधिक गहराई तक पहुंच सकते हैं ...

  ... 100: add r1.xyw, -r0.xyxz, cb12[0].xyxz 101: dp3 r2.x, r1.xywx, r1.xywx 102: rsq r2.x, r2.x 103: mul r1.xyw, r1.xyxw, r2.xxxx 104: mov_sat r2.xy, cb12[205].yxyy 105: dp3 r2.z, -r1.xywx, -r1.xywx 106: rsq r2.z, r2.z 107: mul r1.xyw, -r1.xyxw, r2.zzzz ... 

तो, सबसे पहले, cb12 [0] .xyz कैमरा पोजिशन है, और r0.xyz में हम वर्टेक्स पोजीशन को स्टोर करते हैं (यह वर्टीकल शेडर से आउटपुट है)। इसलिए, लाइन 100 वेक्टर वर्ल्डटॉम्केरा की गणना करता है। लेकिन १०५-१०। लाइनों पर एक नज़र डालें। हम उन्हें सामान्यीकृत (-worldToCamera) के रूप में लिख सकते हैं, अर्थात हम सामान्यीकृत कैमराटाउडर वेक्टर की गणना करते हैं।

  120: dp3_sat r1.x, cb12[203].yzwy, r1.xywx 

तब हम CameraToWorld और sunDirection vectors के अदिश उत्पाद की गणना करते हैं! याद रखें कि उन्हें सामान्य किया जाना चाहिए। हम इस पूर्ण अभिव्यक्ति को अंतराल तक सीमित करने के लिए भी इसे पूरा करते हैं [0-1]।

बहुत बढ़िया! यह अदिश उत्पाद r1.x में संग्रहीत है। आइए देखें कि यह कहां लागू होता है ...

  152: log r1.x, r1.x 153: mul r1.x, r1.x, cb12[203].x 154: exp r1.x, r1.x 155: mul r1.x, r2.y, r1.x 

त्रिमूर्ति "लॉग, mul, ऍक्स्प" घातांक है। जैसा कि आप देख सकते हैं, हम अपने कोसाइन (सामान्यीकृत वैक्टर के स्केलर उत्पाद) को कुछ हद तक बढ़ाते हैं। आप पूछ सकते हैं क्यों। इस तरह, हम एक ढाल बना सकते हैं जो सूर्य की नकल करता है। (और लाइन 155 इस ढाल की अस्पष्टता को प्रभावित करता है, ताकि हम, उदाहरण के लिए, इसे पूरी तरह से सूर्य को छिपाने के लिए रीसेट करें)। यहाँ कुछ उदाहरण हैं:


घातांक = ५४


घातांक = 2400

इस ढाल के होने के बाद, हम इसका इस्तेमाल आसमानी और सूरज की रोशनी के बीच अंतर करने के लिए करते हैं ! कलाकृतियों से बचने के लिए, आपको लाइन 120 पर मूल्य को संतृप्त करने की आवश्यकता है।

यह ध्यान देने योग्य है कि इस चाल का उपयोग चंद्रमा के मुकुट (कम घातांक मानों) पर अनुकरण करने के लिए किया जा सकता है। ऐसा करने के लिए, हमें चंद्रमाडायरेक्शन वेक्टर की आवश्यकता है, जिसे गोलाकार निर्देशांक का उपयोग करके आसानी से गणना की जा सकती है।

रेडी-मेड HLSL कोड निम्नलिखित स्निपेट की तरह लग सकता है:

  float3 vCamToWorld = normalize( PosW – CameraPos ); float cosTheta = saturate( dot(vSunDir, vCamToWorld) ); float sunGradient = pow( cosTheta, sunExponent ); float3 color = lerp( skyColor, sunColor, sunGradient ); 

तारों का मोशन


यदि आप चुड़ैल 3 के स्पष्ट रात्रि आकाश का एक समय व्यतीत करते हैं, तो आप देख सकते हैं कि तारे स्थिर नहीं हैं - वे पूरे आकाश में थोड़े चलते हैं! मैंने इसे लगभग दुर्घटना से देखा और जानना चाहा कि इसे कैसे लागू किया गया।

आइए इस तथ्य से शुरू करें कि चुड़ैल 3 में सितारों को 1024x1024x6 आकार के घन मानचित्र के रूप में प्रस्तुत किया गया है। यदि आप इसके बारे में सोचते हैं, तो आप समझ सकते हैं कि यह एक बहुत ही सुविधाजनक समाधान है जो आपको आसानी से क्यूबिक मैप का नमूना लेने के लिए दिशाओं को स्नैप करने की अनुमति देता है।

आइए निम्नलिखित कोड कोड को देखें:

  159: add r1.xyz, -v1.xyzx, cb1[8].xyzx 160: dp3 r0.w, r1.xyzx, r1.xyzx 161: rsq r0.w, r0.w 162: mul r1.xyz, r0.wwww, r1.xyzx 163: mul r2.xyz, cb12[204].zwyz, l(0.000000, 0.000000, 1.000000, 0.000000) 164: mad r2.xyz, cb12[204].yzwy, l(0.000000, 1.000000, 0.000000, 0.000000), -r2.xyzx 165: mul r4.xyz, r2.xyzx, cb12[204].zwyz 166: mad r4.xyz, r2.zxyz, cb12[204].wyzw, -r4.xyzx 167: dp3 r4.x, r1.xyzx, r4.xyzx 168: dp2 r4.y, r1.xyxx, r2.yzyy 169: dp3 r4.z, r1.xyzx, cb12[204].yzwy 170: dp3 r0.w, r4.xyzx, r4.xyzx 171: rsq r0.w, r0.w 172: mul r2.xyz, r0.wwww, r4.xyzx 173: sample_indexable(texturecube)(float,float,float,float) r4.xyz, r2.xyzx, t0.xyzw, s0 

अंतिम सैंपलिंग वेक्टर (लाइन 173) की गणना करने के लिए, हम सामान्यीकृत वर्ल्डटोमेकेरा वेक्टर (लाइनों 159-162) की गणना करके शुरू करते हैं।

तब हम दो वेक्टर उत्पादों (163-164, 165-166) की गणना चंद्रमाडायरेक्शन के साथ करते हैं , और बाद में हम अंतिम नमूने वेक्टर प्राप्त करने के लिए तीन स्केलर उत्पादों की गणना करते हैं। HLSL कोड:

  float3 vWorldToCamera = normalize( g_CameraPos.xyz - Input.PositionW.xyz ); float3 vMoonDirection = cb12_v204.yzw; float3 vStarsSamplingDir = cross( vMoonDirection, float3(0, 0, 1) ); float3 vStarsSamplingDir2 = cross( vStarsSamplingDir, vMoonDirection ); float dirX = dot( vWorldToCamera, vStarsSamplingDir2 ); float dirY = dot( vWorldToCamera, vStarsSamplingDir ); float dirZ = dot( vWorldToCamera, vMoonDirection); float3 dirXYZ = normalize( float3(dirX, dirY, dirZ) ); float3 starsColor = texNightStars.Sample( samplerAnisoWrap, dirXYZ ).rgb; 

खुद पर ध्यान दें: यह एक बहुत अच्छी तरह से डिज़ाइन किया गया कोड है, और मुझे इसकी और अधिक विस्तार से जाँच करनी चाहिए।

पाठकों को ध्यान दें: यदि आप इस ऑपरेशन के बारे में अधिक जानते हैं, तो मुझे बताएं!

टिमटिमाने वाले तारे


एक और दिलचस्प चाल जिसे मैं और अधिक विस्तार से जानना चाहूंगा वह है सितारों की झिलमिलाहट।उदाहरण के लिए, यदि आप साफ मौसम में नोवग्रेड के चारों ओर घूमते हैं, तो आप देखेंगे कि तारे टिमटिमाते हैं।

मैं उत्सुक था कि इसे कैसे लागू किया गया। यह पता चला कि 2015 के संस्करण और "रक्त और शराब" के बीच का अंतर काफी बड़ा है। सरलता के लिए, मैं 2015 संस्करण पर विचार करूंगा।

इसलिए, हम सितारों के पिछले नमूने से नमूना लेने के ठीक बाद शुरू करते हैं:

  174: mul r0.w, v0.x, l(100.000000) 175: round_ni r1.w, r0.w 176: mad r2.w, v0.y, l(50.000000), cb0[0].x 177: round_ni r4.w, r2.w 178: bfrev r4.w, r4.w 179: iadd r5.x, r1.w, r4.w 180: ishr r5.y, r5.x, l(13) 181: xor r5.x, r5.x, r5.y 182: imul null, r5.y, r5.x, r5.x 183: imad r5.y, r5.y, l(0x0000ec4d), l(0.0000000000000000000000000000000000001) 184: imad r5.x, r5.x, r5.y, l(146956042240.000000) 185: and r5.x, r5.x, l(0x7fffffff) 186: itof r5.x, r5.x 187: mad r5.y, v0.x, l(100.000000), l(-1.000000) 188: round_ni r5.y, r5.y 189: iadd r4.w, r4.w, r5.y 190: ishr r5.z, r4.w, l(13) 191: xor r4.w, r4.w, r5.z 192: imul null, r5.z, r4.w, r4.w 193: imad r5.z, r5.z, l(0x0000ec4d), l(0.0000000000000000000000000000000000001) 194: imad r4.w, r4.w, r5.z, l(146956042240.000000) 195: and r4.w, r4.w, l(0x7fffffff) 196: itof r4.w, r4.w 197: add r5.z, r2.w, l(-1.000000) 198: round_ni r5.z, r5.z 199: bfrev r5.z, r5.z 200: iadd r1.w, r1.w, r5.z 201: ishr r5.w, r1.w, l(13) 202: xor r1.w, r1.w, r5.w 203: imul null, r5.w, r1.w, r1.w 204: imad r5.w, r5.w, l(0x0000ec4d), l(0.0000000000000000000000000000000000001) 205: imad r1.w, r1.w, r5.w, l(146956042240.000000) 206: and r1.w, r1.w, l(0x7fffffff) 207: itof r1.w, r1.w 208: mul r1.w, r1.w, l(0.000000001) 209: iadd r5.y, r5.z, r5.y 210: ishr r5.z, r5.y, l(13) 211: xor r5.y, r5.y, r5.z 212: imul null, r5.z, r5.y, r5.y 213: imad r5.z, r5.z, l(0x0000ec4d), l(0.0000000000000000000000000000000000001) 214: imad r5.y, r5.y, r5.z, l(146956042240.000000) 215: and r5.y, r5.y, l(0x7fffffff) 216: itof r5.y, r5.y 217: frc r0.w, r0.w 218: add r0.w, -r0.w, l(1.000000) 219: mul r5.z, r0.w, r0.w 220: mul r0.w, r0.w, r5.z 221: mul r5.xz, r5.xxzx, l(0.000000001, 0.000000, 3.000000, 0.000000) 222: mad r0.w, r0.w, l(-2.000000), r5.z 223: frc r2.w, r2.w 224: add r2.w, -r2.w, l(1.000000) 225: mul r5.z, r2.w, r2.w 226: mul r2.w, r2.w, r5.z 227: mul r5.z, r5.z, l(3.000000) 228: mad r2.w, r2.w, l(-2.000000), r5.z 229: mad r4.w, r4.w, l(0.000000001), -r5.x 230: mad r4.w, r0.w, r4.w, r5.x 231: mad r5.x, r5.y, l(0.000000001), -r1.w 232: mad r0.w, r0.w, r5.x, r1.w 233: add r0.w, -r4.w, r0.w 234: mad r0.w, r2.w, r0.w, r4.w 235: mad r2.xyz, r0.wwww, l(0.000500, 0.000500, 0.000500, 0.000000), r2.xyzx 236: sample_indexable(texturecube)(float,float,float,float) r2.xyz, r2.xyzx, t0.xyzw, s0 237: log r4.xyz, r4.xyzx 238: mul r4.xyz, r4.xyzx, l(2.200000, 2.200000, 2.200000, 0.000000) 239: exp r4.xyz, r4.xyzx 240: log r2.xyz, r2.xyzx 241: mul r2.xyz, r2.xyzx, l(2.200000, 2.200000, 2.200000, 0.000000) 242: exp r2.xyz, r2.xyzx 243: mul r2.xyz, r2.xyzx, r4.xyzx 

. .

starsColor 173 - offset . offset (r2.xyz, 235), , - (237-242) (243).

, ? , . offset . skydome — .

offset , , UV skydome (v0.xy) , (cb[0].x).

ishr/xor/and, .

, , , . , ( iadd ) ( reversebits ; bfrev ).

, . .

4 «» . , 4 :

  int getInt( float x ) { return asint( floor(x) ); } int getReverseInt( float x ) { return reversebits( getInt(x) ); } // * Inputs - UV and elapsed time in seconds float2 starsUV; starsUV.x = 100.0 * Input.TextureUV.x; starsUV.y = 50.0 * Input.TextureUV.y + g_fTime; // * Iteration 1 int iStars1_A = getReverseInt( starsUV.y ); int iStars1_B = getInt( starsUV.x ); float fStarsNoise1 = integerNoise( iStars1_A + iStars1_B ); // * Iteration 2 int iStars2_A = getReverseInt( starsUV.y ); int iStars2_B = getInt( starsUV.x - 1.0 ); float fStarsNoise2 = integerNoise( iStars2_A + iStars2_B ); // * Iteration 3 int iStars3_A = getReverseInt( starsUV.y - 1.0 ); int iStars3_B = getInt( starsUV.x ); float fStarsNoise3 = integerNoise( iStars3_A + iStars3_B ); // * Iteration 4 int iStars4_A = getReverseInt( starsUV.y - 1.0 ); int iStars4_B = getInt( starsUV.x - 1.0 ); float fStarsNoise4 = integerNoise( iStars4_A + iStars4_B ); 

4 ( , itof ):

1 — r5.x,

2 — r4.w,

3 — r1.w,

4 — r5.y

itof ( 216) :

  217: frc r0.w, r0.w 218: add r0.w, -r0.w, l(1.000000) 219: mul r5.z, r0.w, r0.w 220: mul r0.w, r0.w, r5.z 221: mul r5.xz, r5.xxzx, l(0.000000001, 0.000000, 3.000000, 0.000000) 222: mad r0.w, r0.w, l(-2.000000), r5.z 223: frc r2.w, r2.w 224: add r2.w, -r2.w, l(1.000000) 225: mul r5.z, r2.w, r2.w 226: mul r2.w, r2.w, r5.z 227: mul r5.z, r5.z, l(3.000000) 228: mad r2.w, r2.w, l(-2.000000), r5.z 

S- UV, . तो:

  float s_curve( float x ) { float x2 = x * x; float x3 = x2 * x; // -2x^3 + 3x^2 return -2.0*x3 + 3.0*x2; } ... // lines 217-222 float weightX = 1.0 - frac( starsUV.x ); weightX = s_curve( weightX ); // lines 223-228 float weightY = 1.0 - frac( starsUV.y ); weightY = s_curve( weightY ); 

, :

  229: mad r4.w, r4.w, l(0.000000001), -r5.x 230: mad r4.w, r0.w, r4.w, r5.x float noise0 = lerp( fStarsNoise1, fStarsNoise2, weightX ); 231: mad r5.x, r5.y, l(0.000000001), -r1.w 232: mad r0.w, r0.w, r5.x, r1.w float noise1 = lerp( fStarsNoise3, fStarsNoise4, weightX ); 233: add r0.w, -r4.w, r0.w 234: mad r0.w, r2.w, r0.w, r4.w float offset = lerp( noise0, noise1, weightY ); 235: mad r2.xyz, r0.wwww, l(0.000500, 0.000500, 0.000500, 0.000000), r2.xyzx 236: sample_indexable(texturecube)(float,float,float,float) r2.xyz, r2.xyzx, t0.xyzw, s0 float3 starsPerturbedDir = dirXYZ + offset * 0.0005; float3 starsColorDisturbed = texNightStars.Sample( samplerAnisoWrap, starsPerturbedDir ).rgb; 

offset :


starsColorDisturbed . हुर्रे!

— - starsColor , starsColorDisturbed , :

  starsColor = pow( starsColor, 2.2 ); starsColorDisturbed = pow( starsColorDisturbed, 2.2 ); float3 starsFinal = starsColor * starsColorDisturbed; 


starsFinal in r1.xyz. :

  256: log r1.xyz, r1.xyzx 257: mul r1.xyz, r1.xyzx, l(2.500000, 2.500000, 2.500000, 0.000000) 258: exp r1.xyz, r1.xyzx 259: min r1.xyz, r1.xyzx, l(1.000000, 1.000000, 1.000000, 0.000000) 260: add r0.w, -cb0[9].w, l(1.000000) 261: mul r1.xyz, r0.wwww, r1.xyzx 262: mul r1.xyz, r1.xyzx, l(10.000000, 10.000000, 10.000000, 0.000000) 

.

, starsFinal 2.5 — . . , float3(1, 1, 1).

cb0[9].w . , 1.0 ( ), — 0.0.

10. !

3. ( )


Witcher 3. , , . .

« ». — , , . , , , . .

:


, :


, : , ( ) , ( ). , , ( ). , « ( ).

, .

, — , — .


, , . Witcher 3 -. GBuffer, „“ (), stencil = 8. , „“ , stencil = 4.

, -:



-


स्टैंसिल बफ़र का उपयोग अक्सर गेम में मेश को टैग करने के लिए किया जाता है। जाल की कुछ श्रेणियों को एक ही आईडी दी जाती है। यदि स्टैंसिल परीक्षण सफल है, और अन्य सभी मामलों में रखें ऑपरेटर के साथ ऑलवेज

फ़ंक्शन को रिप्लेसमेंट ऑपरेटर के साथ उपयोग करने का विचार है यहां बताया गया है कि यह D3D11 का उपयोग करके कैसे लागू किया जाता है:



  D3D11_DEPTH_STENCIL_DESC depthstencilState; // Set depth parameters.... // Enable stencil depthstencilState.StencilEnable = TRUE; // Read & write all bits depthstencilState.StencilReadMask = 0xFF; depthstencilState.StencilWriteMask = 0xFF; // Stencil operator for front face depthstencilState.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS; depthstencilState.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_KEEP; depthstencilState.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP; depthstencilState.FrontFace.StencilPassOp = D3D11_STENCIL_OP_REPLACE; // Stencil operator for back face. depthstencilState.BackFace.StencilFunc = D3D11_COMPARISON_ALWAYS; depthstencilState.BackFace.StencilDepthFailOp = D3D11_STENCIL_OP_KEEP; depthstencilState.BackFace.StencilFailOp = D3D11_STENCIL_OP_KEEP; depthstencilState.BackFace.StencilPassOp = D3D11_STENCIL_OP_REPLACE; pDevice->CreateDepthStencilState( &depthstencilState, &m_pDS_AssignValue ); 

बफर को लिखे जाने वाले स्टेंसिल मान को API कॉल में एक StencilRef के रूप में पारित किया जाता है :

  // from now on set stencil buffer values to 8 pDevCon->OMSetDepthStencilState( m_pDS_AssignValue, 8 ); ... pDevCon->DrawIndexed( ... ); 

चमक का प्रतिपादन


R11G11B10_FLOAT, R G.

? , , , .

:



, .

: „“, — :


— :


— :


ठीक है, लेकिन हम यह कैसे निर्धारित करते हैं कि किस पिक्सेल पर विचार करें? हमें स्टैंसिल बफर का उपयोग करना होगा!

इनमें से प्रत्येक कॉल के लिए, एक स्टैंसिल परीक्षण किया जाता है, और केवल उन पिक्सेल को "8" (पहला ड्रॉ कॉल) या "4" के रूप में चिह्नित किया जाता है।

निशान के लिए स्टैंसिल परीक्षण का दृश्य:


... और दिलचस्प वस्तुओं के लिए:


इस मामले में परीक्षण कैसे किया जाता है? आप एक अच्छी पोस्ट में स्टैंसिल परीक्षण की मूल बातें जान सकते हैं सामान्य तौर पर, स्टैंसिल परीक्षण सूत्र में निम्न रूप होते हैं:

  if (StencilRef & StencilReadMask OP StencilValue & StencilReadMask) accept pixel else discard pixel 

:
StencilRef — , API,

StencilReadMask — , (, , ),

OP — , API,

StencilValue — - .

, AND.

, , :







! , ReadMask. ! -:

  Let StencilReadMask = 0x08 and StencilRef = 0: For a pixel with stencil = 8: 0 & 0x08 < 8 & 0x08 0 < 8 TRUE For a pixel with stencil = 4: 0 & 0x08 < 4 & 0x08 0 < 0 FALSE 

चालाक। जैसा कि आप देख सकते हैं, इस मामले में हम स्टेंसिल मान की तुलना नहीं करते हैं, लेकिन यह जांचें कि स्टेंसिल बफर का एक निश्चित बिट सेट है या नहीं। स्टैंसिल बफर के प्रत्येक पिक्सेल में uint8 प्रारूप होता है, इसलिए मानों का अंतराल [0-255] होता है।

नोट: सभी DrawIndexed (36) कॉल निशान के रूप में पैरों के निशान के प्रतिपादन से संबंधित हैं, इसलिए इस विशेष फ्रेम में चमक मानचित्र में निम्नलिखित अंतिम रूप है:


लेकिन स्टैंसिल परीक्षण से पहले एक पिक्सेल shader है। 28738 और 28748 दोनों एक ही पिक्सेल शेडर का उपयोग करते हैं:

  ps_5_0 dcl_globalFlags refactoringAllowed dcl_constantbuffer cb0[2], immediateIndexed dcl_constantbuffer cb3[8], immediateIndexed dcl_constantbuffer cb12[214], immediateIndexed dcl_sampler s15, mode_default dcl_resource_texture2d (float,float,float,float) t15 dcl_input_ps_siv v0.xy, position dcl_output o0.xyzw dcl_output o1.xyzw dcl_output o2.xyzw dcl_output o3.xyzw dcl_temps 2 0: mul r0.xy, v0.xyxx, cb0[1].zwzz 1: sample_indexable(texture2d)(float,float,float,float) r0.x, r0.xyxx, t15.xyzw, s15 2: mul r1.xyzw, v0.yyyy, cb12[211].xyzw 3: mad r1.xyzw, cb12[210].xyzw, v0.xxxx, r1.xyzw 4: mad r0.xyzw, cb12[212].xyzw, r0.xxxx, r1.xyzw 5: add r0.xyzw, r0.xyzw, cb12[213].xyzw 6: div r0.xyz, r0.xyzx, r0.wwww 7: add r0.xyz, r0.xyzx, -cb3[7].xyzx 8: dp3 r0.x, r0.xyzx, r0.xyzx 9: sqrt r0.x, r0.x 10: mul r0.y, r0.x, l(0.120000) 11: log r1.x, abs(cb3[6].y) 12: mul r1.xy, r1.xxxx, l(2.800000, 0.800000, 0.000000, 0.000000) 13: exp r1.xy, r1.xyxx 14: mad r0.zw, r1.xxxy, l(0.000000, 0.000000, 120.000000, 120.000000), l(0.000000, 0.000000, 1.000000, 1.000000) 15: lt r1.x, l(0.030000), cb3[6].y 16: movc r0.xy, r1.xxxx, r0.yzyy, r0.xwxx 17: div r0.x, r0.x, r0.y 18: log r0.x, r0.x 19: mul r0.x, r0.x, l(1.600000) 20: exp r0.x, r0.x 21: add r0.x, -r0.x, l(1.000000) 22: max r0.x, r0.x, l(0) 23: mul o0.xyz, r0.xxxx, cb3[0].xyzx 24: mov o0.w, cb3[0].w 25: mov o1.xyzw, cb3[1].xyzw 26: mov o2.xyzw, cb3[2].xyzw 27: mov o3.xyzw, cb3[3].xyzw 28: ret 

render target, 24-27 .

, — ( ), 1. ( 2-6).

(cb3[7].xyz — , !), ( 7-9).

:

— cb3[0].rgb — . float3(0, 1, 0) () float3(1, 0, 0) ( ),
- cb3 [6] .y - दूरी स्केलिंग कारक। अंतिम आउटपुट की त्रिज्या और चमक को सीधे प्रभावित करता है।

बाद में, हमारे पास गेराल्ट और वस्तु के बीच की दूरी के आधार पर चमक की गणना के लिए बहुत मुश्किल सूत्र हैं। मैं मान सकता हूं कि सभी गुणांक प्रयोगात्मक रूप से चुने गए हैं।

अंतिम आउटपुट रंग * तीव्रता है

एचएलएसएल कोड कुछ इस तरह दिखेगा:

  struct FSInput { float4 param0 : SV_Position; }; struct FSOutput { float4 param0 : SV_Target0; float4 param1 : SV_Target1; float4 param2 : SV_Target2; float4 param3 : SV_Target3; }; float3 getWorldPos( float2 screenPos, float depth ) { float4 worldPos = float4(screenPos, depth, 1.0); worldPos = mul( worldPos, screenToWorld ); return worldPos.xyz / worldPos.w; } FSOutput EditedShaderPS(in FSInput IN) { // * Inputs // Directly affects radius of the effect float distanceScaling = cb3_v6.y; // Color of output at the end float3 color = cb3_v0.rgb; // Sample depth float2 uv = IN.param0.xy * cb0_v1.zw; float depth = texture15.Sample( sampler15, uv ).x; // Reconstruct world position float3 worldPos = getWorldPos( IN.param0.xy, depth ); // Calculate distance from Geralt to world position of particular object float dist_geraltToWorld = length( worldPos - cb3_v7.xyz ); // Calculate two squeezing params float t0 = 1.0 + 120*pow( abs(distanceScaling), 2.8 ); float t1 = 1.0 + 120*pow( abs(distanceScaling), 0.8 ); // Determine nominator and denominator float2 params; params = (distanceScaling > 0.03) ? float2(dist_geraltToWorld * 0.12, t0) : float2(dist_geraltToWorld, t1); // Distance Geralt <-> Object float nominator = params.x; // Hiding factor float denominator = params.y; // Raise to power of 1.6 float param = pow( params.x / params.y, 1.6 ); // Calculate final intensity float intensity = max(0.0, 1.0 - param ); // * Final outputs. // * // * This PS outputs only one color, the rest // * is redundant. I just added this to keep 1-1 ratio with // * original assembly. FSOutput OUT = (FSOutput)0; OUT.param0.xyz = color * intensity; // == redundant == OUT.param0.w = cb3_v0.w; OUT.param1 = cb3_v1; OUT.param2 = cb3_v2; OUT.param3 = cb3_v3; // =============== return OUT; } 

मूल (बाएं) और मेरे (दाएं) कोडांतरक shader कोड की एक छोटी तुलना।


यह चुड़ैल के स्वभाव के प्रभाव का पहला चरण था वास्तव में, यह सबसे सरल है।

भाग 4. द विचर फ्लेयर (रूपरेखा मानचित्र)


एक बार फिर, उस दृश्य को देखें, जिसे हम देख रहे हैं:


, „ “.

R11G11B10_FLOAT, :


„“, — , .

, — „ “.


512x512 R16G16_FLOAT. , „-“. ( ) .

„-“ , ():

  // Declarations Texture2D m_texOutlineMap[2]; uint m_outlineIndex = 0; // Rendering void Render() { pDevCon->SetInputTexture( m_texOutlineMap[m_outlineIndex] ); pDevCon->SetOutputTexture( m_texOutlineMap[!m_outlineIndex] ); ... pDevCon->Draw(...); // after draw m_outlineIndex = !m_outlineIndex; } 

यह दृष्टिकोण, जहां इनपुट हमेशा [m_outlineIndex] होता है , और आउटपुट हमेशा [! M_outlineIndex] होता है , आगे के प्रभावों के उपयोग के लिए लचीलापन प्रदान करता है।

आइए एक नज़र डालते हैं पिक्सेल शेडर पर:

  ps_5_0 dcl_globalFlags refactoringAllowed dcl_constantbuffer cb3[1], immediateIndexed dcl_sampler s0, mode_default dcl_sampler s1, mode_default dcl_resource_texture2d (float,float,float,float) t0 dcl_resource_texture2d (float,float,float,float) t1 dcl_input_ps linear v2.xy dcl_output o0.xyzw dcl_temps 4 0: add r0.xyzw, v2.xyxy, v2.xyxy 1: round_ni r1.xy, r0.zwzz 2: frc r0.xyzw, r0.xyzw 3: add r1.zw, r1.xxxy, l(0.000000, 0.000000, -1.000000, -1.000000) 4: dp2 r1.z, r1.zwzz, r1.zwzz 5: add r1.z, -r1.z, l(1.000000) 6: max r2.w, r1.z, l(0) 7: dp2 r1.z, r1.xyxx, r1.xyxx 8: add r3.xyzw, r1.xyxy, l(-1.000000, -0.000000, -0.000000, -1.000000) 9: add r1.x, -r1.z, l(1.000000) 10: max r2.x, r1.x, l(0) 11: dp2 r1.x, r3.xyxx, r3.xyxx 12: dp2 r1.y, r3.zwzz, r3.zwzz 13: add r1.xy, -r1.xyxx, l(1.000000, 1.000000, 0.000000, 0.000000) 14: max r2.yz, r1.xxyx, l(0, 0, 0, 0) 15: sample_indexable(texture2d)(float,float,float,float) r1.xyzw, r0.zwzz, t1.xyzw, s1 16: dp4 r1.x, r1.xyzw, r2.xyzw 17: add r2.xyzw, r0.zwzw, l(0.003906, 0.000000, -0.003906, 0.000000) 18: add r0.xyzw, r0.xyzw, l(0.000000, 0.003906, 0.000000, -0.003906) 19: sample_indexable(texture2d)(float,float,float,float) r1.yz, r2.xyxx, t1.zxyw, s1 20: sample_indexable(texture2d)(float,float,float,float) r2.xy, r2.zwzz, t1.xyzw, s1 21: add r1.yz, r1.yyzy, -r2.xxyx 22: sample_indexable(texture2d)(float,float,float,float) r0.xy, r0.xyxx, t1.xyzw, s1 23: sample_indexable(texture2d)(float,float,float,float) r0.zw, r0.zwzz, t1.zwxy, s1 24: add r0.xy, -r0.zwzz, r0.xyxx 25: max r0.xy, abs(r0.xyxx), abs(r1.yzyy) 26: min r0.xy, r0.xyxx, l(1.000000, 1.000000, 0.000000, 0.000000) 27: mul r0.xy, r0.xyxx, r1.xxxx 28: sample_indexable(texture2d)(float,float,float,float) r0.zw, v2.xyxx, t0.zwxy, s0 29: mad r0.w, r1.x, l(0.150000), r0.w 30: mad r0.x, r0.x, l(0.350000), r0.w 31: mad r0.x, r0.y, l(0.350000), r0.x 32: mul r0.yw, cb3[0].zzzw, l(0.000000, 300.000000, 0.000000, 300.000000) 33: mad r0.yw, v2.xxxy, l(0.000000, 150.000000, 0.000000, 150.000000), r0.yyyw 34: ftoi r0.yw, r0.yyyw 35: bfrev r0.w, r0.w 36: iadd r0.y, r0.w, r0.y 37: ishr r0.w, r0.y, l(13) 38: xor r0.y, r0.y, r0.w 39: imul null, r0.w, r0.y, r0.y 40: imad r0.w, r0.w, l(0x0000ec4d), l(0.0000000000000000000000000000000000001) 41: imad r0.y, r0.y, r0.w, l(146956042240.000000) 42: and r0.y, r0.y, l(0x7fffffff) 43: itof r0.y, r0.y 44: mad r0.y, r0.y, l(0.000000001), l(0.650000) 45: add_sat r1.xyzw, v2.xyxy, l(0.001953, 0.000000, -0.001953, 0.000000) 46: sample_indexable(texture2d)(float,float,float,float) r0.w, r1.xyxx, t0.yzwx, s0 47: sample_indexable(texture2d)(float,float,float,float) r1.x, r1.zwzz, t0.xyzw, s0 48: add r0.w, r0.w, r1.x 49: add_sat r1.xyzw, v2.xyxy, l(0.000000, 0.001953, 0.000000, -0.001953) 50: sample_indexable(texture2d)(float,float,float,float) r1.x, r1.xyxx, t0.xyzw, s0 51: sample_indexable(texture2d)(float,float,float,float) r1.y, r1.zwzz, t0.yxzw, s0 52: add r0.w, r0.w, r1.x 53: add r0.w, r1.y, r0.w 54: mad r0.w, r0.w, l(0.250000), -r0.z 55: mul r0.w, r0.y, r0.w 56: mul r0.y, r0.y, r0.z 57: mad r0.x, r0.w, l(0.900000), r0.x 58: mad r0.y, r0.y, l(-0.240000), r0.x 59: add r0.x, r0.y, r0.z 60: mov_sat r0.z, cb3[0].x 61: log r0.z, r0.z 62: mul r0.z, r0.z, l(100.000000) 63: exp r0.z, r0.z 64: mad r0.z, r0.z, l(0.160000), l(0.700000) 65: mul o0.xy, r0.zzzz, r0.xyxx 66: mov o0.zw, l(0, 0, 0, 0) 67: ret 

जैसा कि आप देख सकते हैं, आउटपुट समोच्च मानचित्र को चार समान वर्गों में विभाजित किया गया है, और यह पहली चीज है जिसे हमें अध्ययन करने की आवश्यकता है:

  0: add r0.xyzw, v2.xyxy, v2.xyxy 1: round_ni r1.xy, r0.zwzz 2: frc r0.xyzw, r0.xyzw 3: add r1.zw, r1.xxxy, l(0.000000, 0.000000, -1.000000, -1.000000) 4: dp2 r1.z, r1.zwzz, r1.zwzz 5: add r1.z, -r1.z, l(1.000000) 6: max r2.w, r1.z, l(0) 7: dp2 r1.z, r1.xyxx, r1.xyxx 8: add r3.xyzw, r1.xyxy, l(-1.000000, -0.000000, -0.000000, -1.000000) 9: add r1.x, -r1.z, l(1.000000) 10: max r2.x, r1.x, l(0) 11: dp2 r1.x, r3.xyxx, r3.xyxx 12: dp2 r1.y, r3.zwzz, r3.zwzz 13: add r1.xy, -r1.xyxx, l(1.000000, 1.000000, 0.000000, 0.000000) 14: max r2.yz, r1.xxyx, l(0, 0, 0, 0) 

हम फर्श की गणना करके शुरू करते हैं (टेक्सुरयूवी * 2.0), जो हमें निम्नलिखित देता है:


अलग-अलग वर्गों को निर्धारित करने के लिए, एक छोटे से फ़ंक्शन का उपयोग किया जाता है:

  float getParams(float2 uv) { float d = dot(uv, uv); d = 1.0 - d; d = max( d, 0.0 ); return d; } 

, 1.0 float2(0.0, 0.0).

. , texcoords float2(1, 0), float2(0, 1), — float2(1.0, 1.0).

तो:

  float2 flooredTextureUV = floor( 2.0 * TextureUV ); ... float2 uv1 = flooredTextureUV; float2 uv2 = flooredTextureUV + float2(-1.0, -0.0); float2 uv3 = flooredTextureUV + float2( -0.0, -1.0); float2 uv4 = flooredTextureUV + float2(-1.0, -1.0); float4 mask; mask.x = getParams( uv1 ); mask.y = getParams( uv2 ); mask.z = getParams( uv3 ); mask.w = getParams( uv4 ); 

mask , , . , mask.r mask.w :


mask.r


mask.w

हमें मुखौटा मिला है , चलो आगे बढ़ते हैं। लाइन 15 नमूने luminance नक्शा। ध्यान दें कि luminance बनावट R11G11B10_FLOAT प्रारूप में है, हालांकि हम सभी rgba घटकों का नमूना लेते हैं। इस स्थिति में, यह माना जाता है कि .a 1.0f है।

इस ऑपरेशन के लिए उपयोग किए जाने वाले टेक्सकोर्ड्स की गणना फ्राक के रूप में की जा सकती है इसलिए, इस ऑपरेशन का परिणाम, उदाहरण के लिए, इस तरह दिख सकता है:


समानता देखें?

अगला चरण बहुत स्मार्ट है - चार-घटक अदिश उत्पाद (dp4) किया जाता है:

  16: dp4 r1.x, r1.xyzw, r2.xyzw 

( ), — ( ), — ( .w 1.0). . :


masterFilter , . , . — .

: (: 1.0/256.0!) :

  float fTexel = 1.0 / 256; float2 sampling1 = TextureUV + float2( fTexel, 0 ); float2 sampling2 = TextureUV + float2( -fTexel, 0 ); float2 sampling3 = TextureUV + float2( 0, fTexel ); float2 sampling4 = TextureUV + float2( 0, -fTexel ); float2 intensity_x0 = texIntensityMap.Sample( sampler1, sampling1 ).xy; float2 intensity_x1 = texIntensityMap.Sample( sampler1, sampling2 ).xy; float2 intensity_diff_x = intensity_x0 - intensity_x1; float2 intensity_y0 = texIntensityMap.Sample( sampler1, sampling3 ).xy; float2 intensity_y1 = texIntensityMap.Sample( sampler1, sampling4 ).xy; float2 intensity_diff_y = intensity_y0 - intensity_y1; float2 maxAbsDifference = max( abs(intensity_diff_x), abs(intensity_diff_y) ); maxAbsDifference = saturate(maxAbsDifference); 

अब अगर हम maxAbsDifference द्वारा फ़िल्टर को गुणा करते हैं ...


बहुत ही सरल और कुशल।

आकृति प्राप्त करने के बाद, हम पिछले फ्रेम से समोच्च मानचित्र का नमूना लेते हैं।

फिर, "भूतिया" प्रभाव प्राप्त करने के लिए, हम वर्तमान पास पर गणना किए गए मापदंडों और समोच्च मानचित्र से मानों का एक हिस्सा लेते हैं।

हमारे पुराने मित्र को नमस्ते कहें - पूर्णांक का शोर। वह यहां मौजूद है। एनिमेशन पैरामीटर (cb3 [0] .zw) निरंतर बफर से लिया जाता है और समय के साथ बदलता है।

  float2 outlines = masterFilter * maxAbsDifference; // Sample outline map float2 outlineMap = texOutlineMap.Sample( samplerLinearWrap, uv ).xy; // I guess it's related with ghosting float paramOutline = masterFilter*0.15 + outlineMap.y; paramOutline += 0.35 * outlines.r; paramOutline += 0.35 * outlines.g; // input for integer noise float2 noiseWeights = cb3_v0.zw; float2 noiseInputs = 150.0*uv + 300.0*noiseWeights; int2 iNoiseInputs = (int2) noiseInputs; float noise0 = clamp( integerNoise( iNoiseInputs.x + reversebits(iNoiseInputs.y) ), -1, 1 ) + 0.65; // r0.y 

: , [-1;1] ( -). TW3 , .

, ( 1.0/512.0), .x:

  // sampling of outline map fTexel = 1.0 / 512.0; sampling1 = saturate( uv + float2( fTexel, 0 ) ); sampling2 = saturate( uv + float2( -fTexel, 0 ) ); sampling3 = saturate( uv + float2( 0, fTexel ) ); sampling4 = saturate( uv + float2( 0, -fTexel ) ); float outline_x0 = texOutlineMap.Sample( sampler0, sampling1 ).x; float outline_x1 = texOutlineMap.Sample( sampler0, sampling2 ).x; float outline_y0 = texOutlineMap.Sample( sampler0, sampling3 ).x; float outline_y1 = texOutlineMap.Sample( sampler0, sampling4 ).x; float averageOutline = (outline_x0+outline_x1+outline_y0+outline_y1) / 4.0; 

, , , :

  // perturb with noise float frameOutlineDifference = averageOutline - outlineMap.x; frameOutlineDifference *= noise0; 

अगला कदम शोर का उपयोग करके "पुराने" समोच्च मानचित्र से मूल्य को विकृत करना है - यह मुख्य पंक्ति है जो आउटपुट बनावट को एक अवरुद्ध महसूस देती है।

फिर अन्य गणनाएं होती हैं, जिसके बाद, बहुत अंत में, "क्षीणन" की गणना की जाती है।

  // the main place with gives blocky look of texture float newNoise = outlineMap.x * noise0; float newOutline = frameOutlineDifference * 0.9 + paramOutline; newOutline -= 0.24*newNoise; // 59: add r0.x, r0.y, r0.z float2 finalOutline = float2( outlineMap.x + newOutline, newOutline); // * calculate damping float dampingParam = saturate( cb3_v0.x ); dampingParam = pow( dampingParam, 100 ); float damping = 0.7 + 0.16*dampingParam; // * final multiplication float2 finalColor = finalOutline * damping; return float4(finalColor, 0, 0); 

यहां एक छोटा वीडियो दिखाया गया है जो कार्रवाई में एक रूपरेखा मानचित्र प्रदर्शित करता है:


, . RenderDoc.

(, , ) , Witcher 3, RenderDoc !

: (. ) , .r . .g? , - „-“ — , .r .g + - .

5: (» " )


, : , , , . , .

. ! — . : , .

:



के बाद:


:


, , , « », ( ) , .

:

  ps_5_0 dcl_globalFlags refactoringAllowed dcl_constantbuffer cb0[3], immediateIndexed dcl_constantbuffer cb3[7], immediateIndexed dcl_sampler s0, mode_default dcl_sampler s2, mode_default dcl_resource_texture2d (float,float,float,float) t0 dcl_resource_texture2d (float,float,float,float) t2 dcl_resource_texture2d (float,float,float,float) t3 dcl_input_ps_siv v0.xy, position dcl_output o0.xyzw dcl_temps 7 0: div r0.xy, v0.xyxx, cb0[2].xyxx 1: mad r0.zw, r0.xxxy, l(0.000000, 0.000000, 2.000000, 2.000000), l(0.000000, 0.000000, -1.000000, -1.000000) 2: mov r1.yz, abs(r0.zzwz) 3: div r0.z, cb0[2].x, cb0[2].y 4: mul r1.x, r0.z, r1.y 5: add r0.zw, r1.xxxz, -cb3[2].xxxy 6: mul_sat r0.zw, r0.zzzw, l(0.000000, 0.000000, 0.555556, 0.555556) 7: log r0.zw, r0.zzzw 8: mul r0.zw, r0.zzzw, l(0.000000, 0.000000, 2.500000, 2.500000) 9: exp r0.zw, r0.zzzw 10: dp2 r0.z, r0.zwzz, r0.zwzz 11: sqrt r0.z, r0.z 12: min r0.z, r0.z, l(1.000000) 13: add r0.z, -r0.z, l(1.000000) 14: mov_sat r0.w, cb3[6].x 15: add_sat r1.xy, -r0.xyxx, l(0.030000, 0.030000, 0.000000, 0.000000) 16: add r1.x, r1.y, r1.x 17: add_sat r0.xy, r0.xyxx, l(-0.970000, -0.970000, 0.000000, 0.000000) 18: add r0.x, r0.x, r1.x 19: add r0.x, r0.y, r0.x 20: mul r0.x, r0.x, l(20.000000) 21: min r0.x, r0.x, l(1.000000) 22: add r1.xy, v0.xyxx, v0.xyxx 23: div r1.xy, r1.xyxx, cb0[2].xyxx 24: add r1.xy, r1.xyxx, l(-1.000000, -1.000000, 0.000000, 0.000000) 25: dp2 r0.y, r1.xyxx, r1.xyxx 26: mul r1.xy, r0.yyyy, r1.xyxx 27: mul r0.y, r0.w, l(0.100000) 28: mul r1.xy, r0.yyyy, r1.xyxx 29: max r1.xy, r1.xyxx, l(-0.400000, -0.400000, 0.000000, 0.000000) 30: min r1.xy, r1.xyxx, l(0.400000, 0.400000, 0.000000, 0.000000) 31: mul r1.xy, r1.xyxx, cb3[1].xxxx 32: mul r1.zw, r1.xxxy, cb0[2].zzzw 33: mad r1.zw, v0.xxxy, cb0[1].zzzw, -r1.zzzw 34: sample_indexable(texture2d)(float,float,float,float) r2.xyz, r1.zwzz, t0.xyzw, s0 35: mul r3.xy, r1.zwzz, l(0.500000, 0.500000, 0.000000, 0.000000) 36: sample_indexable(texture2d)(float,float,float,float) r0.y, r3.xyxx, t2.yxzw, s2 37: mad r3.xy, r1.zwzz, l(0.500000, 0.500000, 0.000000, 0.000000), l(0.500000, 0.000000, 0.000000, 0.000000) 38: sample_indexable(texture2d)(float,float,float,float) r2.w, r3.xyxx, t2.yzwx, s2 39: mul r2.w, r2.w, l(0.125000) 40: mul r3.x, cb0[0].x, l(0.100000) 41: add r0.x, -r0.x, l(1.000000) 42: mul r0.xy, r0.xyxx, l(0.030000, 0.125000, 0.000000, 0.000000) 43: mov r3.yzw, l(0, 0, 0, 0) 44: mov r4.x, r0.y 45: mov r4.y, r2.w 46: mov r4.z, l(0) 47: loop 48: ige r4.w, r4.z, l(8) 49: breakc_nz r4.w 50: itof r4.w, r4.z 51: mad r4.w, r4.w, l(0.785375), -r3.x 52: sincos r5.x, r6.x, r4.w 53: mov r6.y, r5.x 54: mul r5.xy, r0.xxxx, r6.xyxx 55: mad r5.zw, r5.xxxy, l(0.000000, 0.000000, 0.125000, 0.125000), r1.zzzw 56: mul r6.xy, r5.zwzz, l(0.500000, 0.500000, 0.000000, 0.000000) 57: sample_indexable(texture2d)(float,float,float,float) r4.w, r6.xyxx, t2.yzwx, s2 58: mad r4.x, r4.w, l(0.125000), r4.x 59: mad r5.zw, r5.zzzw, l(0.000000, 0.000000, 0.500000, 0.500000), l(0.000000, 0.000000, 0.500000, 0.000000) 60: sample_indexable(texture2d)(float,float,float,float) r4.w, r5.zwzz, t2.yzwx, s2 61: mad r4.y, r4.w, l(0.125000), r4.y 62: mad r5.xy, r5.xyxx, r1.xyxx, r1.zwzz 63: sample_indexable(texture2d)(float,float,float,float) r5.xyz, r5.xyxx, t0.xyzw, s0 64: mad r3.yzw, r5.xxyz, l(0.000000, 0.125000, 0.125000, 0.125000), r3.yyzw 65: iadd r4.z, r4.z, l(1) 66: endloop 67: sample_indexable(texture2d)(float,float,float,float) r0.xy, r1.zwzz, t3.xyzw, s0 68: mad_sat r0.xy, -r0.xyxx, l(0.800000, 0.750000, 0.000000, 0.000000), r4.xyxx 69: dp3 r1.x, r3.yzwy, l(0.300000, 0.300000, 0.300000, 0.000000) 70: add r1.yzw, -r1.xxxx, r3.yyzw 71: mad r1.xyz, r0.zzzz, r1.yzwy, r1.xxxx 72: mad r1.xyz, r1.xyzx, l(0.600000, 0.600000, 0.600000, 0.000000), -r2.xyzx 73: mad r1.xyz, r0.wwww, r1.xyzx, r2.xyzx 74: mul r0.yzw, r0.yyyy, cb3[4].xxyz 75: mul r2.xyz, r0.xxxx, cb3[5].xyzx 76: mad r0.xyz, r0.yzwy, l(1.200000, 1.200000, 1.200000, 0.000000), r2.xyzx 77: mov_sat r2.xyz, r0.xyzx 78: dp3_sat r0.x, r0.xyzx, l(1.000000, 1.000000, 1.000000, 0.000000) 79: add r0.yzw, -r1.xxyz, r2.xxyz 80: mad o0.xyz, r0.xxxx, r0.yzwy, r1.xyzx 81: mov o0.w, l(1.000000) 82: ret 

82 — , !

:

  // *** Inputs // * Zoom amount, always 1 float zoomAmount = cb3_v1.x; // Another value which affect fisheye effect // but always set to float2(1.0, 1.0). float2 amount = cb0_v2.zw; // Elapsed time in seconds float time = cb0_v0.x; // Colors of witcher senses float3 colorInteresting = cb3_v5.rgb; float3 colorTraces = cb3_v4.rgb; // Was always set to float2(0.0, 0.0). // Setting this to higher values // makes "grey corners" effect weaker. float2 offset = cb3_v2.xy; // Dimensions of fullscreen float2 texSize = cb0_v2.xy; float2 invTexSize = cb0_v1.zw; // Main value which causes fisheye effect [0-1] const float fisheyeAmount = saturate( cb3_v6.x ); 

, — fisheyeAmount . , 0.0 1.0, . , , , fisheye ( ).

, — , :

  0: div r0.xy, v0.xyxx, cb0[2].xyxx 1: mad r0.zw, r0.xxxy, l(0.000000, 0.000000, 2.000000, 2.000000), l(0.000000, 0.000000, -1.000000, -1.000000) 2: mov r1.yz, abs(r0.zzwz) 3: div r0.z, cb0[2].x, cb0[2].y 4: mul r1.x, r0.z, r1.y 5: add r0.zw, r1.xxxz, -cb3[2].xxxy 6: mul_sat r0.zw, r0.zzzw, l(0.000000, 0.000000, 0.555556, 0.555556) 7: log r0.zw, r0.zzzw 8: mul r0.zw, r0.zzzw, l(0.000000, 0.000000, 2.500000, 2.500000) 9: exp r0.zw, r0.zzzw 10: dp2 r0.z, r0.zwzz, r0.zwzz 11: sqrt r0.z, r0.z 12: min r0.z, r0.z, l(1.000000) 13: add r0.z, -r0.z, l(1.000000) 

HLSL :

  // Main uv float2 uv = PosH.xy / texSize; // Scale at first from [0-1] to [-1;1], then calculate abs float2 uv3 = abs( uv * 2.0 - 1.0); // Aspect ratio float aspectRatio = texSize.x / texSize.y; // * Mask used to make corners grey float mask_gray_corners; { float2 newUv = float2( uv3.x * aspectRatio, uv3.y ) - offset; newUv = saturate( newUv / 1.8 ); newUv = pow(newUv, 2.5); mask_gray_corners = 1-min(1.0, length(newUv) ); } 

[-1; 1] UV . «». :


.

, «».

  22: add r1.xy, v0.xyxx, v0.xyxx 23: div r1.xy, r1.xyxx, cb0[2].xyxx 24: add r1.xy, r1.xyxx, l(-1.000000, -1.000000, 0.000000, 0.000000) 25: dp2 r0.y, r1.xyxx, r1.xyxx 26: mul r1.xy, r0.yyyy, r1.xyxx 27: mul r0.y, r0.w, l(0.100000) 28: mul r1.xy, r0.yyyy, r1.xyxx 29: max r1.xy, r1.xyxx, l(-0.400000, -0.400000, 0.000000, 0.000000) 30: min r1.xy, r1.xyxx, l(0.400000, 0.400000, 0.000000, 0.000000) 31: mul r1.xy, r1.xyxx, cb3[1].xxxx 32: mul r1.zw, r1.xxxy, cb0[2].zzzw 33: mad r1.zw, v0.xxxy, cb0[1].zzzw, -r1.zzzw 

«» float2(1, 1):

  float2 uv4 = 2 * PosH.xy; uv4 /= cb0_v2.xy; uv4 -= float2(1.0, 1.0); 

texcoord :


dot(uv4, uv4) , :


texcoords:


: ( ) . (0.0) - R11G11B10_FLOAT. , .

( , fisheyeAmount 0.0 1.0).

  float attenuation = fisheyeAmount * 0.1; uv4 *= attenuation; 

(max/min) .

. uv, , :

float2 colorUV = mainUv — offset;

colorUV , :



— . , texcoords , :

  // * Sample outline map // interesting objects (upper left square) float2 outlineUV = colorUV * 0.5; float outlineInteresting = texture2.Sample( sampler2, outlineUV ).x; // r0.y // traces (upper right square) outlineUV = colorUV * 0.5 + float2(0.5, 0.0); float outlineTraces = texture2.Sample( sampler2, outlineUV ).x; // r2.w outlineInteresting /= 8.0; // r4.x outlineTraces /= 8.0; // r4.y 







, .x .


, . 8 , .

, 8.0.

[0-1] 2 , 1 :


, , . 15-21. , (, - ). (15-21) (41-42):

  15: add_sat r1.xy, -r0.xyxx, l(0.030000, 0.030000, 0.000000, 0.000000) 16: add r1.x, r1.y, r1.x 17: add_sat r0.xy, r0.xyxx, l(-0.970000, -0.970000, 0.000000, 0.000000) 18: add r0.x, r0.x, r1.x 19: add r0.x, r0.y, r0.x 20: mul r0.x, r0.x, l(20.000000) 21: min r0.x, r0.x, l(1.000000) ... 41: add r0.x, -r0.x, l(1.000000) 42: mul r0.xy, r0.xyxx, l(0.030000, 0.125000, 0.000000, 0.000000) 

जैसा कि आप देख सकते हैं, हम प्रत्येक सतह के बगल में केवल 3.०० - ०.०३] से टेक्सल्स पर विचार करते हैं, उनके मूल्यों को संक्षेप में, २० और संतृप्त करते हैं। यहां 15-21 लाइनों के बाद वे क्या दिखते हैं:


और यहाँ 41 लाइन के बाद कैसे है:


42 रेखा पर, हम इसे 0.03 से गुणा करते हैं, यह मान पूरी स्क्रीन के लिए सर्कल का त्रिज्या है। जैसा कि आप देख सकते हैं, स्क्रीन के किनारों के करीब, त्रिज्या छोटा हो जाता है।

अब हम आंदोलन के लिए जिम्मेदार कोडांतरक कोड को देख सकते हैं:

  40: mul r3.x, cb0[0].x, l(0.100000) 41: add r0.x, -r0.x, l(1.000000) 42: mul r0.xy, r0.xyxx, l(0.030000, 0.125000, 0.000000, 0.000000) 43: mov r3.yzw, l(0, 0, 0, 0) 44: mov r4.x, r0.y 45: mov r4.y, r2.w 46: mov r4.z, l(0) 47: loop 48: ige r4.w, r4.z, l(8) 49: breakc_nz r4.w 50: itof r4.w, r4.z 51: mad r4.w, r4.w, l(0.785375), -r3.x 52: sincos r5.x, r6.x, r4.w 53: mov r6.y, r5.x 54: mul r5.xy, r0.xxxx, r6.xyxx 55: mad r5.zw, r5.xxxy, l(0.000000, 0.000000, 0.125000, 0.125000), r1.zzzw 56: mul r6.xy, r5.zwzz, l(0.500000, 0.500000, 0.000000, 0.000000) 57: sample_indexable(texture2d)(float,float,float,float) r4.w, r6.xyxx, t2.yzwx, s2 58: mad r4.x, r4.w, l(0.125000), r4.x 59: mad r5.zw, r5.zzzw, l(0.000000, 0.000000, 0.500000, 0.500000), l(0.000000, 0.000000, 0.500000, 0.000000) 60: sample_indexable(texture2d)(float,float,float,float) r4.w, r5.zwzz, t2.yzwx, s2 61: mad r4.y, r4.w, l(0.125000), r4.y 62: mad r5.xy, r5.xyxx, r1.xyxx, r1.zwzz 63: sample_indexable(texture2d)(float,float,float,float) r5.xyz, r5.xyxx, t0.xyzw, s0 64: mad r3.yzw, r5.xxyz, l(0.000000, 0.125000, 0.125000, 0.125000), r3.yyzw 65: iadd r4.z, r4.z, l(1) 66: endloop 

. 40 — elapsedTime * 0.1 . 43 , .

r0.x ( 41-42) — , , . r4.x ( 44) — , r4.y ( 45) — ( 8!), r4.z ( 46) — .

, 8 . i * PI_4 , 2*PI — . .

sincos ( ) ( 54).

. ( 8) .

  float timeParam = time * 0.1; // adjust circle radius circle_radius = 1.0 - circle_radius; circle_radius *= 0.03; float3 color_circle_main = float3(0.0, 0.0, 0.0); [loop] for (int i=0; 8 > i; i++) { // full 2*PI = 360 angles cycle const float angleRadians = (float) i * PI_4 - timeParam; // unit circle float2 unitCircle; sincos(angleRadians, unitCircle.y, unitCircle.x); // unitCircle.x = cos, unitCircle.y = sin // adjust radius unitCircle *= circle_radius; // * base texcoords (circle) - note we also scale radius here by 8 // * probably because of dimensions of outline map. // line 55 float2 uv_outline_base = colorUV + unitCircle / 8.0; // * interesting objects (circle) float2 uv_outline_interesting_circle = uv_outline_base * 0.5; float outline_interesting_circle = texture2.Sample( sampler2, uv_outline_interesting_circle ).x; outlineInteresting += outline_interesting_circle / 8.0; // * traces (circle) float2 uv_outline_traces_circle = uv_outline_base * 0.5 + float2(0.5, 0.0); float outline_traces_circle = texture2.Sample( sampler2, uv_outline_traces_circle ).x; outlineTraces += outline_traces_circle / 8.0; // * sample color texture (zooming effect) with perturbation float2 uv_color_circle = colorUV + unitCircle * offsetUV; float3 color_circle = texture0.Sample( sampler0, uv_color_circle ).rgb; color_circle_main += color_circle / 8.0; } 

, colorUV , «» .


( ):

  67: sample_indexable(texture2d)(float,float,float,float) r0.xy, r1.zwzz, t3.xyzw, s0 68: mad_sat r0.xy, -r0.xyxx, l(0.800000, 0.750000, 0.000000, 0.000000), r4.xyxx 

HLSL:

  // * Sample intensity map float2 intensityMap = texture3.Sample( sampler0, colorUV ).xy; float intensityInteresting = intensityMap.r; float intensityTraces = intensityMap.g; // * Adjust outlines float mainOutlineInteresting = saturate( outlineInteresting - 0.8*intensityInteresting ); float mainOutlineTraces = saturate( outlineTraces - 0.75*intensityTraces ); 


( 69):

  // * Greyish color float3 color_greyish = dot( color_circle_main, float3(0.3, 0.3, 0.3) ).xxx; 


. « » , . , 0.6, :


, fisheyeAmount . , ( 0.6) ! .

HLSL:

  // * Determine main color. // (1) At first, combine "circled" color with gray one. // Now we have have greyish corners here. float3 mainColor = lerp( color_greyish, color_circle_main, mask_gray_corners ) * 0.6; // (2) Then mix "regular" color with the above. // Please note this operation makes corners gradually gray (because fisheyeAmount rises from 0 to 1) // and gradually darker (because of 0.6 multiplier). mainColor = lerp( color, mainColor, fisheyeAmount ); 

.

( ) .

  // * Determine color of witcher senses float3 senses_traces = mainOutlineTraces * colorTraces; float3 senses_interesting = mainOutlineInteresting * colorInteresting; float3 senses_total = 1.2 * senses_traces + senses_interesting; 


! !

, … - !

. :

  78: dp3_sat r0.x, r0.xyzx, l(1.000000, 1.000000, 1.000000, 0.000000) float dot_senses_total = saturate( dot(senses_total, float3(1.0, 1.0, 1.0) ) ); 

:


() :

  76: mad r0.xyz, r0.yzwy, l(1.200000, 1.200000, 1.200000, 0.000000), r2.xyzx 77: mov_sat r2.xyz, r0.xyzx 78: dp3_sat r0.x, r0.xyzx, l(1.000000, 1.000000, 1.000000, 0.000000) 79: add r0.yzw, -r1.xxyz, r2.xxyz 80: mad o0.xyz, r0.xxxx, r0.yzwy, r1.xyzx 81: mov o0.w, l(1.000000) 82: ret float3 senses_total = 1.2 * senses_traces + senses_interesting; // * Final combining float3 senses_total_sat = saturate(senses_total); float dot_senses_total = saturate( dot(senses_total, float3(1.0, 1.0, 1.0) ) ); float3 finalColor = lerp( mainColor, senses_total_sat, dot_senses_total ); return float4( finalColor, 1.0 ); 


.

.

() () :


, ! « » , .

[ : .]

Source: https://habr.com/ru/post/hi450332/


All Articles