WebGL (Web Graphics Library) часто розглядають як 3D API. Люди думають: "Я використаю WebGL і магія в мене вийде круте 3D". В реальності ж WebGL це лише засіб растеризації. Він малює точки, лінії та трикутники на основі коду, який ви йому надаєте. Те, як змусити WebGL робити що-небудь окрім цього залежить від вас і вашого коду, який використовуючи точки, лінії та трикутники здатний буде виконати ваше завдання.
WebGL виконується на графічному процесорі вашого комп’ютера. Саме тому ви маєте надати код, який працюватиме на цьому графічному процесорі. Цей код подається у вигляді двох функцій. Ці дві функції називають вершинним шейдером та фрагментним шейдером і кожна з них написана на строго типізованій С-подібній мові, яка називається GLSL (GL Shader Language). Разом вони називаються програмою.
Робота вершинного шейдера обчислити позицію вершини. На основі позиції, яку обчислює ця функція, WebGL може растеризувати різні види примітивів такі, як точки, лінії та трикутники. Під час растеризації цих примітивів WebGL викликає іншу функцію, яку ми називаємо фрагментним шейдером. Завданням фрагментного шейдера є обчислення кольору кожного пікселя того примітиву, який зараз відмальовується.
Практично все API WebGL полягає в тому, щоб налаштувати стан для роботи цих двох функцій. Для будь-чого, що ви хочете намалювати ви маєте налаштувати декілька станів та запустити пару функції викликаючи gl.drawArrays
або gl.drawElements
які запустять виші шейдери на графічному процесорі.
Будь-які дані, які мають бути доступними для цих функції необхідно надати графічному процесору. Є 4 способи, якими шейдер може отримати дані:
Атрибути та Буфери
Буфери це масиви бінарних даних, які ви завантажили на графічний процесор. Зазвичай буфери містять такі речі як позиції, нормалі, координати текстур, кольори вершин, та ін. Проте, ви можете помітисти в них будь-що, що вам заманеться.
Атрибути використовуються для визначення способу отримати дані з буферів і передети їх у вершинні шейдери. Наприклад, ви можете помістити певні позиції в буфері у вигляді трьох 32-бітних чисел з рухомою комою. Ви б могли вказати певному атрибуту, з якого буфера йому витягувати позиції, якого типу даних вони повинні бути (три 32-бітних числа з рухомою комою), зміщення в буфері, з якого починається певна позиція та скільки байтів зчитати до наступної позиції.
Буфери не мають можливості довільного доступу. Натомість, вершинний шейдер виконується певну задану кількість разів. Кожного разу, коли він виконується, з буфера витягується наступне значення і присвоюється атрибуту.
Uniform-змінні
Uniforms - глобальні змінні, які ви встановлюєте перед тим, як запустити програму шейдера.
Текстури
Текстури це масиви даних, які мають можливість довільного доступу у ваших шейдерних програмах. Зазвичай в текстури поміщають дані зображення, але текстури це лише дані, тому вони можуть так само легко містити щось, крім кольорів.
Varying-змінні
Varying-змінні - це спосіб передачі даних з вершинного шейдерв в фрагметний шейдер. Залежно від того, що зараз рендериться, точки, лінії чи трикутники, значення встановленні вершинним шейдером в varyings-змінні будуть інтерпольовані під час виконання фрагментного шейдера.
WebGL переймається тільки двома речими: координатами простору відсікання (clip space) та кольорами. Ваше завдання, як програміста, передати ці дві речі до WebGL. Це робиться з допомогою ваших двох шейдерів. Вершинного шейдеру, який надає координати простору кадру та фрагментного шейдеру, який надає колір.
Координати простору відсікання завжди лежать в діапазоні від -1 до +1 незалежно від розміру полотна.
Ось невеликий приклад WebGL, який показує його в найпростіший формі.
Почнімо з вершинного шейдера:
// атрибут отримає дані з буфера
attribute vec4 a_position;
// всі шейдери мають головну функцію main
void main() {
// gl_Position - особлива зміннна, за встановлення якої
// відповідає вершинний шейдер
gl_Position = a_position;
}
Якби цей код був би написаний на JavaScript замість GLSL, то його робота виглядала б так:
// *** PSEUDO CODE!! ***
var positionBuffer = [
0, 0, 0, 0,
0, 0.5, 0, 0,
0.7, 0, 0, 0,
];
var attributes = {};
var gl_Position;
drawArrays(..., offset, count) {
var stride = 4;
var size = 4;
for (var i = 0; i < count; ++i) {
// копіюємо 4 значення з positionBuffer до a_position атрибута
const start = offset + i * stride;
attributes.a_position = positionBuffer.slice(start, start + size);
runVertexShader();
...
doSomethingWith_gl_Position();
}
В реальності все не настільки просто тому, що positionBuffer
потрібно перевести в бінарні дані (дивись нижче) і тому справжні обчислення для отримання даних з буфера будуть виглядати по-іншому, але сподіваємось, що це дасть вам ідею того як вершинний шейдер буде виконуватись.
Далі нам потрібен фрагментний шейдер:
// фрагментний шейдер за замовчуванням немає встановленої точності
// тому нам потрібно її обрати. mediump хороше значення за замовчуванням
// mediump означає середня точність
precision mediump float;
void main() {
// gl_FragColor - особлива змінна, за встановлення якої
// відповідає фрагментний шейдер
gl_FragColor = vec4(1, 0, 0.5, 1); // повернути червонувато-фіолетовий колір
}
Вище ми встановлюємо значення gl_FragColor
в 1, 0, 0.5, 1
, де 1 - червоний, 0 - зелений,
0.5 - синій та 1 - альфа канал. Кольори в WebGL задаються в діапазоні від 0 до 1.
Тепер, коли ми створили дві функції з шейдерами ми можемо перейти до WebGL.
Спочатку нам потрібно створити HTML елемент canvas:
<canvas id="c"></canvas>
Після цього, ми можемо отримати цей елемент в JavaScript:
var canvas = document.querySelector("#c");
Тепер ми можемо створити WebGLRenderingContext
var gl = canvas.getContext("webgl");
if (!gl) {
// webgl для вас недоступний!
...
Далі нам потрібно скомпілювати шейдери для того, щоб помістити їх в графічний процесор тож спочатку нам потрібно перетворити їх у текстові рядки. Ви можете створити ці GLSL рядки у будь-який зручний спосіб, у який ви зазвичай створюєте їх у JavaScript: з допомогою конкатинації, з допомогою AJAX запиту, використовуючи шаблонні літерали, або, як у цьому випадку, вставляючи їх у скриптовий тег позначений як не-JavaScript (notjs):
<script id="vertex-shader-2d" type="notjs">
// атрибут отримає дані з буфера
attribute vec4 a_position;
// всі шейдери мають головну функцію main
void main() {
// gl_Position - особлива змінна, за встановлення якої
// відповідає вершинний шейдер
gl_Position = a_position;
}
</script>
<script id="fragment-shader-2d" type="notjs">
// фрагментний шейдер за замовчуванням немає встановленої точності
// тому нам потрібно її обрати. mediump хороше значення за замовчуванням
// mediump означає середня точність
precision mediump float;
void main() {
// gl_FragColor - особлива змінна, за встановлення якої
// відповідає фрагментний шейдер
gl_FragColor = vec4(1, 0, 0.5, 1); // повернути червонувато-фіолетовий колір
}
</script>
Насправді більшість 3D-рушіїв генерують шейдери GLSL на льоту, використовуючи різні типи шаблонів, конкатенацію тощо. Як приклад шейдери на цьому сайті, хоча жоден із них не є настільки складним, щоб генерувати GLSL під час виконання.
Далі нам потрібна функція, яка створить шейдер, завантажить вихідний код GLSL і скомпілює його. Зверніть увагу, що ми не додавали жодних коментарів, тому, що з назви функцій має бути і так зрозуміло що відбувається:
function createShader(gl, type, source) {
var shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
var success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
if (success) {
return shader;
}
console.log(gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
}
Тепер ми можемо викликати цю функцію, щоб створити наші два шейдера:
var vertexShaderSource = document.querySelector("#vertex-shader-2d").text;
var fragmentShaderSource = document.querySelector("#fragment-shader-2d").text;
var vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
var fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
Далі нам потрібно пов’язати (to link) ці шейдери з програмою (program):
function createProgram(gl, vertexShader, fragmentShader) {
var program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
var success = gl.getProgramParameter(program, gl.LINK_STATUS);
if (success) {
return program;
}
console.log(gl.getProgramInfoLog(program));
gl.deleteProgram(program);
}
І створити її:
var program = createProgram(gl, vertexShader, fragmentShader);
Тепер, коли ми створили програму GLSL, яка виконуватиметься на графічному процесорі, нам потрібно надати їй певні дані. Більшість WebGL API стосується налаштування стану для надання даних нашим GLSL програмам. У цьому випадку єдиними вхідними даними для нашої програми GLSL є змінна a_position
, яка є атрибутом. Перше, що нам варто зробити, це знайти розташування атрибута програми, яку ми щойно створили:
var positionAttributeLocation = gl.getAttribLocation(program, "a_position");
Пошук розташування атрибута (та uniform-змінної) це щось, що потрібно робити саме на етапі ініціалізації програми, а не під час циклу рендерингу.
Атрибути отримують дані з буферів, тому нам потрібно створити новий буфер:
var positionBuffer = gl.createBuffer();
WebGL дозволяє нам маніпулювати багатьма ресурсами WebGL за допомогою точок прив’язки. Ви можете розглядати точки прив’язки як внутрішні глобальні змінні всередині WebGL. Спочатку ви прив’язуєте певний ресурс до однієї з точок прив’язки. Після цього усі інші функції будуть посилатись на цей ресурс саме через цю точку прив’язки. Отже, давайте прив’яжемо наш буфер з позиціями до однієї з таких точок:
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
Тепер ми можемо помістити певні дані в цей буфер, з допомогою точки прив’язки:
// три 2d точки
var positions = [
0, 0,
0, 0.5,
0.7, 0,
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
В цьому коді відбувається багато цікавого. По-перше, у нас є змінна positions
, яка є
масивом JavaScript. WebGL зі свого боку потребує строгої типізації даних, тому ця частина new Float32Array(positions)
відповідає за перетворення масиву positions
в новий масив 32-бітних чисел з рухомою комою. Далі gl.bufferData
копіює ці дані в positionBuffer
, який зберігається на графічному процесорі. Ми використовуємо точку прив’язки ARRAY_BUFFER
, яка посилається на positionBuffer
, щоб скопіювати дані саме туди.
Останній аргумент, gl.STATIC_DRAW
, є підказкою для WebGL про те, як ми будемо використовувати ці дані. WebGL може спробувати використати цю підказку для оптимізації певних речей. gl.STATIC_DRAW
повідомляє WebGL, що ми не плануємо змінювати ці дані часто.
Увесь код до цього моменту був кодом ініціалізації. Код, який запуститься тоді, коли ми завантажемо сторінку. Код, який ми розглядатимемо надалі це код рендерингу, який буде виконуватись кожен раз, коли ми захочемо відрендерити/намалювати щось.
Перш ніж почати щось малювати, ми маємо змінити розмір полотна відповідно до розміру дисплея. Полотно так само як і зображення має 2 розміри. Перший, це фактична кількість пікселів у нії, а другий це фактичний розмір, яким вона відображаються на екрані. CSS задає розмір відображення на екрані. Ви завжди повинні встановлювати розмір полотна за допомогою CSS, оскільки це найбільш гнучкий метод серед інших.
Щоб кількість пікселів полотна збігалась з розміром відображення я використовую допоміжну функцію, про яку можна почитати тут.
Майже в усіх цих прикладах розмір полотна становить 400x300 пікселів, якщо приклад запускається в окремому вікні, але розтягується, щоб заповнити доступний простір, якщо він розташовується всередині iframe, як на цій сторінці. Дозволяючи CSS визначати розмір, а потім коригуючи його до потрібного ми з легкістю впораємось з обома випадками:
webglUtils.resizeCanvasToDisplaySize(gl.canvas);
Нам потрібно вказати WebGL як конвертувати значення простору відсікання, які ми задамо в gl_Position
, назад в пікселі, які часто називають простором екрану. Для цього ми викликаємо функцію gl.viewport
і передаємо їй поточний розмір полотна.
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
Це повідомляє WebGL, що значеня простору відсікання від -1 до +1 перетворюється в значення від 0 до gl.canvas.width
для координат по x та від 0 до gl.canvas.height
для координат по y.
Далі ми очищуємо полотно. Встановлюючи значення 0, 0, 0, 0
для червоного, зеленого, синього та альфа-каналу ми робимо полотно повністю прозорим:
// Очищаємо полотно
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
Тут ми повідомляємо WebGL яку саме програму потрібно запустити:
// вказуємо на потрібну програму (шейдерну пару)
gl.useProgram(program);
Тепер нам потрібно повідомити WebGL, як отримати дані з буфера, який ми створили вище, і надавати їх атрибуту у шейдері. Спочатку нам потрібно увімкнути атрибут:
gl.enableVertexAttribArray(positionAttributeLocation);
Далі потрібно описати як саме отримати ці дані:
// Прив’язуємо буфер з позиціями.
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// вказуємо атрибуту на те, як витягнути звідти дані
var size = 2; // 2 складові на кожну ітерацію
var type = gl.FLOAT; // тип даних - 32-бітні числа
var normalize = false; // нормалізувати дані не потрібно
var stride = 0; // 0 = кожної ітерації просуватись по даних з кроком size * sizeof(type) для отримання наступної позиції
var offset = 0; // починати від початку буфера
gl.vertexAttribPointer(
positionAttributeLocation, size, type, normalize, stride, offset)
Прихована частина функції gl.vertexAttribPointer
полягає в тому, що вона прив’язує поточне значення ARRAY_BUFFER
до переданого атрибута. Іншими словами, тепер цей атрибут пов’язаний напряму з positionBuffer
. Це означає, що ми можемо використовувати ARRAY_BUFFER
як точку прив’язки для чогось іншого, а цей атрибут продовжить використовувати саме positionBuffer
.
Зверніть увагу, що, з точки зору нашого вершинного шейдера, атрибут a_position
має тип даних vec4
:
attribute vec4 a_position;
vec4
це значення з чотирьох чисел. В термінах JavaScript ви може виглядати як a_position = {x: 0, y: 0, z: 0, w: 0}
. Вище ми встановили size = 2
. Значення за замовчуванням для атрибута є 0, 0, 0, 1
, тому цей атрибут отримає перші два значення (x та y) з нашого буфера, а наступні два (z та w) залишуться 0 та 1.
Після всього цього ми нарешті можемо попросити WebGL виконати нашу GLSL програму:
var primitiveType = gl.TRIANGLES;
var offset = 0;
var count = 3;
gl.drawArrays(primitiveType, offset, count);
Через те, що count
дорівнює 3, наший вершинний шейдер виконається 3 рази. Першого разу, значення a_position.x
та a_position.y
в атрибуті вершинного шейдера будуть встановленні з перших двох значень змінної positionBuffer
. Другого разу, значення a_position.x
та a_position.y
будуть встановленні в наступні два значення буфера. Останнього разу, вони будуть встановленні з останніх двох значень буфера.
Через те, що ми встановили значення primitiveType
як gl.TRIANGLES
, кожного разу, коли наш вершинний шейдер буде запускатись, WebGL буде малювати трикутник на основі 3 значень, які ми встановили для gl_Position
. Незалежно від розміру нашого полотна ці значення встановлюються в просторі відсікання, який, в свою чергу, лежить в межах від -1 до 1.
Вершинний шейдер просто копіює значення positionBuffer
до gl_Position
, тому трикутник буде намальований в таких координатих простору відсікання:
0, 0,
0, 0.5,
0.7, 0,
Перетворюючи координати простору відсікання в координати простору екрану для полотна розміром 400x300 пікселів ми отримаємо такі значення:
простір відсікання простір екрану
0, 0 -> 200, 150
0, 0.5 -> 200, 225
0.7, 0 -> 340, 150
Тепер WebGL відрендирить цей трикутник. Для кожного пікселя, який він збирається намалювати, WebGL викличе наш фрагментний шейдер. Наш фрагментний шейдер просто встановлює значення gl_FragColor
як 1, 0, 0.5, 1
. Оскільки елемент canvas для кожного каналу приймає 8-бітні значення, WebGL запише значення [255, 0, 127, 255]
в це полотно.
Ось інтерактивна версія нашого прикладу:
В цьому прикладі ви можете побачити, що вершинний шейдер лише передає далі дані про координати і більше нічого. Оскільки дані про позиції одразу задані в просторі відсікання, то тут немає додаткової роботи, яку потрібно виконати. Якщо ви хочете відображати 3D об’єкти, то відповідальність за створення шейдерів, які перетворюють 3D координати в координати простору відсікання лежить на вас, тому що WebGL це тільки засіб растеризації.
Вас може зацікавити чому цей трикутник починається посередині і прямує до правого верхнього кута. Простір відсікання лежить в значеннях від -1 до +1. Це означає, що 0 це центральна позиція і додатні значення прямуватимуть в праву сторону від нього.
Також, оскільки в просторі відсікання -1 це низ, а +1 це верх, то 0 це середина і додатні числа будуть вище цієї середини.
Для двовимірних задач вам мабуть буде зручніше працювати в піксельних значеннях ніш координатах простору відсікання. Тож давайте змінимо наш шейдер так, щоб ми могли передавати йому значення позицій в пікселях, які він самостійно буде перетворювати в координати простору відсікання. Ось наш новий шейдер:
<script id="vertex-shader-2d" type="notjs">
- attribute vec4 a_position;
* attribute vec2 a_position;
+ uniform vec2 u_resolution;
void main() {
+ // перетворюємо позиції з пікселів в діапазон від 0.0 до 1.0
+ vec2 zeroToOne = a_position / u_resolution;
+
+ // перетворюємо це з 0->1 до 0->2
+ vec2 zeroToTwo = zeroToOne * 2.0;
+
+ // перетворюємо цей діпазон з 0->2 до -1->+1
+ vec2 clipSpace = zeroToTwo - 1.0;
+
* gl_Position = vec4(clipSpace, 0, 1);
}
</script>
Деякі важливі зміни, які варто зауважити. Ми змінили тип змінної a_position
на vec2
оскільки ми використовуємо тільки x
та y
. Тип vec2
схожий до vec4
, але має тільки x
та y
.
Далі ми додали uniform-змінну з назвою u_resolution
. Саме тому, нам потрібно дізнатись її розташування:
var resolutionUniformLocation = gl.getUniformLocation(program, "u_resolution");
Рашту має бути зрозуміло з коментарів. Встановлюючи в змінну u_resolution
значення розширення нашого полотна ми тепер можемо взяти позицію з positionBuffer
задану у пікселях і перетворити їх у координати простору відсікання.
Тепер ми можемо змінити координати наших позицій на пікселі. Цього разу ми збираємось намалювати чотирикутник, який складається з двох трикутників по 3 точки на кожен:
var positions = [
* 10, 20,
* 80, 20,
* 10, 30,
* 10, 30,
* 80, 20,
* 80, 30,
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
Після того, як ми зазначимо, яку саме програму використовувати, ми можемо встановити значення uniform-змінної, яку ми створили. Функція gl.useProgram
схожа на gl.bindBuffer
тим, що встановлює поточну програму. Після цього всі gl.uniformXXX
функції встановлюють значення uniform-змінним уже поточної програми:
gl.useProgram(program);
...
// встановити значення розширення
gl.uniform2f(resolutionUniformLocation, gl.canvas.width, gl.canvas.height);
Звісно для того, щоб намалювати 2 трикутника, ми потребуємо, щоб WebGL викликав наш вершинний шейдер 6 разів:
// намалювати
var primitiveType = gl.TRIANGLES;
var offset = 0;
*var count = 6;
gl.drawArrays(primitiveType, offset, count);
Ось, що ми отримаємо.
Нотатка: Цей приклад і усі наступні приклади використовують файл webgl-utils.js
, який містить в собі допоміжні функції для компіляції та прив’язки шейдерів. Немає потреби захаращувати наші приклади цим повторюваним кодом.
Ви знову можете помітити, що прямокутник розташовується біля нижньої частини цієї зони. WebGL розглядає додатній y як верх, і від’ємний y як низ. У просторі відсікання нижній лівий кут має координати (-1, -1). Ми не міняли жодної знаків тож з поточною логікою нашого шейдера (0, 0) стає нижнім лівим кутом. Щоб перетворити це у більш традиційний для двовимірної графіки верхній лівий кут, ми можемо просто перевернути простір відсікання по координаті y.
* gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
Тепер наш прямокутник там, де ми очікуємо його побачити.
Давайте перетворимо код, який визначає прямокутник, у функцію, щоб ми могли викликати її для прямокутників різного розміру. Поки ми це робимо, ми налаштуємо колір.
Спочатку ми додаємо до фрагментного шейдера unifrom-змінну, яка прийматиме дані про колір. First we make the fragment shader take a color uniform input.
<script id="fragment-shader-2d" type="notjs">
precision mediump float;
+ uniform vec4 u_color;
void main() {
* gl_FragColor = u_color;
}
</script>
А ось і новий код, який малює 50 прямокутників у випадкових місцях і з випадковими кольорами.
var colorUniformLocation = gl.getUniformLocation(program, "u_color");
...
// намалювати 50 випадкових прямокутників
for (var ii = 0; ii < 50; ++ii) {
// задати випадковий прямокутник
setRectangle(
gl, randomInt(300), randomInt(300), randomInt(300), randomInt(300));
// задати випадковий колір
gl.uniform4f(colorUniformLocation, Math.random(), Math.random(), Math.random(), 1);
// намалювати прямокутник
gl.drawArrays(gl.TRIANGLES, 0, 6);
}
}
// повертає випадково значення від 0 до range - 1.
function randomInt(range) {
return Math.floor(Math.random() * range);
}
// заповнює буфер значеннями, які описують прямокутник
function setRectangle(gl, x, y, width, height) {
var x1 = x;
var x2 = x + width;
var y1 = y;
var y2 = y + height;
// gl.bufferData(gl.ARRAY_BUFFER, ...) буде впливати
// на той буфер, який зараз прив’язаний до `ARRAY_BUFFER`.
// Якби ми мали декілька буферів,
// то спершу нам потрібно було б прив’язати той,
// який ми хочемо використати
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
x1, y1,
x2, y1,
x1, y2,
x1, y2,
x2, y1,
x2, y2]), gl.STATIC_DRAW);
}
Ось наші прямокутники:
Я сподіваюсь, що ви могли побачити, що WebGL насправді дуже простий. Гаразд, можливо, простий тут невдале слово. Точніше буде сказати, що WebGL робить прості речі. Він просто виконує 2 функції (вершинний та фрагментний шейдер) та малює трикутники, лінії та точки. Звісно, він може робити складні 3D речі, але цю складність можете додати тільки ви, програмісти, у вигляді більш складних шейдерів. WebGL сам по собі це лише засіб растеризації, досить простий концептуально.
Ми охопили невеликий приклад, який показав нам, як передавати дані в атрибути та uniform-змінні. Це досить поширено мати декілька атрибутів і багато uniform-змінних. На початку цієї статті ми також згадували про varying-змінні та текстури. Ці речі появляться в наступних уроках.
Перш ніж ми підемо далі, я хочу зазначити, що для більшості програм оновлення даних у буфері, як ми це робили в setRectangle
, не є звичною практикою. Я використав цей приклад, тому що вважав, що його найлегше пояснити, оскільки він показує координати пікселів як вхідні дані та демонструє виконання невеликої кількості математики в GLSL. Це також не є неправильно, є багато випадків, коли це цілком прийнятно, але ви повинні продовжувати читати, щоб дізнатися про більш поширений спосіб розташування, зміни орієнтації та масштабування речей у WebGL.
Якщо ви новачок у веброзробці і навіть, якщо ні, то перегляньте Налаштування та встановлення для деяких порад щодо розробки WebGL.
Якщо ви на 100% новачок у WebGL і не знаєте, що таке GLSL, шейдери чи що робить GPU то перегляньте основи того, як насправді працює WebGL. Ви також можете поглянути на інтерактивну діаграму стану для іншого способу розуміння того, як працює WebGL.
Ви також мусите коротко прочитати про шаблон коду, який тут використовується і який використовується в більшості прикладів. Також мусите хоча б пролистати про те, як малювати кілька речей, щоб отримати певне уявлення про структуру більш типових програм WebGL, оскільки, майже всі приклади тут малюють лише одну річ і тому не показують цю структуру.
З цього місця ви можете йти у двох напрямках. Якщо вас цікавить обробка зображень, то можете глянути на те як виконати базову обробку 2D зображень. Якщо вам цікаво дізнатися про переміщення, обертання та масштабування та, зрештою, 3D, то почніть тут.