تاکنون دیدیم که GPU چگونه تعداد زیادی ترد موازی را مدیریت میکند. هر کدام از آن ها وظیفه تخصیص رنگ، به جزئی(fragment) از تصویر را دارند. با اینکه هر ترد موازی نسبت به دیگران کور است، اما باید بتوانیم از CPU ورودی هایی به همه ترد ها ارسال کنیم. به دلیل معماری کارت گرافیک، این ورودی ها برای همه ترد ها یکسان هستند و فقط بصورت خواندنی(read only). به عبارتی هر ترد اطلاعات مشابهی را دریافت میکند که میتواند بخواند، اما نمیتواند تغییر دهد.
به این ورودی ها یونیفرم(uniform) گفته میشود. و معمولا به صورت تایپ های مقابل استفاده میشوند: float, vect2, vect3, vect4, mat2, mat3, mat4, sampler2D, samplerCube. یونیفرم ها در بالای شیدر، پس از تعریف دقت نقطه شناور پیشفرض تعریف میشوند.
#ifdef GL_ES
precision mediump float;
#endif
uniform vec2 u_resolution; // Canvas size (width,height)
uniform vec2 u_mouse; // mouse position in screen pixels
uniform float u_time; // Time in seconds since load
یونیفرم ها را میتوان مانند پل های کوچک بین CPU و GPU تصور کرد. نام ها در محیط های مختلف ممکن از تفاوت داشته باشند، اما در این مجموعه از مثال ها همیشه این نام ها ثابتند: u_time(زمان بر حسب ثانیه از شروع شیدر)، u_resolution(اندازه کنوس، جایی که شیدر کشیده میشود) و u_mouse(موقعیت ماوس در داخل کنوس بر حسب پیکسل).
من این قرار داد را دنبال میکنم که قبل یونیفرم ها u_ بگذارم، اما شکل های دیگری هم ممکن است به چشمتان بخورد، مثلا ShaderToy.com از همین یونیفرم ها اما به شکل زیر استفاده کرده است:
uniform vec3 iResolution; // viewport resolution (in pixels)
uniform vec4 iMouse; // mouse pixel coords. xy: current, zw: click
uniform float iTime; // shader playback time (in seconds)
صحبت کردن کافیست بیایید یونیفرم ها را در عمل ببینیم. در کد زیر ما از u_time استفاده میکنیم. یعنی تعداد ثانیه از زمانی که شیدر شروع شده است را به همراه عملکرد سینوس برای تحریک مقدار قرمز به کار گرفتیم.
همانطور که میبینید GLSL سورپرایز های بیشتری دارد. GPU دارای عملکرد های زاویه شتاب همچنین توابع نمایی و مثلثاتی هست. برخی ازین توابع عبارتند از: sin()
, cos()
, tan()
, asin()
, acos()
, atan()
, pow()
, exp()
, log()
, sqrt()
, abs()
, sign()
, floor()
, ceil()
, fract()
, mod()
, min()
, max()
و clamp()
.
اکنون زمان آن است که دوباره با کد بازی کنیم.
-
فرکانس را کم کنید تا زمانی که تغییر رنگ نامحسوس شود.
-
سرعت را آنقدر زیاد کنید که یک رنگ بدون پرش ببینید.
-
با سه کانال RGB در فرکانس های مختلف بازی کنید تا الگو ها و رفتار های جالبی ببینید.
به همان صورت که GLSL به ما یک خروجی vec4 gl_FragColor میدهد، همچنین یک ورودی پیشفرض در نظر دارد. vec4 gl_FragCoord که مختصات پیکسل ها در صفحه و یا قطعه صفحه ای که ترد روی آن کار میکند را نگه میدارد، با vec4 gl_FragCoord، میتوانیم بفهمیم یک ترد کجای بیلبورد کار میکند. در این مورد ما آن را یونیفرم صدا نمیزنیم، بلکه به gl_FragCoord یک varying میگوییم.
در مثال بالا ما با تقسیم gl_FragCoord بر u_resolution مختصات نرمالایز شده(بین 0 و 1) را در st ذخیره میکنیم. با این کار به راحتی میتوان مقادیر x را به قرمز و مقادیر y را به سبز مپ کرد.
در شیدر نویسی منابع زیادی برای دیباگ کردن نداریم، خواهید فهمید که گاهی اوقات کد نویسی در GLSL مانند قراردادن کشتی در داخل بطری است. به همان اندازه سخت زیبا و خوشایند.
اکنون وقت آن است درک خود را در مورد این کد به چالش بکشیم.
-
میتوانید بگویید مختصات (0.0،0.0) در کنوس کجاست؟
-
در مورد (1.0, 0.0), (0.0, 1.0), (0.5, 0.5), (1.0, 1.0) چطور؟
-
آیا میتوانید نحوه استفاده از u_mouse را بیابید و مقدار واقعی در پیکسل نه نرمالایز شده آن را بفهمید؟ از آن برای جابه جایی رنگ ها میتوانید استفاده کنید؟
-
آیا میتوانید روش جالبی برای تغییر این الگوی رنگ با استفاده از u_time و u_mouse تصور کنید؟
بعد از انجام این تمرینات، ممکن است کنجکاو باشید که در کجا ها میتوانید از توانایی شیدر نویسی خود استفاده کنید. در قسمت بعد خواهید دید که چگونه میتوان ابزار های شیدر نویسی خود را در three.js, Processing و یا openFrameworks بسازید.