تاکنون دیدیم که 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() and clamp().
اکنون زمان آن است که دوباره با کد با بازی کنیم.
به همان صورت که GLSL به ما یک خروجی vec4 gl_FragColor میدهد، همچنین یک ورودی پیشفرض در نظر دارد. vec4 gl_FragCoord که مختصات پیکسل ها در صفحه و یا قطعه صفحه ای که ترد روی آن کار میکند را نگه میدارد، با vec4 gl_FragCoord، میتوانیم بفهمیم یک ترد کجای بیلبورد کار میکند. در این مورد ما آن را یونیفرم صدا نمیزنیم، بلکه به gl_FragCoord یک varying میگوییم.
#ifdef GL_ES precision mediump float; #endif uniform vec2 u_resolution; uniform vec2 u_mouse; uniform float u_time; void main() { vec2 st = gl_FragCoord.xy/u_resolution; gl_FragColor = vec4(st.x,st.y,0.0,1.0); }
تعامل در کد بالا در اینجا
در مثال بالا ما با تقسیم gl_FragCoord بر u_resolution مختصات نرمالایز شده(بین 0 و 1) را در st ذخیره میکنیم. با این کار به راحتی میتوان مقادیر x را به قرمز و مقادیر y را به سبز مپ کرد.
در شیدر نویسی ما منابع زیادی برای دیباگ کردن نداریم، خواهید فهمید که گاهی اوقات کد نویسی در GLSL مانند قراردادن کشتی در داخل بطری است. به همان اندازه سخت زیبا و خوشایند.
اکنون وقت آن است درک خود را در مورد این کد به چالش بکشیم.
بعد از انجام این تمرینات، ممکن است کنجکاو باشید که در کجا ها میتوانید از توانایی شیدر نویسی خود استفاده کنید. در قسمت بعد خواهید دید که چگونه میتوان ابزار های شیدر نویسی خود را در three.js, Processing و یا openFrameworks بسازید.