Краткий курс компьютерной графики, аддендум: GLSL

  • Tutorial

Official translation (with a bit of polishing) is available here.




Очередная вводная статья для начинающих программировать графику реального времени


У меня когда-то возникла задача (быстро) визуализировать молекулы. Например, молекула может быть представлена просто как набор сфер навроде вот этого:



Конкретно этот вирус состоит из примерно трёх миллионов атомов. Вы можете скачать его модель на замечательном сайте rcsb.org.

Это отличный топик для обучения шейдерам.

Для начала я просто покажу, как вызывается OpenGL и как к нему линкуется наш код шейдеров.

OpenGL helloworld


Как обычно, я создал репозиторий для сопутствующего кода. В самом OpenGL нет нормального кроссплатформенного способа создать контекст для рендера, поэтому здесь я пользуюсь библиотекой GLUT, чтобы создать окно, хотя никакого взаимодействия с пользователем я толком не делаю. Заодно помимо GLUT для этого туториала нам понадобятся библиотеки GLU и GLEW.

Вот так выглядит простейшая программа, рисующая чайник:

Скрытый текст
#include <GL/glu.h>
#include <GL/glut.h>
#include <vector>
#include <cmath>

const int SCREEN_WIDTH  = 1024;
const int SCREEN_HEIGHT = 1024;
const float camera[]           = {.6,0,1};
const float light0_position[4] = {1,1,1,0};

void render_scene(void) {
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glLoadIdentity();
	gluLookAt(camera[0], camera[1], camera[2], 0,  0, 0, 0, 1, 0);
	glColor3f(.8, 0., 0.);
	glutSolidTeapot(.7);
	glutSwapBuffers();
}

void process_keys(unsigned char key, int x, int y) {
	if (27==key) {
		exit(0);
	}
}

void change_size(int w, int h) {
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glViewport(0, 0, w, h);
	glOrtho(-1,1,-1,1,-1,8);
	glMatrixMode(GL_MODELVIEW);
}

int main(int argc, char **argv) {
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
	glutInitWindowPosition(100,100);
	glutInitWindowSize(SCREEN_WIDTH, SCREEN_HEIGHT);
	glutCreateWindow("GLSL tutorial");
	glClearColor(0.0,0.0,1.0,1.0);

	glutDisplayFunc(render_scene);
	glutReshapeFunc(change_size);
	glutKeyboardFunc(process_keys);

	glEnable(GL_COLOR_MATERIAL);
	glEnable(GL_DEPTH_TEST);
	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);
	glLightfv(GL_LIGHT0, GL_POSITION, light0_position);

	glutMainLoop();
	return 0;
}



Давайте разбираться, причём начнём сразу с main().

  • Первая строчка просто инициализирует библиотеку, вторая говорит, что мы будем использовать двойной фреймбуффер, цвета и z-буфер.
  • Затем мы даём размеры, местоположение и заголовок окна и фоновый цвет, в данном случае синий.
  • Дальше начинаются интересные вещи: glutDisplayFunc, glutReshapeFunc и glutKeyboardFunc устанавливают коллбэки на наш код, который будет вызываться при событиях перерисовки экрана, изменения геометрии окна а также обработка клавиатуры.
  • Затем мы включаем некий набор чекбоксов, которые просто говорят, что да, у нас будет один источник освещения, что да, z-буфер надо использовать и тд.
  • Ну и финальный аккорд — вызов основного цикла обработки окна, покуда glutMainLoop работает, операционная система показывает наше окно.

Обработка клавиатуры у нас простейшая, я (несколько брутально) выхожу из программы при нажатии клавиши ESC. При изменении геометрии окна я говорю OpenGL, что проекция у нас по-прежнему ортогональная, и что он должен отобразить в полный размер окна квадрат с координатами [-1,1]x[-1,1].

