مشاهده کلاسیک و رایانه‌ای، دوربین و APIهای مشاهده | گرافیک تعاملی با OpenGL

مشاهده کلاسیک و رایانه‌ای، دوربین و APIهای مشاهده

مشاهده کلاسیک و رایانه‌ای، دوربین و APIهای مشاهده

  • عنوان اصلی اثر: Interactive Computer Graphics: A Top-Down Approach with Shader-Based OpenGL, Sixth Edition
  • عنوان ترجمه‌شدهٔ این بخش: مشاهده کلاسیک و رایانه‌ای، دوربین و APIهای مشاهده
  • نویسندگان و سازمان: Edward Angel — University of New Mexico؛ Dave Shreiner — ARM, Inc.
  • زبان اصلی: انگلیسی
  • وضعیت مجوز: حق ترجمه و بازنشر توسط کاربر تأیید شده است.
  • تاریخ ترجمه: ۱۴۰۵/۰۵/۲۲
  • مترجم: ترجمه با کمک هوش مصنوعی

مشاهدهٔ کلاسیک و رایانه‌ای، دوربین و APIهای مشاهده

فصل ۴ ــ مشاهده

بحث نیمهٔ نخست مدل دوربین مصنوعی ــ یعنی مشخص‌کردن اشیای سه‌بعدی ــ را به پایان رساندیم. اکنون شیوه‌های متنوع توصیف دوربین مجازی را بررسی می‌کنیم. در این مسیر، موضوعات مرتبطی مانند رابطهٔ میان روش‌های مشاهدهٔ کلاسیک و مشاهدهٔ رایانه‌ای و نیز چگونگی پیاده‌سازی تصویرسازی با تبدیل‌های تصویری را خواهیم دید.

رویکرد ما سه بخش دارد. نخست انواع نماهایی را بررسی می‌کنیم که می‌توان ایجاد کرد و اینکه چرا به بیش از یک نوع نما نیاز داریم. سپس می‌بینیم یک برنامهٔ کاربردی چگونه می‌تواند در OpenGL یک نمای مشخص را تعریف کند. فرایند مشاهده دو بخش دارد. در بخش نخست، با ماتریس model-view نمایش رأس‌ها را از Frame شیء، که اشیا در آن تعریف شده‌اند، به Frame چشم یا دوربین تبدیل می‌کنیم؛ در Frame دوربین، دوربین در مبدأ قرار دارد. این نمایش هندسه امکان استفاده از روش‌های مشاهدهٔ کانونی را فراهم می‌کند. بخش دوم به نوع تصویرسازی دلخواه ــ موازی یا پرسپکتیو ــ و بخشی از جهان که باید تصویر شود ــ حجم برش یا حجم دید ــ مربوط است. این مشخصات ماتریس تصویرسازی را تعیین می‌کنند که با ماتریس model-view الحاق می‌شود. در پایان، ماتریس‌های تصویرسازی برای مهم‌ترین نماهای موازی و پرسپکتیو را استخراج کرده و پیاده‌سازی آنها در OpenGL را بررسی می‌کنیم.

4.1 مشاهدهٔ کلاسیک و رایانه‌ای

پیش از بررسی رابط میان سامانهٔ گرافیک رایانه‌ای و برنامهٔ کاربردی برای مشاهدهٔ سه‌بعدی، اندکی از مسیر اصلی خارج می‌شویم و مشاهدهٔ کلاسیک را مرور می‌کنیم. دو دلیل برای این کار وجود دارد. نخست، بسیاری از کارهایی که زمانی با ترسیم دستی انجام می‌شدند ــ مانند پویانمایی فیلم، رندر معماری، نقشه‌کشی و طراحی قطعات مکانیکی ــ اکنون به طور معمول با کمک گرافیک رایانه‌ای انجام می‌شوند. متخصصان این حوزه‌ها باید بتوانند نماهای کلاسیکی مانند ایزومتریک، Elevation و گونه‌های مختلف پرسپکتیو را تولید کنند و بنابراین لازم است بتوانند سامانهٔ رایانه‌ای را برای ساخت چنین نماهایی به کار گیرند. دوم، رابطهٔ میان مشاهدهٔ کلاسیک و رایانه‌ای بسیاری از مزیت‌ها و چند دشواری رویکرد متداول در APIها را آشکار می‌کند.

شکل 4.1 ــ مشاهده. در هر دو شیوهٔ کلاسیک و رایانه‌ای، اجزای اساسی یکسان‌اند: شیء، بیننده، Projectorها و صفحهٔ تصویرسازی. Projectorها در مرکز تصویرسازی (COP) به هم می‌رسند.

هنگام معرفی مدل دوربین مصنوعی در فصل ۱ به شباهت‌های مشاهدهٔ کلاسیک و رایانه‌ای اشاره کردیم. COP متناظر با مرکز عدسی در دوربین یا چشم است و در سامانهٔ گرافیک رایانه‌ای، برای نماهای پرسپکتیو، همان مبدأ Frame دوربین است. همهٔ سامانه‌های گرافیکی استاندارد از مدلی پیروی می‌کنند که بر اپتیک هندسی استوار است: سطح تصویرسازی یک صفحه و Projectorها خط‌های مستقیم‌اند. این حالت هم رایج است و هم با مدل خط لوله‌ای ما به‌سادگی پیاده‌سازی می‌شود.

هم در گرافیک کلاسیک و هم در گرافیک رایانه‌ای می‌توان بیننده را در فاصلهٔ بی‌نهایت از اشیا فرض کرد. وقتی COP به بی‌نهایت منتقل می‌شود، Projectorها موازی می‌شوند و COP را می‌توان با «جهت تصویرسازی» (DOP) جایگزین کرد، همان‌گونه که در شکل 4.2 نشان داده شده است.

شکل 4.2 ــ انتقال مرکز تصویرسازی (COP) به بی‌نهایت.

همچنین وقتی COP به بی‌نهایت می‌رود می‌توان صفحهٔ تصویرسازی را ثابت نگه داشت و اندازهٔ تصویر تقریباً بدون تغییر می‌ماند، حتی با آنکه COP بی‌نهایت از اشیا دور شده است. نماهایی با COP محدود را نماهای پرسپکتیو و نماهایی با COP در بی‌نهایت را نماهای موازی می‌نامیم. در نماهای موازی، مبدأ Frame دوربین معمولاً روی صفحهٔ تصویرسازی قرار دارد.

