Подтвердить что ты не робот

Точечные спрайты для системы частиц

Являются ли точечные спрайты наилучшим выбором для создания системы частиц?

Являются ли точечные спрайты в новых версиях OpenGL и драйверах новейших видеокарт? Или я должен делать это с помощью vbo и glsl?

4b9b3361

Ответ 1

Точечные спрайты действительно хорошо подходят для систем частиц. Но они не имеют ничего общего с VBOs и GLSL, что означает, что они являются полностью ортогональной функцией. Независимо от того, используете ли вы точечные спрайты или нет, вам всегда нужно использовать VBOs для загрузки геометрии, будь то просто точки, готовые спрайты или что-то еще, и вам всегда приходится ставить эту геометрию через набор шейдеров (в современном OpenGL конечно).

То, что упомянутые точечные спрайты очень хорошо поддерживаются в современном OpenGL, просто не так автоматически, как со старым подходом с фиксированной функцией. То, что не поддерживается, - это функции затухания точек, которые позволяют масштабировать размер точки на основе расстояния до камеры, вы должны сделать это вручную внутри вершинного шейдера. Точно так же вы должны сделать текстурирование точки вручную в соответствующем фрагментаторном шейдере, используя специальную входную переменную gl_PointCoord (которая говорит, где в [0,1] - квадрат всей точки текущий фрагмент), Например, базовый конвейер для спрайтов может выглядеть так:

...
glPointSize(whatever);              //specify size of points in pixels
glDrawArrays(GL_POINTS, 0, count);  //draw the points

vertex shader:

uniform mat4 mvp;

layout(location = 0) in vec4 position;

void main()
{
    gl_Position = mvp * position;
}

фрагмент шейдера:

uniform sampler2D tex;

layout(location = 0) out vec4 color;

void main()
{
    color = texture(tex, gl_PointCoord);
}

И это все. Конечно, эти шейдеры просто делают основной рисунок текстурированных спрайтов, но являются отправной точкой для дальнейших функций. Например, чтобы вычислить размер спрайта на основе его расстояния до камеры (возможно, чтобы дать ему фиксированный размер пространства в мире), вы должны glEnable(GL_PROGRAM_POINT_SIZE) и записать в специальную выходную переменную gl_PointSize в вершинном шейдере:

uniform mat4 modelview;
uniform mat4 projection;
uniform vec2 screenSize;
uniform float spriteSize;

layout(location = 0) in vec4 position;

void main()
{
    vec4 eyePos = modelview * position;
    vec4 projVoxel = projection * vec4(spriteSize,spriteSize,eyePos.z,eyePos.w);
    vec2 projSize = screenSize * projVoxel.xy / projVoxel.w;
    gl_PointSize = 0.25 * (projSize.x+projSize.y);
    gl_Position = projection * eyePos;
}

Это приведет к тому, что все точечные спрайты будут иметь одинаковый размер пространства в мире (и, следовательно, другой размер экрана в пикселях).


Но точечные спрайты, все еще отлично поддерживаемые в современных OpenGL, имеют свои недостатки. Одним из самых больших недостатков является их обтравочное поведение. Точки обрезаются по их центральной координате (потому что отсечение выполняется до растеризации и, таким образом, до того, как точка становится "увеличенной" ). Поэтому, если центр точки находится за пределами экрана, остальная часть, которая все еще может попасть в область просмотра, не отображается, поэтому в худшем случае, когда точка на полпути выходит из экрана, она внезапно исчезнет. Однако это только примечание (или аннуирование), если точечные спрайты слишком велики. Если они очень маленькие частицы, которые не покрывают гораздо больше, чем несколько пикселей в любом случае, то это не будет большой проблемой, и я бы по-прежнему рассматривал системы частиц в каноническом варианте использования для точечных спрайтов, просто не делайте использовать их для больших рекламных щитов.

Но если это проблема, то современный OpenGL предлагает множество других способов реализации точечных спрайтов, кроме наивного способа предварительного построения всех спрайтов как отдельных квадратиков на процессоре. Вы все равно можете отображать их как буфер, заполненный точками (и, следовательно, таким образом, что они, скорее всего, выйдут из вашего движка частиц на базе графического процессора). Чтобы фактически генерировать квад-геометрию, вы можете использовать геометрический шейдер, который позволяет генерировать квад из одной точки. Сначала вы выполняете только преобразование модели в вершинном шейдере:

uniform mat4 modelview;

layout(location = 0) in vec4 position;

void main()
{
    gl_Position = modelview * position;
}

Затем геометрический шейдер выполняет остальную часть работы. Он объединяет положение точки с четырьмя углами общего [0,1] -квад и завершает преобразование в пространство клипа:

const vec2 corners[4] = { 
    vec2(0.0, 1.0), vec2(0.0, 0.0), vec2(1.0, 1.0), vec2(1.0, 0.0) };

layout(points) in;
layout(triangle_strip, max_vertices = 4) out;

uniform mat4 projection;
uniform float spriteSize;

out vec2 texCoord;

void main()
{
    for(int i=0; i<4; ++i)
    {
        vec4 eyePos = gl_in[0].gl_Position;           //start with point position
        eyePos.xy += spriteSize * (corners[i] - vec2(0.5)); //add corner position
        gl_Position = projection * eyePos;             //complete transformation
        texCoord = corners[i];                         //use corner as texCoord
        EmitVertex();
    }
}

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


Или другая возможность (и, может быть, быстрее, поскольку я помню, как геометрические шейдеры имеют репутацию медленного), нужно использовать инстансный рендеринг. Таким образом, у вас есть дополнительный VBO, содержащий вершины только одного общего двумерного квадранта (т.е. [0,1] -quare) и ваш старый добрый VBO, содержащий только точки. То, что вы тогда делаете, это рисовать этот одиночный квад многократный (instanced), в то время как поиск позиций отдельных экземпляров из точки VBO:

glVertexAttribPointer(0, ...points...);
glVertexAttribPointer(1, ...quad...);
glVertexAttribDivisor(0, 1);            //advance only once per instance
...
glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, count);  //draw #count quads

И в вершинном шейдере вы затем собираете позицию по каждой точке с фактическим угловым/четырехпозиционным расположением (которое также является координатой текстуры этой вершины):

uniform mat4 modelview;
uniform mat4 projection;
uniform float spriteSize;

layout(location = 0) in vec4 position;
layout(location = 1) in vec2 corner;

out vec2 texCoord;

void main()
{
    vec4 eyePos = modelview * position;            //transform to eye-space
    eyePos.xy += spriteSize * (corner - vec2(0.5)); //add corner position
    gl_Position = projection * eyePos;             //complete transformation
    texCoord = corner;
}

Это достигается так же, как подход на основе геометрического шейдера, правильно обрезанные точечные спрайты с постоянным размером пространства в мире. Если вы действительно хотите сопоставить размер пикселя пространства экрана фактических точечных спрайтов, вам нужно добавить в него еще несколько вычислительных усилий. Но это остается как упражнение, и это будет вполне отражением трансформации от экрана к экрану "точка-точка".