Самое интересное у нас в функции render_scene().
  • Сначала стираем экран и соответствующий z-буфер
  • затем обнуляем матрицу ModelView и грузим в неё текущее положение камеры (в данном случае оно неизменно, поэтому можно было бы его определить в main())
  • Устанавливаем красный цвет
  • Рисуем чайник
  • переключаем экранные буферы


В итоге у нас должна получиться вот такая картинка:



GLSL helloworld


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

Картинка должна получиться вот такой:



Что именно добавилось в коде? Для начала добавились два новых файла: frag_shader.glsl и vert_shader.glsl, написанные не на C++, а на GLSL. Это код шейдеров, который будет скормлен непосредственно графической карте. А в main.cpp добавилась обвязка, которая говорит OpenGL, что нужно использовать эти шейдеры.

А именно, создаётся хэндлер prog_hdlr, и к нему линкуются предварительно прочитанные из текстовых файлов, а затем откомпилированные вершинный и фрагментный шейдеры.

Рисуем «молекулу» средствами стандартного OpenGL


Итак, мы научились вызывать OpenGL контекст и к нему линковать шейдеры. Давайте их отложим в сторонку и нарисуем с десяток тысяч случайно расположенных сфер. Я хочу сохранить код настолько простым, насколько возможно, и поэтому не буду грузить настоящую молекулу, хотя формат .pdb вполне себе текстовый и очень простой. Задача ставится следующим образом: давайте рисовать просто большое количество случайно расположенных сфер случайного цвета.

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

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

Вот такая картинка должна получиться:



Лично мне больно смотреть на эту картинку: пересечение двух сфер — это дуга окружности, а у нас тут что угодно, только не окружности. Это следствие того, что мы нарисовали каждую сферу с шестнадцатью параллелями и шестнадцатью меридианами, то есть, примерно пять сотен треугольников на каждую сферу. А помимо плохого качества картинки у нас ещё напрямую встаёт вопрос эффективности: если мы хотим нарисовать десять миллионов атомов, то нам нужно будет отослать пять миллиардов треугольников, что начинает уже больно бить по пропускной способности шины.

Могут ли нам помочь шейдеры?


Могут! Шейдеры — это не только изменение освещения, хотя изначально они задумывались именно для этого. Я хочу минимизировать перенос данных между CPU и GPU, поэтому буду отправлять только одну вершину на каждую сферу, которую нужно отрисовать.

Я пишу код под старый GLSL #120, т.к. мне необходимо, чтобы он исполнялся на очень древних машинах, новый GLSL имеет чуточку другой синтаксис, но общие идеи строго те же самые.

Вот код, рисующий те же сферы, но с использованием шейдеров.

Итак, в чём состоит идея?

Для начала, на стороне CPU мы отправляем одну вершину на каждую сферу, которую нужно отрисовать.
Если не писать никаких шейдеров, то мы получим вот такую картинку:

Скрытый текст


Далее в вершинном шейдере мы можем изменять gl_PointSize, это даст в итоге набор квадратов:

Скрытый текст

Обратите внимание, что фрагментный шейдер будет выполняться для каждого пикселя квадрата!

То есть, теперь всё совсем просто, мы считаем, насколько данный пиксель квадрата далёк от центра, если
он превышает радиус сферы, то мы вызываем discard:

Скрытый текст

Получили набор плоских конфетти, которые проходят через центр каждой сферы.

Что интересно, мы имеем право изменять глубину каждого фрагмента:

Скрытый текст


Осталось только посчитать освещение, вот такая картинка получится в итоге:



Сравните её с картинкой, где мы рисовали триангулированные сферы. Изображение существенно более аккуратное и существенно быстрее отрисовывается.

Теперь осталось добавить чтение .pdb файла, screen-space ambient occlusion и вы получите заглавную картинку этой статьи.




