Трепанация триангуляции
Привет. Раз глобус не идет в сову, возьмемся за более плоские штуки.
Когда шейдерами хотят рисовать плоские фигуры или вообще всё остальное, то в первую очередь пытаются избавиться от конвееееера видеокарты. Тут все просто: если у нас два дэ платформер и на экране всего-лишь куча спрайтов, то, как бы это обидно не звучало, логично понакидать на экран всякие билборды или понарисовать много квадов (четырехугольников) из двух треугольников, а затем текстурировать их спрайтами из атласа. Но можно вообще без треугольников.
glDrawArrays( GL_TRIANGLES, 0, 3 );
Эта абракадабра в глубинах вашего платформера означает, что вы повелеваете нарисовать много треугольников (GL_TRIANGLES), начиная с вершины номер 0, всего 3 вершины, итого один треугольник. В туториалах перед этой строчкой обычно идут загрузки массивов координат и прочие сяськи-масяськи, ахалай-махалай. Мы обойдемся без этого. Будем рисовать единственный несуществующий треугольник. Во весь экран.
Получив команду свыше, ГПУ делает вид что у нас действительно есть треугольник, у которого есть три вершины с номерами 0, 1 и 2. Это произойдет в геометрическом шейдере. Нас это не интересует, пусть как-нибудь обходится без нас, а результат нас устраивает.
Конвейер крутится и дальше по ходу нас ждет вертексный шейдер. Здесь мы можем сделать что-нибудь с координатами вершин нашего несуществующего треугольника. Например, их можно скорректировать. В нашем же случае, для начала их нужно посчитать. Ведь до сих пор их даже не существовало.
Как настоящий художник я стащил картинку:
Там где Clip Space — это ваш экран, а на все остальные части треугольника нам всё равно (их все равно не видно). В этом месте олдскульщики и творцы софтрендеров крякнут или даже квакнут: что нельзя же так впустую расходовать производительность ГПУ, ведь там тратится время, чтобы проверить границы экрана и отрезать лишнее, а затем полученный квадрат все равно автоматически разобьется на два или больше треугольников. Дудки! Это было давно, а сейчас все нормально. ГПУ умный и ленивый там, где нада. И лишнего ничего не произойдет. Там вообще ничего не произойдет: нарисуются только те пиксели, которые попадают в экран, а на остальные никто даже не посмотрит. Смотрим в шейдер:
void main() { float x = -1.0 + float((gl_VertexID & 1) << 2); float y = -1.0 + float((gl_VertexID & 2) << 1); vec2 texCoord = vec2( (x + 1.0) * 0.5,
(y + 1.0) * 0.5 ); gl_Position = vec4(x, y, 0, 1);
}
Что здесь происходит: по номеру вершин треугольника (gl_VertexID), наш шейдер определяет их координаты (x, y). Всё. Ну, почти все...
Кроме этого мы тут же создаем текстурные координаты (texCoord) для прямоугольной текстуры размером во весь экран. В нее мы будем рисовать... что нам нада, то и будем рисовать. Там по ходу конвейера как раз пикселявый и фрагментный шейдер идет. Можно понарисовать два дэ спрайтов, а можно три дэ рейтрейсингом сделать или даже SDF с 3д фракталами.
MrShoor: Специально нагрузил пиксельный посильнее, но по тесту разницы не было совсем.
А если разницы не было совсем, то какая разница как?
MrShoor: Поэтому особо нет смысла выпендриваться с одним треугольником.
Трюк с одним треугольником красиво выглядит в статье и производительность не ухудшает. В идеале я бы вообще хотел избавиться от треугольников, поэтому здесь это выглядит уместным.
Вообще, я стараюсь изложить причины и мотивации разработчиков, а не очередную — «как написать шейдер».
MrShoor: а вот цитата:
А вот еще:
- «No, there won't be any overlapping rendered pixels. But there will be overlapping fragment shaders along the main diagonal. Fragment shaders run in blocks of neighboring samples, and along the main diagonal, shaders will be run twice. The results may ultimately be discarded, but the shader will be executed multiple times for those samples.»
- Triangulation
- «2 Triangle Quad Pixel Shader : 100% performance
1 Full screen Triangle Pixel Shader : 108% performance
Compute Shader : 108% performance (more sensitive to sampling patterns than PS)» link
Кому верить?
- 03 июля 2018, 00:09
- 09
Как же это всё костыльно.
а - В код необходимо тащить умирающий огл, немалая такая либа со многими странностями и зависимостями.
б - Забивать гвозди микроскопом, сложнейший конвеер 3д растеризации юзать для 2д.
в - Ебаться с матрицами, с шейдерами, с векторами, с тем что тут треугольник вместо логично ожидаемого прямоугольника. Тысячи флагов огл: сглаживание, формат текстур, прочего, прочего. Настроить их чтобы 2д выглядел прилично.
А 2д софтрендер: простой прямоугольный массив пикселей. Записал в пиксель цвет - пиксель загорелся на экране этим цветом. Вот это просто!
Напомню, что в юнити только спустя года три после релиза появилась возможность из коробки рисовать 2д пиксельарт. А до этого даже там требовалась такая вот пляска флагов и настроек для 2д.
Извращенцы!
Шейдер-мейдер, это понятно, а какой толк-то это дает? В циферках, плиз. Мы тут не в абстракции (сову), нам игры надо делать.
Совушка такая пушистая, потому что после водных процедур сушится.
Это очень хороший комментарий
Ангри бёрдс
Хочу крякнуть, или даже квакнуть. Конечно у меня нет самых самых свежих данных, но есть данные за 2015 год. В 2015-ом GPU по прежнему клипают треугольник, и треугольник из статьи превратится в 2 треугольника. Поэтому особо нет смысла выпендриваться с одним треугольником.
Вот мой пруф: https://developer.nvidia.com/content/life-triangle-nvidias-logical-pipeline
а вот цитата:
Я когда первый раз с таким трюком столкнулся (не помню уже где, в крайэнджине помоему?), я даже не знал кому верить, пейперу от NVidia или коду в движке, и запилил синтетический тест. Специально нагрузил пиксельный посильнее, но по тесту разницы не было совсем.
А если разницы не было совсем, то какая разница как?
Трюк с одним треугольником красиво выглядит в статье и производительность не ухудшает. В идеале я бы вообще хотел избавиться от треугольников, поэтому здесь это выглядит уместным.
Вообще, я стараюсь изложить причины и мотивации разработчиков, а не очередную - "как написать шейдер".
Ну я преверженец старого подхода:
glDrawArrays( GL_TRIANGLE_STRIP, 0, 4 );
И в шейдере как-то так:
const vec2 QuadCoord[4] = vec2[](vec2(-1.0,-1.0), vec2(-1.0,1.0), vec2(1.0,-1.0), vec2(1.0,1.0));
out vec2 TexCoord;
void main() {
gl_Position = vec4(QuadCoord[gl_VertexID], 0.0, 1.0);
TexCoord = QuadCoord[gl_VertexID]*vec2(0.5, 0.5) + vec2(0.5, 0.5);
}
Пункт 1 верный. Вдоль диагонали некоторые блоки будут считаться дважды
Пункт 2 верный. Тут тоже все правильно
Пункт 3 спорный. Ну то есть я делал следующий тест. Я брал вьюпорт 64*64 размером (чтобы блоки вдоль главной диагонали занимали больший процент от общего количества блоков). В пиксельном шейдере делал что-то в духе:
#define ITERATIONS_COUNT 100500
in vec4 param;
void main {
vec4 p = param;
gl_FragColor = vec4(0.0);
for(int i = 0; i < ITERATIONS_COUNT; i++) {
gl_FragColor += p*p;
p *= 0.9;
}
}
Подгонял ITERATIONS_COUNT так, чтобы FPS на моем железе был в районе 100, а потом для этого фрагментного шейдера менял вершинный шейдер на один треугольник vs два треугольника. И разнцы не было, вообще. Короче я не знаю, на каком железе и что именно измеряли в этой статье, но один треугольник на NVidia не дает никакого профита. Что и было ожидаемо, потому что этот один треугольник клипается вьюпортом и превращается в два.