OpenGl › v3, ефективно изобразяване на 2d игра

Объркан съм относно VBO и модерния openGL. Има директен въпрос в края на тази публикация, след това има пакет от тях по пътя натам. Ако имате някаква представа за нещо от това, ще съм благодарен за отговор. И ако отговорите, моля, смятайте ме за пълен идиот, без никакви познания.

И така, моята история е следната:

Имам игра, която е 2d игра отгоре надолу. Използвах режим immideate за изобразяване на 2d спрайтове. Действителните текстурни координати на моите текстурни атласи бяха статични и предварително дефинирани в отделен клас. Координатите на четворката бяха определени във всеки обект и се актуализираха с напредването на играта. Когато изобразих, просто обвързах конкретна текстура, наречена glBegin(triangles), след което извиках метод за изобразяване на всеки видим обект. Това от своя страна изпрати както квадратни координати, така и координати на текстура към моя клас Renderer, който направи openGl извикванията. След това изчистих текстурата, която извиква само glEnd().

Направих това за всички различни атласи и в реда, така че да получа правилната дълбочина.

Но времената наистина се променят. Искам да премина към използване на VBO и шейдъри. Опитвах няколко пъти в миналото, но не успях. Има просто няколко неща, които не мога да намеря в Google, за да ми дадат пълна представа за това и как мога да го използвам, за да ускоря играта си.

Знам основите. Вместо да изпращам цялата информация по шината към графичния процесор при всяко повикване за рендиране, мога просто да съхраня всичко, което ще ми трябва във фазата на инициализация, и след това да използвам шейдъри, за да изчисля крайния резултат. Но...

Имам идея за координатите на текстурата. Те ще бъдат статични, тъй като никога няма да се променят. Би имало смисъл да ги съхранявате на GPU. Но как да разбера кои координати съответстват на всеки КВАДР/ТРИЪГЪЛНИК. Мисля, че вместо четири float, всеки рендерируем обект в играта може да има някакъв вид индекс, който предава като атрибут на върховия шейдър. върховият шейдър използва индекса, за да търси четирите координати на текстурата във VBO. Това осъществимо решение ли е? Как бихте приложили нещо подобно?

Но що се отнася до четворните върхове, аз съм изгубен. Те ще се движат постоянно. Те ще бъдат видими, след това ще изчезнат и т.н. Това означава, че моят quad VBO ще се променя при всяко повикване за рендиране и кодът, който съм виждал, който актуализира VBO, е доста грозен. Виждал съм нещо като:

  • съхранявайте 4-те четворни координати в масив.
  • създайте плаващ буфер, поставете ги там
  • манипулиране на буфера.
  • изпрати буфера до VBO

Изглежда ми доста скъпо. И не разбирам как мога да изтрия определен запис (ако обект се премести извън екрана и т.н.), нито как мога да манипулирам определен запис (субект се премести). И ако трябва да актуализирам VBO по този начин при всяко повикване за рендиране, каква е печалбата в производителността? По-скоро ми прилича на загуба...

Също така, как мога да следя "дълбочината" на полученото изображение. Правя 2d, но с "дълбочина" имам предвид реда на изобразяване, напр. като се уверите, че обект2 се изобразява върху обект1. Може би различен VBO за всяка дълбочина? Или трябва да използвам z-координатата за това и да балирам неща с дълбочина. Последният няма ли да даде изпълнителски хит?

Има и фактор 2d. Имам голямо уважение към 3d, но искам да използвам 2d и да се възползвам от факта, че на теория би трябвало да доведе до по-добра производителност. От това, което събрах обаче, това не изглежда да е така. В opengl 3+ Изглежда, че за да изобразя 2d неща, трябва първо да ги преведа в 3d, тъй като това са процесите в хардуера. Изглежда ми странно, тъй като крайният резултат на екрана е 2d. Има ли начин да се заобиколи това и да се спести на GPU работата на 2d -> 3d -> 2d?

С други думи, как мога ефективно да променя това:

class main{

void main(){
while(true){
Renderer.bind();
//call render in all gameObjects
Renderer.flush();
}
}
}

class GameObject{

private float X1, X2, Y1, Y2;
private TexureCoordinate tex;

render(float dt){
//update X1, X2...
Renderer.render(tex.getX1(), tex.getX2()... X1, X2 ...);
}

}

class Renderer{

//called once
void bind(Texture texture){
    texture.bind();
    glBegin(GL_TRIANGLES)

}

//called "nr of visable objects" times
void render(texX1, texX2, texY1, texY2, quadX1, quadX2, quadY1, quadY2){

glTexCoo2d(texX1, texY1)
....
etc.
....
}

void flush(){
glEnd();
}
}

В нещо, което използва модерен openGl?


person Jake    schedule 19.04.2015    source източник


Отговори (1)


