3 min. read
שימו ❤:
אני מבצע כמה הנחות יסוד שהינכם יודעים לתכנת באיזושהי שפה או יודעים חלק מהטרמינולוגיה ולכן לא אסביר הכל.

אני אשתדל לדבר כמה שיותר על אפקטים שונים אך זה יהיה המימוש יהיה בסוף מאוד פשוט כדי שיהיה קל להבין את זה גם אם לא תכנתתם גראפיקה בעברכם!

המחשב

מחשב הוא מערכת המורכבת מרכיבים חומרתיים המסוגלים לבצע חישובים.
במחשב יש כמה רכיבים עיקריים אך שני הרכיבים החישוביים החשובים הם - CPU ו-ALU.

Central Processing Unit - CPU

ה-CPU הוא השבב הראשי שמקבל פקודות ויודע להמיר אותם לאותות חשמליים המבצעים פעולות.
איזה סוג של פעולות?

  • ניהול וגישה לזיכרון
  • גישה לרכיבים אחרים כגון דיסק קשיח או זיכרון RAM.
  • חישוב פעולות מתמטיות
  • חישוב פעולות לוגיות - כגון תנאים.

Arithmetic Logic Unit - ALU

ה-ALU הוא תת מערכת בתוך ה-CPU אשר מבצע את החישובים והבדיקות הלוגיות.
ה-ALU בד”כ מבצע פעולות על מספרים שלמים,
ולעיתים קיים גם רכיב מיוחד בשם FLU - Floating Logic Unit שמבצע פעולות על מספרים עשרוניים.

CPU - הבעיה העיקרית

למרות שכיום ה-CPU הוא רכיב מרכזי במערכת המסוגל לחשב ולבצע פעולות לוגיות הוא אינו מסוגל לחשב מספיק מהר ערכים גראפיים כדי לרנדר/לצייר תמונות.
המונח העיקרי הוא Software Renderer - ז”א ציור בעזרת ה-CPU.

החסרונות של רנדור בעזרת ה-CPU:

  • לא מקבילי מספיק ובד”כ כמות ה-Coreים קטנה.
  • מקבל Interrupts אשר גורם לו לעבוד יותר לאט
  • אחראי על ניהול זיכרון של המערכת ולכן מקבל הרבה פקודות.
  • לא מיועד לרנדור

והנקודה האחרונה היא החשובה - הCPU אינו בנוי לרנדר תמונות ואינו מספיק מותאם לפעולות האלו.
ולכן נברא בשנות ה-90 ה-GPU.

Graphical Process Unit - GPU

ה-GPU היא מערכת נוספת חישובית אשר נבנתה כדי לרנדר תמונות.
מה כרוך ברנדור תמונה דו מימדית או תלת מימדית?

  • מידע:
    בעצם מה אנחנו מרנדרים - קווים, נקודות, מלבנים, משולשים וכדו’…
  • שינוי המידע:
    במידה ואנחנו רוצים לחשב נקודות בצורה שונה, או לשנות את הצורה שלנו.
  • פיקסלים:
    נקודות בלבד ייראו כשחור או לבן - מה עם צבע?
    איזה סוג צבע אנחנו רוצים וכיצד אנחנו מחשבים אותו - במידה ויש אפקטים נוספים.
  • חישוב התמונה ל-Buffer:
    באפרים הם מערכים של מידע אשר מכילים את הפיקסלים הסופיים.
  • התאמת הבאפר למסך:
    הבאפר הסופי צריך להיות מותאם למסך שאנחנו מציירים עליו.

אז למה בכלל GPU?

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

בעזרת ה-CPU היינו יכולים למקבל בעזרת תהליכונים:

אך הבעיה העיקרית בה שזה מוגבל לכמות ה-Coreים שיש.

ב-GPU יש הרבה יותר Coreים.
לא 4, לא 8 ולא 16 - מאות ואלפים!

ל-GTX 1080 יש 2560 Cores!

כל Core יוכל לחשב כל פיקסל ואם אלו חישובים מאוד פשוטים אפילו לא ייקח לו הרבה זמן.
תמונות ירונדרו בצורה מאוד מהירה!

GPU Pipeline

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

מה שמסומן בירוק ניתן לתכנת אותו!

Input - קלט

השלב שבו המעבד הגראפי טוען את כל המידע שהוא צריך מהמעבד למעבד הגראפי.
בתהליך הזה עובר המידע דרך צינורות הנקראים Bus ואלו מסוגלים להעביר מידע בצורה מהירה.
לכרטיס הגראפי יש זיכרון משל עצמו ולכן כאשר זיכרון נטען לא צריך להעביר אותו פעמיים.
איזה מידע נטען?

  • נקודות
  • צבעים
  • Normal (ווקטורים העוזרים לחשב אור)
  • טקסטורות ותמונות
    וכדו’…