لوح‌های رنگی ۹ و ۱۰ به‌ترتیب نمونه‌ای از رندر موازی و رندر پرسپکتیو را نشان می‌دهند. این مثال‌ها اهمیت دسترسی به هر دو نوع نما را در کاربردهایی مانند معماری روشن می‌کنند؛ در APIای که هر دو را پشتیبانی کند، کاربر می‌تواند به‌سادگی میان حالت‌های مختلف مشاهده جابه‌جا شود. بیشتر APIهای جدید هر دو نوع موازی و پرسپکتیو را پشتیبانی می‌کنند.

ردهٔ تصویرسازی‌های تولیدشده توسط این سامانه‌ها تصویرسازی هندسی صفحه‌ای نامیده می‌شود، زیرا سطح تصویرسازی صفحه و Projectorها خط‌اند. تصویرسازی‌های پرسپکتیو و موازی هر دو خط‌ها را حفظ می‌کنند، اما در حالت کلی زاویه‌ها را حفظ نمی‌کنند. اگرچه نماهای موازی حالت حدی مشاهدهٔ پرسپکتیو هستند، در مشاهدهٔ کلاسیک و رایانه‌ای معمولاً این دو به‌عنوان دو حالت جداگانه بررسی می‌شوند. در ترسیم کلاسیک، روش‌های ساخت دستی آنها متفاوت است. در گرافیک رایانه‌ای نیز نحوهٔ مشخص‌کردن این دو نوع نما تفاوت دارد. به جای آنکه نمای موازی را صرفاً حد نمای پرسپکتیو بدانیم، معادلات حدی را استخراج می‌کنیم و مستقیماً برای ساخت ماتریس تصویرسازی متناظر به کار می‌بریم. در معماری‌های خط لوله‌ای جدید، ماتریس تصویرسازی هر دو نوع نما را می‌توان در خط لوله بارگذاری کرد.

با وجود آنکه سامانه‌های گرافیک رایانه‌ای دو نوع بنیادی مشاهده دارند، گرافیک کلاسیک ظاهراً نماهای بسیار بیشتری از تصویرسازی‌های قائم چندنما تا پرسپکتیوهای یک‌نقطه‌ای، دونقطه‌ای و سه‌نقطه‌ای ارائه می‌دهد. این تفاوت ظاهری از آنجا ناشی می‌شود که در گرافیک کلاسیک هدف نمایش یک رابطهٔ مشخص میان شیء، بیننده و صفحهٔ تصویرسازی است، در حالی که رویکرد گرافیک رایانه‌ای بر استقلال کامل این مشخصات تأکید دارد.

4.1.1 مشاهدهٔ کلاسیک

وقتی یک معمار تصویر ساختمانی را ترسیم می‌کند، از پیش می‌داند کدام سمت ساختمان باید نمایش داده شود و بنابراین جایگاه مناسب بیننده نسبت به ساختمان را انتخاب می‌کند. هر نمای کلاسیک با رابطه‌ای مشخص میان اشیا و بیننده تعیین می‌شود.

در مشاهدهٔ کلاسیک مفهوم بنیادی وجه اصلی وجود دارد. اشیای کاربردهای واقعی، مانند معماری، غالباً از تعدادی وجه صفحه‌ای تشکیل می‌شوند و هر یک را می‌توان یک وجه اصلی دانست. برای شیئی مستطیلی مانند ساختمان، مفاهیم طبیعیِ جلو، عقب، بالا، پایین، راست و چپ وجود دارد. افزون بر این، در بسیاری از اشیای واقعی وجه‌ها با زاویهٔ قائمه به هم می‌رسند و در نتیجه سه جهت متعامد طبیعی با شیء مرتبط است.

شکل 4.3 برخی از مهم‌ترین انواع نما را نشان می‌دهد. برای هر یک از دو گروه موازی و پرسپکتیو، از محدودترین حالت آغاز می‌کنیم و به سمت حالت‌های کم‌محدودیت‌تر می‌رویم.

4.1.2 تصویرسازی‌های قائم

نخستین نمای کلاسیک، تصویرسازی قائم شکل 4.4 است. در همهٔ نماهای قائم یا Orthogonal، Projectorها بر صفحهٔ تصویرسازی عمودند. در تصویرسازی قائم چندنما، چند تصویرسازی جداگانه می‌سازیم که در هر یک صفحهٔ تصویرسازی با یکی از وجه‌های اصلی شیء موازی است.

شکل 4.3 ــ نماهای کلاسیک: Elevation جلو، Elevation مایل، پلان مایل، ایزومتریک، پرسپکتیو یک‌نقطه‌ای و پرسپکتیو سه‌نقطه‌ای.

شکل 4.4 ــ تصویرسازی‌های قائم.

معمولاً سه نما مانند جلو، بالا و راست برای نمایش شیء به کار می‌روند. دلیل تولید چند نما از شکل 4.5 روشن است: در یک شیء جعبه‌مانند، فقط وجه‌های موازی صفحهٔ تصویرسازی به شکل واقعی خود در تصویر دیده می‌شوند. بیننده معمولاً برای تجسم شکل شیء از تصویرسازی‌های قائم چندنما به بیش از دو نما نیاز دارد و تفسیر این تصاویر می‌تواند مهارت بخواهد. اهمیت این نما در آن است که هم فاصله‌ها و هم زاویه‌ها را حفظ می‌کند و چون نه فاصله و نه شکل دچار اعوجاج نمی‌شوند، برای نقشه‌های اجرایی بسیار مناسب است.

4.1.3 تصویرسازی‌های آکسونومتریک

اگر بخواهیم در یک نما تعداد بیشتری از وجه‌های اصلی شیء جعبه‌مانند را ببینیم، باید یکی از محدودیت‌ها را حذف کنیم. در نماهای آکسونومتریک Projectorها همچنان بر صفحهٔ تصویرسازی عمودند، اما صفحهٔ تصویرسازی می‌تواند نسبت به شیء هر جهت‌گیری دلخواهی داشته باشد.

شکل 4.5 ــ معبد و سه تصویرسازی قائم چندنما.

شکل 4.6 ــ تصویرسازی‌های آکسونومتریک: (الف) ساخت تصویرسازی Trimetric، (ب) نمای بالا، (ج) نمای جانبی.

اگر صفحهٔ تصویرسازی نسبت به سه وجه اصلی که در یک گوشهٔ شیء مستطیلی به هم می‌رسند به‌طور متقارن قرار گیرد، نمای ایزومتریک داریم. اگر نسبت به دو وجه اصلی متقارن باشد، نما دیمتریک است و حالت عمومی‌تر را تریمتریک می‌نامیم. این نماها در شکل 4.7 نشان داده شده‌اند. در نمای ایزومتریک، طول یک پاره‌خط در فضای تصویر کوتاه‌تر از طول آن در فضای شیء است؛ با این حال این کوتاه‌شدگی در سه جهت اصلی یکسان است.

