درباره به تصویر کشیدن آسمان، غروب خورشید و سیارات

این عکسی وجود دارد که مدتی است روی تخته الهام من نشسته است، از شاتل فضایی اندیور که در فضا در مدار پایین زمین در غروب آفتاب معلق شده است. این اتمسفر بالای زمین را به عنوان پسزمینه نشان میدهد که دارای لایههای زیبا و رنگارنگی است که از نارنجی عمیق تا آبی قبل از محو شدن در سیاهی عمیق فضا متغیر است. نه تنها این گرادیان رنگ از نظر زیبایی شناسی دلپذیر است، بلکه پدیده ای که در پشت این رنگ ها قرار دارد، پراکندگی اتمسفر، وقتی شروع به بررسی نحوه عملکرد و نحوه بازتولید آن کنید، موضوع جالبتری است.
من میخواستم نسخهای از این افکت را با سایهزنها بسازم و رنگ آبی متمایز آسمان و غروبها و طلوعهای واقعی خورشید را مستقیماً در مرورگر ارائه کنم. هدف این بود که تا حد امکان به این عکس نزدیک شویم و در عین حال به سمت نوعی رندر جوی حرکت کنیم که اغلب در بازی ها و سایر رسانه های مبتنی بر سایه زن دیده می شود.
در اینجا مجموعه ای از آنچه از آن سفر یک ماهه به دست آمد، در زمان واقعی ارائه شده است:
من در ابتدا قصد نداشتم در مورد این موضوع بنویسم، اما هیجان پیرامون ماموریت اخیر Artemis II، همراه با علاقه شخصی من به همه چیز در فضا، ارزش کاوش عمیق را داشته باشد. همچنین به نظر میرسد فرصتی عالی برای ایجاد یک تجربه تعاملی است که میتواند موضوع را در دسترستر کند.
در این نوشته خواهیم دید نحوه اجرای گام به گام اثر سایه زن پراکنده جوی پس از پردازش، با اجرای بلوک های مختلف ساختمانی (ریمارچینگ، پراکندگی رایلی و می و همچنین جذب ازن) به تصویر کشیده می شود. گنبد آسمان واقعیو سپس نتیجه را برای ارائه آن به صورت تطبیق دهید یک پوشش جوی در اطراف یک سیاره.
در نهایت، ما رویکرد مبتنی بر LUT سباستین هیلر را برای یک نتیجه کارآمدتر، یا حداقل من، بررسی خواهیم کرد. تجربه در کاربرد آن، همانطور که تا حد زیادی بود من از منطقه راحتی خود خارج می شوم فاز برای این پروژه
ممکن است زمانی سعی کرده باشید که پسزمینهای با گرادیان آبی در پشت برخی از کارهای خود قرار دهید تا ظاهری «جویانهتر» به آن ببخشید و آن را یک روز بنامید، اما بهسرعت متوجه شدید که انجام این کار هرگز درست نیست. 1.
برای اجرای واقعی تر، ما باید آسمان و رنگ آن را به عنوان نتیجه برهمکنش نور با هوا و اجزای آن است، ضمن در نظر گرفتن چندین متغیر از جمله ارتفاع ناظر، میزان گرد و غبار، زمان روز و غیره، همه در حجم.
با مشخص شدن این موضوع، هدف ما برای این بخش اول استفاده از آن به عنوان یک اصل راهنما برای ایجاد زمینه ای برای سایه بان اتمسفر و رسیدن به نتیجه ای است که تقریباً در هر زمانی از روز از یک آسمان واقعی قابل تشخیص نیست.
نمونه برداری چگالی جوی
مشابه نحوه نزدیک شدن به ابرهای حجیم یا نور حجیم، یک راه آسان برای نمونه برداری از جو است راهپیمایی مجدد. میتوانیم پرتوهایی را از موقعیت دوربین در صحنه پرتاب کنیم و از محیط شفاف عبور کنیم تا به دو سؤال زیر پاسخ دهیم:
-
چه مقدار نور از طریق جو زنده می ماند؟ همین است نفوذپذیری مدت
-
در هر نمونه چقدر نور به دوربین هدایت می شود؟ همچنین به عنوان شناخته شده است حواس پرتی.
برای پاسخ به اولی، باید چگالی جوی موجود در طول پرتو را جمع آوری کنیم تا آنچه به عنوان شناخته شده عمق نوری. ما این را با استفاده از مدل سازی می کنیم تابع چگالی ریلی، که به ما می گوید چقدر است “هوا” در ارتفاع معین دارد h. مهم است که در نظر داشته باشید که با افزایش ارتفاع، جو نازک تر می شود.
نمونه برداری چگالی ریلی و انباشت عمق نوری
1
const float RAYLEIGH_SCALE_HEIGHT = 8.0;2
const float ATMOSPHERE_HEIGHT = 100.0;3
const float VIEW_DISTANCE = 200.0;4
const int PRIMARY_STEPS = 24;5
const vec3 SUN_DIRECTION = normalize(vec3(0.0, 1.0, 1.0));7
float rayleighDensity(float h) {8
return exp(-max(h, 0.0) / RAYLEIGH_SCALE_HEIGHT);12
vec2 p = vUv * 2.0 - 1.0;14
vec3 color = vec3(0.0);15
vec3 viewDir = normalize(vec3(p.x, p.y, 1.0));16
vec3 skyDir = normalize(vec3(viewDir.x, max(viewDir.y, 0.0), viewDir.z));18
float stepSize = VIEW_DISTANCE / float(PRIMARY_STEPS);19
float viewOpticalDepth = 0.0;21
for (int i = 0; i PRIMARY_STEPS; i++) {22
float t = (float(i) + 0.5) * stepSize;23
float h = t * skyDir.y;26
if (h > ATMOSPHERE_HEIGHT) break;28
float dR = rayleighDensity(h);29
viewOpticalDepth += dR * stepSize;36
color = ACESFilm(color);38
fragColor = vec4(color, 1.0);
سپس از عمق نوری می توانیم محاسبه کنیم نفوذپذیری T در یک نقطه معین در امتداد پرتو: کسری از نوری که در طی سفر در جو زنده می ماند.
اگر مقاله من در مورد ابرهای حجمی را خوانده اید 2، ما از فرمولی استفاده می کنیم که ممکن است برای این کار آشنا به نظر برسد: قانون آبجو:
3
float dR = rayleighDensity(h);4
viewOpticalDepth += dR * stepSize;6
vec3 transmittance = exp(-rayleighBeta * viewOpticalDepth);7
scattering += dR * transmittance * stepSize;
با وجود آن، اکنون میتوانیم توضیح دهیم که چقدر سبک است ضعیف شده همانطور که در جو حرکت می کند. با این حال، چگالی و انتقال تنها به ما می گوید که چه مقدار نور برای پراکندگی در دسترس است، نه اینکه چگونه آن نور به بیننده توزیع می شود. برای این کار باید زاویه بین نور خورشید ورودی و خط دید را محاسبه کنیم تابع فاز ریلی مدل ها
4
const vec3 SUN_DIRECTION = normalize(vec3(0.0, 1.0, 1.0));6
float rayleighPhase(float mu) {7
return 3.0 / (16.0 * PI) * (1.0 + mu * mu);13
float phase = rayleighPhase(dot(skyDir, SUN_DIRECTION));17
scattering *= SUN_INTENSITY * phase * rayleighBeta;19
float horizon = smoothstep(-0.12, 0.05, skyDir.y);20
vec3 color = mix(SPACE_COLOR, scattering, horizon);21
color = ACESFilm(color);23
fragColor = vec4(color, 1.0);
با کنار هم گذاشتن همه اینها، میتوانیم ایدهای تا حدودی دقیق از اینکه چه مقدار نور سرگردان در طول یک پرتو معین در هر ارتفاع معینی جمع میشود، داشته باشیم.
ویجت زیر روندی را نشان می دهد که ما با نشان دادن شما به شما توضیح دادیم:
همانطور که می بینید، ما سایه های آبی را در ارتفاعات پایین تر جمع می کنیم! این بیشتر به دلیل مقدار ضریب پراکندگی ریلی است:
-
قرمز بسیار کم پخش می شود
-
کمی بیشتر سبز شود
-
آبی ترین
از آنجایی که طول موجهای کوتاهتر با شدت بیشتری پراکنده میشوند، نور آبی بیشتری به سمت بیننده هدایت میشود و در نتیجه آسمان ظاهر میشود. آبی در طول روز
اگر این ایده را به یک شیدر قطعه کامل گسترش دهیم، از یک پرتو به یک پرتو در هر پیکسل، میتوانیم یک آسمان واقعی را مطابق شکل زیر ارائه کنیم:
این روند بازسازی، زیبایی را به ارمغان می آورد آسمان آبیبا مه سفید روشنتر به سمت افق که پرتوها از اتمسفر بیشتری در آنجا عبور میکنند، و رنگهای آبی تیرهتر و عمیقتر با افزایش ارتفاع و نازک شدن جو.
می پراکندگی و ازن
اگرچه پراکندگی ریلی به تنهایی نتیجه مناسبی می دهد، هنوز هم اثرات جوی اضافی وجود دارد که می توانیم برای نزدیکتر کردن آسمان به واقعیت در نظر بگیریم:
-
حواس پرتی منکه برهمکنش نور با ذرات بزرگتر در جو، مانند گرد و غبار یا ذرات معلق در هوا را توصیف می کند. تابع چگالی برای محاسبه مقدار ماده در محیط و همچنین تابع فازی دارد که مانند همتای ریلی خود نحوه توزیع مجدد نور در جهات مختلف را توضیح می دهد.
-
جذب ازن، که نحوه جذب ازن مقداری از نور عبوری از جو فوقانی را مدل می کند. این یکی نور را پراکنده نمی کند. فقط برخی از طول موج ها را در طول مسیر حذف می کند. سهم اصلی او به رنگ آسمان را تغییر داده و عمیق تر کندبه خصوص در نزدیکی افق و هنگام غروب یا غروب آفتاب.
اولی را می توان با دو تابع زیر مدل کرد:
چگالی Mie و تابع فاز
1
float miePhase(float mu) {2
float gg = MIE_G * MIE_G;3
float num = 3.0 * (1.0 - gg) * (1.0 + mu * mu);4
float den = 8.0 * PI * (2.0 + gg) * pow(max(1.0 + gg - 2.0 * MIE_G * mu, 1e-4), 1.5);8
float mieDensity(float h) {9
return exp(-max(h, 0.0) / MIE_SCALE_HEIGHT);
برای دریافت اصطلاح پراکندگی به روز شده که پراکندگی Mie و ازن را در نظر می گیرد، به سادگی آن را به اجرای فعلی سایه بان آسمان خود در تابع چگالی و فاز ریلی اضافه می کنیم:
رایلی، اصطلاحات Mie و پراکندگی ازن
9
for (int i = 0; i PRIMARY_STEPS; i++) {10
float t = (float(i) + 0.5) * stepSize;11
float h = uObserverAltitude + t * skyDir.y;14
if (h > ATMOSPHERE_HEIGHT) break;16
float dR = rayleighDensity(h);17
float dM = mieDensity(h);18
float dO = ozoneDensity(h);20
viewODR += dR * stepSize;21
viewODM += dM * stepSize;22
viewODO += dO * stepSize;24
vec3 tau = BETA_R * viewODR25
+ BETA_M_EXT * viewODM26
+ BETA_OZONE_ABS * viewODO;27
vec3 transmittance = exp(-tau);29
sumR += dR * transmittance * stepSize;30
sumM += dM * transmittance * stepSize;31
sumO += dO * transmittance * stepSize;34
vec3 scattering = SUN_INTENSITY * (35
phaseR * BETA_R * sumR +36
phaseM * BETA_M_SCATTER * sumM +37
BETA_OZONE_SCATTER * sumO40
float horizon = smoothstep(-0.12, 0.05, skyDir.y);41
vec3 color = mix(SPACE_COLOR, scattering, horizon);42
color = ACESFilm(color);44
fragColor = vec4(color, 1.0);
ویجت زیر نتیجه ادغام این دو عبارت جدید را در sky shader ما نشان می دهد:
همانطور که می بینید، این نسخه هر دو را ارائه می دهد:


نور و قابلیت عبور
در این مرحله ما یک سایهزن قطعه آسمان مناسب داریم که میتواند رنگ طبیعی را برای هر ارتفاعی نشان دهد و مجموعه متنوعی از مدلهای انتقال (Mie، Rayleigh و Ozone) را در نظر بگیرد. که هنوز نورپردازی را برای کار کردن در اختیار ما می گذارد.
ممکن است در ویجت قبلی متوجه شده باشید که حرکت خورشید در نزدیکی افق فقط منجر به a درخشش سفید و مه آلود، بدون محو شدن نور یا جلوه غروب/طلوع خورشید. این مورد انتظار است زیرا چرخه جستجوی مجدد فعلی ما فقط نوری را که در طول خط دید دوربین به هر نمونه کاهش مییابد محاسبه میکند. هنوز هیچ گزارشی در مورد اینکه چه مقدار نور خورشید هنگام عبور از جو قبل از رسیدن به این نقطه نمونه برداری از بین می رود وجود ندارد.
همانطور که در مقالههای قبلی مرتبط انجام دادهایم، باید برای هر نقطه نمونه در کنار پرتو، یک حلقه تو در تو خود را معرفی کنیم که در جهت منبع نور حرکت میکند و از آن نمونهبرداری میکند. نفوذپذیری در این جاده
در اجرای قبلی ما، عمق نوری فقط در طول پرتو محاسبه می شد viewODR، viewODM، و viewODO. برای این نسخه به روز شده، ما انجام خواهیم داد:
1
vec3 lightMarch(float start, float sunY) {2
float denom = max(sunY + 0.15, 0.04);3
float maxDist = (ATMOSPHERE_HEIGHT - start) / denom;4
float stepSize = max(maxDist, 0.0) / float(LIGHTMARCH_STEPS);9
for (int i = 0; i int(LIGHTMARCH_STEPS); i++) {10
float t = (float(i) + 0.5) * stepSize;11
float h = start + t * sunY;12
if (h 0.0 || h > ATMOSPHERE_HEIGHT) {16
odR += rayleighDensity(h) * stepSize;17
if (uMieEnabled) odM += mieDensity(h) * stepSize;18
if (uOzoneEnabled) odO += ozoneDensity(h) * stepSize;21
return vec3(odR, odM, odO);
1
float dR = rayleighDensity(h);2
float dM = mieDensity(h);3
float dO = ozoneDensity(h);5
viewODR += dR * stepSize;6
viewODM += dM * stepSize;7
viewODO += dO * stepSize;9
vec3 sunOD = uSunAngle > 0.0 && uSunAngle PI ? lightMarch(h, sunDirection.y) : vec3(1000.0);10
vec3 tau = BETA_R * (viewODR + sunOD.x)11
+ BETA_M_EXT * (viewODM + sunOD.y)12
+ BETA_OZONE_ABS * (viewODO + sunOD.z);13
vec3 transmittance = exp(-tau);
با وجود این، ما اکنون این توانایی را داریم که آسمان خود را در هر شرایط نوری نمایش دهیم. غروب خورشید، طلوع خورشید، اوج و هر چیزی در این بین.
من از شما دعوت میکنم کمی استراحت کنید و با ویجت بالا بازی کنید تا از رنگهای مختلف آسمانی که سایهزن ما اکنون میتواند از طریق این مدل آسمان که قبلاً کاملاً پیادهسازی شده ارائه کند، قدردانی کنید. توجه کنید که چگونه:
-
رنگ آبی آسمان در طول روز تغییر می کند، که در اینجا توسط
sun angleیکنواخت و اینکه چگونه نور به لطف پراکندگی Mie به خوبی با افق در هنگام غروب و طلوع خورشید ترکیب می شود. -
ازن به آسمان ما زیبایی خوبی می بخشد بنفش زمانی که خورشید کم است صدا کنید
سایهزنی که در این بخش اول ایجاد کردیم، کادرهای زیادی را بررسی میکند، اما در حال حاضر فقط یک پسزمینه صاف داریم. اگر بخواهیم از آن در یک صحنه React Three Fiber در حالت فعلی استفاده کنیم، فقط یک پسزمینه خوب برای صحنههای خود خواهیم داشت و نه بیشتر.
در این بخش ما شیدر تخت خود را به یک شیدر مناسب تبدیل می کنیم اثر پس از پردازشکه به ما اجازه می دهد جو را به صورت زیر نمایش دهیم:
-
حجم و عمق صحنه در طول مسیر را با بازسازی مختصات فضایی جهان از
screenUVمختصات -
پوسته ای در اطراف یک شبکه سیاره ای.
بازسازی فضای جهان، عمق و مه جوی
برای اعمال تاری اتمسفر در یک صحنه، ما فقط یک آسمان را نقاشی نمی کنیم. ما باید فضای بین دوربین و اشیاء مختلف نشان داده شده روی صفحه را پر کنیم. ما خوش شانس هستیم که قبلاً بخشی از این کار را در بخش اول انجام داده ایم: ما تمام داده های چگالی مورد نیاز برای محاسبه را داریم. چیزها در حجمی که صحنه سه بعدی ماست. تنها چیزی که در اینجا لازم است این است:
-
یک افکت پس از پردازش ایجاد کنید که بتواند آسمان ما را سایه بان کند.
-
بافر عمق را در صحنه ما و دوربین دریافت کنید
projectionMatrixInverse،matrixWorld، وposition، آنها را به عنوان یکنواخت اثر منتقل کنید. -
با تبدیل مختصات فضای صفحه به مختصات فضای جهان با تابع زیر، پرتوهای سه بعدی دوربین خود را از طریق هر پیکسل از جلوه خود بازسازی کنید:
تابع getWorldPosition
1
vec3 getWorldPosition(vec2 uv, float depth) {2
float clipZ = depth * 2.0 - 1.0;3
vec2 ndc = uv * 2.0 - 1.0;4
vec4 clip = vec4(ndc, clipZ, 1.0);6
vec4 view = projectionMatrixInverse * clip;7
vec4 world = viewMatrixInverse * view;9
return world.xyz / world.w;
اکنون که می دانیم چگونه به دست آوریم worldPosition از پیکسل فعلی، می توانیم:
انجام این کار تضمین می کند که خط raymarch ما اکنون در امتداد a پیش می رود پرتو سه بعدی.
1
float depth = readDepth(depthBuffer, uv);2
vec3 rayOrigin = uCameraPosition;3
vec3 worldPosition = getWorldPosition(uv, depth);4
vec3 rayDir = normalize(worldPosition - rayOrigin);
آخرین کاری که اکنون باید انجام دهیم این است که مرور مجدد ما هر هندسه صحنه را در نظر بگیرد. برای انجام این کار، از بافر عمق صحنه خود برای تعریف raymark خود استفاده می کنیم stepSize به جای اینکه از یک ثابت استفاده کنیم تا بتوانیم نقاط نمونه خود را متناسب با پرتویی که در حال حاضر روی آن راه می رویم قرار دهیم.
1
float depth = readDepth(depthBuffer, uv);2
vec3 rayOrigin = uCameraPosition;3
vec3 worldPosition = getWorldPosition(uv, depth);4
vec3 rayDir = normalize(worldPosition - rayOrigin);6
float sceneDepth = depthToRayDistance(uv, depth);10
float SKY_MARCH_DISTANCE_MULTIPLIER = 8.0;12
bool isBackground = depth >= 1.0 - 1e-7;17
sceneDepth = atmosphereHeight * SKY_MARCH_DISTANCE_MULTIPLIER;21
float rayEnd = max(sceneDepth, 0.0);25
tGround = observerAltitude / max(-rayDir.y, 1e-4);26
rayEnd = min(rayEnd, tGround);29
float stepSize = (rayEnd - rayStart) / float(PRIMARY_STEPS);
-
این به ما امکان می دهد در نمونه برداری از پرتوهایی که به اجسام مجاور یا زمین برخورد می کنند بسیار دقیق باشیم:
stepSizeکوچک خواهد بود. -
We can afford to be a little less precise for rays that travel farther, since they cover greater distances and we distribute an equivalent amount of sample points along them.
The playground below renders the same shader we assembled earlier, but this time as a post-processing effect, allowing us to render atmospheric scattering throughout the scene, taking into account its geometries, with our sky shader as the background.
توجه کنید که چگونه:
-
هر چه اجسام به دوربین نزدیکتر باشند، واضح تر ظاهر می شوند.
-
هر چه اشیاء از دوربین دورتر باشند، محو خواهند شد.
با این پیاده سازی، می توانیم شروع کنیم به ارائه یک آسمان محیط واقعی تر برای هر صحنه ای که به آن نیاز دارد و همچنین با آن کمی سرگرم شویم. تعاملات احمقانه مانند مورد زیر که با a پیاده سازی شده است Raycaster:
اثر جوی پس از درمان
اکنون با اجرام آسمانی کشویی
https://t.co/xejzC5SWuc https://t.co/U342icnvxz
تصویری از سیارات
سرانجام به بخشی می رسیم که احتمالاً در وهله اول به اینجا آمده اید: ارائه اتمسفر واقعی در اطراف سیارات! خوشبختانه، با همه چیزهایی که تا این لحظه ساخته ایم، تنها دو قدم تا رسیدن به این هدف فاصله داریم:
-
برای رسیدگی به مقیاس های بزرگتر، به یک بافر عمق لگاریتمی بروید.
-
تعیین کنید که اتمسفر در امتداد یک پرتو مشخص از کجا شروع و متوقف می شود تا شکل آن را مشخص کنید، که می توانید حدس بزنید که یک کره است.
از آنجایی که ما در مقیاس سیارهای در این بخش کار میکنیم، وقتی از دور به سیاره خود نگاه میکنیم، میتوانیم انتظار “مبارزه در عمق” زیادی داشته باشیم، زیرا تشخیص عمق بین جو و پوشش سیاره از فاصله دور برای سایهبان دشوار است (ارتفاع اتمسفر فقط چند کیلومتر است). ما باید هم نحوه تعریف بافر عمق ما در صحنه React Three Fiber و هم نحوه خواندن آن را تنظیم کنیم. برای این کار تنظیم کردیم logarithmicDepthBuffer به true ج gl حمایت ما Canvas مؤلفه ای که کل تعریف صحنه را در بر می گیرد:
یک بافر عمق لگاریتمی را برای صحنه ما فعال کنید
5
logarithmicDepthBuffer: true,
سپس، در سایهزن خود، عمق صحنه خود را به صورت زیر تعریف میکنیم تا بافر عمق لگاریتمی بهدستآمده از اثر پس از پردازش را تبدیل کرده و آن را به فاصله در طول پرتو تبدیل کنیم.
تابع getWorldPosition به روز شد
1
float logDepthToViewZ(float depth) {2
float d = pow(2.0, depth * log2(cameraFar + 1.0)) - 1.0;6
float logDepthToRayDistance(vec2 uv, float depth) {7
float viewZ = logDepthToViewZ(depth);8
vec2 ndc = uv * 2.0 - 1.0;9
vec4 clipAtZ1 = vec4(ndc, -1.0, 1.0);10
vec4 viewAtZ1 = projectionMatrixInverse * clipAtZ1;11
viewAtZ1 /= viewAtZ1.w;12
vec3 viewRayDir = normalize(viewAtZ1.xyz);13
float cosTheta = max(-viewRayDir.z, 1e-5);14
return (-viewZ) / cosTheta;17
vec3 getWorldPosition(vec2 uv, float depth) {18
float viewZ = logDepthToViewZ(depth);19
vec2 ndc = uv * 2.0 - 1.0;20
vec4 clipAtZ1 = vec4(ndc, -1.0, 1.0);21
vec4 viewAtZ1 = projectionMatrixInverse * clipAtZ1;22
viewAtZ1 /= viewAtZ1.w;23
vec3 viewPos = viewAtZ1.xyz * (viewZ / viewAtZ1.z);24
vec4 world = viewMatrixInverse * vec4(viewPos, 1.0);
برای نکته دوم استفاده خواهیم کرد تست تقاطع پرتو کره برای تشخیص اینکه خط دید ما از کجا وارد و از کجا خارج می شود کره اتمسفر. هنگامی که این دو نقطه را داشته باشیم، میتوانیم خط راهاندازی مجدد خود را بدون از دست دادن نمونههای خارج از جو به این بخش محدود کنیم.
با این حال، انجام تنها یک آزمایش کافی نیست. ما همچنین میخواهیم سیاره خود را بهعنوان یک شبکه کروی که توسط یک کره جوی کمی بزرگتر احاطه شده است مدلسازی کنیم، و بنابراین باید همان آزمایش را بر روی خود سیاره انجام دهیم. اگر پرتو قبل از خروج از جو به زمین برخورد کند، از آن تقاطع زمینی به عنوان انتهای بخش راهپیمایی پرتوی خود استفاده می کنیم.
استفاده از تقاطع پرتو-کره در حلقه جستجوی مجدد
1
vec3 planetCenter = vec3(0.0);3
vec2 atmosphereHit = raySphereIntersect(10
vec2 planetHit = raySphereIntersect(18
if (atmosphereHit.x > 0.0 || atmosphereHit.y > 0.0) {19
float atmosphereNear = max(atmosphereHit.x, 0.0);20
float atmosphereFar = atmosphereHit.y;23
if (planetHit.x > 0.0) {24
atmosphereFar = min(atmosphereFar, planetHit.x);27
atmosphereFar = min(atmosphereFar, sceneDepth);31
if (atmosphereFar > atmosphereNear) {
یکی از چیزهای اضافی که ما باید آن را تطبیق دهیم، انتهای بخش ریمارشینگ ما برای رسیدگی به اشیاء در صحنه است. جو می تواند به دو دلیل مختلف متوقف شود:
2
if (planetHit.x > 0.0) {3
atmosphereFar = min(atmosphereFar, planetHit.x);6
if (sceneDepth planetHit.x - 2.0) {7
atmosphereFar = min(atmosphereFar, sceneDepth);12
atmosphereFar = min(atmosphereFar, sceneDepth);
در هر دو مورد، ما می خواهیم راهپیمایی را در نزدیکترین شی مناسب متوقف کنیم.

توجه کنید که سطح سیاره بدون این منطق چگونه خواهد بود در مقابل در سایت ما
با این دو بخش که قبلاً کد شده است، ما یک پیادهسازی کامل از پراکندگی جوی به عنوان یک اثر پس پردازش داریم و میتوانیم اتمسفرهای اطراف سیارات را ارائه کنیم. صحنه زیر یک “سیستم خورشید-زمین” ساده را در React Three Fiber با جلوه ای سفارشی در جای خود به تصویر می کشد. من از شما دعوت می کنم که لحظه ای برای تنظیم موقعیت خورشید، بزرگنمایی و لذت بردن از رنگ های آسمانی که این سایه زن می تواند از زوایای مختلف، از زمین تا مدار، بازتولید کند، صرف کنید.
جلوهای که در این دمو میبینید، همان اثری است که برای ساختن عکسهای پوسترهایی که در اوایل آوریل برای اعلام این مقاله ارسال کردم، استفاده کردم:
برای مقاله آتی و بسیار موضوعی من در مورد پراکندگی جوی برنامه ریزی کنید من احساس الهام گرفتم و پوسترهایی با عکس های رندر واقعی با تکنیک هایی که در آن یاد خواهید گرفت درست کردم 🙂
برای این https://t.co/wSjdQPyoI0 بسیار هیجان زده هستم
مقابله با کسوف
این کمی است جایزه بخشی که می خواهم به این سوال پاسخ دهم: چگونه می توانیم با اجرام بزرگ آسمانی که خورشید را مسدود می کنند مقابله کنیم؟ ما در حال حاضر درک مناسبی از آنچه در این سایهزن پراکنده جوی در حال بازی است در مورد نورپردازی داریم، و اضافه کردن این آزمایش اضافی نسبتاً آسان است.
ما میتوانیم پس از تابع lightMarch، یک تابع فراخوانی اضافه کنیم که باز خواهد گشتsunVisibility اعم از [0, 1] و نفوذپذیری را در این مقدار ضرب کنید. خود تابع می تواند به سادگی انجام یک محصول نقطه ای بین:
اگر خیلی تطابق داشتند، یعنی. نزدیک به 1.0، این بدان معنی است که ماه با خورشید تداخل پیدا می کند و بالعکس. اگر آنها متعامد بودند، نزدیک به 0.0، هیچ مانعی وجود نخواهد داشت. با این حال، این اندازه و مقیاس جسم در صحنه را در نظر نمی گیرد.

ما به تابعی نیاز داریم که بتواند سه مورد توضیح داده شده در نمودار بالا را مدیریت کند:
-
وقتی ماه جلوی خورشید را نمی گیرد.
-
وقتی هست، اما بزرگتر یا نزدیک به اندازه خورشید از POV دوربین است.
-
وقتی هست، اما در شعاع خورشید از POV دوربین قرار می گیرد.
1
float sunVisibility(vec3 point) {2
vec3 sunDir = normalize(sunDirection);3
vec3 toMoon = moonPosition - point;4
float moonDist = length(toMoon);5
vec3 moonDir = normalize(toMoon);12
float angularSep = acos(clamp(dot(sunDir, moonDir), -1.0, 1.0));13
float sunAngularRadius = SUN_RADIUS / SUN_DISTANCE;14
float moonAngularRadius = moonRadius / moonDist;15
float outerEdge = sunAngularRadius + moonAngularRadius;18
if (dot(sunDir, moonDir) 0.9) {23
if (moonAngularRadius >= sunAngularRadius) {24
float innerEdge = moonAngularRadius - sunAngularRadius;25
return max(0.075, smoothstep(innerEdge, outerEdge, angularSep));28
float innerEdge = sunAngularRadius - moonAngularRadius;29
float minVisibility = clamp(30
1.0 - (moonAngularRadius * moonAngularRadius) / (sunAngularRadius * sunAngularRadius),36
return mix(minVisibility, 1.0, smoothstep(innerEdge, outerEdge, angularSep));
اینجا float angularSep = acos(clamp(dot(sunDir, moonDir), -1.0, 1.0)) نشان دهنده جدایی زاویه ای بین جهت های خورشید و ماه است.
-
dot(sunDir, moonDir)نشان دهنده تراز بین دو جهت است. -
acosآن را دوباره به زاویه تبدیل می کند.
سپس می توانیم از این مقدار برای مقایسه آن با آستانه های زاویه ای مختلف استفاده کنیم outerEdge و innerEdge، به ترتیب زوایایی را نشان می دهد که در آن دو دیسک شروع به تماس خارجی / داخلی می کنند.
نسخه ی نمایشی زیر این را پیاده سازی می کند sunVisibility در مثال قبلی ما عمل می کند و همچنین یک شبکه ماه به سیستم ما اضافه می کند. سعی کنید ماه را با خورشید هماهنگ کنید و متوجه شوید که سایه انداز پراکنده جوی ما چگونه به درستی کمبود نور را در این موارد مدیریت می کند.
جو دنیای بیرون
دیگری جایزه بخش این روز شانس شماست! مدلی که در این مقاله برای شبیهسازی چگالی و پراکندگی اتمسفر استفاده کردیم، عمدتاً توسط چند ثابت کنترل میشود:
-
شعاع سیاره و جو
-
RayleighScaleHeightوRayleighBeta. -
MieScaleHeight،MieBeta،mieBetaExt، وmieG -
OzoneHeightوOzoneWidth
اینها دستگیره های اصلی هستند که باعث می شوند اتمسفر رندر شده ما به همان شکلی به نظر برسد. بنابراین، با تنظیم آنها بر روی مجموعه مقادیر مناسب، می توانیم در تئوری جو مریخ یا حتی سیارات دیگر را تقریب بزنیم. در زیر مجموعه مقادیری است که من برای مریخ تعیین کردم:
5
atmosphereRadius: 3500,6
rayleighScaleHeight: 11.1,7
rayleighBeta: new THREE.Vector3(0.019, 0.013, 0.0057),12
ozoneCenterHeight: 0.0,14
ozoneBetaAbs: new THREE.Vector3(0.0, 0.0, 0.0),16
planetSurfaceColor: '#8B4513',
فقط جایگزین کردن ثابت هایمان با این ها، فضایی غبارآلودتر و نارنجی به ما می دهد. حتی بهتر از آن، ما رنگ آبی متمایز مریخ را در غروب خورشید دریافت می کنیم! در زیر اسکرین شات هایی که در حین کار بر روی آن گرفته ام آورده شده است. می توانید سعی کنید این مقادیر را به نسخه آزمایشی قبلی متصل کنید تا نتیجه را خودتان ببینید.
پراکندگی اتمسفر بر اساس LUT
سایهزن بهدستآمده که ما ایجاد کردیم، اگرچه بصری است و قادر به رندر کردن اتمسفرها در مقیاسهای کوچک و بزرگ است، اما متاسفانه اجرای آن بسیار گران است:
-
ما مقدار زیادی داریم
PRIMARY_STEPSدر چرخه راهپیمایی ما -
ما یک چرخه راهپیمایی نور تو در تو داریم.
-
ما تمام محاسبات ریاضی را با وضوح تمام صفحه انجام می دهیم.
در کنار رفع این کاستی ها می خواستم یاد بگیرم چگونه حرفه ای ها این کار را انجام دادند وقتی در مطالعه ام در مورد پراکندگی جوی به این نقطه رسیدم. سباستین هیلر در مقاله خود با عنوان تکنیک مقیاس پذیر و آماده تولید برای تصویربرداری از آسمان و جو، یک روش تصویربرداری جوی بر اساس جستجو در جداول (LUTs)، یعنی بافت هایی که می توانند محاسبات پراکندگی گران قیمت را پشتیبانی کنند، بنابراین رندر نهایی این بافت های از پیش محاسبه شده را نمونه برداری و ترکیب می کند.
در این قسمت به پیاده سازی های مربوطه می پردازیم:
-
پهنای باند LUT، که مقدار نوری را که از طریق جو زنده می ماند ذخیره می کند.
-
نمایش آسمان LUT، که رنگ آسمان حاصل را برای یک موقعیت دوربین مشخص ذخیره می کند
-
پرسپکتیو هوایی LUT، که مه/مه جوی را بین دوربین و هندسه های صحنه قابل مشاهده، از جمله میزان نور اضافه شده توسط پراکندگی و تأثیر آن بر رنگ های صحنه، ذخیره می کند.
پهنای باند LUT
در سایه زن اصلی ما، هر نقطه نمونه تماس می گیرد lightmarch عملکردی برای دریافت مقدار نوری که از خورشید به آن می رسد را دریافت می کند، که حدس زدید بسیار گران است. هدف این LUT این است که این داده ها را از قبل ذخیره کند، ترجیحاً با وضوح پایین، به طوری که در صورت نیاز به این داده های سبک، بتوانیم آن را در LUT های بعدی بارگذاری کنیم.
اجرای من برای این LUT و هر LUT بعدی شامل موارد زیر است:
-
یک شیء فریم بافر ویژه با وضوح خاص تعریف کنید. مخصوصا برای این یکی که انتخاب کردم
250 x 64. -
متریالی را با سایه زن سفارشی تعریف کنید که از منطق تولید داده های LUT ما پشتیبانی می کند.
-
آن را در یک صحنه خاص روی یک چهارگوش تمام صفحه اعمال کنید، در این مورد،
transmittanceLUTScene. -
صحنه را رندر کنید و بافت حاصل را به صورت یکنواخت به پایین دست LUT منتقل کنید.
ممکن است کمی پیچیده به نظر برسد، اما همانطور که قبلاً گفتیم، در حالت ایدهآل شما از WebGPU و محاسبات سایهزنان برای این کار استفاده میکنید، و بنابراین به آن FBOها نیاز ندارید.
برای عبور، خط گران قیمت مارش نور را با قرار دادن آن در گذرگاه خود استخراج می کنیم transmittanceLUTFragmentShader. کد زیر همان چیزی است که من برای ایجاد بافت خود استفاده کردم:
2
float mu = mix(-1.0, 1.0, vUv.x);4
float radius = mix(planetRadius, atmosphereRadius, vUv.y);5
vec3 rayOrigin = vec3(0.0, radius, 0.0);6
float sinTheta = sqrt(max(1.0 - mu * mu, 0.0));7
vec3 rayDir = normalize(vec3(sinTheta, mu, 0.0));9
vec2 atmosphereHit = raySphereIntersect(16
vec2 planetHit = raySphereIntersect(23
float rayLength = atmosphereHit.y;26
gl_FragColor = vec4(1.0);30
if (planetHit.x > 0.0) {31
gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);35
float stepSize = rayLength / float(TRANSMITTANCE_STEPS);36
float rayleighOD = 0.0;40
for (int i = 0; i TRANSMITTANCE_STEPS; i++) {41
float t = (float(i) + 0.5) * stepSize;42
vec3 samplePoint = rayOrigin + rayDir * t;43
rayleighOD += rayleighDensity(samplePoint) * stepSize;44
mieOD += mieDensity(samplePoint) * stepSize;45
ozoneOD += ozoneDensity(samplePoint) * stepSize;49
rayleighBeta * rayleighOD +51
ozoneBetaAbs * ozoneOD;53
gl_FragColor = vec4(exp(-tau), 1.0);
-
برای هر پیکسل، ما از آن پرتو میکنیم
vec3(0.0, radius, 0.0)، که بین رشد می کندplanetRadiusوatmosphereRadiusدر امتدادvUv.yهماهنگ کردن -
جهت
rayDirجهت نور را برای هر پیکسل معینی از LUT ما تعیین می کند که بین آنها متفاوت استmu = -1، یعنی جهت پایین به سطح سیاره،rayDir = vec3(0.0, -1.0, 0.0)، وmu = 1جهت بالا به فضا،rayDir = vec3 (0.0, 1.0, 0.0). چه زمانیmu = 0،rayDir = vec3(1.0, 0.0, 0.0)به این معنی که نور به صورت افقی حرکت می کند و جو را می چراند. -
ما از همان استفاده می کنیم
raySphereIntersectو توابع پراکندگی اتمسفر که قبلاً معرفی شد.
این منجر به بافت انتقال زیر LUT می شود:
در اینجا نحوه تفسیر این بافت آمده است:
-
محور x نشان دهنده زاویه نور است. در سمت چپ ما نوری داریم که مستقیماً به زمین نگاه می کند، از این رو رنگ های تیره است. سمت راست، از سوی دیگر، نور را نشان می دهد که مستقیم به بالا به نظر می رسد.
-
محور y نشان دهنده ارتفاع است. پایین تصویر سطح زمین/دریا است، در حالی که قسمت بالایی مرز جو ما است.
-
هنگامی که نور مسیر روشنی داشته باشد، سفید خالص نشان دهنده 100٪ عبور است.
-
نواحی سیاه/رنگی نمایانگر زمین/قسمتی است که هوا در آن متراکمتر است، بهویژه در نزدیکی زمین که مقداری از نور ناپدید شده است.
LUT های بعدی اکنون می توانند به این سوال پاسخ دهند “چقدر نور در یک زاویه و ارتفاع معین در جو ما زنده می ماند” خیلی سریع فقط من به بالا نگاه می کنم این مقدار در این بافت
نمای هوایی از آسمان و LUT
این دو LUT از داده های انتقالی که ما در بافت مربوطه آنها محاسبه کردیم استفاده می کنند و به دو سؤال مکمل پاسخ می دهند:
-
اگر از زمین به سمت بالا نگاه کنم، آسمان چه رنگی است؟ رنگ آسمان
-
بین موقعیت فعلی من و هر شیء در صحنه چقدر اتمسفر وجود دارد؟ مه جوی

ترکیب دو LUT به ما اثر پراکندگی جوی کامل را می دهد. اولی رنگ میدان دور را کنترل می کند، در حالی که دومی مه میدان نزدیک را محاسبه می کند. با استفاده از یک فرآیند مشابه شامل FBO ها و صحنه های خارج از صفحه، می توانیم سایه بان های مختلفی را برای تولید هر دو LUT تعریف کنیم.
برای بافت Sky View کد زیر را دریافت کردم:
گزیده ای از Sky View LUT
1
vec3 getSkyViewForward(vec3 up) {3
vec3 projectedSun = sunDirection - up * dot(sunDirection, up);4
return normalize(projectedSun);7
vec3 getSkyViewRayDir(vec2 uv, vec3 up) {8
vec3 forward = getSkyViewForward(up);9
vec3 right = normalize(cross(forward, up));12
float azimuth = (uv.x * 2.0 - 1.0) * PI;15
float elevation = (uv.y * uv.y - 0.5) * PI;17
float cosElevation = cos(elevation);18
vec3 horizontal = cos(azimuth) * forward + sin(azimuth) * right;20
return normalize(horizontal * cosElevation + up * sin(elevation));24
vec3 rayOrigin = uCameraPosition;25
vec3 up = normalize(rayOrigin);26
vec3 rayDir = getSkyViewRayDir(vUv, up);27
vec3 planetCenter = vec3(0.0);29
vec2 atmosphereHit = raySphereIntersect(rayOrigin, rayDir, planetCenter, atmosphereRadius);30
vec2 planetHit = raySphereIntersect(rayOrigin, rayDir, planetCenter, planetRadius);33
if (atmosphereHit.y 0.0) {34
gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);39
float atmosphereNear = max(atmosphereHit.x, 0.0);40
float atmosphereFar = atmosphereHit.y;41
if (planetHit.x > atmosphereNear) {42
atmosphereFar = min(atmosphereFar, planetHit.x);45
float atmosphereSegmentLength = atmosphereFar - atmosphereNear;46
float stepSize = atmosphereSegmentLength / float(SKY_VIEW_STEPS);53
gl_FragColor = vec4(scatteredLight, 1.0);
نکته اصلی که در اینجا باید تأکید کرد این است getSkyViewRayDir، که جهت پرتوهای پرتوهای ما را تعیین می کند. در این مورد:
-
محور x، vUv.x به تبدیل می شود آزیموت، یعنی جهت های چپ به راست از
[-PI, PI]. -
محور y، vUv.y، مطابقت دارد ارتفاع، به عنوان یک تبدیل درجه دوم
(vUv.y * vUv.y - 0.5) * PI3، یعنی زاویه عمودی ما از آسمان در محدوده[-PI/2, PI/2]. -
در نهایت این دو زاویه را به سه بعدی تبدیل می کنیم
rayDir:upبه آسمان اشاره کردforwardدر امتداد افق به سمت خورشید وrightبه ما این امکان را می دهد که به چپ و راست در اطراف آسمان بکشیم.
با این تعریف ما rayDir، خط مجدد ما در اینجا بافتی را ارائه می دهد که رنگ آسمان را برای جهت ها در سراسر گنبد آسمان نشان می دهد.
در مورد پرسپکتیو هوایی، همانطور که قبلا ذکر شد، من کمی از مقاله هیلر منحرف شدم. بافت به دست آمده من یک بافت دو بعدی است که در آن هر پیکسل با یک پیکسل صفحه قابل مشاهده مطابقت دارد. من به بافر عمق صحنه تکیه میکنم تا بگویم چقدر باید به پایین پرتو برویم و پراکندگی را جمع کنیم.
در نتیجه، این به من اجازه می دهد تا از همان کد پراکندگی معرفی شده در قسمت اول استفاده کنم، با این تفاوت که اکنون هر نمونه دید نور خورشید را از Transmittance LUT دریافت می کند. نتیجه، پراکندگی اتمسفر انباشته شده را در RGB و یک مقدار انتقال نمایش بسته شده در آلفا ذخیره می کند، که بعداً در طول ترکیب از آن استفاده خواهیم کرد.
نمونه پرسپکتیو هوایی LUT
2
float depth = texture2D(depthBuffer, vUv).x;5
vec3 rayOrigin = uCameraPosition;6
vec3 worldPosition = getWorldPosition(vUv, depth);7
vec3 rayDir = normalize(worldPosition - rayOrigin);8
float sceneDepth = logDepthToRayDistance(vUv, depth);10
vec2 atmosphereHit = raySphereIntersect(rayOrigin, rayDir, vec3(0.0), atmosphereRadius);11
vec2 planetHit = raySphereIntersect(rayOrigin, rayDir, vec3(0.0), planetRadius);13
if (atmosphereHit.y 0.0) {14
gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);20
float atmosphereNear = max(atmosphereHit.x, 0.0);21
float atmosphereFar = atmosphereHit.y;23
if (planetHit.x > 0.0) {24
atmosphereFar = min(atmosphereFar, planetHit.x);26
if (sceneDepth planetHit.x - 2.0) {27
atmosphereFar = min(atmosphereFar, sceneDepth);30
atmosphereFar = min(atmosphereFar, sceneDepth);33
float segmentLength = atmosphereFar - atmosphereNear;34
float stepSize = segmentLength / float(AERIAL_PERSPECTIVE_STEPS);37
for (int i = 0; i AERIAL_PERSPECTIVE_STEPS; i++) {38
float t = atmosphereNear + (float(i) + 0.5) * stepSize;39
vec3 samplePoint = rayOrigin + rayDir * t;42
vec3 sunTransmittance = sampleTransmittanceLUT(samplePoint, sunDirection);49
gl_FragColor = vec4(scatteredLight, packedTransmittance);
ترکیب
با نمای آسمان و پرسپکتیوهای هوایی تولید شده، فقط یک مرحله باقی مانده است: ترکیب آنها در یک گذر نهایی پس از پردازش برای رسیدن به نتیجه کامل پراکندگی جوی مبتنی بر LUT. کد عمدتاً شامل موارد زیر است:
-
تبدیل rayDir فعلی به مختصات skyViewUV به طوری که در هر جهتی در آسمان، می دانیم از کجا از LUT از پیش محاسبه شده Sky-view نمونه برداری کنیم.
1
vec2 getSkyViewLUTUv(vec3 rayDir, vec3 planetCenter) {2
vec3 up = normalize(uCameraPosition - planetCenter);3
vec3 forward = getSkyViewForward(up);4
vec3 right = normalize(cross(forward, up));6
float vertical = clamp(dot(rayDir, up), -1.0, 1.0);7
vec3 horizontal = rayDir - up * vertical;11
float azimuth = atan(dot(horizontal, right), dot(horizontal, forward));12
float elevation = asin(vertical);13
float elevation01 = clamp(elevation / PI + 0.5, 0.0, 1.0);16
azimuth / (2.0 * PI) + 0.5,21
vec3 sampleSkyViewLUT(vec3 rayDir, vec3 planetCenter) {22
vec2 uv = getSkyViewLUTUv(rayDir, planetCenter);23
return texture2D(skyViewLUT, uv).rgb;
-
بازسازی پرتو دید از بافر عمق و بررسی اینکه آیا آن پرتو به سیاره برخورد می کند یا خیر.
-
استفاده از یک پرسپکتیو هوایی LUT برای هندسه صحنه، با استفاده از کانال آلفا آن به عنوان انتقال دید و کانال های RGB آن به عنوان نور منتشر.
-
نمونهبرداری از Sky View LUT برای پیکسلهای پسزمینه.
1
void mainImage(const in vec4 inputColor, const in vec2 uv, out vec4 outputColor) {2
float depth = readDepth(depthBuffer, uv);3
vec3 rayOrigin = uCameraPosition;4
vec3 rayDir = normalize(getWorldPosition(uv, depth) - rayOrigin);5
vec3 planetCenter = vec3(0.0);6
vec2 planetHit = raySphereIntersect(rayOrigin, rayDir, planetCenter, planetRadius);7
vec3 color = inputColor.rgb;9
bool isBackground = depth >= 1.0 - 1e-7;12
if (aerialPerspectiveEnabled && !isBackground) {13
vec4 aerialPerspective = sampleAerialPerspectiveLUT(uv);14
color = color * aerialPerspective.a + aerialPerspective.rgb;18
if (skyViewEnabled && isBackground) {19
color = inputColor.rgb + sampleSkyViewLUT(rayDir, planetCenter);22
color = ACESFilm(color);23
color = pow(color, vec3(1.0 / 2.2));25
outputColor = vec4(color, 1.0);
زمین بازی زیر شامل اجرای کامل اتمسفر مبتنی بر LUT ما است: همه LUT ها و سایه زن مربوط به آنها، و همچنین پاس نهایی پس از پردازش. کمی متراکم است، بنابراین توصیه میکنم پیادهسازی را مستقیماً در این پیوند Github بررسی کنید، جایی که کدی را که صحنه را در زیر ارائه میکند، پیدا خواهید کرد.
این نسخه از پراکندگی جوی ممکن است تقریباً مشابه نسخه ای باشد که در قسمت های قبلی این پست روی آن کار کردیم، اما روند اساسی متفاوت است: ما کار را به LUT های کوچکتر تقسیم می کنیم که سپس آنها را در اثر نهایی ترکیب می کنیم. مهمتر از همه، به جای تابش مکرر به خورشید برای اینکه بفهمیم چه مقدار نور به هر نمونه می رسد، می توانیم این اطلاعات روشنایی را مستقیماً از Transmittance LUT استخراج کنیم، حلقه تودرتو پرهزینه را با یک جستجوی بافت ساده جایگزین کنیم و منجر به بهبود عملکرد غیر قابل اغماض برای صحنه نهایی شود.
با این حال، پیاده سازی مبتنی بر LUT من در مقایسه با آنچه سباستین هیلر و دیگران در این زمینه به آن دست یافته اند، کمرنگ است:
-
برخی رگه ها و سوسو زدن ها به خصوص در نمای آسمان رخ می دهد
-
میانبرهایی که من انتخاب کردم، روند را کمتر از آنچه که میتوانست بهینه کرد.
-
احتمالا باید از همان ابتدا از WebGPU استفاده می کردم.
اگر میخواهید به یک پیادهسازی در سطح تولید واقعی نگاه کنید، من به شدت توصیه میکنم سه فضای مکانی را توسط شدا ماتسودا بررسی کنید.@shotamatsuda). کار او بر روی آسمان ها، ابرها و تصویربرداری فضایی نقطه شروع بزرگی برای من بود و تصاویری که او در رسانه های اجتماعی به اشتراک می گذارد خود گویای این موضوع است.
با این حال یاد گرفتم زیاد در طول این پروژه، به ویژه از طریق رویکرد مبتنی بر LUT، که وقتی نوبت به ایجاد جلوههای پس از پردازش متناسب با عمق فضای صفحه میرسد، من را از منطقه راحتی خود خارج کرد. همچنین برخی از دانش قبلی را تثبیت کرد و منجر به یک سری تجسم های زیبا شد (که در نهایت مهمترین چیز است).
من از نتیجه این آزمایش ها بسیار راضی هستم. من همچنین روی افزودن ابرهای حجم کار کردم، اما نتیجه هنوز کمی مختلط است و قبل از اینکه بتوانم آنقدر به آن افتخار کنم که آن را در یک بررسی نشان دهم، به کار بیشتری نیاز دارد. که باید صبر کرد. تا آن زمان، من مشتاقانه منتظر استفاده از این اثر برای تکمیل پروژه ها و صحنه های آینده ام هستم کم کم داشتم در سرم شکل می گرفتم.
-
من این را امتحان کرده ام به طور مکرر
-
منظرههای ابری بیدرنگ با ریمارشینگ حجمی بسیاری از مفاهیم مورد استفاده در اینجا را معرفی می کند.
-
این یک راه حل برای جلوگیری از پلک زدن بیش از حد در نمای بلند آسمان است



