Изменение размера спрайтов в зависимости от расстояния до камеры - PullRequest
7 голосов
/ 22 декабря 2011

Я пишу клон Wolfenstein 3D с использованием только ядра OpenGL 3.3 для университета, и у меня возникла небольшая проблема со спрайтами, а именно - заставить их правильно масштабироваться в зависимости от расстояния.

Из того, что я могу сказать, предыдущие версии OGL фактически сделали бы это для вас, но эта функциональность была удалена, и все мои попытки переопределить ее привели к полному провалу.

Моя текущая реализация проходима на расстоянии, не слишком потертая на среднем расстоянии и странная на близком расстоянии.

Основная проблема (я думаю) заключается в том, что я не понимаю, какую математику я использую.
Целевой размер спрайта немного больше, чем область просмотра, поэтому он должен «уходить из картинки», как только вы доберетесь до него, но это не так. Он становится меньше, и это меня сильно смущает.
Я записал небольшое видео об этом, на случай, если слов не хватит. (Шахта справа)

Expected Result Actual Result

Может кто-нибудь направить меня туда, где я иду не так, и объяснить, почему?

Код:
C ++

// setup
glPointParameteri(GL_POINT_SPRITE_COORD_ORIGIN, GL_LOWER_LEFT);
glEnable(GL_PROGRAM_POINT_SIZE);

// Drawing
glUseProgram(StaticsProg);
glBindVertexArray(statixVAO);
glUniformMatrix4fv(uStatixMVP, 1, GL_FALSE, glm::value_ptr(MVP));
glDrawArrays(GL_POINTS, 0, iNumSprites);

Вершинный шейдер

#version 330 core

layout(location = 0) in vec2 pos;
layout(location = 1) in int spriteNum_;

flat out int spriteNum;

uniform mat4 MVP;

const float constAtten  = 0.9;
const float linearAtten = 0.6;
const float quadAtten   = 0.001;

void main() {
    spriteNum = spriteNum_;
    gl_Position = MVP * vec4(pos.x + 1, pos.y, 0.5, 1); // Note: I have fiddled the MVP so that z is height rather than depth, since this is how I learned my vectors.
    float dist = distance(gl_Position, vec4(0,0,0,1));
    float attn = constAtten / ((1 + linearAtten * dist) * (1 + quadAtten * dist * dist));
    gl_PointSize = 768.0 * attn;
}

Фрагмент шейдера

#version 330 core

flat in int spriteNum;

out vec4 color;

uniform sampler2DArray Sprites;

void main() {
    color = texture(Sprites, vec3(gl_PointCoord.s, gl_PointCoord.t, spriteNum));
    if (color.a < 0.2)
        discard;
}

Ответы [ 4 ]

13 голосов
/ 23 декабря 2011

Прежде всего, я не очень понимаю, почему вы используете pos.x + 1.

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

uniform mat4 MV;       //modelview matrix

vec3 eyePos = MV * vec4(pos.x, pos.y, 0.5, 1); 

Кроме того, я не совсем понимаю ваше вычисление ослабления. На данный момент более высокое значение constAtten означает меньшее затухание. Почему бы вам просто не использовать модель, которую использовали устаревшие параметры точки OpenGL:

float dist = length(eyePos);   //since the distance to (0,0,0) is just the length
float attn = inversesqrt(constAtten + linearAtten*dist + quadAtten*dist*dist);

РЕДАКТИРОВАТЬ: Но в целом я думаю, что эта модель затухания не является хорошим способом, потому что часто вы просто хотите, чтобы спрайт сохранял размер своего пространства объектов, что вам приходится довольно сильно возиться с коэффициентами затухания, чтобы достичь того, что я думаю.

Лучший способ - ввести размер пространства объекта и просто вычислить размер экранного пространства в пикселях (что на самом деле является gl_PointSize) на основе этого с использованием текущего вида и настройки проекции:

uniform mat4 MV;                //modelview matrix
uniform mat4 P;                 //projection matrix
uniform float spriteWidth;      //object space width of sprite (maybe an per-vertex in)
uniform float screenWidth;      //screen width in pixels