شکل 4.7 ــ نماهای آکسونومتریک: دیمتریک، تریمتریک و ایزومتریک.

از آنجا که نسبت کوتاه‌شدگی در هر سه جهت اصلیِ نمای ایزومتریک یکسان است، هنوز می‌توان اندازه‌گیری فاصله انجام داد. در نمای دیمتریک دو نسبت کوتاه‌شدگی و در نمای تریمتریک سه نسبت متفاوت وجود دارد. همچنین هرچند خط‌های موازی در تصویر موازی باقی می‌مانند، زاویه‌ها حفظ نمی‌شوند و دایره ممکن است به بیضی تصویر شود. این اعوجاج بهایی است که برای دیدن بیش از یک وجه اصلی در نمایی که هم با دست و هم با رایانه به‌سادگی تولید می‌شود می‌پردازیم. نماهای آکسونومتریک در طراحی معماری و مکانیکی کاربرد فراوان دارند.

4.1.4 تصویرسازی‌های مایل

نماهای مایل عمومی‌ترین نماهای موازی هستند. با اجازه‌دادن به Projectorها برای داشتن زاویه‌ای دلخواه با صفحهٔ تصویرسازی، یک تصویرسازی مایل به دست می‌آوریم.

شکل 4.8 ــ نمای مایل: (الف) ساخت، (ب) نمای بالا، (ج) نمای جانبی.

در این حالت زاویه‌های صفحه‌هایی که با صفحهٔ تصویرسازی موازی‌اند حفظ می‌شوند. دایره‌ای در صفحهٔ موازی صفحهٔ تصویرسازی همچنان به صورت دایره تصویر می‌شود، در حالی که می‌توان بیش از یک وجه اصلی شیء را دید. ساخت دستی نماهای مایل دشوارتر از سایر نماهاست و تا حدی نیز غیرطبیعی به نظر می‌رسند.

بیشتر ابزارهای فیزیکی مشاهده، از جمله دستگاه بینایی انسان، عدسی‌ای دارند که رابطه‌ای ثابت با صفحهٔ تصویر دارد و معمولاً عدسی با صفحه موازی است. این ابزارها نماهای پرسپکتیو تولید می‌کنند؛ اگر بیننده از شیء بسیار دور باشد، نما تقریباً موازی می‌شود، اما به علت موازی‌بودن صفحهٔ تصویرسازی و عدسی، حالت حاصل قائم است. دوربین فانوسی (bellows camera) که در بخش 1.6 برای توسعهٔ مدل دوربین مصنوعی به کار بردیم انعطاف لازم برای تقریب نماهای موازی مایل را دارد. یکی از کاربردهای چنین دوربینی ساخت تصویر ساختمان‌ها به شکلی است که اضلاع ساختمان به جای همگراشدن، موازی دیده شوند.

از دید برنامه‌نویس کاربردی، تفاوت اساسی میان گونه‌های مختلف نماهای موازی وجود ندارد. برنامه‌نویس نوع نما ــ موازی یا پرسپکتیو ــ و مجموعه‌ای از پارامترهای توصیف‌کنندهٔ دوربین را مشخص می‌کند. چالش اصلی این است که این پارامترها در رویه‌های مشاهده چگونه انتخاب شوند تا شیء به بهترین شکل دیده شود یا یک نمای کلاسیک مشخص تولید شود.

4.1.5 مشاهدهٔ پرسپکتیو

مشخصهٔ همهٔ نماهای پرسپکتیو کاهش اندازه با افزایش فاصله است. هرچه اشیا از بیننده دورتر شوند، تصویر آنها کوچک‌تر می‌شود. این تغییر اندازه ظاهر طبیعی نمای پرسپکتیو را ایجاد می‌کند؛ اما چون مقدار کوتاه‌شدگی یک خط به فاصلهٔ آن از بیننده وابسته است، اندازه‌گیری دقیق از روی نمای پرسپکتیو ممکن نیست. بنابراین کاربرد اصلی نماهای پرسپکتیو در زمینه‌هایی مانند معماری و پویانمایی است که طبیعی‌بودن تصویر اهمیت دارد.

در نماهای پرسپکتیو کلاسیک، بیننده نسبت به صفحهٔ تصویرسازی به‌صورت متقارن قرار می‌گیرد، همان‌گونه که در شکل 4.9 نشان داده شده است. در نتیجه هرمی که پنجرهٔ صفحهٔ تصویرسازی و COP تعیین می‌کنند یک هرم متقارن یا قائم است.

شکل 4.9 ــ مشاهدهٔ پرسپکتیو.

این تقارن از رابطهٔ ثابت میان پشت چشم (شبکیه) و عدسی در بینایی انسان، یا میان پشت دوربین و عدسی در دوربین‌های معمولی، ناشی می‌شود. برخی دوربین‌ها مانند bellows camera صفحهٔ فیلم متحرک دارند و می‌توانند نماهای پرسپکتیو عمومی‌تر تولید کنند. مدل مورد استفاده در گرافیک رایانه‌ای این حالت عمومی را نیز در بر می‌گیرد.

نماهای پرسپکتیو کلاسیک معمولاً با نام‌های یک‌نقطه‌ای، دونقطه‌ای و سه‌نقطه‌ای شناخته می‌شوند. تفاوت آنها بر تعداد جهت‌های اصلی شیء که با صفحهٔ تصویرسازی موازی‌اند استوار است. در عمومی‌ترین حالت، یعنی پرسپکتیو سه‌نقطه‌ای، خط‌های موازی در هر سه جهت اصلی به نقاط گریز محدود همگرا می‌شوند. اگر یکی از جهت‌های اصلی با صفحهٔ تصویرسازی موازی باشد، پرسپکتیو دونقطه‌ای داریم و فقط دو جهت اصلی همگرا می‌شوند. در پرسپکتیو یک‌نقطه‌ای، دو جهت اصلی با صفحهٔ تصویرسازی موازی‌اند و تنها یک نقطهٔ گریز وجود دارد.

شکل 4.10 ــ نماهای پرسپکتیو کلاسیک: (الف) سه‌نقطه‌ای، (ب) دونقطه‌ای، (ج) یک‌نقطه‌ای.