Для тех, кто хочет понять, как рисовать сферы при помощи шейдеров, но с перспективой, а не с glOrtho, есть прекрасная статья.
Поддержать автора
Поделиться публикацией
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 64

    0
    Пару лет назад пытался найти библиотеку для отображения и взаимодействия с молекулами в 3D, что бы можно было использовать как виджет в своих программах. GLmol позволяет отображать молекулы, но не позволяет выделять атомы. Может с тех пор что-то готовое появилось?
      +1
      А почему glut, а не freeglut, glfw или тому подобные вещи? Мне казалось, он уже мертв.
        0
        Я использую freeglut, конечно же. Хотя он тоже мёртв. Но мне просто контекст создать и он у меня установлен уже. Можно было бы какой-нибудь sfml, но мне удобно было использовать чайник и полигональную сферу из glut'a.
          +2
          Вообще, для обучения програмированию шейдеров, куда лучше подходят интерактивные glslsandbox.com или shadertoy.com чем платформозависимые полумёртвые фреймворки. К тому же эти сервисы вполне нормально работают на планшетах.
            +1
            Не очень понимаю, как мне это поможет нарисовать настоящую молекулу.
              0
              А причёт тут молекулы?
              Ваш пост называется «Краткий курс компьютерной графики, аддендум: GLSL»
              а вовсе не «Изображение молекулы средствами компьютерной графики».
        0
        Для тех, кто хочет понять, как рисовать сферы при помощи шейдеров, но с перспективой, а не с glOrtho, есть прекрасная статья.

        Правильно ли я понял, что там используют Point Sprites вместо того чтобы топтаться в экранных координатах? Я слабо представляю как эта цепь магических констант будет выглядеть в случае перспективы:

        vec2 ndc_current_pixel = ((2.0 * gl_FragCoord.xy) - (2.0 * viewport.xy)) / (viewport.zw) - 1;
        

        Или это возможно даже без расширений ARB? И есть ли какая-то разница между PointSprite и просто Point?

        В общем, самое таинственное осталось за кадром.
          0
          Мой код в случае перспективы работает без спрайтов, ровно так же в экранных координатах. И я не понял про таинственность, там же всё крайне примитивно, не сильно сложнее случая glOrtho…
          0
          А насколько сложно дергать какой-нибудь egl вместо всего этого дела?
            +6
            Возможно, я пропустил какой-то дисклеймер при чтении статьи, но замечу — для обучения лучше подходят статьи с использованием нового API, где нет матричных стеков и прочих старых функций. Интернет запружен примерами для новичков со старым API, что приводит к результату, когда начинающие в openGL вынуждены переучиваться на новый лад (сам в своё время попался на эту удочку).

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

            В любом случаи, автору спасибо. Статья достаточно интересная.
              +1
              Мне API не сильно интересен в принципе, см. мой предыдущий цикл статей, я там вообще всё писал руками с нуля без использования библиотек.

              Мне интересно показать, что возможно делать этими инструментами и как оно внутри устроено. О чём я и говорил раньше, я не учу как писать что-то под OpenGL, я показываю кишки.
                +2
                Я просто посмотрел на данную статью с точки зрения человека, который мог бы найти её при обучении openGL и воспринять как учебную конкретно по этому API.
                  0
                  Это понятно и ваш комментарий вполне уместен, спасибо. Действительно, я не очень был чёток в моём позиционировании.
                  Лично мне при чтении чужих статей интересны идеи, а не синтаксис, который нужно смотреть в справочнике. С точки зрения хороших практик программирования я отнюдь не являюсь эталоном.
              0
              Как у вас с производительностью на рендеринге 3-х миллионов сфер? Рисовать такое в immediate mode, это жесть.
              Следующим шагом должен быть переход на VBO, а дальше, чтобы не ограничивать себя непрозрачными сферами, можно пробовать визуализировать объемы, заданные, например, явным образом через 3D-текстуры, с рейкастингом в фрагментном шейдере.
                +1
                Я не собирался говорить про выжимание всех соков, я собирался показать, что шейдеры используются не только для отрисовки затенения, что было не совсем очевидно из моего предыдущего (краткого) курса. Моей целью было иметь как можно более простой и короткий код.

                Чтобы ответить на ваш вопрос, я спокойно отрисовываю десятки миллионов атомов без малейших тормозов вообще, FPS не считал, но отрисовка (и редактирование молекул) идёт в интерактивном режиме. Проблемы подобного подхода в том, что первая же запись в gl_FragDepth полностью отключает early-z culling.

                Поэтому я вдобавок к тому пользуюсь ещё hierarchical occlusion culling.
                +1
                Понимаю, что статья обучающая, но все же предупрежу новичков, что использование discard существенно замедляет работу фрагментного шейдера, как и ветвление ( if ). Это не значит, что нужно отказаться от discard и if, но следует быть осторожным.
                  +2
                  Ну, чтобы замечание было полнее, то нужно сказать, что это естественно для параллелизма данных. К слову, тут на ресурсе очень мало рассказано об opencl/cuda, наверное, имеет смысл об этом поговорить отдельно.
                    0
                    Ну, смотря как используется discard/clip, где-то можно заменить BS, а где-то без них не обойтись. Но да, больше всего страдают SGX-девайсы.

                    Ну и ветвление, если condition — статический (например параметр для шейдера) — ничего страшного. Ну и если еще используется branch-«режим» if-ов (не знаю, если ли в GLSL такое, я про HLSL) — тоже особо не теряется на производительности.

                    Ну а так да, все верно, к этим штукам осторожно нужно подходить :)
                      0
                      Погоди, разве дискард не просто-напросто прерывает работу текущего шейдера, а следовательно невидимые фрагменты выполняются только до проверки if(d2>r2), далее никаких вычислений и пиксель просто не перекрашивается и всё? Следовательно, фрагмент с discard заканчивает свою работу быстрее, чем фрагмент не попадающий в блок с дискардом.
                        0
                        Это многопоточное программирование, в рамках одной рабочей группы потоки, которые сделали дискард, обязаны дождаться тех, кто дискард не сделал. И часть GPU просто простаивает.
                          0
                          тогда То на ТО и выходит по-лбасе, но никак не медленней))
                          Погоди, а разве невозможно запустить следующие еще неотработанные фрагменты?
                          Например у меня 1000 ядер и около 500000 фрагментов, зачем GPU будет простаивать если все фрагменты могут работать независимо друг от друга? Фрагменты то эти соответствуют пикселям на экране. Все они — видимые — уже прошли Z-Test. Это вся рабочая группа с фрагментами должна ждать выполнения всей рабочей группы с вершинами, а затем должна ждать растеризацию и Z-Test и всю промежутоную хрень. Количества ядер на все пиксели ни в одной суперкрутой видюхе по-любасе не хватает, их всего то от силы 1000-5000. А пикселей — 1920x1080, а бывает и больше.
                          Фрагменты можно рассматривать как очередь задач, которую мы скармливаем пулу потоков.
                            0
                            Тысяча ядер не является независимой. Она разбивается на группы ядер, которые работают параллельно, но синхронно. Если внутри одной такой группы половина ядер зашла в if () {}, а половина не зашла — то эта самая вторая половина простаивает, покуда первая не выйдет из блока if.
                              0
                              Зачем так делать?
                              Значит, особенности архитектуры?
                              Инженерам из NVidia и AMD конечно виднее.

                              Но по-любому discard скорости не уменьшит, просто будет простой части вычислительной мощности и всё.
                                0
                                Не уменьшит скорости по сравнению с чем?
                                  0
                                  по сравнению с таким же шейдером но с отсутствующим оператором discard
                                    0
                                    Только «такой же, но без discard» не нарисует нужную картинку, поэтому некорректно об этом говорить…
                                      0
                                      Вы же сами начали говорить, что использование discard замедляет работу фрагментного шейдера. А теперь вы говорите про некорректность.
                                        0
                                        Я не говорил нигде про замедление работы, я говорил про простаивающие потоки. Мы имеем право говорить про замедление работы кода, если один код по сравнению с другим работает дольше, выдавая такой же результат. Иначе мы сравниваем компилятор с тетрисом.
                                          0
                                          я то говорил про замедление работы.

                                          "
                                          perfectdaemon25 марта 2015 в 05:51#
                                          Понимаю, что статья обучающая, но все же предупрежу новичков, что использование discard существенно замедляет работу фрагментного шейдера, как и ветвление ( if ). Это не значит, что нужно отказаться от discard и if, но следует быть осторожным.
                                          kovalexius25 марта 2015 в 21:38#↵↑
                                          Погоди, разве дискард не просто-напросто прерывает работу текущего шейдера, а следовательно
                                          невидимые фрагменты выполняются только до проверки if(d2>r2), далее никаких вычислений и пиксель
                                          просто не перекрашивается и всё? Следовательно, фрагмент с discard заканчивает свою работу быстрее,
                                          чем фрагмент не попадающий в блок с дискардом.
                                          "

                                          Но даже несмотря на то что вы меня убедили насчет простаивания кода, всё равно discard может даже слегка увеличить скорость окрашивания всех фрагментов в пределах статистической погрешности выполнения одного и того же куска кода (тот что под else). Ибо один и тот же кусок кода запускаемый многократно слегка колеблется по времени выполнения. Например, у нас было N одновременно выполняющихся потоков с «кодом под else». Время выполнения всех потоков равно max времени выполнения из всех N. Если мы уменьшим N, возьмем M < N, то возникает вероятность что и max из всех M будет меньше чем max из N
                                            0
                                            Опять же, я не говорил, что использовать if и discard нельзя. Например, в этой статье я использую и то, и то. Надо только их использовать, имея в виду простой ресурсов. Если есть выигрыш в простоте и, как следствие, быстрой работе шейдеров, то можно пожертвовать частью вычислительных ресурсов. Универсального рецепта нет.
                                              0
                                              Понятно.
                                              Ну может быть инженеры GPU в будущем выкинут рабочие группы или изобретут способ перераспределения рабочих групп на разных этапах конвейера, если в настоящее время этого еще нет.
                                  0
                                  Шейдер, использующий discard, обычно отключает механизмы типа EarlyZ на всех чипах.
                                  Z-Test в 3D конвеере выполняется после выполнения шейдера. EarlyZ позволяет определить видимость пикселя заранее, не вызывая шейдер если не нужно.
                                    0
                                    Если я не ошибаюсь, то discard не отключает earlyz. А вот запись в gl_FragDepth отключает.
                                      0
                                      Альфатест, Discard и экспорт Z отключают
                                        0
                                        Не везде и далеко не всегда. Только троганье z отключает его всегда.
                                          0
                                          Эти операции концептуально идентичны. От вычислений в шейдере зависит будет пиксель записан или нет, поэтому шейдер нельзя не выполнить.
                                          В общем, хотите верьте, а хотите нет =)
                                            0
                                            Нет, не идентичны.

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

                                            Я не отрицаю существование архитектур, где использование дискарда отключаёт раннее отсечение по z, но это далеко не все архитектуры.
                                              0
                                              Z можно тоже консервативный выдавать. Аналогично описанному вами случаю.
                                              www.opengl.org/registry/specs/ARB/conservative_depth.txt

                                              В моём посте не случайно использовано слово «обычно».
                                              Есть нюансы, но они зависят от конкретного железа.

                                              amd-dev.wpengine.netdna-cdn.com/wordpress/media/2013/05/GCNPerformanceTweets.pdf

                                              GCN Performance Tip 18: clip, discard, alpha-to-mask and writing to oMask or
                                              oDepth disable Early-Z when depth writes are on.
                                              Notes: Early-Z is the per-pixel depth rejection test that occurs before the pixel shader
                                              stage in the GPU. For performance reasons it should be enabled as often as possible. If
                                              depth writes are disabled then doing clip, discard, alpha-to-mask and writing to oMask
                                              will not disable Early-Z.

                                              GCN Performance Tip 19: Writing to a UAV or oDepth disables both Early-Z and
                                              Hi-Z unless conservative oDepth is used.
                                              Notes: Declaring [earlydepthstencil] in front of a pixel shader guarantees the depth test
                                              happens early even if the shader writes to a UAV.
                                      0
                                      Ипануться можно.
                                      Читаешь спецификацию OpenGL — действительно depth test выполняется после fragment shader И управляется glEnable/glDisable (GL_DEPTH_TEST)
                                      А Early вроде как автоматический и действительно не произойдет, если писать в gl_FragDepth или дискард.

                                      Всё через жопу короче и ломает мне шаблоны напрочь. Абсолютное отсутствие логики, ппц.
                                      Может, потому, что спецификация OpenGL очень древняя.

                                      www.opengl.org/wiki/Early_Fragment_Test

                                      www.opengl.org/wiki/Depth_Test
                                        +1
                                        Нашел красивые картинки.

                                        openglinsights.com/pipeline.html
                                          0
                                          Блин, чувак, респект. Забрал себе в топ-ссылки. Действительно очень круто!.. Сам такое когда-то рисовать хотел — а тут вон как, готовое уже есть.
                                        0
                                        Маркетинговых «тысяч ядер» на самом деле не существует.
                                        Есть просто пачка SIMD ALU, которые выполняют одну операцию над группами из 16-64 элементов параллельно.
                                          0
                                          Вот этих АЛУ и есть около тысячи
                                            +1
                                            Но никак не «ядер», которыми оперируют маркетологи.

                                            Собственно вот, крайне рекомендуется к просмотру:
                                            www.slideshare.net/DevCentralAMD/gs4106-the-amd-gcn-architecture-a-crash-course-by-layla-mah
                                              0
                                              Ну ясно)
                                              А какая разница что называть ядрами.
                                              16 pixel pipes написано.
                                              также написано 16 SIMDS для одной модели 4 SIMDS для другой
                                              Также — 2560 threads как 64 threads x 10 waves x 4 SIMDs на один CU (компьют юнит наверно)
                                              Короче — тоже вынос мозга)) Маркетинговые картинки, а на самом деле у них всего 4 процессора стоят и все, хаха))

                              0
                              Год назад мне надо было рисовать много шариков, в итоге пришлось рисовать кубы… Скажите данный метод подойдет для DX?
                                0
                                Да, конечно.
                                  0
                                  Спасибо весьма полезная идея, наверняка подобным образом можно рисовать не только шарики.
                                    0
                                    Лично я также рисую куски торов, а вообще можно рисовать вообще любую неявную функцию, единственное что лучше бы она считалась просто, чтобы не нагружать шейдер слишком.
                                0
                                Очень понятно и отлично описываете. С нетерпением жду еще статтей.
                                  0
                                  Круть, я пока код шейдеров не посмотрел, не врубился как ты сделал вычисление расстояния от центра.
                                  Спс за подсказку!
                                  А подскажи, как можно на этой же версии шейдеров определять, какой фрагмент либо лежит на ребре треугольника, либо насколько удалён от ребра треугольника? Ведь инфы о соседних вершинах старые шейдеры по имени Верт и Фраг не знают…
                                  Неужели хардкорно передавать в шейдер с помощью uniform координаты остальных двух вершин?
                                  Похоже я сам ответил на свой вопрос…
                                  Это я думаю как может быть реализован toon shader на старом пайплайне просто.
                                  Поясню про toon shader — я имею в виду техника, раскрашивающая ребра и все около этого, можно и углы между треугольником и направлением взора считать и соответственно раскрашивать силуэт для замкнутых мешей.
                                    +1
                                    Пример интересный, портировала его на Delphi XE7. Вот exe для Windows + исходники: yadi.sk/d/-GSPdSvUgVUM2

                                    image

                                      0
                                      Добавила тестовый проект в своё репо, исходники тут:
                                      sourceforge.net/p/delphioga/code/ci...ee/TestShaders
                                      Exe + папка /shaders в zip тут:
                                      sourceforge.net/p/delphioga/code/ci....zip?format=raw

                                      Есть доработка в шейдерах, при неквадратном размере окна шарики остаются шариками.
                                      Изображение можно повращать мышкой.

                                      Вопрос к автору: требуется все то же самое, но в перспективной проекции.
                                      +3
                                      Вот, насчет перспективной проекции в шейдерах:
                                      stackoverflow.com/questions/1739772...particle-system

                                      Почему я так волнуюсь насчет проекции: пишу кросс-платформенную граф. библиотеку OGL/GLES (Windows/Android), настало время выбора — рисовать 3D примитивы классически (а-ля OGL 2.0, glDrawArrays <массив вершин>), либо рисовать их в шейдерах. Пример автора хорош, но у меня не получилось пока что перевести его в перспективную проекцию. Написала свой примерчик с тучкой сфер, каждая сфера получена первым дроблением икосаедра, 20*4=80 треугольников. Запустить пример можно на смартфонах/планшетах Android или на PC под Windows, камера там свободно перемещается.
                                      Android mc.apk 3.8 MB yadi.sk/d/UaGYqsu9ff8Dd
                                      Windows mc.zip 1.9 MB yadi.sk/d/tBKQxaV7ff8KW

                                      image
                                        0
                                        Есть неплохое описание технологии рендеринга шариков спрайтами с перспективой в книге «Learning Modern 3D Graphics Programming» в главе 13 «Lies and Impostors». Код урока на bitbucket.

                                        P.S. К сожалению, у вас ссылка на stackoverflow сломана.
                                        +2
                                        Триангулированные сферы
                                        Скрытый текст


                                        против отрисованных шейдером
                                        Скрытый текст


                                        Сорцы (без комментариев) доступны здесь. Это напрямую закодированный метод, предложенный в пдфе, ссылка на который есть в самом конце статьи.
                                          0
                                          Здорово, теперь тема раскрыта.
                                          В моём случае, к сожалению, пример сработал в обратную сторону — почитав текст Ваших шейдеров я сожгла все свои книги по glsl как опасную ересь :)) Если серьёзно — до этого надо еще дорасти.
                                        0
                                        Можно ли подобным способом отрисовать затекстурированный куб?
                                          0
                                          Да, конечно. Но отрисовать 12 треугольников стандартным методом будет проще и быстрее.
                                            0
                                            Имеете ввиду производительнее? Необходимые для этого вычисления в шейдере будут работать дольше, чем если просто передать в него информацию о геометрии куба?
                                              0
                                              Да, производительнее. Растеризаторы треугольников работают быстро, каждый пиксель будет прорисован всего два раза. При отрисовке триангулированных сфер мы упираемся в пропускную способность шины — треугольников надо тысячу минимум на каждую сферу, у куба их всего 12.
                                                0
                                                Спасибо за разъяснение :)
                                                  0
                                                  Видеокарта рисует вейвфронтами: blogs.msdn.microsoft.com/nativeconcurrency/2012/03/26/warp-or-wavefront-of-gpu-threads
                                                  И треугольник, который занимает хотя бы один пиксель на экране запросто приводит к тому, что 63 из 64 тредов лопатят воздух.
                                                  Хорошая демонстрация того, как даже топология треугольников может влиять на производительность тут:
                                                  www.humus.name/index.php?page=News&ID=228

                                          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                          Самое читаемое