OpenGL: проекция координат пространства вида на НЦД, результаты которых, по-видимому, находятся за пределами диапазона [-1,1]

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

Мое понимание метода заключается в следующем. Коэффициент окклюзии окружающей среды определяется по пробам, взятым из полушария, выровненным по нормали данного фрагмента. Чтобы определить, влияет ли образец на коэффициент внешней окклюзии, я должен проверить глубину образца в пространстве обзора по текстуре глубины пространства обзора (включенной в нижний левый угол изображений этого сообщения). Итак, я знаю, какие координаты для выборки из текстуры глубины, я должен преобразовать координаты семпла из пространства просмотра в нормализованные координаты устройства (в диапазоне [-1,1]), а затем в диапазон [0,1], поэтому текстура глубины эффективно "отображается" в окне просмотра.

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

Поскольку у меня нет стабильного отладчика шейдеров, отладка, которую я могу сделать, ограничена тем, что я могу отобразить на экране. Следующее изображение создается с помощью следующего кода, причем ndcs - это координаты нормализованного устройства для данного образца:

if (ndcs.x > 1.0f || ndcs.y > 1.0f || ndcs.x < -1.0f || ndcs.y < -1.0f)
{
  gl_FragColor = vec4(1.0f, 0.0f, 0.0f, 1.0f);
}
else
{
  gl_FragColor = vec4(vec3(1.0f), 1.0f);
}

Я ожидаю, что изображение будет полностью белым (точнее, битами, для которых я использую этот шейдер), однако, похоже, предполагается, что создаваемые мной NDC находятся вне диапазона [-1,1], который я верить должно быть неверным. Это также не является постоянной областью экрана, как вы можете видеть на следующем изображении, где камера находится очень близко к поверхности:

Я никогда не использовал эту процедуру, чтобы получить NDC раньше, поэтому я уверен, что моя логика где-то не так. Я скачал демонстрационный код, поставляемый с руководством, и не вижу, чем отличается мой код. Я также искал в Интернете (в том числе на этом самом сайте), и мне кажется, что я не могу найти кого-то с такими же симптомами, как у меня.

Вот соответствующий код из моих шейдеров:

Верт Шейдер:

v_eye_space_position = u_mvpMatrix * a_position;
v_world_space_normal = normalize(u_rotationMatrix * a_normal);
v_eye_space_normal = normalize(u_mvpMatrix * a_normal);
gl_Position = v_eye_space_position;

Frag Shader:

// --- SSAO Testing ---
// Convert from the noise texture back to [-1,1] range
// We want the noise texture to tile across the screen.
vec3 kernel_rotation = (texture2D(s_noise, gl_FragCoord.xy * u_noise_scale) * 2.0f - 1.0f).xyz;
vec3 eye_space_tangent = normalize(kernel_rotation - v_eye_space_normal.xyz * dot(kernel_rotation, v_eye_space_normal.xyz));
vec3 eye_space_bitangent = cross(v_eye_space_normal.xyz, eye_space_tangent);
mat3 tbn = mat3(eye_space_tangent, eye_space_bitangent, v_eye_space_normal);

float ambient_occlusion = 0.0f;
const float hemisphere_radius = 0.05f;

for (int i=0; i<16; i++)
{
  vec3 kernel_sample = tbn * u_ssao_kernel[i];
  kernel_sample = kernel_sample * hemisphere_radius + v_eye_space_position.xyz;

  // Project the sample position into screen space.
  vec4 offset = vec4(kernel_sample, 1.0f);
  offset = u_projection_matrix * offset;
  offset.xy /= offset.w;
  vec4 ndcs = offset;
  offset.xy = 1.0f - (offset.xy * 0.5f + 0.5f);

  // Find the depth at this sample point.
  float sample_depth = texture2D(s_camera_depth, offset.xy).z;

  // Check if the sample point is occluded.
  float range_check = 0.0f;

  float linear_eye_space_position = (v_eye_space_position.z - u_near_plane)/(u_far_plane - u_near_plane);

  // Range check.
  if (abs(linear_eye_space_position - sample_depth) < hemisphere_radius)
  {
    range_check = 1.0f;
  }

  float linear_kernel_sample_depth = (kernel_sample.z - u_near_plane)/(u_far_plane - u_near_plane);
  if (sample_depth <= linear_kernel_sample_depth)
  {
    ambient_occlusion += 1.0f * range_check;
  }
}

// Average and invert the ambient occlusion.
ambient_occlusion = 1.0f - (ambient_occlusion/16.0f);

Я смотрел на каждый элемент по отдельности и не вижу проблемы с ними.

  • Я вставил в проекцию собственную позицию фрагмента в пространстве просмотра (вместо позиции в пространстве образца) и получил те же результаты.
  • Матрица проекции - это матрица перспективы, которую я использую для преобразования вершин модели (я построил матрицу MVP в моем вершинном шейдере, чтобы убедиться, что матрица проекции в такте достигает программы шейдера, и это так).
  • Сама операция проецирования - это не то, что я делал раньше, но я читал статьи в Интернете и вопросы от людей с проблемами проецирования, и я не вижу, что я делаю неправильно.

Таким образом, я могу только заключить, что в моем понимании перспективной проекции должно быть что-то фундаментальное, что неверно, но я просто не могу понять, что именно. Если бы кто-нибудь из вас, ребята, мог пролить некоторый свет на проблему или дальнейшие пути для проверки, я был бы очень признателен. Если есть какая-то полезная информация, которую я пропустил, или что-то, что я могу уточнить, пожалуйста, дайте мне знать.

1 ответ

Решение

Из вашего вершинного шейдера:

v_eye_space_position = u_mvpMatrix * a_position;
[...]

pMatrix * a_normal); gl_Position = v_eye_space_position;

Отсюда видно, что v_eye_space_position не позиция глазного пространства вершины, а позиция пространства клипа, которая также должна быть назначена gl_Position, Имя вашей униформы матрицы также предполагает, что это проекция ModelView -Matrix.

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

Таким образом, правильный код будет:

v_eye_space_position = u_mvMatrix * a_position;
[...]
gl_Position = u_projection_matrix * v_eye_space_position;

Теперь вы можете применить проекцию к v_eye_space_position снова во фрагментном шейдере. Но мой вопрос: зачем делать это снова? Если вы хотите работать в пространстве экрана, gl_FragCoord уже в пространстве окна. Вам нужно только добавить несколько переходов, чтобы перейти из пространства окна в NDC, просто инвертировав преобразование области просмотра (и диапазона глубины).

Другие вопросы по тегам