Изменение размера спрайтов в зависимости от расстояния до камеры
Я пишу клон Wolfenstein 3D с использованием только ядра OpenGL 3.3 для университета, и у меня возникла небольшая проблема со спрайтами, а именно - заставить их корректно масштабироваться в зависимости от расстояния.
Из того, что я могу сказать, предыдущие версии OGL фактически сделали бы это для вас, но эта функциональность была удалена, и все мои попытки переопределить ее привели к полному провалу.
Моя текущая реализация проходима на расстоянии, не слишком потертая на среднем расстоянии и странная на близком расстоянии.
Основная проблема (я думаю) заключается в том, что я не понимаю, какую математику я использую.
Целевой размер спрайта немного больше, чем область просмотра, поэтому он должен "уходить из картинки", как только вы доберетесь до него, но это не так. Он становится меньше, и это меня сильно смущает.
Я записал небольшое видео об этом, на случай, если слов не хватит. (Шахта справа)
Может кто-нибудь направить меня туда, где я иду не так, и объяснить, почему?
Код:
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 ответа
Прежде всего, я не очень понимаю, почему вы используете 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
,
РЕДАКТИРОВАТЬ: Конечно, вы также должны иметь в виду ограничения точечных спрайтов. Точечный спрайт обрезается в зависимости от его центрального положения. Это означает, что когда его центр выходит за пределы экрана, весь спрайт исчезает. С большими спрайтами (как в вашем случае, я думаю) это действительно может быть проблемой.
Поэтому я бы предпочел использовать простые текстурированные четырехугольники. Таким образом, вы обойдете всю проблему затухания, поскольку квады просто трансформируются, как и любой другой трехмерный объект. Вам нужно только осуществить вращение в сторону средства просмотра, что можно сделать либо на процессоре, либо в вершинном шейдере.
Основываясь на ответе Кристиана Рау (последнее редактирование), я реализовал геометрический шейдер, который создает рекламный щит в ViewSpace, который, похоже, решает все мои проблемы:
Вот шейдеры: (Обратите внимание, что я исправил проблему с выравниванием, которая требовала, чтобы оригинальный шейдер добавил 1 к x)
Вершинный шейдер
#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;
}
Я не думаю, что вы хотите основывать вычисление расстояния в вашем вершинном шейдере на проектируемой позиции. Вместо этого просто рассчитайте положение относительно вашего вида, то есть используйте матрицу вида модели вместо проекции модель-вид-проекция.
Подумайте об этом так: в проецируемом пространстве, когда объект приближается к вам, его расстояние в горизонтальном и вертикальном направлениях становится преувеличенным. Это видно по движению ламп от центра к верхней части экрана, когда вы приближаетесь к ним. Это преувеличение этих измерений приведет к увеличению расстояния, когда вы действительно приблизитесь, поэтому вы видите, как объект сжимается.
По крайней мере, в OpenGL ES 2.0 существует ограничение максимального размера для gl_PointSize, налагаемое реализацией OpenGL. Вы можете запросить размер с ALIASED_POINT_SIZE_RANGE.