چگونه من، یک توسعه دهنده C++، شروع به یادگیری زنگ زدگی کردم

سلام، من جونیور ناسیمنتو هستم. من الان کمی بیش از 1 دهه است که برنامه نویسی می کنم. من شروع به یادگیری زبان پاسکال کردم و سپس به جاوا مهاجرت کردم و در نهایت به C و سپس C++.
در بیشتر سفرم از C++ استفاده میکردم، بیشتر پروژههای ساده و برنامهنویسی رقابتی انجام میدهم، هیچ چیز دیوانهکنندهای نیست، فقط برای سرگرمی.
سپس پس از چند ترم ورود به کالج، جاوا اسکریپت را یاد گرفتم زیرا می خواستم شغلی برای ثبات مالی داشته باشم. زندگی من تقریباً برنامه نویسی جاوا اسکریپت بود، از برخی چارچوب ها برای یادگیری و انجام مصاحبه ها و پایتون برای نیازهای آکادمیک خود استفاده می کردم.
به زودی، چند سالی با استفاده از TypeScript و هسته دات نت شغلی پیدا کردم.
گاهی اوقات پروژه های جانبی را با استفاده از SolidJS و Tailwind انجام می دادم. اما چند هفته پیش میخواستم کار «سطح پایینتری» انجام دهم که از استفاده از Typescript و انجام کاری برای وب خسته شده بودم. من از ساختن API با دات نت خسته شده بودم، یک جور چالش می خواستم.
من مدتی است که میخواهم rust را یاد بگیرم، اما واقعاً هرگز ننشینم تا مقداری کدنویسی در Rust انجام دهم.
با اجرای شبکه های عصبی و دینامیک سیالات در سی پلاس پلاس، به این فکر افتادم که این پروژه ها را به صورت زنگ زده بازنویسی کنم تا با این زبان آشنا شوم. اما بعداً این کتابی را که تقریباً یک سال پیش در مورد ساختن Raytracer در یک آخر هفته ذخیره کرده بودم، به یاد میآورم، بنابراین من فقط وارد آن شدم.
این کتاب یک راهنمای گام به گام در مورد نحوه پیاده سازی raytracer در C++ و نحوه عملکرد فیزیک و نحوه ترجمه به کد را نشان می دهد.
اما شاید از خود بپرسید: raytracer چیست؟
raytracer یک برنامه یا الگوریتم کامپیوتری است که با شبیه سازی رفتار نور، تصاویر دو بعدی واقعی از یک محیط مجازی سه بعدی ایجاد می کند. این کار را با ردیابی مسیر پرتوهای نور هنگام تعامل با اشیاء در صحنه و تعیین نحوه انعکاس، شکست و جذب آنها انجام می دهد.
برای ایجاد یک تصویر، یک ردیاب پرتو یک دوربین مجازی را به محیط سه بعدی می اندازد و پرتوهای نور دوربین را از طریق هر پیکسل صفحه تصویر ردیابی می کند. سپس رنگ و شدت نوری را که باید در آن نقطه قابل مشاهده باشد بر اساس ویژگی های اشیا و نور صحنه محاسبه می کند.
ردیابی پرتو میتواند تصاویر بسیار واقعی را با نورپردازی، سایهها، بازتابها و شکست دقیق ایجاد کند، اما میتواند برای منابع کامپیوتری، بهویژه برای صحنههای پیچیده با اشیا و منابع نوری زیاد، سخت باشد. این تکنیک در زمینه هایی مانند گرافیک کامپیوتری، انیمیشن و جلوه های بصری برای فیلم مورد استفاده قرار می گیرد که تصاویر بسیار واقعی مورد نیاز است.
با این روش، بیایید به قسمت سرگرم کننده برویم.
من واقعاً بدون هیچ پیش زمینه ای در مورد زنگ زدگی وارد این کار شدم، اما تجربه زیادی با C++ دارم، بنابراین درک مثال های کتاب آسان بود.
در بیشتر موارد، توابع تبدیل یک به یک هستند، در اینجا جای تعجب نیست، زیرا ما در حال محاسبه پرتوها و ساختن مقداری جبر خطی هستیم، ریاضی است، بنابراین هر زبانی باید بتواند این کار را بدون تفاوت انجام دهد.
کلاس های C++ و ساختارهای Rust بسیار شبیه به هم هستند، بنابراین من در ترجمه و یادگیری نحو Rust مشکلی نداشتم.
اما برای این پیاده سازی، نویسنده کتاب انتخاب کرده است که یک کلاس انتزاعی Httable بسازد تا هر جسم ممکنی را که نور می تواند به آن برخورد کند را نشان دهد، و در Rust ما مفهوم کلاس ها را نداریم، بنابراین من در جستجوی چگونگی انجام کاری مشابه بودم و در مورد ویژگی ها یاد گرفت.
صفات رفتار یا عملکرد مشترک را در چندین نوع بدون نیاز به سلسله مراتب وراثت مشترک تعریف می کنند. آنها شبیه رابط ها هستند و مجموعه ای از روش ها را مشخص می کنند که انواع پیاده سازی این ویژگی باید ارائه کنند.
به عنوان مثال، این یک ویژگی Httable است:
pub trait Hittable : Sync {
fn hit(&self, ray: &Ray, t_min: f32, t_max: f32) -> Option<HitRecord>;
}
این ویژگی به این معنی است که یک ساختار باید تابع hit را پیاده سازی کند تا بتواند از نوع Hittable باشد.
این hit
تابع دریافت یک پرتو نور و مقداری محدودیت (برای تسهیل محاسبات)
چیز دیگری که rust پیادهسازی میکند چرخههای عمر است، اما C++ فقط فرض میکند که میدانید چه کار میکنید و چیزی را بررسی نمیکنید.
به عنوان مثال این ساختار را ببینید:
pub struct HitRecord<'a> {
pub point : Vector3<f32>,
pub t: f32,
pub normal: Vector3<f32>,
pub material: &'a dyn Material,
}
اعتراف میکنم که وقتی این قسمت را اجرا میکردم، عصبانی بودم زیرا از یک Trait بهعنوان یک نوع بدون علامتگذاری بهعنوان پویا و در نتیجه نیاز به چرخه حیات استفاده نمیکنم.
این ساختار نشان دهنده محل برخورد پرتوی نور به چیزی است. و برای محاسبه انعکاس، شکست و رنگ پرتو در دوربین به موادی نیاز داریم که پرتو در آن اصابت کرده است.
از آنجایی که ما بیش از یک ماده داریم، میدان ماده باید یک صفت باشد، برای استفاده از یک صفت به عنوان یک فایل، باید میدان را به عنوان پویا علامت گذاری کنیم و بنابراین یک نشانگر چرخه حیات داشته باشیم. این <'a>
اساسا به این معنی است که مواد تا زمانی که زنده هستند Hitrecord
هدف – شی.
من همچنین در مورد آن یاد می کنم Box
“نوع” در زنگ زدگی، این یک اشاره گر هوشمند است که حافظه را بر روی پشته و برای مالکیت و مدیریت طول عمر داده ها اختصاص می دهد.
من قبلاً لیستی از اشیاء قابل ضربه تهیه می کردم:
pub struct HittableList {
pub objects: Vec<Box<dyn Hittable>>,
}
این آرایه ای از نقاط برای اشیاء قابل ضربه زدن است، در این پروژه من فقط کره ها را پیاده سازی کردم اما هر شیئی که Hittable
ابر صفت در این لیست باشد.
آخرین چیزی که من کاملاً در مورد زنگ زدگی دوست دارم این بود Option
و نحوه استفاده از تاپل ها بسیار آسان است.
در C++ نویسنده (و همچنین در بیشتر مواردی که به یاد میآورم) از اشارهگرها برای تغییر متغیرها در توابع استفاده میکند تا یک مقدار بولی را کپی نکرده و برگرداند تا بررسی کند آیا نتیجه موفقیت آمیز بوده است یا خیر، در Rust من توانستم این را حذف کنم. با استفاده از Option
نوع
این Option
type به این معنی است که یک تابع می تواند هر نوع را که می خواهید برگرداند یا None
مثلا:
fn refract(v: &Vector3<f32>, n: &Vector3<f32>, etai_over_etat: f32) -> Option<Vector3<f32>> {
let uv = v.normalize();
let dt = uv.dot(&n); // Angle between the ray and the normal
let discriminant = 1.0 - etai_over_etat.powi(2) * (1.0 - dt.powi(2)); // Using law of cosines and the Snell's law
if discriminant > 0.0 {
// Can refract
let refracted = etai_over_etat * (uv - n * dt) - n * discriminant.sqrt();
Some(refracted)
} else {
// Must reflect
None
}
ریاضیات و تمرکز را در بخشی نادیده بگیرید که اگر نور منعکس میشود ما شکست نمیشویم، بنابراین None را برمیگردانیم، پس وقتی از این تابع استفاده میکنیم فقط باید بررسی کنیم که آیا چیزی برای شکست داریم یا خیر:
if let Some(refracted) = refract(&ray.direction(), &outward_normal, etai_over_etat) {
let reflect_prob = schlick(cosine, self.ir);
if rand::thread_rng().gen::<f32>() >= reflect_prob {
let scattered = Ray::new(record.p, refracted);
return Some((scattered, attenuation));
}
}
این بررسی می کند که آیا ما شکست می خوریم، سپس بر اساس یک تابع schlick پرتو را پراکنده می کنیم.
در فینال توانستم این تصویر را رندر کنم:
من واقعاً از این تجربه لذت بردم و عاشق زنگ زدم، می خواهم پروژه های دیگری را با آن انجام دهم!
اگر کسی می خواهد کل پروژه را ببیند، می توانید مخزن پروژه را در GitHub بررسی کنید!