از دید برنامه‌نویس، این سه حالت تنها موارد ویژه‌ای از مشاهدهٔ پرسپکتیو عمومی هستند که در بخش 4.4 پیاده‌سازی خواهد شد.

4.2 مشاهده با رایانه

اکنون به گرافیک سه‌بعدی از دید رایانه بازمی‌گردیم. چون مشاهده در گرافیک رایانه‌ای بر مدل دوربین مصنوعی مبتنی است، باید بتوانیم هر یک از نماهای کلاسیک را بسازیم. تفاوت بنیادی این است که همهٔ نماهای کلاسیک بر رابطه‌ای مشخص میان اشیا، بیننده و Projectorها تکیه دارند، در حالی که در گرافیک رایانه‌ای استقلال مشخصات شیء و پارامترهای دوربین مورد تأکید است. بنابراین برای ساخت یک نمای کلاسیک، برنامهٔ کاربردی باید از اطلاعات اشیا برای ساخت و جای‌گذاری دوربین مناسب استفاده کند.

در OpenGL برای اینکه عملیات مشاهده کجا و چگونه انجام شود گزینه‌های متعددی داریم. همهٔ رویکردها از قابلیت‌های قدرتمند تبدیل در GPU استفاده می‌کنند. چون هر تبدیل معادل تغییر Frame است، می‌توان مشاهده را برحسب Frameها و دستگاه‌های مختصات فصل ۳ توضیح داد. به‌طور ویژه با مختصات شیء، مختصات دوربین و مختصات Clip کار خواهیم کرد.

نقطهٔ شروع مناسب، خروجی Vertex Shader است. در فصل‌های ۲ و ۳ از این واقعیت استفاده کردیم که تا زمانی که رأس‌های خروجی Vertex Shader داخل حجم برش باشند، به Rasterizer می‌روند. در فصل ۲ موقعیت رأس‌ها را مستقیماً داخل مکعب مشاهدهٔ پیش‌فرض مشخص کردیم. در فصل ۳ یاد گرفتیم چگونه با تبدیل‌های آفین موقعیت‌ها را مقیاس کنیم تا داخل آن مکعب نگاشت شوند. همچنین به این واقعیت تکیه کردیم که اشیای فرستاده‌شده به Rasterizer با یک تصویرسازی قائم ساده تصویر می‌شوند.

حذف سطح پنهان، با این حال، پس از Fragment Shader رخ می‌دهد. در نتیجه، حتی اگر شیئی توسط اشیای دیگر از دید دوربین پوشیده شده باشد، Rasterizer هنوز برای شیء پوشیده‌شده‌ای که در حجم برش قرار دارد Fragment تولید می‌کند؛ حذف نهایی در مرحلهٔ بعد انجام می‌شود. برای انعطاف بیشتر در تعریف اشیا و مشاهدهٔ آنها چهار قابلیت اصلی لازم داریم:

  1. امکان کار با واحدهای خود برنامهٔ کاربردی؛
  2. جای‌گذاری دوربین مستقل از اشیا؛
  3. تعریف حجم برش با واحدهای مرتبط با برنامهٔ کاربردی؛
  4. انتخاب تصویرسازی موازی یا پرسپکتیو.

سه مورد نخست را با تبدیل‌های آفین و مورد آخر را با فرایندی به نام نرمال‌سازی پرسپکتیو انجام می‌دهیم. همهٔ این تبدیل‌ها باید در کد برنامه یا Vertex Shader اجرا شوند.

از Frameهای مورد استفاده در OpenGL، سه Frame در فرایند مشاهده اهمیت ویژه دارند: Frame شیء، Frame دوربین و Frame مختصات Clip. در فصل‌های ۲ و ۳ با پیش‌فرض یکسان‌بودن هر سه Frame، نیازی به مشخص‌کردن صریح دو Frame نخست نداشتیم. موقعیت رأس‌ها یا مستقیماً در مختصات Clip مشخص می‌شد یا با تبدیل آفین به داخل مکعب برش منتقل می‌شد. دوربین نیز در مبدأ و رو به جهت منفی محور (z) در مختصات Clip ثابت بود.¹

برای مشاهدهٔ انعطاف‌پذیرتر، فرایند را به دو عملیات بنیادی تقسیم می‌کنیم. نخست باید دوربین را جای‌گذاری و جهت‌دهی کنیم؛ این وظیفهٔ تبدیل model-view است.

¹ دوربین پیش‌فرض حتی می‌تواند اشیای «پشت» خود را، اگر در حجم برش باشند، ببیند.

پس از تبدیل model-view، رأس‌ها در مختصات چشم یا دوربین نمایش داده می‌شوند. گام دوم اعمال تبدیل تصویرسازی است. این تبدیل، تصویرسازی تعیین‌شده ــ قائم یا پرسپکتیو ــ را روی رأس‌ها اعمال می‌کند و اشیای داخل حجم برش مشخص‌شده را به همان مکعب برش استاندارد در مختصات Clip می‌برد. یکی از کارکردهای هر نوع تصویرسازی این است که بتوانیم حجم دید را به جای مقیاس‌کردن دستی شیء برای جاگرفتن در حجم پیش‌فرض، مستقیماً در مختصات دوربین تعریف کنیم.

شکل 4.11 ــ تبدیل‌های مشاهده: رأس‌ها از مختصات شیء با تبدیل model-view به مختصات دوربین و سپس با تبدیل projection به مختصات Clip می‌روند.

آنچه پیش‌تر «ماتریس تبدیل جاری» می‌نامیدیم، حاصل‌ضرب دو ماتریس خواهد بود: ماتریس model-view و ماتریس projection. ماتریس model-view رأس‌ها را از مختصات شیء به مختصات دوربین می‌برد و بنابراین باید جایگاه و جهت‌گیری دوربین را در خود داشته باشد. ماتریس projection هم تصویرسازی مطلوب ــ قائم یا پرسپکتیو ــ را انجام می‌دهد و هم حجم دید مشخص‌شده در مختصات دوربین را به مکعب دید در مختصات Clip تبدیل می‌کند.

4.3 جای‌گذاری دوربین

در این بخش جایگاه و جهت‌گیری دوربین را بررسی می‌کنیم؛ بخش 4.4 به تعیین تصویرسازی مطلوب می‌پردازد. تمرکز اصلی بر APIای است که با OpenGL سازگار باشد، اما چند API دیگر برای مشخص‌کردن دوربین نیز به اختصار بررسی می‌شوند.

4.3.1 جای‌گذاری Frame دوربین

