Обриси рендерингу, якщо ви не надаєте всього лише десяток символів, залишається "не йти" через кількість вершин, необхідних на один символ, щоб наблизити кривизну. Хоча існують підходи до оцінювання кривих безьє в піксельному шейдері, вони страждають від того, що вони не легко піддаються антиадалізірованию, що тривіально за допомогою квадратичного текстурованого квадрату на відстані, а оцінка кривих в шейдері все ще обчислювально набагато дорожче, ніж потрібно.
Найкращі компроміси між "швидким" та "якісним" - це все-таки текстуровані квадратики з підписаною текстурою поля відстані. Це дуже трохи повільніше, ніж використання звичайного звичайного текстурованого квадратика, але не дуже. Якість, з іншого боку, полягає в зовсім іншому бальному парку. Результати по-справжньому приголомшливі, це так швидко, як ви можете отримати, і такі ефекти, як світіння, також нелегко додати. Крім того, при необхідності техніку можна зручно перенести на старі апаратури.
Дивіться знаменитий папір Valve про техніку.
Методика концептуально схожа на те, як працюють неявні поверхні (метаболи тощо), хоча вона не генерує багатокутників. Він працює повністю в піксельному шейдері і приймає відстань, відібрану від текстури, як функцію відстані. Все вище обраного порогу (як правило, 0,5) - "in", все інше - "out". У найпростішому випадку на 10-річному апараті, що не працює на шейдері, встановлення порогу альфа-тесту до 0,5 зробить саме таку річ (хоча без спеціальних ефектів і антиаліазингу).
Якщо ви хочете додати шрифту трохи більше ваги (штучний жирний шрифт), трохи менший поріг зробить трюк, не змінюючи жодного рядка коду (просто змініть форму "font_weight"). Для ефекту світіння просто розглядається все, що перевищує один поріг, як "в", а все над іншим (меншим) порогом як "поза, але в світіння", і середнє значення LERP. Антилізінг працює аналогічно.
Використовуючи 8-бітове значення підписаного відстані, а не один біт, ця методика збільшує ефективну роздільну здатність вашої карти текстури в 16 разів у кожному вимірі (замість чорного та білого використовуються всі можливі відтінки, таким чином, у нас в 256 разів більше інформація з використанням одного і того ж сховища). Але навіть якщо ви збільшуєте далеко за 16 разів, результат все одно виглядає цілком прийнятним. Довгі прямі лінії з часом стануть трохи хитрішими, але типових артефактів для вибірки не буде.
Ви можете використовувати шейдер для геометрії для генерації квадратиків з точок (зменшення пропускної здатності шини), але, чесно кажучи, виграші досить незначні. Те саме стосується інстальованого відображення символів, як описано в GPG8. Накладні витрати припадають на амортизацію лише в тому випадку, якщо у вас є багато тексту для малювання. На мою думку, виграш не пов’язаний із додатковою складністю та неспроможністю відновлення. Крім того, ви або обмежені кількістю постійних регістрів, або вам доводиться читати з буфера текстури, що є неоптимальним для узгодженості кешу (і наміром було оптимізувати для початку!).
Простий, простий старий вершинний буфер настільки ж швидкий (можливо, швидший), якщо ви заплануєте завантаження трохи вперед за часом і буде працювати на кожному обладнання, побудованому протягом останніх 15 років. І це не обмежується якоюсь конкретною кількістю символів у вашому шрифті, а також певною кількістю символів для відображення.
Якщо ви впевнені, що у вашому шрифті не більше 256 символів, текстурні масиви, можливо, варто розглянути, щоб зняти пропускну здатність шини аналогічним чином, як генерувати квадратики з точок на шейдері геометрії. При використанні текстури масиву координати текстури всіх квадратиків мають однакові, постійні s
та t
координати і відрізняються лише r
координатою, яка дорівнює індексу символів для візуалізації.
Але, як і в інших методах, очікувані вигоди незначні за рахунок того, що вони несумісні з обладнанням попереднього покоління.
Існує зручний інструмент Джонатана Даммера для створення текстур відстані: сторінка з описом
Оновлення:
Як нещодавно вказувалося в програмованій вершині витягування (Д. Ракос, "OpenGL Insights", стор. 239), немає значної додаткової затримки або накладних витрат, пов'язаних із витягненням вершинних даних програмно з шейдера на новітні покоління GPU, порівняно з тим, як робити те саме, використовуючи стандартну фіксовану функцію.
Крім того, останні покоління графічних процесорів мають все більш і більш розумні розміри кешів L2 загального призначення (наприклад, 1536kiB на nvidia Kepler), тому можна очікувати непослідовної проблеми доступу, коли витягувати випадкові зрушення для чотирьох кутів з текстури буфера менше проблема.
Це робить ідею витягування постійних даних (наприклад, квадратиків) з текстури буфера більш привабливою. Таким чином, гіпотетична реалізація могла б звести до мінімуму передачу PCIe та пам'яті, а також пам'яті GPU, з таким підходом:
- Завантажуйте лише індекс символів (один на символ, який потрібно відобразити) як єдиний вхід до вершинного шейдера, який передає цей індекс
gl_VertexID
, і посилюйте його до 4-х точок в шейдері геометрії, все ще маючи індекс символів та ідентифікатор вершини (це буде "gl_primitiveID, доступним у вершинній шейдері") як єдиний атрибут, і фіксує це за допомогою зворотного зв'язку з перетворенням.
- Це буде швидко, тому що є лише два вихідних атрибути (головне вузьке місце в GS), і він близький до "no-op" в іншому випадку на обох етапах.
- Зв’яжіть текстуру буфера, яка містить для кожного символу шрифту текстуровані вершини квадрата відносно базової точки (це в основному "метрики шрифту"). Ці дані можна стиснути до 4 чисел на квадратик, зберігаючи лише зміщення нижньої лівої вершини та кодуючи ширину та висоту вікна, вирівнюваного по осі (припустимо, що напівплавків буде це 8 байт постійного буфера на символ - типовий шрифт 256 символів міг повністю вміститися в кеш-пам'ять L1 2kiB).
- Встановити форму для базової лінії
- Зв’яжіть текстуру буфера з горизонтальними зміщеннями. Їх, можливо, можна було б навіть обчислити на графічному процесорі, але це набагато простіше та ефективніше з подібними речами в процесорі, оскільки це суворо послідовна операція і зовсім не банальна (подумайте про кернінг). Крім того, знадобиться ще одна передача зворотного зв'язку, яка була б ще одним моментом синхронізації.
- Відображаючи раніше створені дані з буфера зворотного зв’язку, вершина шейдера витягує горизонтальне зміщення базової точки та зміщення кутових вершин від буферних об'єктів (використовуючи примітивний ідентифікатор та індекс символів). Оригінальний ідентифікатор вершин поданих вершин тепер є нашим «примітивним ідентифікатором» (пам’ятайте, що GS перетворило вершини на квадри).
Так, в ідеалі можна зменшити необхідну смугу вершин на 75% (амортизовано), хоча вона зможе надати лише один рядок. Якщо хотіли б мати змогу візуалізувати кілька рядків за один виклик малювання, потрібно було б додати базову лінію до текстури буфера, а не використовувати рівномірний (зменшення пропускної здатності збільшується).
Однак, навіть якщо припустити 75-відсоткове зменшення - оскільки дані вершин для відображення "розумних" обсягів тексту лише десь близько 50-100кіБ (що практично дорівнює нулюдо GPU або PCIe-шині) - я все ще сумніваюся, що додаткова складність та втрата зворотної сумісності дійсно вартують клопоту. Зниження нуля на 75% все ще лише нульове. Я, правда, не спробував вищезазначеного підходу, і для того, щоб зробити справді кваліфіковану заяву, знадобиться більше досліджень. Але все-таки, якщо хтось не зможе продемонструвати справді приголомшливу різницю у виконанні (використовуючи "нормальну" кількість тексту, а не мільярди символів!), Моя точка зору залишається, що для даних вершин простий, звичайний старий вершинний буфер є виправдано досить хорошим вважати частиною "найсучаснішого рішення". Це просто і прямо, це працює, і це добре працює.
Вже посилаючись на " OpenGL Insights " вище, варто також вказати на розділ "2D-рендеринг форми за допомогою дистанційних полів" Стефана Густавсона, який дуже детально пояснює подання поля на відстані.
Оновлення 2016:
Тим часом існує кілька додаткових прийомів, які мають на меті вилучити артефакти кутового округлення, які стають тривожними при надзвичайних збільшеннях.
Один із підходів просто використовує поля псевдо відстані замість полів відстані (різниця полягає в тому, що відстань - найкоротша відстань не до фактичного контуру, а до контуру чи уявної лінії, що виступає через край). Це дещо краще, і працює з однаковою швидкістю (однаковий шейдер), використовуючи однаковий об'єм текстурної пам'яті.
Інший підхід використовує медіану з трьох у триканальних деталях текстури та реалізації, доступних у github . Це спрямоване на покращення порівняно із та / або хаками, які використовувались раніше для вирішення проблеми. Хороша якість, трохи, майже не помітно, повільніше, але використовує втричі більше пам'яті текстури. Крім того, зайві ефекти (наприклад, світіння) важче виправити.
Нарешті, зберігання фактичних кривих безьє, що складають символи, та оцінювання їх у фрагменті шейдера стало практичним , з дещо неповноцінною продуктивністю (але не настільки, що це проблема) та приголомшливими результатами навіть при найвищих масштабах.
Демонстрація веб-версії WebGL з великою формою PDF з цією технікою в режимі реального часу доступна тут .