מבוא למסננים מותאמים אישית (נקראים גם 'הצללות CSS')

מסננים בהתאמה אישית, או הצללות CSS, כפי שקראו בעבר, מאפשרים לכם למנף את העוצמה של תוכנות הצללה (shader) של WebGL עם תוכן ה-DOM שלכם. מאחר שבהטמעה הנוכחית כלי ההצללה (shader) שבהם נעשה שימוש זהים לאלו שב-WebGL, צריך לקחת צעד אחורה ולהבין כמה טרמינולוגיה של תלת-ממד וקצת מצינור עיבוד הנתונים הגרפי.

הוספתי גרסה מוקלטת של מצגת שהעברתי לאחרונה ל-LondonJS. בסרטון הזה אסביר על הטרמינולוגיה של התלת-ממד שצריך להבין, מהם סוגי המשתנים השונים שתמצאו ואיך אפשר להתחיל להשתמש בהם כבר היום עם מסננים מותאמים אישית. מומלץ גם לקחת את השקפים כדי שתוכלו לשחק עם ההדגמות בעצמכם.

מבוא לצללים

כתבתי בעבר מבוא להצללה (shader) שיסביר לכם בצורה טובה יותר על תוכנות הצללה (shader) ואיך להשתמש בהם מנקודת המבט של WebGL. אם מעולם לא התמודדתם עם תוכנת הצללה (shader), נדרשת קריאה רבה לפני שתמשיכו הלאה, מאחר שרבים מהמושגים ומהשפה של מסננים מותאמים אישית מסתמכים על המונחים הקיימים של תוכנת ההצללה של WebGL.

אז אחרי שנמשיך, צריך להפעיל מסננים מותאמים אישית ולהמשיך את העבודה!

הפעלת מסננים מותאמים אישית

מסננים בהתאמה אישית זמינים ב-Chrome וב-Canary, וגם ב-Chrome עבור Android. פשוט עוברים אל about:flags ומחפשים "CSS Shaders", מפעילים אותם ומפעילים מחדש את הדפדפן. עכשיו הכול מוכן.

התחביר

התכונה 'מסננים מותאמים אישית' מרחיבה את קבוצת המסננים שאפשר כבר להחיל, כמו blur או sepia, על רכיבי ה-DOM. אריק בידלמן כתב כלי מעולה למגרש משחקים, שכדאי לכם לנסות.

כדי להחיל מסנן מותאם אישית על רכיב DOM, יש להשתמש בתחביר הבא:

.customShader {
    -webkit-filter:

    custom(
        url(vertexshader.vert)
        mix(url(fragment.frag) normal source-atop),

    /* Row, columns - the vertices are made automatically */
    4 5,

    /* We set uniforms; we can't set attributes */
    time 0)
}

כלומר, אנחנו מצהירים על תוכנות הצללה (shader) ובקודקוד שלנו, את מספר השורות והעמודות שאנחנו רוצים שרכיב ה-DOM שלנו יפוצל אליהן, ואז את המדים שנרצה להעביר דרכם.

הדבר האחרון שחשוב לציין כאן הוא שאנחנו משתמשים בפונקציה mix() מסביב לרכיב ההצללה של המקטעי באמצעות מצב מיזוג (normal) ובמצב מרוכב (source-atop). בואו נבחן את תוכנת ההצללה בקטעים עצמה כדי להבין למה אנחנו צריכים בכלל פונקציית mix().

פעולת דחיפת Pixel

אם אתם מכירים את תוכנות הצללה של WebGL, תראו שבמסננים מותאמים אישית יש דברים קצת שונים. במקרה כזה, אנחנו לא יוצרים את המרקמים שבהם משתמש ההצללה בקטעים כדי למלא את הפיקסלים. במקום זאת, תוכן ה-DOM שהמסנן הוחל עליו ממופה למרקם באופן אוטומטי, והמשמעות היא שני דברים:

  1. מטעמי אבטחה, אנחנו לא יכולים להריץ שאילתות על ערכי צבעים של פיקסלים בודדים של מרקם ה-DOM
  2. אנחנו לא (לפחות בהטמעות הנוכחיות) מגדירים בעצמנו את צבע הפיקסל הסופי, כלומר אין גישה ל-gl_FragColor. לעומת זאת, ההנחה היא שתרצו לעבד את תוכן ה-DOM, ושתבצעו שינוי הפיקסלים שלו באופן עקיף דרך css_ColorMatrix ו-css_MixColor.

פירוש הדבר הוא ש- Hello World of fragment shader נראה יותר כך:

