[JavaScript, WebGL] Знакомство фронтендера с WebGL: первые наброски (часть 2)
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Это история в несколько частей:
Вспоминаем ради чего мы начали изучать WebGLПосле недельного чтения ресурса и экспериментов у меня появился кустарный REPL, в котором я мог быстро набрасывать шейдеры и другой код, чтоб поэкспериментировать и найти решение.
Я пишу статью, спустя 2 месяца после того как закончил работу над задачей и осталась только такая песочница ->
Извините, данный ресурс не поддреживается. :( Вооружись знаниями и реплом, я пошел искать то, что сможет распарсить мне .obj файлик на вершины.Единственное в интернете которое более/менее правильно распарсило мне файлик это был npm пакет webgl-obj-loader.Первые наброскиС помощью библиотеки, я сразу смог добиться какого-то результата в своей песочнице.
// vertex
attribute vec4 a_position; // объявляем переменную в которую будем прокидывать вершины яблока.
uniform mat4 u_matrix; // матрица которая будет нам помогать трансформировать модель
void main(){
gl_Position = u_matrix * a_position; // у glsl есть встроенные возможности по работе с матрицами. Тут он сам за нас перемножает вершины на матрицы и тем самым смещает их куда надо.
}
// fragment
precision mediump float; // точность для округления.
void main() {
gl_FragColor = vec4(1., 0., 0., 1.); // заливаем красным
}
Код
import { vertex, fragment } from './shaders'; // через parcel импортирует тексты
import { createCanvas, createProgramFromTexts } from "./helpers";
import { m4 } from "./matrix3d"; // после изучение webgl на webgl fund, мне в наследство досталась библиотека которая умеет работает с 3д матрицами.
import appleObj from "./apple.obj"; // моделька яблока
import * as OBJ from "webgl-obj-loader"; // наша либа которая распарсит obj
function main() {
const apple = new OBJ.Mesh(appleObj); // загружаем модель
const canvas = createCanvas(); // создаю canvas и вставляю в body
const gl = canvas.getContext("webgl"); // получаю контекст
const program = createProgramFromTexts(gl, vertex, fragment); // создаю программу из шейдеров
gl.useProgram(program); // линкую программу к контексту
// получаю ссылку на атрибут
const positionLocation = gl.getAttribLocation(program, "a_position");
// у либы была готовая функция, которая за меня создавала буфер и прокидывала распарсенные данные в буферы. Из .obj можно было достать не только вершины, но и другие координаты которые могут быть полезны.
OBJ.initMeshBuffers(gl, apple);
gl.enableVertexAttribArray(positionLocation); // активирую атрибут, зачем это делать не знаю, но не сделаешь, ничего не заработает.
gl.vertexAttribPointer(
positionLocation,
apple.vertexBuffer.itemSize, // либа сама определяла сколько нужно атрибуту брать чисел, чтоб получить вершину
gl.FLOAT,
false, // отключаем нормализацию (это чтоб не пыталось конвертировать числа больше 1 в 1. Аля 255 -> 0.255.
0,
0
); // объясняю как атрибуту парсить данные
// получаем ссылку на глобальную переменную которая будет доступна внутри шейдеров. В нее же мы будем прокидывать матрицы
const matrixLocation = gl.getUniformLocation(program, "u_matrix");
let translation = [canvas.width / 2, 400, 0]; // смещаю на центр экрана по вертикали и 400 px вниз
let rotation = [degToRad(180), degToRad(0), degToRad(0)]; // вращение по нулям
let scale = [5, 5, 5]; // увеличиваю модельку в 5 раз. scaleX, scaleY, scaleZ
// выставляю вью порт
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.enable(gl.DEPTH_TEST); // включаем специальный флаг, который заставляет проверять видеокарту уровень вложенности и если какой-то треугольник перекрывает другой, то другой не будет рисоваться, потому, что он не виден.
function drawScene() {
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); // очищаем канвас на каждый рендер
const matrix = m4.multiply(
m4.identity(), // создаем единичную матрицу. Матрицу у которой все значения по умолчанию.
m4.orthographic(
0,
gl.canvas.width,
gl.canvas.height,
0,
400,
-400
), // Создаем матрицу которая конвертирует неудобные размеры модельки яблока в координатное пространство -1 до 1.
m4.translation(...translation), // перемещаем модельку
m4.xRotation(rotation[0]), // крутим по X
m4.yRotation(rotation[1]), // крутим по Y
m4.zRotation(rotation[2]), // крутим по Z
m4.scaling(...scale) // увеличиваем модельку
); // перемножаем матрицы друг на друга, чтоб в конце получить 1 матрицу которую и прокинем в шейдер
gl.uniformMatrix4fv(matrixLocation, false, matrix); // прокидываем матрицу
// подключаю буфер с индексами
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, apple.indexBuffer);
// рисуем яблоко треугольниками с помощью индексов
gl.drawElements(
gl.TRIANGLES,
apple.indexBuffer.numItems,
gl.UNSIGNED_SHORT,
0
);
}
drawScene();
// Тут код который настраивает всякие слайдеры, чтоб изменять матрицы.
// ...
//
}
main();
На выход я получил такую штуку:
Мне кажется это маленький шаг для webgl, но огромный скачок для фронтендера.Что такое index?При работе с либой узнал про новую вещь: индексы.
На самом деле в .obj файле кроме вершин есть текстурные координаты, нормали, поверхности (faces).
Что это вообще такое?
- Текстурные координаты это массив цифр который прокидывается в фрагментный шейдер и позволяет шейдеру понять где он вообще сейчас находится в модельке, чтоб наложить пиксель в зависимости от общего положения. Если их нет, то шейдер получается вообще изолированным и может только красить пиксели не зная где именно сейчас он закрашивает. Текстурные координаты прокидываются как атрибуты.
- Нормали это тоже координаты, но их можно использовать в фрагментом шейдере, чтоб понять как рисовать тень от объекта(модель) в зависимости от того как должен падать свет на объект.
- Поверхность - это массив индексов которые указывают на индекс в массиве вершин, текстур и нормалей. Поверхности это служебные данные для редактора моделей (аля cinema4d и других), которые позволяют объединять полигоны в квадраты и другие более сложные фигуры. В частности это нужно для того как рендерить именно модельку. Так вот индексы это и есть поверхности. Допустим мы прокинули в 2 атрибута данные от вершин и текстурных координат. И webgl смотрит на текущий индекс и по параметрам атрибутов (помните мы указывали size, сколько нужно брать чисел, чтоб получить вершину) берет из каждого атрибута нужный набор чисел и прокидывает их в шейдеры.
Дальше я попробовал изменить gl.TRIANGLES на gl.LINES. И получил следующий результат:
Мда, совершенно не то, что я ожидал. Где мои красивые линии как у дизайнера и че за треугольники. Я тогда впервые осознал простую истину, что все блин на треугольниках. В данной ситуации, я побежал в чат и тогда породил локальный мем.
у меня начали появляться подозрения, что рисовать фигуры можно только через треугольники.
Я просто не знал что делать дальше и спрашивал советов. Среди них было несколько:
- Используй в фрагментом шейдере uv, чтоб рисовать линии сам.- Распарси .obj сам и получили нужные значения.- Сделай uv разветку и натяну текстуру картинку.
Я не понял из 1 ответа, что такое uv, почему-то тогда мне никто не объяснил, что это и есть текстурные координаты. Да и где эти uv брать тоже было не понятно.Из второе ответа, я тоже не понял что мне делать и какие значения использовать.А третий ответ оказался хоть тоже загадочным, но мне объяснили что это значит. Нужно было через редактор моделей создать текстурные координаты и нарисовать под них текстуру.В интернете я нашел гайды о том как сделать в cinema 4d uv разметку и там же нашел как нарисовать текстуру. В редакторе была возможность создать картинку и залить по граням поверхностей(faces) нужный цвет. Я считал, что это сразу решает мою проблему. Выплюнув texture.png и новый obj с uv (то есть так называются текстурные координаты).Баг который попортил нервыЯ побежал читать статью на webgl fund как натянут текстуру. Кода стало больше, но не увидел сложностей. Сделал как в гайде и думал, щас будет все отлично!
// vertex
precision mediump float;
attribute vec4 a_position;
attribute vec2 a_texture_coords; // текстурные координаты из модели
uniform mat4 u_matrix;
varying vec2 v_texture_coords;
void main(){
gl_Position = u_matrix * a_position;
v_texture_coords = a_texture_coords; // прокидываем во фрагментный шейдер
}
// fragment
precision mediump float;
varying vec2 v_texture_coords; // координаты из вершины
uniform sampler2D u_texture; // текстура
void main(){
gl_FragColor = texture2D(u_texture, v_texture_coords);
}
//...
const textureCoordsLocation = gl.getAttribLocation(
program,
"a_texture_coords"
); // получили ссылку на новый атрибут
// ...
gl.enableVertexAttribArray(textureCoordsLocation);
gl.bindBuffer(gl.ARRAY_BUFFER, apple.textureBuffer); // забиндили буфер которая выдала либа из модели
gl.vertexAttribPointer(
textureCoordsLocation,
apple.textureBuffer.itemSize,
gl.FLOAT,
false,
0,
0
);
const texture = gl.createTexture(); // запрашиваем место для текстуры
gl.bindTexture(gl.TEXTURE_2D, texture); // биндим
gl.texImage2D(
gl.TEXTURE_2D,
0,
gl.RGBA,
1,
1,
0,
gl.RGBA,
gl.UNSIGNED_BYTE,
new Uint8Array([0, 0, 255, 255])
); // сначала прокидываем пустышку, пока грузится текстура
const image = new Image();
image.src = textureImg; // загружаем текстуру
image.onload = () => {
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
gl.generateMipmap(gl.TEXTURE_2D);
gl.texParameteri(
gl.TEXTURE_2D,
gl.TEXTURE_MIN_FILTER,
gl.LINEAR_MIPMAP_LINEAR
);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); // какие-то неведомые настройки, чтоб все было круто
drawScene();
};
// ...
const textureLocation = gl.getUniformLocation(program, "u_texture");
function drawScene() {
// ...
gl.uniform1i(textureLocation, 0);
// ...
}
И после тонны кода получаем вот это чудовище:
Что за??И вот тут у меня началась эпопея в целый рабочий день с целью решить проблему. Я мало, что понимал и считал, что это я косячу, а не либа которую использую. Сначала я на самом деле откатил код с текстурой и просто попробовал закрасить и получил опять какой-то невероятный результат.
Что за фигня?
Я тогда решил, что проблема в экспорте и вообще в том, что я делал с uv mapping. Поиграв пару часов с экспортом, я решил попробовать в blender экспортировать и о чудо, моделька починилась!
Потратив еще кучу часов в попытке разобраться, в чем же дело. Я заметил, что blender по умолчанию преобразовывал поверхности из 4 точек в поверхности из 3 точек. И когда я отключал данную функцию, то модельки опять ломались. И тогда, я понял, что проблема все это время была в библиотеке webgl-obj-loader. Она ломалась если ей подавали поверхности из 4 точек (на самом деле мне это объяснили в чате).Я сразу побежал писать жалобу на проблему, а потом нашел pull request который правил эту багу и прикрепил его к своему issue.Отказ от webgl-obj-loaderПосмотрев на результат мучительной работы, я понял, что это не то, чего я хотел. Линии были толстыми, плюс чем сильней было скругление, тем плотней становилась область.
Еще я понимал, что есть какое-то другое решение, потому что когда я открывал модельку в просмотрщиках моделей, они правильно рисовали результат и красиво прорисовали линии.
Видя это, я понимал, что все можно рассчитать программно, но не знал как...И в это время появился рыцарь в сияющих доспехах и спас меня из логова бессилия. Он был тем кто предложил:
Распарси .obj сам и получили нужные значения.
На тот момент я не понимал, что вообще это значит и как мне это поможет. И человек накидал в песочнице пример на three.js.Извините, данный ресурс не поддреживается. :( Этот пример был светом. Я сразу же понял, что можно выкидывать webgl-obj-loader и зажить как человек. Выбросил его без каких либо сожалений.
===========
Источник:
habr.com
===========
Похожие новости:
- Выпуск jsii 1.31, генератора кода C#, Go, Java и Python из TypeScript
- [Программирование, Java] Spring Boot + ControllerAdvice + ResponseBodyAdvice или как обернуть ответ контроллеров
- [JavaScript, WebGL] Знакомство фронтендера с WegGL (часть 1)
- [JavaScript] DTO в JS
- [Информационная безопасность, Java, Разработка под Android, GitHub] Пишем паническую кнопку под андроид (Часть 2)
- [JavaScript, Разработка мобильных приложений] Разработка под iOS без Xcode
- [JavaScript] Как я писал тестовое задание на Angular и почему некоторым разработчикам не стоит давать тестовое задание
- [JavaScript, Node.JS] Создаем свой сайт или блог на Ghost в образе Docker
- [Информационная безопасность, Java, Разработка под Android, Хакатоны] Пишем паническую кнопку под Android (Часть 1)
- [JavaScript, Алгоритмы] Быстрая математика для графиков, на примере вычисления среднего
Теги для поиска: #_javascript, #_webgl, #_java, #_webgl, #_novichok (новичок), #_frontend, #_vanilla (ванилла), #_javascript, #_webgl
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 06:47
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Это история в несколько частей: Вспоминаем ради чего мы начали изучать WebGLПосле недельного чтения ресурса и экспериментов у меня появился кустарный REPL, в котором я мог быстро набрасывать шейдеры и другой код, чтоб поэкспериментировать и найти решение. Я пишу статью, спустя 2 месяца после того как закончил работу над задачей и осталась только такая песочница ->
// vertex
attribute vec4 a_position; // объявляем переменную в которую будем прокидывать вершины яблока. uniform mat4 u_matrix; // матрица которая будет нам помогать трансформировать модель void main(){ gl_Position = u_matrix * a_position; // у glsl есть встроенные возможности по работе с матрицами. Тут он сам за нас перемножает вершины на матрицы и тем самым смещает их куда надо. } // fragment
precision mediump float; // точность для округления. void main() { gl_FragColor = vec4(1., 0., 0., 1.); // заливаем красным } import { vertex, fragment } from './shaders'; // через parcel импортирует тексты
import { createCanvas, createProgramFromTexts } from "./helpers"; import { m4 } from "./matrix3d"; // после изучение webgl на webgl fund, мне в наследство досталась библиотека которая умеет работает с 3д матрицами. import appleObj from "./apple.obj"; // моделька яблока import * as OBJ from "webgl-obj-loader"; // наша либа которая распарсит obj function main() { const apple = new OBJ.Mesh(appleObj); // загружаем модель const canvas = createCanvas(); // создаю canvas и вставляю в body const gl = canvas.getContext("webgl"); // получаю контекст const program = createProgramFromTexts(gl, vertex, fragment); // создаю программу из шейдеров gl.useProgram(program); // линкую программу к контексту // получаю ссылку на атрибут const positionLocation = gl.getAttribLocation(program, "a_position"); // у либы была готовая функция, которая за меня создавала буфер и прокидывала распарсенные данные в буферы. Из .obj можно было достать не только вершины, но и другие координаты которые могут быть полезны. OBJ.initMeshBuffers(gl, apple); gl.enableVertexAttribArray(positionLocation); // активирую атрибут, зачем это делать не знаю, но не сделаешь, ничего не заработает. gl.vertexAttribPointer( positionLocation, apple.vertexBuffer.itemSize, // либа сама определяла сколько нужно атрибуту брать чисел, чтоб получить вершину gl.FLOAT, false, // отключаем нормализацию (это чтоб не пыталось конвертировать числа больше 1 в 1. Аля 255 -> 0.255. 0, 0 ); // объясняю как атрибуту парсить данные // получаем ссылку на глобальную переменную которая будет доступна внутри шейдеров. В нее же мы будем прокидывать матрицы const matrixLocation = gl.getUniformLocation(program, "u_matrix"); let translation = [canvas.width / 2, 400, 0]; // смещаю на центр экрана по вертикали и 400 px вниз let rotation = [degToRad(180), degToRad(0), degToRad(0)]; // вращение по нулям let scale = [5, 5, 5]; // увеличиваю модельку в 5 раз. scaleX, scaleY, scaleZ // выставляю вью порт gl.viewport(0, 0, gl.canvas.width, gl.canvas.height); gl.enable(gl.DEPTH_TEST); // включаем специальный флаг, который заставляет проверять видеокарту уровень вложенности и если какой-то треугольник перекрывает другой, то другой не будет рисоваться, потому, что он не виден. function drawScene() { gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); // очищаем канвас на каждый рендер const matrix = m4.multiply( m4.identity(), // создаем единичную матрицу. Матрицу у которой все значения по умолчанию. m4.orthographic( 0, gl.canvas.width, gl.canvas.height, 0, 400, -400 ), // Создаем матрицу которая конвертирует неудобные размеры модельки яблока в координатное пространство -1 до 1. m4.translation(...translation), // перемещаем модельку m4.xRotation(rotation[0]), // крутим по X m4.yRotation(rotation[1]), // крутим по Y m4.zRotation(rotation[2]), // крутим по Z m4.scaling(...scale) // увеличиваем модельку ); // перемножаем матрицы друг на друга, чтоб в конце получить 1 матрицу которую и прокинем в шейдер gl.uniformMatrix4fv(matrixLocation, false, matrix); // прокидываем матрицу // подключаю буфер с индексами gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, apple.indexBuffer); // рисуем яблоко треугольниками с помощью индексов gl.drawElements( gl.TRIANGLES, apple.indexBuffer.numItems, gl.UNSIGNED_SHORT, 0 ); } drawScene(); // Тут код который настраивает всякие слайдеры, чтоб изменять матрицы. // ... // } main(); Мне кажется это маленький шаг для webgl, но огромный скачок для фронтендера.Что такое index?При работе с либой узнал про новую вещь: индексы. На самом деле в .obj файле кроме вершин есть текстурные координаты, нормали, поверхности (faces). Что это вообще такое?
Мда, совершенно не то, что я ожидал. Где мои красивые линии как у дизайнера и че за треугольники. Я тогда впервые осознал простую истину, что все блин на треугольниках. В данной ситуации, я побежал в чат и тогда породил локальный мем. у меня начали появляться подозрения, что рисовать фигуры можно только через треугольники.
- Используй в фрагментом шейдере uv, чтоб рисовать линии сам.- Распарси .obj сам и получили нужные значения.- Сделай uv разветку и натяну текстуру картинку.
// vertex
precision mediump float; attribute vec4 a_position; attribute vec2 a_texture_coords; // текстурные координаты из модели uniform mat4 u_matrix; varying vec2 v_texture_coords; void main(){ gl_Position = u_matrix * a_position; v_texture_coords = a_texture_coords; // прокидываем во фрагментный шейдер } // fragment
precision mediump float; varying vec2 v_texture_coords; // координаты из вершины uniform sampler2D u_texture; // текстура void main(){ gl_FragColor = texture2D(u_texture, v_texture_coords); } //...
const textureCoordsLocation = gl.getAttribLocation( program, "a_texture_coords" ); // получили ссылку на новый атрибут // ... gl.enableVertexAttribArray(textureCoordsLocation); gl.bindBuffer(gl.ARRAY_BUFFER, apple.textureBuffer); // забиндили буфер которая выдала либа из модели gl.vertexAttribPointer( textureCoordsLocation, apple.textureBuffer.itemSize, gl.FLOAT, false, 0, 0 ); const texture = gl.createTexture(); // запрашиваем место для текстуры gl.bindTexture(gl.TEXTURE_2D, texture); // биндим gl.texImage2D( gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([0, 0, 255, 255]) ); // сначала прокидываем пустышку, пока грузится текстура const image = new Image(); image.src = textureImg; // загружаем текстуру image.onload = () => { gl.bindTexture(gl.TEXTURE_2D, texture); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image); gl.generateMipmap(gl.TEXTURE_2D); gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR ); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); // какие-то неведомые настройки, чтоб все было круто drawScene(); }; // ... const textureLocation = gl.getUniformLocation(program, "u_texture"); function drawScene() { // ... gl.uniform1i(textureLocation, 0); // ... } Что за??И вот тут у меня началась эпопея в целый рабочий день с целью решить проблему. Я мало, что понимал и считал, что это я косячу, а не либа которую использую. Сначала я на самом деле откатил код с текстурой и просто попробовал закрасить и получил опять какой-то невероятный результат. Что за фигня? Я тогда решил, что проблема в экспорте и вообще в том, что я делал с uv mapping. Поиграв пару часов с экспортом, я решил попробовать в blender экспортировать и о чудо, моделька починилась! Потратив еще кучу часов в попытке разобраться, в чем же дело. Я заметил, что blender по умолчанию преобразовывал поверхности из 4 точек в поверхности из 3 точек. И когда я отключал данную функцию, то модельки опять ломались. И тогда, я понял, что проблема все это время была в библиотеке webgl-obj-loader. Она ломалась если ей подавали поверхности из 4 точек (на самом деле мне это объяснили в чате).Я сразу побежал писать жалобу на проблему, а потом нашел pull request который правил эту багу и прикрепил его к своему issue.Отказ от webgl-obj-loaderПосмотрев на результат мучительной работы, я понял, что это не то, чего я хотел. Линии были толстыми, плюс чем сильней было скругление, тем плотней становилась область. Еще я понимал, что есть какое-то другое решение, потому что когда я открывал модельку в просмотрщиках моделей, они правильно рисовали результат и красиво прорисовали линии. Видя это, я понимал, что все можно рассчитать программно, но не знал как...И в это время появился рыцарь в сияющих доспехах и спас меня из логова бессилия. Он был тем кто предложил: Распарси .obj сам и получили нужные значения.
=========== Источник: habr.com =========== Похожие новости:
|
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 06:47
Часовой пояс: UTC + 5