Първото и най-важно ключово прозрение е, че върховете не са само позиция. Върхът е целият кортеж от атрибути, използвани за предварително задаване на извиквания за изчертаване в незабавен режим, преди извикване на glVertex. Ако промените само един от атрибутите, ще получите много различен връх.

Нека се отдръпнем от VBO за момент, за да премахнем целия glBuffer[Sub]Data от пътя и да разгледаме обикновените стари върхови масиви от страна на клиента (приблизително толкова дълго, колкото незабавния режим).

Да кажем, че имате два масива от позиции, които имат абсолютно еднакво оформление, но с различни стойности:

GLfloat quad_pos_a[2][4] = {
  {1,2}, {2,2}, {2,3}, {1,3}
};

GLfloat quad_pos_b[2][4] = {
  {5,5}, {10,5}, {10,20}, {5,20}
};

Освен техните стойности, тяхното оформление е идентично: четири последователни атрибута от 2 елемента. Това тривиално позволява да се използва общ координатен масив на текстурата, съответстващ на оформлението на тези два квадрата:

GLfloat quad_texc[2][4] = {
  {0,0},{1,0},{1,1},{0,1}
};

Мисля, че трябва да е очевидно за вас как да използвате извикванията на незабавен режим, за да рисувате quad_pos_a и quad_pos_b споделяне quad_texc. Ако не е очевидно, сега е моментът да го изясните. Този отговор е търпелив и ще изчака, докато приключите...


МЕЖДУНАРОДНО


… тъй като поставянето на геометрични данни в масиви е толкова очевидно нещо, че не е лесно, OpenGL доста скоро въвежда концепция, наречена върхови масиви: Можете да кажете на OpenGL откъде да вземе данните за върха и след това просто да кажете това е или колко върхове има за начертаване, или кои върхове да изберете от масивите, дадени в списък с индекси.

Използването на VAs изглежда така:

glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);

glVertexPointer(
  2 /* = number of elements per attribute */,
  GL_FLOAT /* type of attribute elements */,
  0 /* = the byte distance between attributes OR zero if tightly packed */,
  quad_pos_a );
glTexCoordPointer(
  2 /* = number of elements per attribute */,
  GL_FLOAT /* type of attribute elements */,
  0 /* = the byte distance between attributes OR zero if tightly packed */,
  quad_texc );

glDrawArrays(
  GL_QUADS /* what to draw */,
  0 /* which index to start with */,
  4 /* how many vertices to process*/ );

или ако просто искате да начертаете триъгълник с 0-ти, 1-ви и 3-ти връх:

GLushort indices[] = {0,1,3};
glDrawElements(
  GL_TRIANGLES /* what */,
  3 /* how many */,
  GL_UNSIGNED_SHORT /* type of index elements */,
  indices );

Сега ключовата разлика между обикновените стари масиви от върхове и VBO е, че VBO поставят данните в OpenGL – това е всичко. Ако сте разбрали VA, вие разбирате VBO. Въпреки това, за разлика от VA, вие не можете да промените съдържанието на VBOs толкова лесно. Разликата с шейдърите е, че видът атрибут вече не е предварително дефиниран. Вместо това има общи атрибути на върха, зададени с glEnableVertexAttribArray (вместо glEnableClientState) и glVertexAttribPointer.

И така, как да спестите режийните разходи за качване на актуализираните данни? Е, зависи какво смятате за скъпо: данните трябва да преминат към GPU в крайна сметка. Така че опаковането му в обединен буфер за прехвърляне на данни за качване вероятно е от полза, тъй като спестява режийни разходи за повикване за разделяне на всяко извикване на glVertex.

person datenwolf    schedule 19.04.2015
comment
Това е наистина хубаво пояснение, благодаря. Само няколко допълнителни въпроса: - person Jake; 19.04.2015
comment
Как да променя ефективно своя VBO. Предполагам, че трябва да използвам GL_STREAM_DRAW? Ако използвам glBufferSubData, как да разбера кои Vertecies са заменени и кои не? Как да изтрия целия shabang и да кача напълно ново съдържание? - person Jake; 19.04.2015
comment
Ами дълбочината. Четворките ще бъдат ли изтеглени в същия ред, в който съм ги поставил във VBO? - person Jake; 19.04.2015
comment
@Jake: Можете напълно да замените VBO просто като извикате glBufferData с новите данни; имайте предвид, че това няма да повлияе отрицателно на текущите операции по изобразяване; ако OpenGL все още се нуждае от някои данни, съдържащи се преди това във VBO, ще се запази скрито копие (това се нарича осиротяване). Примитивите се изобразяват в реда, определен от извикването за изтегляне. С glDrawArrays в реда, в който данните за върха са във VBO, с glDrawElements в реда на индексите в индексния буфер. - person datenwolf; 19.04.2015