четверг, 15 мая 2014 г.

WebGL Lessons. Framebuffer.

Рендеринг в текстуру в WebGL. Инициализация.

Пояснение на русском ключевой части урока http://learningwebgl.com/blog/?p=1786

Создаются 2 глобальные переменные, одна из которых является переменной фреймбуфера, вторая - переменной с адресом текстуры.

Переменная самого фреймбуфера:
var rttFramebuffer;
И переменная текстуры, которая будет накладываться на меш:
var rttTexture;
Функция инициализации текстурного фрэймбуфера.

function initTextureFramebuffer() {
rttFramebuffer = gl.createFramebuffer();
// Посредством WebGL создается новый фреймбуфер.
gl.bindFramebuffer(gl.FRAMEBUFFER, rttFramebuffer);
// Делаем его текущим (активным)
rttFramebuffer.width = 512;
rttFramebuffer.height = 512;
// Сохраняем желаемые размеры.
// Данные переменные не связаны с переменной фреймбуфера.
// И никуда не назначаются.
// Мощь JavaScript.
// Не забываем, размер текстур должен быть степенью двойки.

// Далее мы создаем объект текстуры, как и ранее.
rttTexture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, rttTexture);
// Делаем ее текущей.
// И немного мипмапинга по бабушкиному рецепту.
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);    
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST);
gl.generateMipmap(gl.TEXTURE_2D);

// Далее есть отличие от обычной работы с текстурой.
// Вызов gl.texImage2D имеет другие параметры:
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA,
rttFramebuffer.width, rttFramebuffer.height, 0,
gl.RGBA, gl.UNSIGNED_BYTE, null);
// Традиционный вариант для сравнения:
// gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 
// gl.RGBA, gl.UNSIGNED_BYTE, texture.image);

Обычно, когда мы создаем текстуры чтоб показать изображения загруженные в JavaScript. Мы вызываем gl.texImage2D, чтоб загрузить изображение в текстуру.
Но в данном случае, изображение не загружено. Сейчас нам нужно вызвать специальную версию функции gl.texImage2D, в которую мы не загружаем изображение. А просто просим выделить некоторое количество свободной памяти видеокарты для текстуры.
Строго говоря, последний параметр функции - это массив, которые копируется в свежевыделенную память как стартовая точка. Передавая null - мы ничего не копируем в видеопамять. (Раньше требовалось передавать пустой массив соответствующего разрмера, но сейчас это исправлено.)

Хорошо, сейчас у нас есть пустая текстура, которую мы можем заполнить цветами для нашей сцены.

Далее, мы создаем буфер глубины (depth buffer) и размещаем данные о глубине.
var renderbuffer = gl.createRenderbuffer();
gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer);
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, rttFramebuffer.width, rttFramebuffer.height);
Здесь мы создали объект renderbuffer. Это (generic) тип объекта, который содержит некоторый кусок памяти, который мы намереваемся ассоциировать с фреймбуфером.
Мы забиндили его, - как с текстурами, фреймбуферами и всем остальным.
WebGL имеет текущий renderbuffer.
И вызываем gl.renderbufferStorage, чтоб сказать WebGL'у, что забинденный renderbuffer  нуждается в хранилище достаточном для 16-битных значений, размерностью с заданную ширину и высоту буфера.

Далее:
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, rttTexture, 0);
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, renderbuffer);
Мы подключаем все к текущему фреймбуферу (помните, мы забиндили новый буфер, чтоб он стал текщим сразу после создания, в начале функции). Мы сообщаем, что это пространство (память) фреймбуфера для рендеринга цветов (gl.COLOR_ATTACHMENT0) - это наша текстура. И что эта память должна быть использована для данных о глубине (gl.DEPTH_ATTACHMENT- это буфер глубины, который мы сейчас создали.
Сейчас у нас настроена вся память под наш фреймбуфер. WebGL знает что рендерить, когда мы используем его. Поэтому сейчас мы наведем порядок. Установим текущую текстуру, renderbuffer и фреймбуфер к стандартным значениям:
    gl.bindTexture(gl.TEXTURE_2D, null);
    gl.bindRenderbuffer(gl.RENDERBUFFER, null);
    gl.bindFramebuffer(gl.FRAMEBUFFER, null);
  }
и мы закончили. Наш фреймбуфер настроен правильно. Как мы будем его использовать? Место, с которого можно начинать  смотреть - drawScene, внизу файла. Прямо вначале функции, перед стандартным кодом настройки viewport'а и очистки canvas'а, Вы увидите что-то новое:
var laptopAngle = 0;

function drawScene() {
   gl.bindFramebuffer(gl.FRAMEBUFFER, rttFramebuffer);
   drawSceneOnLaptopScreen();

   gl.bindFramebuffer(gl.FRAMEBUFFER, null);

   gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
   gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)

   mat4.perspective(45, gl.viewportWidth / gl.viewportHeight, 0.1, 100.0, pMatrix);
Пост об инициализации закончен. Далее будет пост об использовании.



Разница между framebuffer'ом и renderbuffer'ом

( http://stackoverflow.com/questions/2213030/whats-the-concept-of-and-differences-between-framebuffer-and-renderbuffer-in ):

This page has some details which I think explain the difference quite nicely. Firstly:
The final rendering destination of the OpenGL pipeline is called [the] framebuffer.
Whereas:
Renderbuffer Object
In addition, renderbuffer object is newly introduced for offscreen rendering. It allows to render a scene directly to a renderbuffer object, instead of rendering to a texture object. Renderbuffer is simply a data storage object containing a single image of a renderable internal format. It is used to store OpenGL logical buffers that do not have corresponding texture format, such as stencil or depth buffer.