همان‌گونه که در فصل ۳ دیدیم، رأس‌ها را می‌توان با هر واحد دلخواهی تعریف کرد و ماتریس model-view را با دنباله‌ای از تبدیل‌های آفین ساخت که این رأس‌ها را جابه‌جا می‌کنند. تبدیل model-view الحاقِ یک تبدیل مدل‌سازی است که نمونه‌های اشیا را از مختصات شیء به Frame جهان می‌برد، و تبدیل دوم مختصات جهان را به مختصات چشم می‌برد. چون معمولاً نیازی به دسترسی مستقیم به مختصات جهان نداریم، به جای دو ماتریس جداگانهٔ مدل‌سازی و مشاهده از یک ماتریس model-view استفاده می‌کنیم.

ابتدا ماتریس model-view را ماتریس همانی قرار می‌دهیم، بنابراین Frame دوربین و Frame شیء یکسان‌اند. در نتیجه دوربین در آغاز رو به جهت منفی محور (z) است.

شکل 4.12 ــ موقعیت اولیهٔ دوربین.

در بیشتر برنامه‌ها اشیا را پیرامون مبدأ مدل می‌کنیم؛ در نتیجه دوربین پیش‌فرض که در مبدأ است نمی‌تواند همهٔ اشیای صحنه را به شکل مطلوب ببیند. باید یا دوربین را از اشیا دور کنیم یا اشیا را به جلوی دوربین منتقل کنیم.

این دو عملیات معادل‌اند، زیرا هر دو را می‌توان به صورت جای‌گذاری Frame دوربین نسبت به Frame اشیا تعبیر کرد.

تصور کنید چند شیء را با تعیین همهٔ رأس‌ها و قرار دادن موقعیت آنها در یک آرایه تعریف کرده‌ایم. کار را با ماتریس model-view همانی آغاز می‌کنیم. هر تغییر در این ماتریس، Frame شیء را نسبت به دوربین جابه‌جا می‌کند و بر دید دوربین از همهٔ اشیایی که پس از آن تعریف می‌شوند اثر می‌گذارد، زیرا رأس‌های آنها نسبت به Frame جدید شیء مشخص می‌شوند. از دید جریان برنامه نیز ماتریس‌های projection و model-view بخشی از State برنامه‌اند. این ماتریس‌ها یا در خود برنامه روی موقعیت رأس‌ها اعمال می‌شوند یا، که رایج‌تر است، به Vertex Shader فرستاده می‌شوند تا هر بار دادهٔ رأس به Shader می‌رسد خودکار اعمال شوند.

شکل 4.13 ــ حرکت Frame دوربین و شیء: (الف) پیکربندی اولیه، (ب) پیکربندی پس از تغییر ماتریس model-view.

در حالت اولیه، رأسی در (p) در هر دو Frame نمایش یکسان دارد. پس از تغییر model-view به (C)، دو Frame دیگر یکسان نیستند. (C) اطلاعات لازم برای تبدیل میان آنها را در خود دارد. رأسی که پس از این تغییر در نقطهٔ (q) در Frame شیء تعریف می‌شود، در Frame دوربین موقعیت (Cq) دارد. این تبدیل می‌تواند در برنامه ذخیره شود یا در GPU انجام گیرد.

دیدگاه معادل این است که دوربین همچنان در مبدأ Frame خودش قرار دارد و ماتریس model-view بر Primitiveهای تعریف‌شده در این دستگاه اعمال می‌شود. در عمل هر یک از دو تعبیر قابل استفاده است، اما باید بسیار مراقب بود Primitiveها در چه نقطه‌ای از برنامه و نسبت به تغییرات model-view تعریف می‌شوند.

در هر لحظه، ماتریس model-view رابطهٔ میان Frame دوربین و Frame شیء را در خود کپسوله می‌کند. ترکیب تبدیل مدل‌سازی و مشاهده در یک ماتریس شاید در ابتدا گیج‌کننده باشد، اما انتخاب مناسبی است. اگر دوربین را شیئی با خواص هندسی بدانیم، تبدیل‌هایی که جایگاه و جهت اشیا را عوض می‌کنند باید رابطهٔ جایگاه و جهت دوربین نسبت به آنها را نیز تغییر دهند.

اکنون باید جایگاه دلخواه دوربین را مشخص و آن را در OpenGL پیاده‌سازی کنیم. در این فصل چند رویکرد معرفی می‌شود. روش نخست، تعیین غیرمستقیم جایگاه با اعمال دنباله‌ای از دوران‌ها و انتقال‌ها به ماتریس model-view است. این همان کاربرد مستقیم تبدیل نمونهٔ فصل ۳ است، ولی دو نکته اهمیت دارد: معمولاً می‌خواهیم پیش از جای‌گذاری اشیا، جایگاه و جهت دوربین را مشخص کنیم؛ و ترتیب تبدیل‌های دوربین ممکن است برعکس انتظار اولیه به نظر برسد.

شیئی را در مرکز مبدأ و دوربین را نیز در حالت اولیه، در مبدأ و رو به محور (z) منفی، فرض کنید. اگر بخواهیم وجه‌هایی را ببینیم که رو به جهت مثبت (z) هستند، باید دوربین را از مبدأ دور کنیم. با ثابت نگه‌داشتن جهت نگاه به (z) منفی، دوربین باید روی محور (z) مثبت به عقب برود. تبدیل مناسب، برای (d>0)، چنین است:

T=\begin{bmatrix} 1&0&0&0\\0&1&0&0\\0&0&1&-d\\0&0&0&1 \end{bmatrix}.

بسیاری این عملیات را بهتر با حرکت Frame دوربین نسبت به Frame شیء درک می‌کنند. این تعبیر ریشه در مشاهدهٔ کلاسیک دارد.

در گرافیک رایانه‌ای معمولاً اشیا را در Frame ثابت می‌دانیم و بیننده برای رسیدن به نمای مطلوب حرکت می‌کند. در مشاهدهٔ کلاسیک، بیننده نقش غالب دارد: از نظر مفهومی شیء را برمی‌داریم، آن را جهت‌دهی می‌کنیم و به مکان مناسب می‌آوریم. یکی از پیامدهای این رویکرد آن است که فاصله‌ها از بیننده به شیء سنجیده می‌شوند، نه مانند بسیاری از سامانه‌های فیزیکی از شیء به بیننده. مشاهدهٔ کلاسیک اغلب Frame دوربین چپ‌گرد ایجاد می‌کرد. سامانه‌های اولیهٔ گرافیک مدل‌سازی را در مختصات راست‌گرد و مشاهده را در مختصات چپ‌گرد انجام می‌دادند؛ تصمیمی که از نظر فنی درست بود اما کاربران را سردرگم می‌کرد. در OpenGL Frameهای داخلی راست‌گرد هستند و برنامه‌نویس عمدتاً در مختصات شیء کار می‌کند، بنابراین معمولاً با این جزئیات داخلی درگیر نمی‌شود.