Vertex Shader - שלב הוורטקסים

זהו שלב שניתן לתכנת אותו!
Vertex הוא נקודה או ווקטור.
שלב הוורטקסים הוא בעצם תוכנה אשר מחשבת את מיקום הנקודות מחדש.
אילו חשובים נכנסים לכאן?

  • תזוזה
  • סיבוב
  • גודל

Tessellation - חלוקה

שלב החלוקה מחלק את הצורות הגדולות לצורות קטנות יותר.
למשל מלבנים למשולשים!

תזכרו את זה כי ברנדור גראפי תלת מימדי עובדים הרבה עם משולשים ולא ריבועים.
גם אם קיימים ריבועים שלב החלוקה מחלק אותם למשולשים!
לכן הכי נוח לעבוד עם משולשים מלכתחילה.

Geometry Shader - גאומטריה

זהו שלב שניתן לתכנת אותו!

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

  • הגדלות
  • שינוי צורה
  • יצירה של צורות חדשות - למשל מערכות חלקיקים.

Rasterization - הפיכה לדו מימד

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

Fragment Shader - צבעים

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

למשל להפוך תמונה לשחור לבן:

Color Blending

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

OpenGL & GLSL

OpenGL

OpenGL זהו API עבור רנדור דו מימדי ותלת מימדי.
לעיתים גם נקרא כ-“האסמבלי של כרטיסי המסך”.
ניתן להפיק בעזרתו גראפיקה עבור אפליקציות שונות - וידאו, בניית סצנות, אפליקציות רגילות, משחקים וכדו’…
בעצם כל דבר שצריך רנדור מהיר של תמונות.

בפוסט הזה אני לא אדבר על ה-API של OpenGL מכיוון שנכתבו ספרים רבים עליו והוא מאוד מורכב.

GLSL - OpenGL Shading Language

זוהי השפה הנותנת לנו אפשרות לתכנת את החלקים שניתן לתכנת אותם בכרטיס המסך, שהם -

  • Vertex Shader - שינוי נקודות וצבעים
  • Geometry Shader - יצירת צורות חדשות מנקודות
  • Fragment Shader - שינוי צבע הפיקסל הסופי

שפה הדומה ל-C

תחביר השפה דומה מאוד לשפת C.
בעל כמה פיצ’רים מיוחדים לתכנות כרטיסי מסך.
בשפה יש מבנים שימושיים כגון vec2,vec3,mat3, וכדו’…

Input/Output

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

נכיר את הפיצ’רים הבסיסיים בשפה ע”י היכרות ראשונית עם שיידרים:

Vertex Shader

איך נראה Vertex Shader בסיסי?

1
2
3
4
5
6
7
#version 330 core

void main()
{
// transform the vertex position
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}

קודם כל צריך לבצע הפנייה באיזה גרסא אנחנו משתמשים - #version 330 core.
גרסא - 3.3.

משתנים של OpenGL

כל משתנה שמתחיל ב-gl_ הוא משתנה של OpenGL פנימי.
ז”א שיש משתנים שיכולים להיות מאותחלים ולקבל ערך בשיידר ויש כאלו משתנים שכבר אותחלו לפני זה ואפשר להשתמש בהם.
למשל gl_vertex הוא משתנה שאותחל לפני.
משתנה gl_Position זה משתנה שהקוד של השיידר שלנו צריך לאתחל.

gl_Position

המשתנה מייצג את המיקום הסופי של הורטקס בתלת מימד.

gl_vertex

הוא הורטקס הנוכחי שהשיידר עובד עליו.
זהו בעצם עצם מסוג vec3.

gl_ModelViewProjectionMatrix
ModelViewProjection Matrices

בתכנות תלת מימדי משתמשים במטריצות.
מטריצה זה מערך דו מימדי של ערכים.
ה-ModelViewProjection Matrix משלב 3 מטריצות לאחד כדי לבצע חישובים קלים על הוורטקסים שלנו.
מהו הוא מחזיק?

  • מיקום
  • סיבוב
  • גודל
Model matrix

מחזיק את הערכים לאובייקט שלנו שבצעים המרה מקורדינטות לוקאליות לקורדינטות של העולם שלנו.
למשל אם הדמות שלנו זזה קדימה זה ייצוג פה.

View Matrix

אחרי שהזזנו את כל העולם שלנו ואת הדמות המטריצה הזו מזיזה הכל עבור המצלמה שלנו.
אם למשל סיבבנו את המצלמה אחורה אז אנחנו צריכים לסובב גם את העולם שלנו לפי התזוזה הזו.

