Эффективная система частиц в javascript? (В WebGL)

Я пытаюсь написать программу, которая делает некоторые основные моделирования физики гравитации на частицах. Первоначально я написал программу, используя стандартную графику Javascript (с 2d-контекстом), и таким образом я мог получить около 25 fps с 10000 частицами. Я переписал инструмент в WebGL, потому что я предполагал, что таким образом смогу получить лучшие результаты. Я также использую библиотеку glMatrix для векторной математики. Однако с этой реализацией я получаю только около 15fps с 10000 частицами.

в настоящее время я студент EECS, и у меня было разумное количество опыта программирования, но никогда с графикой, и я мало знаю о том, как оптимизировать код Javascript. Я многого не понимаю в том, как работают WebGL и Javascript. Какие ключевые компоненты влияют на производительность при использовании этих технологий? Есть ли более эффективная структура данных для управления моими частицами (я просто использую простой массив)? Какое объяснение может быть для исполнения отказаться от использования WebGL? Может быть, задержки между GPU и Javascript?

любые предложения, объяснения или помощь в целом были бы весьма признательны.

Я постараюсь включить только критические области моего кода для справки.

вот мой код настройки:

gl = null;
try {
    // Try to grab the standard context. If it fails, fallback to experimental.
    gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
    gl.viewportWidth = canvas.width;
    gl.viewportHeight = canvas.height;
}
catch(e) {}

if(gl){
        gl.clearColor(0.0,0.0,0.0,1.0);
        gl.clearDepth(1.0);                 // Clear everything
        gl.enable(gl.DEPTH_TEST);           // Enable depth testing
        gl.depthFunc(gl.LEQUAL);            // Near things obscure far things

        // Initialize the shaders; this is where all the lighting for the
        // vertices and so forth is established.

        initShaders();

        // Here's where we call the routine that builds all the objects
        // we'll be drawing.

        initBuffers();
    }else{
        alert("WebGL unable to initialize");
    }

    /* Initialize actors */
    for(var i=0;i<NUM_SQS;i++){
        sqs.push(new Square(canvas.width*Math.random(),canvas.height*Math.random(),1,1));            
    }

    /* Begin animation loop by referencing the drawFrame() method */
    gl.bindBuffer(gl.ARRAY_BUFFER, squareVerticesBuffer);
    gl.vertexAttribPointer(vertexPositionAttribute, 2, gl.FLOAT, false, 0, 0);
    requestAnimationFrame(drawFrame,canvas);

цикл розыгрыша:

function drawFrame(){
    // Clear the canvas before we start drawing on it.
    gl.clear(gl.COLOR_BUFFER_BIT);

    //mvTranslate([-0.0,0.0,-6.0]);
    for(var i=0;i<NUM_SQS;i++){
        sqs[i].accelerate();
        /* Translate current buffer (?) */
        gl.uniform2fv(translationLocation,sqs[i].posVec);
        /* Draw current buffer (?) */;
        gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
    }
    window.requestAnimationFrame(drawFrame, canvas);
}

вот класс, который Square наследует от:

function PhysicsObject(startX,startY,size,mass){
    /* Class instances */
    this.posVec = vec2.fromValues(startX,startY);
    this.velVec = vec2.fromValues(0.0,0.0);
    this.accelVec = vec2.fromValues(0.0,0.0);
    this.mass = mass;
    this.size = size;

    this.accelerate = function(){
            var r2 = vec2.sqrDist(GRAV_VEC,this.posVec)+EARTH_RADIUS;
            var dirVec = vec2.create();
            vec2.set(this.accelVec,
                G_CONST_X/r2,
                G_CONST_Y/r2
            );

        /* Make dirVec unit vector in direction of gravitational acceleration */
        vec2.sub(dirVec,GRAV_VEC,this.posVec)
        vec2.normalize(dirVec,dirVec)
        /* Point acceleration vector in direction of dirVec */
        vec2.multiply(this.accelVec,this.accelVec,dirVec);//vec2.fromValues(canvas.width*.5-this.posVec[0],canvas.height *.5-this.posVec[1])));

        vec2.add(this.velVec,this.velVec,this.accelVec);
        vec2.add(this.posVec,this.posVec,this.velVec);
    };
}

