تصویرسازی موازی و پرسپکتیو و ماتریسهای تصویرسازی
در رابطهٔ انتهای مقالهٔ قبل، (I) یک ماتریس همانی (4\times4) است و ماتریس تصویرسازی قائم ساده برابر است با
M=\begin{bmatrix}
1&0&0&0\\0&1&0&0\\0&0&0&0\\0&0&0&1
\end{bmatrix}.
تصویرسازی توصیفشده توسط (M) پس از Vertex Shader در سختافزار انجام میشود. بنابراین فقط اشیایی که داخل مکعبی با ضلع ۲ و مرکز مبدأ قرار دارند تصویر و احتمالاً قابل مشاهده خواهند بود. اگر بخواهیم تعیین کنیم کدام اشیا دیده شوند، میتوان ماتریس همانی را با تبدیلی (N) جایگزین کرد که در برنامهٔ کاربردی یا Vertex Shader اجرا میشود و کنترل حجم برش را در اختیار ما میگذارد. برای مثال، جایگزینکردن (I) با یک ماتریس مقیاسدهی میتواند تعداد بیشتری یا کمتری از اشیا را وارد میدان دید کند.
4.4.2 مشاهدهٔ موازی با OpenGL
روی یک تابع مشاهدهٔ قائم تمرکز میکنیم که در آن حجم دید یک متوازیالسطوح قائم است، همانگونه که در شکل 4.22 دیده میشود. چهار صفحهٔ جانبی حجم برش عبارتاند از:
x=right,\qquad x=left,\qquad y=top,\qquad y=bottom.
صفحهٔ برش نزدیک یا جلویی در فاصلهٔ near از مبدأ و صفحهٔ برش دور یا پشتی در فاصلهٔ far قرار دارد. همهٔ این مقادیر در مختصات دوربیناند. تابع زیر را استخراج میکنیم:
mat4 Ortho(GLfloat left, GLfloat right, GLfloat bottom, GLfloat top,
GLfloat near, GLfloat far)
که ماتریس projection یعنی (N) را میسازد.⁴
اگرچه از نظر ریاضی نمای موازی با بردن دوربین به بینهایت حاصل میشود، چون Projectorها موازیاند میتوان دوربین را در امتداد جهت تصویرسازی لغزاند، بدون آنکه تصویر تغییر کند. بنابراین مفید است یک دوربین قائم را در ابتدا در مبدأ مختصات دوربین تصور کنیم.
⁴ کاربران Microsoft Windows ممکن است مجبور شوند شناسههای near و far را تغییر دهند، زیرا far در Visual C++ میتواند واژهٔ رزروشده باشد.
شکل 4.22 ــ مشاهدهٔ Orthographic. حجم پیشفرض توسط صفحههای
x=\pm1,\qquad y=\pm1,\qquad z=\pm1
تعیین میشود. بهطور معادل، projection پیشفرض (N) ماتریس همانی است. برای ساخت حجم دید دلخواه، ماتریس غیرهمانی (N) را با استفاده از انتقال و مقیاسدهی میسازیم تا رأسها در مختصات دوربین داخل حجم دید پیشفرض جا بگیرند. این فرایند نرمالسازی تصویرسازی نام دارد و همان ماتریسی است که Ortho تولید میکند. چون تصویرسازی نهایی GPU ثابت است، ناچاریم چنین رویکردی داشته باشیم؛ اما نرمالسازی بسیار کارآمد است و اجازه میدهد هر دو تصویرسازی موازی و پرسپکتیو با یک خط لوله اجرا شوند.
4.4.3 نرمالسازی تصویرسازی
در فصل ۱ تصویرسازی را عملیاتی دانستیم که نقاط سهبعدی را به نقاط روی یک سطح دوبعدی نگاشت میکند. چنین تبدیلی وارونپذیر نیست، زیرا همهٔ نقاط واقع روی یک Projector به یک نقطهٔ واحد روی سطح تصویرسازی میروند.
در سامانههای گرافیک رایانهای کمی متفاوت عمل میکنیم. نخست، با مختصات همگن در چهار بعد کار میکنیم. دوم، اطلاعات عمق ــ فاصله در امتداد Projector ــ را تا حد ممکن حفظ میکنیم تا حذف سطح پنهان در مراحل بعدی خط لوله انجام شود. سوم، با نرمالسازی تصویرسازی همهٔ تصویرسازیها را به تصویرسازی قائم تبدیل میکنیم: ابتدا اشیا را چنان دچار اعوجاج میکنیم که تصویر قائمِ شیء تغییرشکلیافته دقیقاً با تصویر مطلوبِ شیء اصلی برابر شود. این ایده در شکل 4.23 نمایش داده شده است.
شکل 4.23 ــ پیشاعوجاج اشیا: (الف) نمای پرسپکتیو، (ب) تصویرسازی قائم از شیء اعوجاجیافته.
شکل 4.24 ــ تبدیل نرمالسازی: ابتدا اعوجاج/نرمالسازی و سپس تصویرسازی قائم.
الحاق ماتریس نرمالسازی، که اعوجاج را انجام میدهد، با ماتریس سادهٔ تصویرسازی قائم بخش 4.4.2 یک ماتریس مختصات همگن میسازد که تصویر دلخواه را تولید میکند.
مزیت مهم این رویکرد آن است که ماتریس نرمالسازی را میتوان طوری طراحی کرد که حجم دید را به حجم دید کانونی تبدیل کند؛ یعنی مکعبی که با صفحههای
x=\pm1,\qquad y=\pm1,\qquad z=\pm1
تعریف میشود. افزون بر امکان پشتیبانی از نماهای موازی و پرسپکتیو در یک خط لوله با بارگذاری ماتریس نرمالسازی مناسب، حجم دید کانونی عملیات برش را نیز ساده میکند، زیرا اضلاع آن با محورهای مختصات همراستا هستند.
فرایند نرمالسازی همان چیزی را تعریف میکند که بیشتر سامانهها ماتریس projection مینامند. این ماتریس اشیا را به مختصات Clip چهاربعدی میآورد؛ سپس تقسیم پرسپکتیو، رأسها را به مختصات دستگاه نرمالشدهٔ سهبعدی تبدیل میکند. این مختصات بعداً با تبدیل Viewport به مختصات پنجره نگاشت میشوند. تمرکز فعلی ما گام نخست، یعنی استخراج ماتریس projection، است.
4.4.4 ماتریسهای تصویرسازی قائم
اگرچه مشاهدهٔ موازی حالت ویژهٔ مشاهدهٔ پرسپکتیو است، ابتدا حالت قائم را بررسی و سپس نرمالسازی را به پرسپکتیو گسترش میدهیم.
در OpenGL، projection پیشفرض ماتریس همانی است؛ معادل فراخوانی:
mat4 N = Ortho(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0);
در این حالت حجم دید همان حجم دید کانونی است. نقاط داخل مکعب (x,y,z=\pm1) به خود همان مکعب نگاشت میشوند و نقاط بیرون آن نیز بیرون میمانند. همین مشاهدهٔ ساده نشان میدهد برای نمای قائم عمومی کافی است ماتریسی بیابیم که متوازیالسطوح مشخصشده توسط Ortho را به همین مکعب کانونی نگاشت کند.
دو پارامتر آخر Ortho فاصله تا صفحههای نزدیک و دور از دوربینی در مبدأ و رو به (z) منفی هستند. بنابراین صفحهٔ نزدیک (z=-near) و صفحهٔ دور (z=-far) است. اهمیت near و far در تصویرسازی قائم، تعیین اشیایی است که برش میخورند.
حال اگر بنویسیم:
mat4 N = Ortho(left, right, bottom, top, near, far);
یک حجم دید متوازیالسطوح قائم مشخص کردهایم که صفحههای جانبی آن با left/right/top/bottom و صفحههای جلو و عقب آن با (z=-near) و (z=-far) تعیین میشوند. ماتریس projection این حجم را به مکعبی با مرکز مبدأ و ضلع ۲ تبدیل میکند. در نتیجه رأسهای داخل حجم تعیینشده به داخل حجم کانونی و رأسهای بیرون آن به بیرون حجم کانونی منتقل میشوند.
نوع نما و حجم دید، projection را تعیین میکنند و این مشخصات نسبت به دوربیناند؛ جایگاه و جهت دوربین را model-view تعیین میکند. دو ماتریس با هم الحاق میشوند و رأس اشیا با حاصلضرب آنها تبدیل میگردد.
برای یافتن projection دو کار لازم است: ابتدا مرکز حجم دید تعیینشده را با انتقال به مبدأ، یعنی مرکز حجم کانونی، ببریم؛ سپس طول هر ضلع را به ۲ مقیاس کنیم.
شکل 4.25 ــ نگاشت حجم دید به حجم دید کانونی.
شکل 4.26 ــ تبدیلهای آفین مورد استفاده در نرمالسازی: انتقال و سپس مقیاسدهی.
تبدیلها عبارتاند از:
T=T\left(-\frac{right+left}{2},-\frac{top+bottom}{2},+\frac{far+near}{2}\right),
و
S=S\left(\frac{2}{right-left},\frac{2}{top-bottom},\frac{2}{near-far}\right).
حاصلضرب آنها projection را میدهد:
N=ST=
\begin{bmatrix}
\frac{2}{right-left}&0&0&-\frac{right+left}{right-left}\0&\frac{2}{top-bottom}&0&-\frac{top+bottom}{top-bottom}\0&0&-\frac{2}{far-near}&-\frac{far+near}{far-near}\0&0&0&1
\end{bmatrix}.
این ماتریس صفحهٔ نزدیک (z=-near) را به (z=-1) و صفحهٔ دور (z=-far) را به (z=1) میبرد. چون دوربین رو به (z) منفی است، Projectorها از بینهایت روی محور (z) منفی به سمت مبدأ جهت دارند.
4.4.5 تصویرسازیهای مایل
تابع Ortho فقط ردهٔ محدودی از تصویرسازیهای موازی، یعنی مواردی که Projectorها بر صفحهٔ تصویرسازی عمودند، تولید میکند. تصویرسازیهای موازی مایل در بسیاری از حوزهها مفیدند.⁵ به جای استخراج مستقیم یک ماتریس مایل، همان راهبرد قبلی را دنبال میکنیم: تصویرسازی مطلوب را به تصویرسازی قائم کانونی از اشیای اعوجاجیافته تبدیل میکنیم.
⁵ بدون تصویرسازی مایل نمیتوانستیم محورهای مختصات را به همان روشی که در سراسر کتاب رسم کردهایم نمایش دهیم؛ تمرین 4.15 را ببینید.
شکل 4.27 ــ تصویرسازی مایل.
شکل 4.28 ــ حجم برش مایل.
یک تصویرسازی مایل را میتوان با زاویهای که Projectorها با صفحهٔ تصویرسازی میسازند مشخص کرد. در APIهایی که مشاهدهٔ موازی عمومی را پشتیبانی میکنند، صفحههای برش نزدیک و دور با صفحهٔ دید موازیاند و صفحههای راست، چپ، بالا و پایین با جهت تصویرسازی موازیاند. با بررسی نماهای بالا و جانبی شکل 4.29 و فرض صفحهٔ تصویرسازی (z=0)، دو زاویهٔ (\theta) و (\phi) درجهٔ مایلبودن را تعیین میکنند. در نقشهکشی، تصویرسازیهایی مانند Cavalier و Cabinet با مقادیر خاصی از این زاویهها تعریف میشوند.
از نمای بالا:
\tan\theta=\frac{z}{x_p-x},
پس
x_p=x+z\cot\theta.
به همین ترتیب:
y_p=y+z\cot\phi.
شکل 4.29 ــ تصویرسازی مایل: (الف) نمای بالا، (ب) نمای جانبی.
با (z_p=0)، ماتریس همگن تصویرسازی مایل برابر است با:
P=\begin{bmatrix}
1&0&\cot\theta&0\0&1&\cot\phi&0\0&0&0&0\0&0&0&1
\end{bmatrix}.
میتوان آن را به صورت زیر تجزیه کرد:
P=M_{orth}H(\theta,\phi),
که در آن (H(\theta,\phi)) یک ماتریس برش است. بنابراین تصویرسازی مایل را میتوان با نخست برش اشیا و سپس تصویرسازی قائم انجام داد. شکل 4.30 اثر این برش را بر مکعبی در حجم دید مایل نشان میدهد: اضلاع حجم برش نسبت به صفحهٔ دید قائم میشوند اما خود مکعب نیز به همان نسبت مایل میشود. با این حال تصویر قائم مکعب اعوجاجیافته دقیقاً با تصویر مایل مکعب اصلی یکسان است.
هنوز کار تمام نشده است، زیرا حجم دید حاصل از برش همان حجم کانونی نیست. باید همان ماتریسهای انتقال و مقیاس بخش 4.4.4 را نیز اعمال کنیم.
شکل 4.30 ــ اثر تبدیل برش.
تبدیل نهایی به صورت
N=M_{orth}STH
است. مقادیر left، right، bottom و top رأسهای حجم دید متوازیالسطوح قائمِ حاصل از برشاند و بسته به شیوهٔ تعریف حجم دید اصلی در برنامه باید از نتیجهٔ اعمال برش به گوشههای حجم اولیه محاسبه شوند.
میتوان تصویرسازی مایل را با زاویههای (\theta) و (\phi) Projectorها نسبت به صفحهٔ تصویرسازی تعریف کرد. پارامترهای near و far با برش تغییر نمیکنند، ولی مختصات (x) و (y) نقاط تقاطع اضلاع حجم دید با صفحهٔ نزدیک تغییر میکنند و به left/right/top/bottom جدید تبدیل میشوند. اگر این نقاط به صورت ((x_{min},near))، ((x_{max},near))، ((y_{min},near)) و ((y_{max},near)) باشند، روابط برش عبارتاند از:
left=x_{min}-near\cot\theta,
right=x_{max}-near\cot\theta,
top=y_{max}-near\cot\phi,
bottom=y_{min}-near\cot\phi.
4.4.6 یک بینندهٔ تعاملی
برنامهٔ مکعب چرخان را گسترش میدهیم تا هم ماتریس model-view و هم ماتریس projection قائم با پارامترهای تعاملی را در بر بگیرد. در این مثال هر دو ماتریس را به Vertex Shader میفرستیم. چون model-view هم برای تبدیل شیء و هم برای جایگذاری دوربین قابل استفاده است، فعلاً تابع ماوس را کنار میگذاریم و بر جایگاه دوربین و projection قائم تمرکز میکنیم.
مکعب رنگی در مبدأ مختصات شیء مرکز شده است، بنابراین هر جا دوربین باشد نقطهٔ at همان مبدأ است. جایگاه دوربین را در مختصات قطبی تعریف میکنیم:
eye=
\begin{bmatrix}
r\cos\theta\r\sin\theta\cos\phi\r\sin\theta\sin\phi
\end{bmatrix},
که (r) فاصله از مبدأ است. جهت بالا را میتوان محور (y) در مختصات شیء گرفت. این مقادیر با LookAt ماتریس model-view را میسازند. Callback نمایش میتواند چنین باشد:
void display( )
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
eye[0] = radius*cos(theta);
eye[1] = radius*sin(theta)*cos(phi);
eye[2] = radius*sin(theta)*sin(phi);
model_view = LookAt(eye, at, up);
projection = Ortho(left, right, bottom, top, near, far);
glUniformMatrix4fv(matrix_loc, 1, GL_TRUE, model_view);
glUniformMatrix4fv(projection_loc, 1, GL_TRUE, projection);
glDrawArrays(GL_TRIANGLES, 0, N);
glutSwapBuffers();
}
Vertex Shader متناظر در صفحهٔ بعد آمده است.
in vec4 vPosition;
in vec4 vColor;
out vec4 color;
uniform mat4 model_view;
uniform mat4 projection;
void main()
{
gl_Position = projection*model_view*vPosition;
color = vColor;
}
با Callback صفحهکلید میتوان projection و جایگاه دوربین را تغییر داد. کلیدهای r، o و p موقعیت دوربین و کلیدهای x، y و z اضلاع متوازیالسطوح دید را کنترل میکنند:
void mykey(unsigned char key, int mousex, int mousey)
{
float dr = M_PI/180.0*5.0; // 5 degrees in radians
if(key=='q'||key=='Q') exit(0);
if(key == 'x') {left *= 1.1; right *= 1.1;}
if(key == 'X') {left *= 0.9; right *= 0.9;}
if(key == 'y') {bottom *= 1.1; top *= 1.1;}
if(key == 'Y') {bottom *= 0.9; top *= 0.9;}
if(key == 'z') {near *= 1.1; far *= 1.1;}
if(key == 'Z') {near *= 0.9; far *= 0.9;}
if(key == 'r') radius *= 2.0;
if(key == 'R') radius *= 0.5;
if(key == 'o') theta += dr;
if(key == 'O') theta -= dr;
if(key == 'p') phi += dr;
if(key == 'P') phi -= dr;
glutPostRedis();
}
با حرکت دوربین اندازهٔ تصویر مکعب تغییر نمیکند؛ این نتیجهٔ استفاده از تصویرسازی قائم است. با این حال، بسته به radius ممکن است بخشی یا همهٔ مکعب برش بخورد، زیرا پارامترهای Ortho نسبت به دوربین اندازهگیری میشوند. اگر دوربین را با افزایش شعاع عقب ببریم اما near/far را تغییر ندهیم، ابتدا پشت مکعب و سپس در فاصلههای بیشتر کل مکعب از حجم دید خارج میشود.
تغییر right/left تصویر مکعب را در راستای (x) میکشد و تغییر bottom/top اثر مشابهی در راستای (y) دارد. این اعوجاج ناشی از نگاشت مستطیل غیرمربعی Ortho به تمام Viewport ثابت است.
میتوان برنامه را طوری تغییر داد که left/right/bottom/top همزمان افزایش یا کاهش یابند، یا در هر تغییر Ortho، Viewport نیز متناسب تغییر کند.
4.5 تصویرسازیهای پرسپکتیو
اکنون به تصویرسازی پرسپکتیو میپردازیم؛ حالتی که دوربین فاصلهٔ کانونی محدود دارد یا در مدل دوربین مصنوعی، مرکز تصویرسازی در فاصلهای محدود قرار گرفته است.
مانند تصویرسازی موازی، مشاهدهٔ پرسپکتیو را به دو بخش تقسیم میکنیم: جایگذاری دوربین و تصویرسازی. جایگذاری دقیقاً با همان روش و مثلاً تابع LookAt انجام میشود. بخش projection معادل انتخاب عدسی دوربین است. در دوربین فیزیکی ترکیب عدسی و اندازهٔ فیلم یا پشت دوربین مشخص میکند چه مقدار از جهان جلوی دوربین در تصویر دیده شود؛ در گرافیک رایانهای انتخاب نوع projection و پارامترهای مشاهده نقش مشابه دارد.
لنز واید پرسپکتیو شدیدتری تولید میکند و اشیای نزدیک نسبت به اشیای دور بسیار بزرگتر دیده میشوند؛ لنز تله تصویری تختتر و نزدیکتر به نمای موازی میدهد.
ابتدا ریاضیات یک تصویرسازی ساده را بررسی میکنیم. مختصات همگن را میتوان به فرایند projection گسترش داد تا یک تصویرسازی مشخص با ماتریس (4\times4) توصیف شود.
4.5.1 تصویرسازیهای پرسپکتیو ساده
فرض کنید در Frame دوربین هستیم، دوربین در مبدأ قرار دارد و رو به (z) منفی است. در حالت رایج، پشت دوربین بر جهت (z) عمود و با عدسی موازی است؛ این همان وضعیت متداول در چشم انسان و دوربین ساده است. حالت عمومیتر اجازه میدهد پشت دوربین هر جهتگیری دلخواهی نسبت به جلو داشته باشد.
اگر صفحهٔ تصویرسازی را جلوی COP قرار دهیم و نقطهٔ ((x,y,z)) را روی ((x_p,y_p,z_p)) تصویر کنیم، همهٔ Projectorها از مبدأ میگذرند. چون صفحهٔ تصویرسازی بر محور (z) عمود است:
z_p=d,
و چون دوربین رو به (z) منفی است، (d<0).
شکل 4.31 ــ دو دوربین: (الف) پشت موازی جلو، (ب) پشت غیرموازی جلو.
شکل 4.32 ــ سه نمای تصویرسازی پرسپکتیو: نمای سهبعدی، نمای بالا و نمای جانبی.
از مثلثهای مشابه در نمای بالا:
\frac{x}{z}=\frac{x_p}{d},
پس
x_p=\frac{x}{z/d}.
از نمای جانبی نیز:
y_p=\frac{y}{z/d}.
این معادلات غیرخطیاند. تقسیم بر (z) همان کوتاهشدگی غیریکنواخت را ایجاد میکند: تصویر اشیایی که از COP دورترند نسبت به اشیای نزدیکتر کوچکتر میشود.
شکل 4.33 ــ خط لولهٔ projection: model-view، سپس projection و در پایان تقسیم پرسپکتیو.
میتوان projection را تبدیلی دانست که نقاط ((x,y,z)) را به ((x_p,y_p,z_p)) میبرد. این تبدیل پرسپکتیو خطها را حفظ میکند، اما آفین نیست و وارونپذیر هم نیست؛ همهٔ نقاط روی یک Projector به یک نقطه تصویر میشوند. در بخشهای بعد گونهای وارونپذیر از این تبدیل خواهیم ساخت که اطلاعات عمق لازم برای حذف سطح پنهان را حفظ میکند.
در مختصات همگن، به جای نمایش صرف ((x,y,z,1))، نقطهٔ سهبعدی را میتوان با هر چهارتایی همارز زیر نمایش داد:
p=\begin{bmatrix}wx\\wy\\wz\\w\end{bmatrix},\qquad w\ne0.
برای بازیابی نقطهٔ سهبعدی کافی است سه مؤلفهٔ نخست را بر (w) تقسیم کنیم. در این نمایش، نقاط سهبعدی در چهار بعد به خطهایی از مبدأ تبدیل میشوند. تبدیلها همچنان ماتریس (4\times4) هستند، اما اکنون سطر آخر ماتریس نیز میتواند تغییر کند و بنابراین (w) میتواند توسط تبدیل عوض شود.
هرچند ترجیح میدهیم (w=1) باشد تا از تقسیم اجتناب شود، اجازهدادن به تغییر (w) ردهٔ بزرگتری از تبدیلها، از جمله پرسپکتیو، را ممکن میکند. ماتریس زیر را در نظر بگیرید:
M=\begin{bmatrix}
1&0&0&0\\0&1&0&0\\0&0&1&0\\0&0&1/d&0
\end{bmatrix}.
این ماتریس نقطهٔ همگن (p=[x;y;z;1]^T) را تبدیل میکند.
نتیجهٔ تبدیل عبارت است از:
q=\begin{bmatrix}x\\y\\z\\z/d\end{bmatrix}.
با تقسیم سه مؤلفهٔ نخست بر مؤلفهٔ چهارم به فضای سهبعدی برمیگردیم:
x_p=\frac{x}{z/d},\qquad y_p=\frac{y}{z/d},\qquad z_p=\frac{z}{z/d}=d.
این همان معادلات تصویرسازی پرسپکتیو ساده است. در مختصات همگن، تقسیم (q) بر (w) آن را با نقطهٔ همارز
q'=\begin{bmatrix}x/(z/d)\\y/(z/d)\\d\\1\end{bmatrix}
=\begin{bmatrix}x_p\\y_p\\z_p\\1\end{bmatrix}
جایگزین میکند.
پس میتوان یک تصویرسازی پرسپکتیو ساده را با ماتریس projection چهاربعدی پس از model-view انجام داد، مشروط بر اینکه در پایان تقسیم پرسپکتیو انجام شود. این تقسیم میتواند بخشی از خط لوله باشد.
4.6 تصویرسازی پرسپکتیو با OpenGL
تصویرسازیهای قبلی ویژگیهای فیزیکی دوربین، مانند فاصلهٔ کانونی عدسی یا اندازهٔ صفحهٔ فیلم، را در نظر نمیگرفتند. شکل 4.34 زاویهٔ دید یک دوربین سوراخسوزنی ساده را نشان میدهد. فقط اشیایی که داخل زاویهٔ دید قرار دارند در تصویر ظاهر میشوند. اگر پشت دوربین مستطیلی باشد، فقط اشیای داخل یک هرم نیمهبینهایت با رأس COP میتوانند دیده شوند؛ اشیای بیرون این حجم از صحنه Clip میشوند.
شکل 4.34 ــ تعیین حجم دید.
در بیشتر APIها، برنامهٔ کاربردی پارامترهای برش را همراه projection مشخص میکند. هرم بینهایت با افزودن صفحههای برش جلو و عقب به یک حجم محدود تبدیل میشود.
شکل 4.35 ــ صفحههای برش جلو و عقب.
حجم حاصل یک Frustum، یعنی هرم بریدهشده، است. فقط COP را با قرار دادن آن در مبدأ Frame دوربین ثابت کردهایم. در اصل میتوان شش وجه Frustum را تقریباً با هر جهتگیری دلخواه تعیین کرد، اما چنین انعطافی API را دشوار و پیادهسازی را پیچیده میکند و معمولاً نیز لازم نیست. در عمل دو تابع مشاهدهٔ پرسپکتیو کافیاند؛ APIهای دیگر ممکن است نامها و امضاهای متفاوتی داشته باشند ولی محدودیتهایی مشابه اعمال میکنند.
4.6.1 توابع پرسپکتیو
دو تابع برای نماهای پرسپکتیو توسعه میدهیم و در کنار آنها تابع موازی را داریم. همچنین میتوان projection را مستقیماً با بارگذاری ماتریس یا با اعمال دوران، انتقال و مقیاسدهی به ماتریس همانی ساخت.
یک نمای دوربین پرسپکتیو را میتوان با تابع زیر مشخص کرد:
mat4 Frustum(GLfloat left, GLfloat right, GLfloat bottom, GLfloat top,
GLfloat near, GLfloat far);
شکل 4.36 ــ تعیین Frustum.
پارامترها در Frame دوربین تعریف میشوند. فاصلههای near و far از COP، یعنی مبدأ مختصات چشم، تا صفحههای برش جلو و عقباند و هر دو با صفحهٔ (z=0) موازیاند. چون دوربین رو به (z) منفی است، صفحهٔ نزدیک (z=-near) و صفحهٔ دور (z=-far) است. left/right/top/bottom روی صفحهٔ نزدیک اندازهگیری میشوند.
در تقریباً همهٔ کاربردها
far>near>0
است، اما تا زمانی که near != far باشد ماتریس projection از نظر جبری معتبر است؛ اشیای پشت COP، اگر بین صفحههای برش قرار گیرند، در تصویر وارونه خواهند شد.
این مشخصات لازم نیست نسبت به محور (z) متقارن باشند و Frustum نیز مجبور نیست یک Frustum قائم باشد. در بخش 4.7 ماتریس متناظر را استخراج میکنیم.
در بسیاری از کاربردها تعریف زاویه یا میدان دید طبیعیتر است. اگر صفحهٔ تصویرسازی مستطیل باشد، زاویهٔ دید در نمای بالا و جانبی متفاوت است. fovy زاویهٔ میان صفحههای بالا و پایین حجم برش است. تابع زیر میدان دید عمودی و نسبت ابعاد صفحه را میگیرد:
mat4 Perspective(GLfloat fovy, GLfloat aspect, GLfloat near, GLfloat far);
شکل 4.37 ــ تعیین تصویرسازی با میدان دید.
aspect برابر عرض تقسیم بر ارتفاع صفحهٔ تصویرسازی است و near/far همان معنای Frustum را دارند.
4.7 ماتریسهای تصویرسازی پرسپکتیو
برای پرسپکتیو نیز همان مسیر کلی نماهای موازی را دنبال میکنیم: تبدیلی مییابیم که با اعوجاج رأس اشیا اجازه دهد یک تصویرسازی کانونی ساده، تصویر مطلوب را تولید کند. ابتدا حجم دید کانونی را تعیین میکنیم، سپس تبدیل نرمالسازی پرسپکتیو را معرفی میکنیم که پرسپکتیو را به تصویرسازی قائم تبدیل میکند و در پایان projection مورد استفاده در OpenGL را استخراج میکنیم.
4.7.1 نرمالسازی پرسپکتیو
برای صفحهٔ تصویرسازی (z=-1) و COP در مبدأ، ماتریس پرسپکتیو ساده چنین است:
M=\begin{bmatrix}
1&0&0&0\\0&1&0&0\\0&0&1&0\\0&0&-1&0
\end{bmatrix}.
برای تشکیل تصویر به حجم برش نیز نیاز داریم. میدان دید را ۹۰ درجه میگیریم، بهگونهای که اضلاع حجم دید با صفحهٔ projection زاویهٔ ۴۵ درجه بسازند. در نتیجه هرم نیمهبینهایت دید با صفحههای
x=\pm z,\qquad y=\pm z
تعریف میشود.
شکل 4.38 ــ تصویرسازی پرسپکتیو ساده.
حجم را با صفحهٔ نزدیک (z=-near) و صفحهٔ دور (z=-far) محدود میکنیم، با شرط
0<near<far.
ماتریس زیر را در نظر بگیرید:
N=\begin{bmatrix}
1&0&0&0\\0&1&0&0\\0&0&\alpha&\beta\\0&0&-1&0
\end{bmatrix},
که شبیه (M) است اما برای (\alpha,\beta\ne0) نامنفرد است. با اعمال آن به (p=[x;y;z;1]^T) داریم:
x'=x,\qquad y'=y,\qquad z'=\alpha z+\beta,\qquad w'=-z.
پس از تقسیم بر (w'):
x''=-\frac{x}{z},\qquad y''=-\frac{y}{z},\qquad z''=-\left(\alpha+\frac{\beta}{z}\right).
اگر پس از (N) یک تصویرسازی قائم در راستای (z) اعمال کنیم، ماتریس حاصل همان تصویرسازی پرسپکتیو ساده است و پس از تقسیم پرسپکتیو:
x_p=-\frac{x}{z},\qquad y_p=-\frac{y}{z}.
پس میتوانیم نخست با تبدیل نامنفرد (N) نقاط را اعوجاج دهیم و سپس با تصویرسازی قائم همان نتیجهٔ پرسپکتیو را به دست آوریم؛ دقیقاً مشابه تبدیل تصویرسازی مایل به قائم با برش.
اکنون (\alpha) و (\beta) را چنان انتخاب میکنیم که حجم دید اصلی به حجم برش کانونی تبدیل شود. اضلاع (x=\pm z) با (x''=-x/z) به صفحههای (x''=\pm1) و اضلاع (y=\pm z) به (y''=\pm1) تبدیل میشوند.
صفحهٔ نزدیک (z=-near) به
z''=-\left(\alpha-\frac{\beta}{near}\right)
و صفحهٔ دور (z=-far) به
z''=-\left(\alpha-\frac{\beta}{far}\right)
میرود. اگر انتخاب کنیم:
\alpha=-\frac{near+far}{near-far},
\qquad
\beta=-\frac{2\,near\,far}{near-far},
آنگاه صفحهٔ نزدیک به (z''=-1) و صفحهٔ دور به (z''=1) نگاشت میشود و حجم برش کانونی حاصل میگردد.
شکل 4.39 ــ نرمالسازی پرسپکتیو حجم دید.
در نتیجه (N) Frustum را به متوازیالسطوح قائم تبدیل میکند و تصویرسازی قائم در فضای تبدیلیافته دقیقاً همان تصویر پرسپکتیو را میدهد. (N) ماتریس نرمالسازی پرسپکتیو نام دارد.
نگاشت عمق
z''=-\left(\alpha+\frac{\beta}{z}\right)
غیرخطی است، اما ترتیب عمق را حفظ میکند. اگر (z_1>z_2) باشد، پس از تبدیل نیز ترتیب متناظر حفظ میشود.
بنابراین حذف سطح پنهان در حجم نرمالشده درست کار میکند، هرچند غیرخطیبودن نگاشت میتواند به دلیل تفکیک محدود Depth Buffer مشکلات عددی ایجاد کند. صفحهٔ projection اولیهٔ (z=-1) نیز به صفحهای دیگر تبدیل میشود، اما چون پس از (N) تصویرسازی قائم داریم، این موضوع پیامد مهمی ندارد.
این واقعیت که هم تبدیلهای پرسپکتیو و هم موازی را میتوان به تصویرسازی قائم تبدیل کرد بیشترین اهمیت را در پیادهسازی دارد. کافی است پیش از تعریف رأسها projection مناسب را در خط لوله قرار دهیم؛ در نتیجه یک خط لولهٔ مشاهده برای همهٔ نماها کافی است. تبدیل همهٔ حجمهای دید به متوازیالسطوح قائم، عملیات برش و حذف سطح پنهان را نیز ساده میکند.
4.7.2 تبدیلهای پرسپکتیو OpenGL
تابع Frustum حجم دید را به Frustum متقارن محدود نمیکند. برای ساخت ماتریس میتوان ابتدا Frustum نامتقارن را به Frustum متقارن با اضلاع ۴۵ درجه تبدیل کرد. نخست یک برش انجام میدهیم تا Frustum نامتقارن متقارن شود. زاویههای برش چنان انتخاب میشوند که نقطهٔ
\left(\frac{left+right}{2},\frac{top+bottom}{2},-near\right)
به ((0,0,-near)) نگاشت شود. ماتریس برش را میتوان با ضرایب متناظر با
\cot^{-1}\left(\frac{left+right}{-2near}\right),
\qquad
\cot^{-1}\left(\frac{top+bottom}{-2near}\right)
ساخت.
پس از برش، Frustum جدید توسط صفحههای زیر توصیف میشود.
x=\pm\frac{right-left}{-2near},
\qquad
y=\pm\frac{top-bottom}{-2near},
z=-near,\qquad z=-far.
گام بعد مقیاسکردن اضلاع است تا به (x=\pm z) و (y=\pm z) برسند، بدون تغییر صفحههای near/far. ماتریس مقیاسدهی لازم:
S\left(\frac{-2near}{right-left},\frac{-2near}{top-bottom},1\right).
این تبدیل بدون ارجاع به جای صفحهٔ far به طور یکتا تعیین میشود؛ در سه بعد یک تبدیل آفین با نتیجهٔ تبدیل چهار نقطه مشخص میشود و این چهار نقطه همان چهار رأس تقاطع اضلاع Frustum با صفحهٔ near هستند.
سپس ماتریس نرمالسازی پرسپکتیو (N) با همان (\alpha) و (\beta) بخش قبل اعمال میشود. ماتریس نهایی projection برای Frustum عبارت است از:
P=NSH=
\begin{bmatrix}
\frac{2near}{right-left}&0&\frac{right+left}{right-left}&0\0&\frac{2near}{top-bottom}&\frac{top+bottom}{top-bottom}&0\0&0&-\frac{far+near}{far-near}&-\frac{2far\,near}{far-near}\0&0&-1&0
\end{bmatrix}.
برای تابع Perspective(fovy, aspect, near, far) از تقارن استفاده میکنیم:
left=-right,\qquad bottom=-top,
و با مثلثات ساده داریم:
top=near\tan(fovy),
\qquad
right=top\times aspect.
بنابراین ماتریس ساده میشود:
P=
\begin{bmatrix}
near/right&0&0&0\0&near/top&0&0\0&0&-(far+near)/(far-near)&-2far\,near/(far-near)\0&0&-1&0
\end{bmatrix}.
4.7.3 مثال پرسپکتیو
برای تبدیل مثال قبلی از projection قائم به پرسپکتیو تقریباً هیچ تغییر دیگری لازم نیست. کافی است Frustum را جایگزین Ortho کنیم و همان پارامترها را بدهیم، با شرط مناسب:
far>near>0.
برای دیدن کوتاهشدگی مشخص پرسپکتیو میتوان مکعب را از محور (z) کنار برد یا چند مکعب دیگر در سمت راست و چپ اضافه کرد.
میتوان تقسیم پرسپکتیو را در Vertex Shader لحاظ کرد:
in vec4 vPosition;
in vec4 vColor;
out vec4 color;
uniform mat4 model_view;
uniform mat4 projection;
void main()
{
gl_Position = projection*model_view*vPosition/vPosition.w;
color = vColor;
}
برنامهٔ کامل در ضمیمهٔ A آمده است.
4.8 حذف سطح پنهان
پیش از مثالها و گسترشهای بیشتر مشاهده، باید درک عمیقتری از فرایند حذف سطح پنهان به دست آوریم. مکعب مثالهای قبلی را در نظر بگیرید. وقتی مکعبی با وجههای کدر را میبینیم، بسته به جهتگیری آن فقط یک، دو یا سه وجه رو به دوربین دیده میشوند. از دید مدل پایهٔ مشاهده، فقط این وجهها دیده میشوند زیرا مسیر Projectorها را به سمت سطوح دیگر مسدود میکنند. ادامهٔ این بحث در مقالهٔ بعد خواهد آمد.