یک مولد اعداد تصادفی خوب اعدادی را تولید می کند که هیچ رابطهای با هم ندارند و هیچ الگوی قابل تشخیصی را نشان نمی دهند. همانطور که می بینیم، کمی تصادفی بودن می تواند در برنامه ریزی رفتارهای ارگانیک و واقعی چیز خوبی باشد. با این حال، تصادفی بودن به عنوان اصل راهنمای واحد لزوماً طبیعی نیست.
الگوریتمی وجود دارد که نتایج طبیعی تری را به همراه دارد و به آن "Perlin noise" میگویند. کن پرلین در اوایل دهه 1980، عملکرد نویز را در حین کار بر روی فیلم اصلی Tron توسعه داد؛ او از آن برای ایجاد بافت های رویهای برای جلوههای کامپیوتری استفاده کرد. در سال 1997، پرلین برای این کار برنده جایزه اسکار در دستاورد فنی شد. این الگوریتم را می توان برای ایجاد جلوه های مختلف با کیفیت های طبیعی مانند ابرها، مناظر و بافت های طرح دار مانند سنگ مرمر استفاده کرد.
این الگوریتم ظاهر ارگانیک تری دارد زیرا یک توالی منظم («صاف») از اعداد شبه تصادفی تولید می کند. نمودار زیر الگوریتم را در طول زمان نشان می دهد که محور x نشان دهنده زمان است. به نرمی منحنی توجه کنید.
در مقابل، نمودار زیر اعداد تصادفی خالص را در طول زمان نشان می دهد.
برنامه ProcessingJS یک پیاده سازی از الگوریتم دارد: تابع noise(). تابع noise () یک، دو یا سه آرگومان می گیرد، زیرا نویز در یک، دو یا سه بعد محاسبه می شود. بیایید با نگاه کردن به نویز یک بعدی شروع کنیم.
مرجع نویز به ما می گوید که نویز در چندین "اکتاو" محاسبه می شود. فراخوانی تابع noiseDetail هم تعداد اکتاوها و هم اهمیت آنها را نسبت به یکدیگر تغییر می دهد. این به نوبه خود نحوه عملکرد نویز را تغییر می دهد.
ترسیم یک دایره در پنجره ProcessingJS در یک مکان تصادفی x را در نظر بگیرید:
var x = random(0, width); ellipse(x, 180, 16, 16);
اکنون، بهجای یک مکان تصادفی x، یک مکان-x برای Perlin noise میخواهیم که «صافتر» باشد. ممکن است فکر کنید که تنها کاری که باید انجام دهید این است که ()random را با noise() جایگزین کنید.
var x = noise(0, width); ellipse(x, 180, 16, 16);
در حالی که از نظر مفهومی این دقیقاً همان کاری است که ما میخواهیم انجام دهیم (محاسبه یک مقدار x که بین 0 و عرض با توجه به نویز پرلین متغیر است) این پیادهسازی صحیح نیست. در حالی که آرگومان های تابع ()random محدوده ای از مقادیر را بین حداقل و حداکثر مشخص می کند، noise() به این شکل کار نمی کند. در عوض، noise() از ما انتظار دارد که در یک آرگومان عبور کنیم که به معنای "لحظه ای در زمان" است، و همیشه مقداری بین 0 و 1 برمیگرداند. می توانیم Perlin noise یک بعدی را به عنوان یک دنباله خطی از مقادیر در طول زمان در نظر بگیریم.
حال، برای دسترسی به یکی از آن مقادیر نویز در ProcessingJS، باید یک لحظه خاص از زمان را به تابع noise() منتقل کنیم. مثلا:
var n = noise(0.03);
ما میتوانیم برنامهای بنویسیم که یک متغیر زمان را ذخیره میکند و آن مقدار نویز را به طور مداوم در draw() درخواست میکند.
// Adapted from Dan Shiffman, natureofcode.com var t = 0.03; draw = function() { var n = noise(t); println(n); };
کد بالا منجر به چاپ همان مقدار می شود. این به این دلیل اتفاق میافتد که ما بارها و بارها نتیجه تابع noise() را در یک نقطه از زمان میخواهیم. با این حال، اگر متغیر زمان t را افزایش دهیم، نتیجه متفاوتی خواهیم گرفت.
// Adapted from Dan Shiffman, natureofcode.com var t = 0; draw = function() { var n = noise(t); println(n); // Move forward in time t += 0.01; };
سرعتی که t را افزایش میدهیم نیز بر صافی نویز تأثیر میگذارد. اگر به موقع پرش های بزرگی انجام دهیم، در این صورت از جلو می گذریم و مقادیر تصادفی تر خواهند بود.
کد بالا را چندین بار اجرا کنید و t را 0.01، 0.02، 0.05، 0.1، 0.0001 افزایش دهید، نتایج متفاوتی خواهید دید.
نگاشت نویز
اکنون ما آماده پاسخ به این سوال هستیم که با مقدار نویز چه کنیم. هنگامی که مقداری با محدوده بین 0 و 1 داشتیم، این به ما بستگی دارد که آن محدوده را با آنچه می خواهیم ترسیم کنیم.
ما فقط میتوانیم در حداکثر عدد در محدوده ضرب کنیم، اما این فرصت خوبی برای معرفی تابع نقشه () ProcessingJS است که در موقعیتهای بعدی به ما کمک میکند. تابع map () پنج آرگومان می گیرد. ابتدا مقداری است که می خواهیم ترسیم کنیم، در این مورد n. سپس باید محدوده فعلی مقدار (حداقل و حداکثر) و به دنبال آن محدوده مورد نظر خود را به آن بدهیم.
در این مورد، ما می دانیم که نویز محدوده ای بین 0 و 1 دارد، اما ما می خواهیم یک مستطیل با عرض بین 0 و عرض فعلی بکشیم.
// Adapted from Dan Shiffman, natureofcode.com var t = 0; draw = function() { var n = noise(t); // Using map() to customize the range of Perlin noise var x = map(n, 0, 1, 0, width); rect(0, t*100, x, 1); t += 0.01; };
میتوانیم دقیقاً همان منطق را برای واکر تصادفی خود اعمال کنیم و هر دو مقدار x و y آن را با توجه به Perlin noise نسبت دهیم.
// Adapted from Dan Shiffman, natureofcode.com var Walker = function() { this.x = width/2; this.y = height/2; this.tx = 0; this.ty = 10000; }; Walker.prototype.display = function() { stroke(0, 0, 0); point(this.x, this.y); }; // Randomly move up, down, left, right, or stay in one place Walker.prototype.walk = function() { this.x = map(noise(this.tx), 0, 1, 0, width); this.y = map(noise(this.ty), 0, 1, 0, height); // Move forward through “time.” this.tx += 0.01; this.ty += 0.01; }; var w = new Walker(); draw = function() { w.walk(); w.display(); };
توجه کنید که چگونه مثال بالا به یک جفت متغیر اضافی نیاز دارد: tx و ty. این به این دلیل است که ما باید دو متغیر زمانی را دنبال کنیم، یکی برای مکان x شی Walker و دیگری برای مکان y.
اما یک چیز کمی در مورد این متغیرها وجود دارد. چرا tx از 0 و ty از 10000 شروع می شود؟ در حالی که این اعداد انتخاب های دلخواه هستند، ما به طور خاص دو متغیر زمانی خود را با مقادیر مختلف مقداردهی اولیه کرده ایم. این به این دلیل است که تابع نویز قطعی است: هر بار برای یک زمان خاص نتیجه یکسانی را به شما می دهد. اگر مقدار نویز t را همزمان برای x و y بخواهیم، x و y همیشه برابر خواهند بود، به این معنی که جسم واکر فقط در امتداد یک مورب حرکت می کند. در عوض، ما به سادگی از دو قسمت مختلف فضای نویز استفاده میکنیم که از 0 برای x و 10000 برای y شروع میکنیم تا x و y بتوانند مستقل از یکدیگر عمل کنند.
در حقیقت، هیچ مفهوم واقعی از زمان در اینجا وجود ندارد. این یک استعاره مفید است که به ما کمک می کند تا بفهمیم عملکرد نویز چگونه کار می کند، اما در واقع آنچه ما داریم مکان است، نه زمان. نمودار بالا یک توالی خطی از مقادیر نویز را در یک فضای یک بعدی نشان می دهد و هر زمان که بخواهیم می توانیم مقداری را در یک مکان x خاص بخواهیم. در مثالها، اغلب متغیری به نام xoff را میبینید که به جای t برای زمان (همانطور که در نمودار ذکر شده است) مقدار x-offset را در امتداد نمودار نویز نشان میدهد.
این مقاله ترجمهای از مقاله Perlin noise در سایت خان آکادمی است.