это шейдеры, которые я использую:

 <script id="shader-fs" type="x-shader/x-fragment">
        void main(void) {
        gl_FragColor = vec4(0.7, 0.8, 1.0, 1.0);
        }
    </script>

    <!-- Vertex shader program -->

    <script id="shader-vs" type="x-shader/x-vertex">
        attribute vec2 a_position;

        uniform vec2 u_resolution;

        uniform vec2 u_translation;

        void main() {
        // Add in the translation.
        vec2 position = a_position + u_translation;
        // convert the rectangle from pixels to 0.0 to 1.0
        vec2 zeroToOne = position / u_resolution;

        // convert from 0->1 to 0->2
        vec2 zeroToTwo = zeroToOne * 2.0;

        // convert from 0->2 to -1->+1 (clipspace)
        vec2 clipSpace = zeroToTwo - 1.0;

        gl_Position = vec4(clipSpace*vec2(1,-1), 0, 1);
        }
    </script>

I извинись за то, что это так долго. Опять же, любые предложения или толчки в правильном направлении были бы огромными.

2 ответов


вы никогда не должны рисовать примитивы индивидуально. Рисовать их все сразу, когда это возможно. Создайте ArrayBuffer, содержащий позицию и другие необходимые атрибуты всех частиц, а затем нарисуйте весь буфер одним вызовом gl.создаются все трехмерные объекты. Я не могу дать точные инструкции, потому что я на мобильном телефоне, но поиск vbo, чередующихся массивов и частиц в opengl, безусловно, поможет вам найти примеры и другие полезные ресурсы.

Я рендеринг 5M статических точек таким образом с 10фпс. Динамические точки будут медленнее, так как вам придется постоянно отправлять обновленные данные на графическую карту, но это будет быстрее, чем 15fps для 10000 точек.

Edit:

возможно, вы захотите использовать gl.Точка вместо TRIANGLE_STRIP. Таким образом, вам нужно только указать позицию и и gl_PointSize(в шейдере вершин) для каждого квадрата. глоссарий.Точки отображаются в виде квадратов!

вы можете взглянуть на источник этих двух облаков точек визуализация:

  • https://github.com/asalga/XB-PointStream
  • http://potree.org/wp/download/ (по мне, следующие файлы могут помочь вам: WeightedPointSizeMaterial.js, pointSize.vs, colouredPoint.файловая система )

  • это зависит от того, что вы пытаетесь сделать. Когда вы говорите "гравитация", вы имеете в виду какое-то физическое моделирование с столкновениями или вы просто имеете в виду velocity += acceleration; position += velocity?

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

    https://www.khronos.org/registry/webgl/sdk/demos/google/particles/index.html

    эти частицы выполняются полностью в шейдере. Единственный вход после установки -time. Каждая "частица" состоит из 4 вершин. Каждая вершина содержит

    • local_position (для блока quad)
    • texture_coord
    • продолжительность жизни
    • starting_position
    • starting_time
    • скорость
    • ускорение
    • end_size
    • ориентация (quaterion)
    • цвета множитель

    заданное время вы можете вычислить местное время частиц (время с начала)

     local_time = time - starting_time;
    

    затем вы можете вычислить позицию с

     base_position = start_position + 
                     velocity * local_time + 
                     acceleration * local_time * local_time;
    

    это ускорение * время^2. Затем вы добавляете local_position к этой base_position, чтобы получить позицию, необходимую для рендеринга quad.

    вы также можете вычислить от 0 до 1 lerp за время жизни частицы

     lerp = local_time / lifetime;
    

    это дает вам значение, которое вы можете использовать для lerp всех других значений

     size = mix(start_size, end_size, lerp);
    

    если частица размером 0 если она вне это жизнь

     if (lerp < 0.0 || lerp > 1.0) {
       size = 0.0;
     }
    

    это заставит GPU ничего не рисовать.

    используя текстуру рампы (текстуру пикселя 1xN), вы можете легко менять цвета частиц с течением времени.

     color = texture2D(rampTexture, vec4(lerp, 0.5));
    

    etc...

    если вы выполнить шейдеры вы увидите другие вещи, которые аналогично обрабатываются, включая вращение частицы (что было бы сложнее с точечными спрайтами), анимацию по текстуре для кадров, выполнение как 2D, так и 3D ориентированные частицы. 2D частицы отлично подходят для дыма, выхлопных газов, огня, взрывов. 3D-частицы хороши для ряби, возможно, следов шин, и могут быть объединены с 2D-частицами для наземных затяжек, чтобы скрыть некоторые из z-вопросов 2D только частиц. так далее..

    есть также примеры одного выстрела (взрывы, затяжки), а также тропы. Нажмите " P " для затяжки. Держись, чтобы увидеть след.

    AFAIK это довольно эффективные частицы в том, что JavaScript делает почти ничего.