Я пишу небольшой проект с поддержкой Oculus Rfit и использую точечные спрайты для рендеринга моих частиц. Я рассчитываю размер точечных спрайтов в пикселях, исходя из их расстояния от «камеры» в вершинном шейдере. При рисовании на экране по умолчанию (не на Rift) размер работает идеально, но когда я переключаюсь на Rift, я замечаю следующие явления:
Частицы на левом глазу маленькие и очень быстро уменьшаются в размерах.
Частицы на правом глазу огромны и не меняются в размерах.
Скриншоты:
Разлом отключен: http://i.imgur.com/EoguiF0.jpg
Rift включен: http://i.imgur.com/4IcBCf0.jpg
Вот вершинный шейдер:
#version 120
attribute vec3 attr_pos;
attribute vec4 attr_col;
attribute float attr_size;
uniform mat4 st_view_matrix;
uniform mat4 st_proj_matrix;
uniform vec2 st_screen_size;
varying vec4 color;
void main()
{
vec4 local_pos = vec4(attr_pos, 1.0);
vec4 eye_pos = st_view_matrix * local_pos;
vec4 proj_vector = st_proj_matrix * vec4(attr_size, 0.0, eye_pos.z, eye_pos.w);
float proj_size = st_screen_size.x * proj_vector.x / proj_vector.w;
gl_PointSize = proj_size;
gl_Position = st_proj_matrix * eye_pos;
color = attr_col;
}
Форма st_screen_size — это размер области просмотра. Так как при рендеринге на Rift я использую один frambuffer (1 половина для каждого глаза), значение st_screen_size должно быть (frabuffer_width / 2.0, frambuffer_height).
Вот мой розыгрыш:
/*Drawing starts with a call to ovrHmd_BeginFrame.*/
ovrHmd_BeginFrame(game::engine::ovr_data.hmd, 0);
/*Start drawing onto our texture render target.*/
game::engine::ovr_rtarg.bind();
glClearColor(0, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//Update the particles.
game::engine::nuc_manager->update(dt, get_msec());
/*for each eye... */
for(unsigned int i = 0 ; i < 2 ; i++){
ovrEyeType eye = game::engine::ovr_data.hmd->EyeRenderOrder[i];
/* -- Viewport Transformation --
* Setup the viewport to draw in the left half of the framebuffer when we're
* rendering the left eye's view (0, 0, width / 2.0, height), and in the right half
* of the frambuffer for the right eye's view (width / 2.0, 0, width / 2.0, height)
*/
int fb_width = game::engine::ovr_rtarg.get_fb_width();
int fb_height = game::engine::ovr_rtarg.get_fb_height();
glViewport(eye == ovrEye_Left ? 0 : fb_width / 2, 0, fb_width / 2, fb_height);
//Send the Viewport size to the shader.
set_unistate("st_screen_size", Vector2(fb_width /2.0 , fb_height));
/* -- Projection Transformation --
* We'll just have to use the projection matrix supplied but he oculus SDK for this eye.
* Note that libovr matrices are the transpose of what OpenGL expects, so we have to
* send the transposed ovr projection matrix to the shader.*/
proj = ovrMatrix4f_Projection(game::engine::ovr_data.hmd->DefaultEyeFov[eye], 0.01, 40000.0, true);
Matrix4x4 proj_mat;
memcpy(proj_mat[0], proj.M, 16 * sizeof(float));
//Send the Projection matrix to the shader.
set_projection_matrix(proj_mat);
/* --view/camera tranformation --
* We need to construct a view matrix by combining all the information provided by
* the oculus SDK, about the position and orientation of the user's head in the world.
*/
pose[eye] = ovrHmd_GetHmdPosePerEye(game::engine::ovr_data.hmd, eye);
camera->reset_identity();
camera->translate(Vector3(game::engine::ovr_data.eye_rdesc[eye].HmdToEyeViewOffset.x,
game::engine::ovr_data.eye_rdesc[eye].HmdToEyeViewOffset.y,
game::engine::ovr_data.eye_rdesc[eye].HmdToEyeViewOffset.z));
/*Construct a quaternion from the data of the oculus SDK and rotate the view matrix*/
Quaternion q = Quaternion(pose[eye].Orientation.w, pose[eye].Orientation.x,
pose[eye].Orientation.y, pose[eye].Orientation.z);
camera->rotate(q.inverse().normalized());/*Translate the view matrix with the positional tracking*/
camera->translate(Vector3(-pose[eye].Position.x, -pose[eye].Position.y, -pose[eye].Position.z));
camera->rotate(Vector3(0, 1, 0), DEG_TO_RAD(theta));
//Send the View matrix to the shader.
set_view_matrix(*camera);game::engine::active_stage->render(STAGE_RENDER_SKY | STAGE_RENDER_SCENES | STAGE_RENDER_GUNS |
STAGE_RENDER_ENEMIES | STAGE_RENDER_PROJECTILES, get_msec());
game::engine::nuc_manager->render(RENDER_PSYS, get_msec());
game::engine::active_stage->render(STAGE_RENDER_COCKPIT, get_msec());
}
/* After drawing both eyes into the texture render target, revert to drawing directly to the display,
* and we call ovrHmd_EndFrame, to let the Oculus SDK draw both images properly, compensated for lens
* distortion and chromatic abberation onto the HMD screen.
*/
game::engine::ovr_rtarg.unbind();
ovrHmd_EndFrame(game::engine::ovr_data.hmd, pose, &game::engine::ovr_data.fb_ovr_tex[0].Texture);
Эта проблема беспокоила меня уже много дней … и я чувствую, что зашел в тупик. Я мог бы просто использовать щиты с билбордами ….. но я не хочу так легко сдаваться 🙂 Плюс точечные спрайты быстрее.
Есть ли математика позади затухания размера точек на основе изменения расстояния при рендеринге на Rift?
Разве что-то не учитывается?
Математика не является (пока, по крайней мере) моей сильной стороной. 🙂 Любое понимание будет с благодарностью!
PS: Если потребуется дополнительная информация о размещенном мною коде, я с радостью предоставлю его.
vec4 local_pos = vec4(attr_pos, 1.0); vec4 eye_pos = st_view_matrix * local_pos; vec4 proj_voxel = st_proj_matrix * vec4(attr_size, 0.0, eye_pos.z, eye_pos.w); float proj_size = st_screen_size.x * proj_voxel.x / proj_voxel.w;
gl_PointSize = proj_size;
По сути, вы сначала трансформируете свою точку в пространство просмотра, чтобы выяснить ее координату Z в пространстве просмотра (расстояние от зрителя), а затем вы строите вектор, выровненный по оси X с желаемым размером частиц, и проецируете его, чтобы увидеть, как много пикселей он покрывает, когда проецируется и трансформируется в область просмотра (sortof).
Это вполне разумно, если предположить, что ваша матрица проекции симметрична. Это предположение неверно при работе с разломом. Я нарисовал диаграмму, чтобы лучше проиллюстрировать проблему:
http://i.imgur.com/vm33JUN.jpg
Как вы можете видеть, когда усеченная фигура асимметрична, что, безусловно, имеет место с рифтом, использование расстояния проецируемой точки от центра экрана даст вам дико отличающиеся значения для каждого глаза и, безусловно, отличается от «правильного» "Размер проекции, который вы ищете.
Вместо этого вы должны спроектировать две точки, скажем, (0, 0, z, 1) И (attr_size, 0, z, 1), используя один и тот же метод, и вычислить их разницу в пространстве экрана (после проекции, разделения перспективы). и область просмотра).
Я могу порекомендовать несколько методов устранения неполадок.
Во-первых, измените ваш код, чтобы автоматически писать скриншот самого первого отображаемого кадра (или, если это не удобно, просто установите статический логический тип, который заставляет основное рисование пропускать все, кроме вызовов начала / конца кадра после первого прогона. SDK иногда может испортить конечный автомат OpenGL, и если это происходит, то, что вы видите, вероятно, является результатом работы, выполненной в ovrHmd_EndFrame (), которая испортила ваш рендеринг при последующих проходах через цикл рендеринга. Код (после рендеринга частиц) может непреднамеренно восстановить желаемое состояние, поэтому второй визуализированный глаз выглядит нормально.
Во-вторых, я бы попытался разбить визуализированные глаза на два кадровых буфера. Возможно, в вашем коде есть что-то, что неожиданно делает что-то с кадровым буфером в целом (например, очищает буфер глубины), что вызывает разницу. Вы могли бы работать вторым глазом с другим состоянием для кадрового буфера, чем вы ожидаете, основываясь на вашем коде верхнего уровня. Разбиение на два фрейм-буфера скажет вам, если это так.
Другой тест, который вы можете запустить, похожий на второй, заключается в рефакторинге кода рендеринга, чтобы вы могли проходить через этот цикл с использованием кадрового буфера по умолчанию и без вызовов Oculus SDK. Это еще один метод, который поможет вам определить, есть ли проблема в SDK или в вашем собственном коде рендеринга. Просто визуализируйте два взгляда на две половины экрана, а не на две половины закадрового кадрового буфера.