Программирование игр, создание игрового движка, OpenGL, DirectX, физика, форум
GameDev.ru / Программирование / Статьи / Frustum Culling (4 стр)

Frustum Culling (4 стр)

Автор:

Кулинг на GPU.

GPU как раз предназначен для выполнения одинаковых операций для разных данных. У gpu гораздо больше число потоковых процессоров (тысячи) чем у СPU (2-8 в основном).
Но кулить на gpu не всегда удобно:
    1. во-первых, это подразумевает определенную архитектуру движка.
    2. есть неприятный момент в том что нужно послать дип на выполнение на стороне CPU. Для этого нужно знать количество сгенерированных примитивов (видимых объектов в нашем случае) на стороне GPU, получить фидбэк с GPU / обратную связь. Для этого существуют специальные запросы.

Небольшая проблема в том, что если получать фидбэк с этого же кадра на котором произошла отрисовка, то получаем GPU-stall. Связанный стем что нужно дождаться выполнения запроса. Это очень плохо может сказаться на общей производительности.
Если читать фидбэк с предыдущего кадра, то можно получить артефакты/баги.
Полным решением является использование DrawIndirect команд и подготовка информации о дипе на стороне GPU. Но это только начиная с DirectX11 и Opengl 4.

Реализация кулинга на GPU выглядит следующим образом:
    1. упаковываем все инстанс-данные объектов в вершинный буфер. По сути 1 вершинка = 1 объект. Количество атрибутов у вершины равно количеству инстанс данных на один объект.
    2. включаем transform feedback. Посылаем на рендер подготовленный буфер. Все вершины перенаправляются в отдельный буфер.
    3. в вершинном шейдере определяем видимость указанного объекта.
    4. в геометрическом шейдерере отбрасываем/убиваем вершинку, если инстанс не видимый во врустуме.
    5. таким образом у нас сформировался буфер с инстанс-данными только видимых объектов в сцене.
    6. но теперь нам нужно считать общее количество видимых объектов, чтобы сформировать дип на стороне СPU. В данном случае, делаем это через transform feedback с предыдущего кадра. Исключительно для примера, чтобы не нагромождать код.


void do_gpu_culling()
{
  culling_shader.bind();

  int cur_frame = frame_index % 2;
  int prev_frame = (frame_index + 1) % 2;

//включить transform feedback (перенаправление результатов в указанный буфер)
//и query (запрос, который вернет нам количество сгенерированных элементов)
  //выключаем растеризацию, она не нужна. Все сгенерированные шейдером данные будут
  //перенаправлены в отдельный буфер через механизм TransformFeedback
  glEnable(GL_RASTERIZER_DISCARD);
  glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, dips_texture_buffer);
  glBeginQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, num_visible_instances_query[cur_frame]);
  glBeginTransformFeedback(GL_POINTS);

//рендерим точки, которые в шейдере интерпретируем как объекты
//напомню, что каждая вершинка хранит данные своего инстанса
  glBindVertexArray(all_instances_data_vao);
  glDrawArrays(GL_POINTS, 0, MAX_SCENE_OBJECTS);
  glBindVertexArray(0);

//выключаем
  glEndTransformFeedback(); //фидбэк, перенаправление результатов

  //запрос, собственно все что было сгенерированно в шейдерах
  //между вызовами glBeginQuery и glEndQuery будет нашим результатом
  glEndQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN);
  glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, 0);
  glDisable(GL_RASTERIZER_DISCARD);

//get feedback from prev frame
  num_visible_instances = 0;
  glGetQueryObjectiv(num_visible_instances_query[prev_frame], GL_QUERY_RESULT, &num_visible_instances);

//next frame
  frame_index++;
}


Вершинный шейдер для кулинга из шага 3:
#version 330 core

//атрибуты инстанса
in vec4 s_attribute_0; //Параметры окружающей сферы: позиция инстанса и радиус шарика
in vec4 s_attribute_1; //цвет инстанса, но здесь он нам не нужен

out vec4 instance_data1;
out vec4 instance_data2;
out int visible;


uniform mat4 ModelViewProjectionMatrix;
uniform vec4 frustum_planes[6];


int InstanceCloudReduction()
{
//тест сфера-фрустум
  bool inside = true;
  //нужно обработать все плоскости фрустума
  for (int i = 0; i < 6; i++)
  {
    //расстояние от центра сферы до плоскости
    //если находимся за плоскостью и расстояние больше радиуса сферы - объект находится вне фрустума
    if (dot(frustum_planes[i].xyz, s_attribute_0.xyz) + frustum_planes[i].w <= -s_attribute_0.w)
      inside = false;
  }
    return inside ? 1 : 0;
}


void main()
{
//читаем данные инстансов, перенаправляем их дальше, в геометрический шейдер
  instance_data1 = s_attribute_0;
  instance_data2 = s_attribute_1;

//видимость
  visible = InstanceCloudReduction();
  
  gl_Position = ModelViewProjectionMatrix * vec4(s_attribute_0.xyz,1);
}


Геометрический шейдер для кулинга из шага 4:
#version 400 core

layout (points) in;
layout (points, max_vertices = 1) out;

//входные данные с вершинного шейдера
in vec4 instance_data1[];
in vec4 instance_data2[];
in int visible[];

//transform feedback берет эти 2 атрибута и записывает их в отдельный массив
//это делается специальным кодом, в котором указываются имена атрибутов,
//которые будут записаны/перенаправлены в буфер
out vec4 output_instance_data1;
out vec4 output_instance_data2;

void main(  )
{
  //если примитив не видим - игнорируем его, т.е. убиваем вершинку
  //visible мы посчитали в вершинном шейдере, здесь только проверяем
  if (visible[0] == 1)
  {
    //просто передать данные дальше
    output_instance_data1 = instance_data1[0];
    output_instance_data2 = instance_data2[0];
    
    gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
    EmitVertex();
    EndPrimitive();
  }
}

Время всего кадра при кулинге на GPU 1.19 ms. Тестировалось на Radeon R9 380. Примерно такое же время всего кадра при использовании много поточного SSE CPU кулинга сфер. Т.е. GPU кулинг такой же быстрый как и самый соптимизированный CPU вариант.

Страницы: 1 2 3 4 5 Следующая »

8 февраля 2017

#Frustum Culling, #multithreading, #SSE


Обновление: 3 мая 2017

2001—2018 © GameDev.ru — Разработка игр