اگر بخواهیم همان شیء را از محور (x) مثبت ببینیم، علاوه بر دورشدن باید دوربین را حول محور (y) نیز بچرخانیم. باید پس از چرخاندن ۹۰ درجه‌ای دوربین حول (y)، انتقال را انجام دهیم. در کد، به علت ترتیب ضرب تبدیل‌ها، فراخوانی‌ها به ترتیب معکوس ظاهر می‌شوند:

mat4 model_view;
model_view = Translate(0.0, 0.0, -d)*RotateX(-90.0);

شکل 4.14 ــ جای‌گذاری دوربین.

از دید Frameها، نخست Frame شیء را نسبت به Frame دوربین می‌چرخانیم و سپس دو Frame را از هم دور می‌کنیم.

در فصل‌های ۲ و ۳ با استفاده از ماتریس همانی به عنوان projection پیش‌فرض، مثال‌های سه‌بعدی ساده را نمایش دادیم. این پیش‌فرض یک تصویرسازی قائم با دوربین در مبدأ و رو به (z) منفی ایجاد می‌کند. در مثال مکعب فصل ۳، خود مکعب را برای دیدن وجه‌های مطلوب چرخاندیم؛ این کار معادل چرخاندن Frame مکعب نسبت به Frame دوربین است و همان نما را می‌توان با چرخاندن دوربین نسبت به مکعب به دست آورد. این راهبرد انتقال و دوران دوربین را می‌توان برای ساخت نماهای قائم دیگر نیز گسترش داد. نماهای پرسپکتیو به تغییر projection پیش‌فرض نیاز دارند.

برای ساخت نمای ایزومتریک مکعب، مکعبی را در مبدأ و هم‌راستا با محورها در نظر بگیرید. دوربین پیش‌فرض درون مکعب قرار دارد، پس ابتدا باید آن را با انتقال دور کنیم. نمای ایزومتریک زمانی حاصل می‌شود که دوربین نسبت به سه وجه مجاور مکعب متقارن باشد؛ مثلاً در امتداد خطی از مبدأ که از نقطهٔ ((1,1,1)) می‌گذرد.

یکی از هشت نمای ایزومتریک ــ یکی برای هر رأس ــ را می‌توان با دو دوران ساخت. ابتدا مکعب را حول محور (x) به اندازهٔ ۴۵ درجه می‌چرخانیم تا دو وجه به‌طور متقارن دیده شوند. سپس حول محور (y) به اندازهٔ (-35.26) درجه می‌چرخانیم. زاویهٔ دوم از هندسهٔ مکعب پس از دوران نخست به دست می‌آید: رأس اولیهٔ ((-1,1,1)) به ((-1,0,\sqrt2)) می‌رود و مثلث قائم حاصل زاویهٔ 35.26 درجه را تعیین می‌کند؛ چون دوران ساعت‌گرد لازم است، علامت زاویه منفی می‌شود. در پایان دوربین را از مبدأ دور می‌کنیم.

شکل 4.15 ــ مکعب پس از دوران حول محور x: (الف) دید از محور (z) مثبت، (ب) دید از محور (y) مثبت.

پس راهبرد کلی نخست چرخاندن Frame دوربین نسبت به Frame شیء و سپس جداکردن دو Frame است و ماتریس model-view شکل زیر را دارد:

M=TR_xR_y.

برای نمای ایزومتریک، الحاق ماتریس‌های دوران در مختصات همگن ماتریس دورانی می‌دهد که مؤلفه‌های آن از (\sqrt2/2)، (\sqrt3/3)، (\sqrt6/6) و (\sqrt6/3) تشکیل می‌شوند. این ماتریس همان جهت‌گیری ایزومتریک متقارن را ایجاد می‌کند.

به‌سادگی می‌توان بررسی کرد که رأس اولیهٔ ((-1,1,1)) با این ماتریس به ((0,0,\sqrt3)) تبدیل می‌شود. اگر انتقال ((0,0,-d)) را نیز الحاق کنیم، درایهٔ انتقالی (z) برابر (-d) می‌شود. در OpenGL می‌توان model-view را چنین تنظیم کرد:

mat4 model_view;
model_view = Translate(0.0, 0.0, -d)*RotateX(35.26)*RotateY(45.0);

اکنون از نمایش اشیا در مختصات شیء به نمایش آنها در مختصات دوربین رسیده‌ایم. دوران و انتقال اندازهٔ شیء و در نتیجه اندازهٔ تصویر قائم آن را تغییر نمی‌دهند، اما می‌توانند تعیین کنند که شیء برش بخورد یا نه. چون حجم برش نسبت به دوربین اندازه‌گیری می‌شود، انتقال شیء به فاصلهٔ زیاد ممکن است آن را از حجم برش خارج کند؛ در این حالت، حتی اگر تصویر هندسی شیء تغییر نکرده و دوربین هنوز به آن نگاه کند، شیء در تصویر ظاهر نمی‌شود.

4.3.2 دو API مشاهده

ساخت model-view برای نمای ایزومتریک کمی نامطلوب است. هرچند روش شهودی بود، APIای که برنامه‌نویس را مجبور کند پیش از مشخص‌کردن تبدیل‌ها زاویه‌های منفرد را محاسبه کند مناسب نیست. می‌توان از رویکردی شبیه PHIGS، یکی از APIهای استاندارد اولیهٔ گرافیک سه‌بعدی، استفاده کرد.

نقطهٔ شروع باز هم Frame شیء است. جایگاه و جهت دوربین را در این Frame توصیف می‌کنیم و نوع دقیق تصویر ــ پرسپکتیو یا موازی ــ جداگانه با ماتریس projection تعیین می‌شود. این بخش دوم فرایند مشاهده اغلب تبدیل نرمال‌سازی نامیده می‌شود. مسئله را تغییر Frame در نظر می‌گیریم. دوربین ابتدا در مبدأ و رو به (z) منفی است. مکان مطلوب آن حول نقطه‌ای به نام نقطهٔ مرجع دید (VRP) قرار دارد که در Frame شیء داده می‌شود.