vec4 eyePos = MV * vec4(pos.x, pos.y, 0.5, 1); 
vec4 projCorner = P * vec4(0.5*spriteWidth, 0.5*spriteWidth, eyePos.z, eyePos.w);
gl_PointSize = screenWidth * projCorner.x / projCorner.w;
gl_Position = P * eyePos;

Таким образом, спрайт всегда получает размер, который он будет иметь при рендеринге в виде текстурированного четырехугольника с шириной spriteWidth.

РЕДАКТИРОВАТЬ: Конечно, вы также должны иметь в виду ограничения точечных спрайтов. Точечный спрайт обрезается в зависимости от его центрального положения. Это означает, что когда его центр выходит за пределы экрана, весь спрайт исчезает. С большими спрайтами (как в вашем случае, я думаю) это действительно может быть проблемой.

Поэтому я бы лучше предложил вам использовать простые текстурированные четырехугольники. Таким образом, вы обойдете всю эту проблему ослабления, поскольку квады просто трансформируются, как и любой другой трехмерный объект. Вам нужно только осуществить поворот к средству просмотра, что может быть сделано либо на процессоре, либо в вершинном шейдере.

5 голосов
/ 24 декабря 2011

Основываясь на ответе Кристиана Рау (последнее редактирование), я реализовал геометрический шейдер, который создает рекламный щит в ViewSpace, который, похоже, решает все мои проблемы:

Expected Result Actual Result

Вот шейдеры: (Обратите внимание, что я исправил проблему с выравниванием, которая требовала, чтобы исходный шейдер добавил 1 к x)

Vertex Shader

#version 330 core

layout (location = 0) in vec4 gridPos;
layout (location = 1) in int  spriteNum_in;

flat out int spriteNum;

// simple pass-thru to the geometry generator
void main() {
    gl_Position = gridPos;
    spriteNum = spriteNum_in;
}

Геометрический шейдер

#version 330 core

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

flat in int spriteNum[];

smooth out vec3 stp;

uniform mat4 Projection;
uniform mat4 View;

void main() {
    // Put us into screen space. 
    vec4 pos = View * gl_in[0].gl_Position;

    int snum = spriteNum[0];

    // Bottom left corner
    gl_Position = pos;
    gl_Position.x += 0.5;
    gl_Position = Projection * gl_Position;
    stp = vec3(0, 0, snum);
    EmitVertex();

    // Top left corner
    gl_Position = pos;
    gl_Position.x += 0.5;
    gl_Position.y += 1;
    gl_Position = Projection * gl_Position;
    stp = vec3(0, 1, snum);
    EmitVertex();

    // Bottom right corner
    gl_Position = pos;
    gl_Position.x -= 0.5;
    gl_Position = Projection * gl_Position;
    stp = vec3(1, 0, snum);
    EmitVertex();

    // Top right corner
    gl_Position = pos;
    gl_Position.x -= 0.5;
    gl_Position.y += 1;
    gl_Position = Projection * gl_Position;
    stp = vec3(1, 1, snum);
    EmitVertex();

    EndPrimitive();
}

Фрагментный шейдер

#version 330 core

smooth in vec3 stp;

out vec4 colour;

uniform sampler2DArray Sprites;

void main() {
    colour = texture(Sprites, stp);
    if (colour.a < 0.2)
        discard;
}
1 голос
/ 22 декабря 2011

Я не думаю, что вы хотите основывать вычисление расстояния в вашем вершинном шейдере на проекции . Вместо этого просто вычислите положение относительно вашего вида, то есть используйте матрицу вида модели вместо проекции модель-вид.

Подумайте об этом так: в проецируемом пространстве, когда объект приближается к вам, его расстояние в горизонтальном и вертикальном направлениях преувеличивается. Это видно по движению ламп от центра к верхней части экрана по мере приближения к ним. Это преувеличение этих измерений приведет к увеличению расстояния, когда вы действительно приблизитесь, вот почему вы видите, как объект сжимается.

0 голосов
/ 14 декабря 2012

По крайней мере в OpenGL ES 2.0 существует ограничение максимального размера для gl_PointSize, налагаемое реализацией OpenGL. Вы можете запросить размер с ALIASED_POINT_SIZE_RANGE.

...