Как лучше всего использовать буферы вершин при рендеринге различных объектов с помощью Stage3D

У меня есть массив простых 2D-объектов, в основном состоящих из двух треугольников. Хотя объекты довольно просты, каждый из них рисуется с использованием трафаретных операций, поэтому для каждого объекта потребуется собственный вызов drawTriangles().

Как лучше всего хранить и обрабатывать эти объекты и их буферы вершин и индексов?

Я могу придумать несколько разных способов сделать это:

  1. При запуске создайте один большой буфер вершин и индексов и добавьте в него каждый объект, например:

    public function initialize():void{
        for each(object in objectList){
            vertexData.push(object.vertices);
            indexData.push(triangle1, triangle2);
        }
    
        indices  = context3D.createIndexBuffer( numTriangles * 3 );
        vertices = context3D.createVertexBuffer( numVertices, dataPerVertex );
        indices.uploadFromVector( indexData, 0, numIndices );
        vertices.uploadFromVector( vertexData, 0, numVertices );
    }
    

    Во время рендеринга просматривайте все объекты, проверяйте, какие из них видны на экране, и обновляйте их вершины. Затем повторно загрузите весь буфер вершин. Затем выполните цикл по видимым объектам и нарисуйте треугольники каждого объекта с отдельными вызовами drawTriangles()

    public function onRender():void{
        for each(object in objectList){
            if(object.visibleOnScreen)
                object.updateVertexData();
        }
    
        vertices.uploadFromVector( vertexData, 0, numVertices );
    
        for each(object in objectsOnScreen){
            drawTriangles(indices, object.vertexDataOffset, object.numTriangles);
        }
    }
    
  2. Вы также можете сделать что-то похожее на номер 1, за исключением повторной загрузки данных вершин каждого объекта только при необходимости:

    public function onRender():void{
        for each(object in objectList){
            if(object.visibleOnScreen){
                if(object.hasChanged)
                    vertices.uploadFromVector( vertexData, object.vertexDataOffset, object.numTriangles );
                drawTriangles(indices, object.vertexDataOffset, object.numTriangles);
            }
        }
    }
    
  3. Вы также можете создать новый буфер вершин, состоящий только из видимых объектов в каждом кадре:

    public function onRender():void{
        for each(object in objectList){
            if(object.visibleOnScreen){
                vertexData.push(object.vertices);
                indexData.push(triangle1, triangle2);
            }
        }
    
        indices  = context3D.createIndexBuffer( numTriangles * 3 );
        vertices = context3D.createVertexBuffer( numVertices, dataPerVertex );
        indices.uploadFromVector( indexData, 0, numIndices );
        vertices.uploadFromVector( vertexData, 0, numVertices );
    
        for each(object in objectsOnScreen){
            drawTriangles(indices, object.vertexDataOffset, object.numTriangles);
        }
    }
    
  4. Другой вариант — создать отдельные буферы вершин для каждого объекта:

    public function initialize():void{
        for each(object in objectList){
            object.indices  = context3D.createIndexBuffer( object.numTriangles * 3 );
            object.vertices = context3D.createVertexBuffer( object.numVertices, dataPerVertex );
        }
    }
    
    public function onRender():void{
        for each(object in objectList){
            if(object.visibleOnScreen){
                if(object.hasChanged)
                    object.vertices.uploadFromVector( object.vertexData, 0, object.numTriangles );
                drawTriangles(indices, 0, object.numTriangles);
            }
        }
    }
    

Я могу себе представить, что вариант 1 будет медленным, потому что весь буфер вершин должен загружаться в каждом кадре.
Вариант 2 имеет то преимущество, что ему нужно загружать только данные вершин, которые изменились, но могут пострадать от нескольких вызовов uploadFromVector.

ОБНОВИТЬ

Я просмотрел исходный код фреймворка Starling, в частности класс QuadBatch:
http://gamua.com/starling/
https://github.com/PrimaryFeather/Starling-Framework

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


person cmann    schedule 08.04.2012    source источник
comment
Я не уверен, что stage3D действительно предназначен для такого использования. Я не очень разбираюсь в этом во flash, но как разработчик C++/OpenGL могу сказать вам, что повторная загрузка всех ваших данных вершин в GPU при каждом вызове рендеринга — плохая практика, даже если вы используете молниеносно скомпилированную сборку. (из С++). Я также могу сказать вам, что во многих версиях Android есть проблемы с этим, в частности, он фактически вызывает GC при каждом отдельном вызове, что приводит к полной остановке приложения. Просто мои 2 цента. :)   -  person    schedule 08.04.2012
comment
В качестве предложения загляните в библиотеку Away3D 4.0 (на основе stage3D). Это открытый исходный код, поэтому вы можете точно увидеть, как они решают эту и другие проблемы, с которыми вы можете столкнуться. Также вы можете просто использовать эту библиотеку вместо того, чтобы изобретать велосипед и писать свой собственный код.   -  person    schedule 08.04.2012
comment
Так что если это так, то лучшим вариантом будет создание отдельных буферов вершин для каждого объекта и вместо этого использование матрицы преобразования и шейдеров для перемещения и вращения объектов.   -  person cmann    schedule 08.04.2012
comment
Если ваши данные меняются каждый кадр, нет ничего плохого в том, чтобы загружать данные каждый кадр.   -  person NateS    schedule 01.06.2014
comment
Если вы пишете свой собственный 2D-движок, соответствующий вашим потребностям, я бы не стал присматриваться к исходному коду Starling, который явно не оптимизирован в том, как он обновляет и повторно загружает свои буферы вершин.   -  person jauboux    schedule 19.06.2014


Ответы (1)


Вам нужно загружать свои данные для каждого кадра, несмотря ни на что, потому что Stage3D должен знать, что и где рисовать. В конечном итоге оптимизация начинается с сокращения вызовов отрисовки. Вы можете ускорить загрузку данных, используя byteArray вместо данных, которые не изменились, и используя Vector для данных, которые изменились. Загрузка вектора происходит медленнее, но установка векторных данных происходит быстрее, загрузка байтового массива выполняется быстрее, но установка данных байтового массива выполняется медленнее (поэтому используйте только для кэшированных данных). Вам также не нужно каждый раз создавать буферы индексов и вершин. Создайте их один раз с удобной длиной и создавайте новые, только если эта длина станет слишком маленькой. Все это должно хорошо все ускорить, но количество вызовов отрисовки все равно все замедлит (после 25+ вызовов отрисовки вы должны начать видеть обратную сторону).

person BotMaster    schedule 26.06.2014