void main() {
    css_ColorMatrix = mat4(1.0, 0.0, 0.0, 0.0,
                            0.0, 1.0, 0.0, 0.0,
                            0.0, 0.0, 1.0, 0.0,
                            0.0, 0.0, 0.0, 1.0);

    css_MixColor = vec4(0.0, 0.0, 0.0, 0.0);

    // umm, where did gl_FragColor go?
}

כל פיקסל של תוכן ה-DOM מוכפל ב-css_ColorMatrix, שבמקרה הזה לא עושה דבר בתור מטריצת הזהויות שלו ולא משנה אף אחד מערכי ה-RGBA. אם כן, למשל, עדיף להשאיר את הערכים האדומים, נשתמש ב-css_ColorMatrix כמו בדוגמה הבאה:

// keep only red and alpha
css_ColorMatrix = mat4(1.0, 0.0, 0.0, 0.0,
                        0.0, 0.0, 0.0, 0.0,
                        0.0, 0.0, 0.0, 0.0,
                        0.0, 0.0, 0.0, 1.0);

כפי שאפשר לראות, כשמכפילים את ערכי הפיקסלים של 4D (RGBA) במטריצה, מקבלים ערך פיקסלים שעבר שינוי מהצד השני, ובמקרה הזה אחד מאפס את הרכיבים הירוקים והכחולים.

css_MixColor משמש בעיקר כצבע בסיס שברצונך לשלב עם תוכן ה-DOM שלך. השילוב מתבצע באמצעות מצבי השילוב שאתם מכירים מחבילות התמונה: שכבת-על, מסך, התחמקות של צבעים, תאורה חזקה וכו'.

יש הרבה דרכים שבהן שני המשתנים האלה יכולים להשפיע על הפיקסלים. מומלץ לעיין במפרט האפקטים של המסנן כדי להבין טוב יותר את האינטראקציה בין מצבי שילוב ומצב מרוכבים.

יצירה של קוד מוצר

ב-WebGL, אנחנו לוקחים אחריות מלאה על יצירת הנקודות התלת-ממדיות ברשת שלנו, אבל במסננים מותאמים אישית כל מה שצריך לעשות הוא לציין את מספר השורות והעמודות הרצויות, והדפדפן יפרק באופן אוטומטי את תוכן ה-DOM למספר משולשים:

יצירה של Vertex
תמונה שמחולקת לשורות ולעמודות

כל אחד מהקודקודים האלה מועבר דרך תוכנת ההצללה (shader) של הקודקוד שלנו לצורך מניפולציה, ופירוש הדבר הוא שאנחנו יכולים להתחיל להזיז אותם במרחב תלת-ממדי לפי הצורך. עוד מעט תהיה לך אפשרות ליצור אפקטים מדהימים!

אפקט אקורדיון
תמונה שמעוותת על ידי אפקט אקורדיון

אנימציות עם צלליות

הוספת אנימציות להצללות היא הדבר שהופך אותן למהנות ולמעניינות. לשם כך, פשוט משתמשים במעבר (או באנימציה) ב-CSS כדי לעדכן ערכים אחידים:

.shader {
    /* transition on the filter property */
    -webkit-transition: -webkit-filter 2500ms ease-out;

    -webkit-filter: custom(
    url(vshader.vert)
    mix(url(fshader.frag) normal source-atop),
    1 1,
    time 0);
}

    .shader:hover {
    -webkit-filter: custom(
    url(vshader.vert)
    mix(url(fshader.frag) normal source-atop),
    1 1,
    time 1);
}

הדבר שיש לשים לב אליו בקוד שלמעלה הוא שהזמן הולך לרדת מ-0 ל-1 במהלך המעבר. בתוך תוכנת ההצללה אפשר להצהיר על time האחיד ולהשתמש בערך הנוכחי שלו:

    uniform float time;

uniform mat4 u_projectionMatrix;
attribute vec4 a_position;

void main() {
    // copy a_position to position - attributes are read only!
    vec4 position = a_position;

    // use our time uniform from the CSS declaration
    position.x += time;

    gl_Position = u_projectionMatrix * position;
}

מתחילים לשחק!

כיף לשחק עם מסננים מותאמים אישית, וקשה (ובמקרים מסוימים בלתי אפשרי) ליצור אפקטים מיוחדים בלעדיהם. עדיין בראשית הדרך ודברים משתנים לא מעט, אבל אם מוסיפים אותם מוסיפים קצת תוכן עסקי לפרויקטים, אז למה לא לנסות?

מקורות נוספים