شکل 4.16 ــ Frame دوربین.

برای مشخص‌کردن VRP می‌توان تابعی مانند زیر داشت:

set_view_reference_point(x, y, z);

برای جهت‌گیری دوربین، مشخصات را به دو بخش تقسیم می‌کنیم: بردار نرمال صفحهٔ دید (VPN) و بردار بالای دید (VUP). VPN جهت صفحهٔ تصویرسازی یا پشت دوربین را تعیین می‌کند.

بخشی از API می‌تواند تابعی مانند زیر باشد:

set_view_plane_normal(nx, ny, nz);

تنها دانستن VPN تعیین نمی‌کند از دید دوربین کدام جهت «بالا» است، زیرا دوربین می‌تواند با پشت خود در همان صفحه بچرخد. بردار VUP این آزادی را حذف می‌کند:

set_view_up(vup_x, vup_y, vup_z);

بردار VUP را روی صفحهٔ دید تصویر می‌کنیم تا جهت بالای واقعی (v) به دست آید.

شکل 4.17 ــ تعیین بردار بالای دید.

این تصویرسازی به کاربر اجازه می‌دهد هر برداری را که با (n) موازی نیست وارد کند، بدون آنکه مجبور باشد برداری دقیقاً واقع در صفحهٔ تصویرسازی محاسبه کند. بردار (v) بر (n) عمود است. با ضرب برداری، جهت متعامد سوم (u) را به دست می‌آوریم. این دستگاه متعامد جدید معمولاً دستگاه مختصات مشاهده یا دستگاه (u-v-n) نامیده می‌شود. با افزودن VRP، Frame مطلوب دوربین کامل می‌شود. ماتریس تغییر Frame را ماتریس جهت‌دهی دید می‌نامیم که متناظر با جزء مشاهدهٔ model-view است.

برای استخراج این ماتریس، VRP را به صورت نقطهٔ همگن

p=[x\;y\;z\;1]^T,

VPN را به صورت

n=[n_x\;n_y\;n_z\;0]^T,

و VUP را به صورت

vup=[vup_x\;vup_y\;vup_z\;0]^T

در نظر می‌گیریم. Frame جدید با VRP به عنوان مبدأ، VPN به عنوان یک جهت مختصاتی و دو جهت متعامد دیگر (u) و (v) ساخته می‌شود.

در حالت پیش‌فرض محورهای (x,y,z) اولیه به‌ترتیب به (u,v,n) تبدیل می‌شوند. VRP با انتقال سادهٔ (T(-x,-y,-z)) به مبدأ منتقل می‌شود. بقیهٔ model-view یک دوران است، بنابراین:

V=TR.

چون (v) باید بر (n) عمود باشد:

n\cdot v=0.

از شکل 4.17 می‌دانیم (v) تصویر VUP روی صفحه‌ای است که با (n) و VUP ساخته می‌شود؛ بنابراین ترکیب خطی آنهاست:

v=\alpha n+\beta vup.

اگر موقتاً طول بردارها را نادیده بگیریم، (\beta=1) می‌گذاریم و داریم:

\alpha=-\frac{vup\cdot n}{n\cdot n},

و در نتیجه

v=vup-\frac{vup\cdot n}{n\cdot n}n.

جهت متعامد سوم با ضرب برداری حاصل می‌شود:

u=v\times n.

این بردارها عموماً طول واحد ندارند. با نرمال‌سازی مستقل آنها بردارهای واحد (u')، (v') و (n') را به دست می‌آوریم. ماتریسی که یک بردار را در دستگاه (u'v'n') نسبت به دستگاه اصلی جهت‌دهی می‌کند ماتریس دوران (A) است. اما ما جهت عکس را می‌خواهیم؛ چون (A) ماتریس دوران است:

R=A^{-1}=A^T.

با ضرب ماتریس دوران در انتقال، ماتریس مشاهدهٔ نهایی به دست می‌آید:

V=RT= \begin{bmatrix} u'_x&u'_y&u'_z&-xu'_x-yu'_y-zu'_z\v'_x&v'_y&v'_z&-xv'_x-yv'_y-zv'_z\n'_x&n'_y&n'_z&-xn'_x-yn'_y-zn'_z\0&0&0&1 \end{bmatrix}.

در این استخراج، ماتریس انتقال در سمت راست قرار دارد، حال آنکه در استخراج نخست سمت چپ بود. یک تعبیر این است که در روش نخست ابتدا یکی از Frameها را دوران دادیم و سپس دو Frame را در جهتی که در Frame دوربین بیان می‌شد از هم دور کردیم؛ در روش دوم موقعیت دوربین از ابتدا در Frame شیء داده شده بود. ماتریس‌های (RT) و (TR) بخش دورانی و سطر پایینی یکسان دارند، اما سه درایهٔ بالایی ستون راست متفاوت‌اند، زیرا Frame دوران روی ضرایب انتقال در (RT) اثر دارد ولی در (TR) نه.

برای مثال ایزومتریک می‌توان VPN و VUP را به‌ترتیب متناسب با ((-1,1,1)) و ((0,1,0)) انتخاب کرد. جایگاه دوربین نیز باید روی قطر متناظر Frame اصلی باشد. با انتخاب مناسب (p)، همان ماتریس model-view بخش 4.3.1 به دست می‌آید.

4.3.3 تابع LookAt

استفاده از VRP، VPN و VUP تنها یکی از روش‌های طراحی API برای تعیین دوربین است. در بسیاری از موقعیت‌ها روشی مستقیم‌تر مناسب‌تر است. دوربینی را در نقطهٔ (e)، موسوم به نقطهٔ چشم (eye point)، در Frame شیء در نظر بگیرید که به نقطهٔ دوم (a)، موسوم به نقطهٔ هدف (at point)، نگاه می‌کند. این دو نقطه VPN و VRP را تعیین می‌کنند.

شکل 4.18 ــ جای‌گذاری Look-at.

VPN از تفریق نقطهٔ چشم و نقطهٔ هدف و سپس نرمال‌سازی به دست می‌آید:

vpn=a-e, \qquad n=\frac{vpn}{|vpn|}.

VRP همان نقطهٔ چشم است. بنابراین فقط لازم است جهت بالای مطلوب دوربین را نیز بدهیم. تابع ساخت ماتریس LookAt می‌تواند یکی از دو شکل زیر را داشته باشد:

mat4 LookAt(point4 eye, point4 at, vec4 up)

یا:

mat4 LookAt(GLfloat eyex, GLfloat eyey, GLfloat eyez,
            GLfloat atx, GLfloat aty, GLfloat atz,
            GLfloat upx, GLfloat upy, GLfloat upz);

پس از محاسبهٔ vpn می‌توان مانند بخش قبل ادامه داد. محاسبهٔ ساده‌تر برای بردار متعامد (u) عبارت است از:

u=\frac{vup\times n}{|vup\times n|}.

چون در مختصات همگن کار می‌کنیم، vup می‌تواند از نوع vec4 باشد، مشروط بر اینکه مؤلفهٔ چهارم آن صفر باشد.

در پایان، تصویر نرمال‌شدهٔ بردار بالا روی صفحهٔ دوربین را با ضرب برداری دوم به دست می‌آوریم:

v=\frac{n\times u}{|n\times u|}.

می‌توان دوران‌ها، انتقال‌ها و مقیاس‌دهی‌های استاندارد را نیز در تعریف اشیا به کار برد. هرچند این تبدیل‌ها model-view را تغییر می‌دهند، از نظر مفهومی غالباً مفید است LookAt را عملیات جای‌گذاری دوربین و تبدیل‌های بعدی model-view را عملیات مربوط به جای‌گذاری یا مدل‌سازی اشیا در نظر بگیریم. توابعی مانند LookAt که دوربین را جای‌گذاری می‌کنند در مختصات شیء مشخص می‌شوند؛ در مقابل، توابعی که برای ساخت ماتریس projection معرفی خواهیم کرد در مختصات چشم تعریف می‌شوند.

4.3.4 APIهای دیگر مشاهده

در بسیاری از کاربردها هیچ‌یک از رابط‌های مشاهدهٔ قبلی مناسب نیست. شبیه‌ساز پرواز را در نظر بگیرید. خلبان معمولاً سه زاویهٔ Roll، Pitch و Yaw را برای تعیین جهت‌گیری خود به کار می‌برد. این زاویه‌ها نسبت به مرکز جرم وسیله و دستگاه مختصاتی هم‌راستا با محورهای آن تعریف می‌شوند.

شکل 4.19 ــ Roll، Pitch و Yaw.

بنابراین خلبان شیء را برحسب سه زاویه و فاصلهٔ آن تا مرکز جرم وسیله می‌بیند. می‌توان تبدیل مشاهده را از یک انتقال و سه دوران ساده بر پایهٔ این مشخصات ساخت.

در بسیاری از برنامه‌ها مشاهده به‌طور طبیعی در مختصات قطبی، نه دکارتی، تعریف می‌شود؛ به‌ویژه وقتی اشیا حول اشیای دیگر می‌چرخند. برای مثال جهت یک ستاره در آسمان از دید ناظر با ارتفاع (elevation) و سمت (azimuth) بیان می‌شود.

شکل 4.20 ــ Elevation و Azimuth.

Elevation زاویه‌ای است که ستاره بالاتر از صفحهٔ ناظر دیده می‌شود. با تعریف یک نرمال در محل ناظر و صفحه‌ای بر پایهٔ آن، می‌توان Elevation را مستقل از وجود یک سطح فیزیکی تعریف کرد. دو محور دیگر در این صفحه یک دستگاه مختصات مشاهده می‌سازند. Azimuth زاویهٔ میان یکی از این محورها و تصویر خط بین ناظر و ستاره روی صفحه است.

دوربین همچنان می‌تواند با زاویه‌ای به نام Twist حول جهت نگاه خود دوران کند.

4.4 تصویرسازی‌های موازی

تصویرسازی موازی حالت حدی تصویرسازی پرسپکتیو است که در آن مرکز تصویرسازی بی‌نهایت از اشیای مورد مشاهده دور است؛ در نتیجه Projectorها به جای همگرایی در COP، موازی‌اند. به‌طور معادل، چنین حالتی شبیه دوربینی با لنز تله و فاصلهٔ کانونی بی‌نهایت است. به جای استخراج نخست معادلات پرسپکتیو و سپس گرفتن حد آنها، معادلات تصویرسازی موازی را مستقیماً با استفاده از این واقعیت استخراج می‌کنیم که Projectorها موازی و هم‌جهت با DOP هستند.

4.4.1 تصویرسازی‌های قائم

تصویرسازی قائم یا Orthographic حالت ویژه‌ای از تصویرسازی موازی است که در آن Projectorها بر صفحهٔ دید عمودند. از دید دوربین، این حالت متناظر با دوربینی است که صفحهٔ پشتی آن با عدسی موازی و فاصلهٔ کانونی آن بی‌نهایت است. شکل 4.21 یک تصویرسازی قائم روی صفحهٔ (z=0) را نشان می‌دهد.

شکل 4.21 ــ تصویرسازی قائم.

وقتی نقاط روی این صفحه تصویر می‌شوند، مقادیر (x) و (y) خود را حفظ می‌کنند:

x_p=x,\qquad y_p=y,\qquad z_p=0.

در مختصات همگن:

\begin{bmatrix}x_p\\y_p\\z_p\\1\end{bmatrix} = \begin{bmatrix} 1&0&0&0\0&1&0&0\0&0&0&0\0&0&0&1 \end{bmatrix} \begin{bmatrix}x\\y\\z\\1\end{bmatrix}.

برای آماده‌شدن جهت بررسی تصویرسازی قائم عمومی‌تر، این رابطه را به صورت

q=M_Ip

می‌نویسیم که در آن (p=[x;y;z;1]^T) است. ادامهٔ استخراج ماتریس تصویرسازی در مقالهٔ بعد دنبال می‌شود.

امتیاز کاربران به این مقاله

☆☆☆☆☆

0 نفر امتیاز داده اند. میانگین: 0.0 از 5

 

0 نظر

نظر محترم شما در مورد مقاله های وب سایت برنامه نویسی و پایگاه داده

نظرات محترم شما در خدمات رسانی بهتر ما را یاری می نمایند. لطفا اگر مایل بودید یک نظر ما را مهمان فرمائید. آدرس ایمیل و وب سایت شما نمایش داده نخواهد شد.

0 / 500

اطلاعات تماس

  • آدرس:اصفهان-خیابان ام کلثوم غربی - بعد خیابان تخم چی - بیست متر بعد از پیتزا ننه شب - کوچه تعمیر گاه سمار زغالی - پلاک 354 - درب مشکی - طبقه هفتم
  • آدرس ایمیل:najafzade@gmail.com
  • وب سایت:http://www.a00b.com/
  • تلفن ثابت:(+98)9131253620
  • تلفن همراه:09131253620