Projection Matrix

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

שימו לב לטרפז הסגול, הטרפז הזה הוא שימוש קלאסי במצלמה שנקראת - Perspective View.

  • Perpesctive - דברים שקרובים יותר למצלמה נראים גדול יותר.
  • Orthographic - דברים שרחוקים יותר נראים באותו גודל כמו דברים קרובים.

Fragment Shader

השיידר הבסיסי צריך לאתחל את המשתנה gl_FragColor.

1
2
3
4
5
6
#version 330 core

void main(void)
{
gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
}

המשתנה הזה אומר באיזה צבע לצבוע את הפיקסל.
vec4 מכיל את המידע rgba של הפיקסל.

  • r - Red
  • g - Green
  • b - Blue
  • a - Alpha

למשל הצבא הזה יהיה - ירוק כי רכיב ה-Green שלו הוא 1.0.
הערכים יכולים לנוע בין 0.0 ל1.0.

Geometry Shader

מטרת השיידר הזה הוא לייצר גאומטריה חדשה - למשל קווים, ריבועים, משולשים וכדו’…

1
2
3
4
5
6
7
8
9
10
11
12
13
#version 330 core
layout (points) in;
layout (line_strip, max_vertices = 2) out;

void main() {
gl_Position = gl_in[0].gl_Position + vec4(-0.1, 0.0, 0.0, 0.0);
EmitVertex();

gl_Position = gl_in[0].gl_Position + vec4( 0.1, 0.0, 0.0, 0.0);
EmitVertex();

EndPrimitive();
}

זהו שיידר קצת מורכב אז לא אסביר על הכל בו, שימו לב לכמה דברים.

  • gl_Position הוא המיקום הנוכחי של הורטקס.
  • EmitVertex היא קריאה לפונקציה ליצירה של ורטקס ע”פ הערכים הקיימים.
  • EndPrimitive - גאומטריה פשוטה כמו קווים ומשולשים נקראים צורות פרימיטיביים.
    צורות מורכבות יותר בנויות מהפרמיטיביים האלו - אז כל האובייקט שלנו יכול להיות מיוצר מקווים או משולשים.
    הקריאה הזו אומרת שייצרנו צורה פרימיטיבית.

אנחנו נעבור על layout בחלקים הבאים כשאסביר יותר על כתיבת שיידרים.


התנסות עם Shader

לכתוב תכנית שטוענת שיידרים ועושה בהם שימוש זה עניין מורכב - ולכן יש באינטרנט אתרים שנותנים לכתוב שיידרים ולנסות אותם.

נסו כאן לכתוב שיידרים

ניתן להחליף בין Vertex ל-Fragment.

יש בקוד הבסיסי פיצ’רים שעוד לא דיברתי עליהם, העקרון החשוב הוא.
ב-Vertex Shader הם עורכים את gl_Position:

1
2
3
vec4 pos = modelViewMatrix * vec4(position, 1.0);
fPosition = pos.xyz;
gl_Position = projectionMatrix * pos;

וב-Fragment Shader הם עורכים את gl_FragColor:

1
gl_FragColor = vec4(fNormal, 1.0);

שינוי קוד

1
2
3
4
5
6
7
8
9
10
precision highp float;
uniform float time;
uniform vec2 resolution;
varying vec3 fPosition;
varying vec3 fNormal;

void main()
{
gl_FragColor = vec4(fNormal, 1.0);
}

אם נוריד ערכים מהצבעים בעזרת החסרת vec4 נקבל תוצאה שונה:
vec4 לצבע עובד בקומפוננטות rgba.
זאת אומרת:

1
2
3
4
red - 0.2
green - 0.8
blue - 0.1
alpha - 0.0

מכיוון שהורדנו את הקומפוננטה הכחולה בהכי קצת התמונה שלנו יוצאת יותר כחולה:

1
2
3
4
5
6
7
8
9
10
precision highp float;
uniform float time;
uniform vec2 resolution;
varying vec3 fPosition;
varying vec3 fNormal;

void main()
{
gl_FragColor = vec4(fNormal, 1.0) - vec4(0.2,0.8,0.1,0.0) ;
}

תכנות גראפי הוא אחד התחומים הכיפיים והמעניינים בעולם התוכנה,
התוצאות של כתיבת הקוד הן מול העיניים.

בפרקים הבאים נעסוק יותר בשפת Glsl, בפיצ’רים שלה ונסביר יותר גם על OpenGl.

תודה על הקריאה!


אהבתם? מוזמנים להביע תמיכה כאן: כוס קפה