Выделение текста/шрифта в OpenGLES 2 (iOS - CoreText?) - варианты и передовая практика?

Есть много вопросов по рендерингу шрифтов OpenGL, многие из них удовлетворяются атласами текстур (быстрыми, но неправильными) или строковыми текстурами (только для фиксированного текста).

Однако эти подходы плохи и кажутся устаревшими (что делать с шейдерами, чтобы сделать это лучше/быстрее?). Для OpenGL 4.1 есть отличный вопрос, "что вы должны использовать сегодня?":

Что представляет собой современное состояние для текстового рендеринга в OpenGL с версии 4.1?

Итак, что мы должны использовать на iOS GL ES 2 сегодня?

Я разочарован тем, что, похоже, нет открытого (или даже коммерческого) решения. Я знаю, что многие команды высасывают его и тратят несколько недель на время восстановления своего колеса, постепенно учась тому, как ядро ​​и пространство и т.д. (Тьфу) - но должен быть лучший способ, чем переписывать все "шрифты", с нуля?


Насколько я вижу, есть две части:

  • Как визуализировать текст с помощью шрифта?
  • Как отобразить вывод?

Для 1 (как для рендеринга) Apple предоставляет МНОГИЕ способы получения "правильного" визуализированного вывода, но "простые" не поддерживают OpenGL (возможно, некоторые из них - например, есть простой способ отобразить вывод CoreText в OpenGL?).

Для 2 (как отображать) у нас есть шейдеры, у нас есть VBOs, у нас есть глиф-текстуры, у нас есть текстуры lookup и другие теки (например, связанные с OpenGL 4.1 вещи, которые были связаны выше?)

Вот два общих подхода OpenGL, о которых я знаю:

  • Атлас текстуры (визуализируйте все глифы один раз, затем визуализируйте 1 x текстурированный квадратик на символ, из общей текстуры)
    • Это неправильно, если вы не используете эпоху растрового изображения 1980-х годов (и даже тогда: текстурный атлас требует больше работы, чем может показаться, если вам нужно это правильно для нетривиальных шрифтов)
    • (шрифты не являются "коллекцией глифов", там огромное количество позиционирования, макета, обертывания, интервала, кернинга, стилизации, раскраски, взвешивания и т.д. Текстуры текстуры терпят неудачу)
  • Фиксированная строка (используйте любой класс Apple для правильной рендеринга, затем снимите снимки с фоновыми изображениями и загрузите их как текстуру)
    • В человеческих терминах это быстро. При рендеринге кадра это очень, очень медленно. Если вы делаете это с большим количеством изменяемого текста, частота кадров проходит через пол.
    • Технически, это в основном правильное (не полностью: вы теряете некоторую информацию таким образом), но чрезвычайно неэффективны

Я тоже видел, но слышал и хорошие, и плохие вещи о:

  • Воображение /PowerVR "Print3D" (ссылка сломана) (от парней, которые производят GPU! Но их сайт переместил/удалил страницу отображения текста).
  • FreeType (требуется предварительная обработка, интерпретация, много кода, дополнительные библиотеки?)
  • ... и/или FTGL http://sourceforge.net/projects/ftgl/ (слухи: медленный? buggy? не обновляется в течение длительного времени?)
  • Font-Stash http://digestingduck.blogspot.co.uk/2009/08/font-stash.html (высокое качество, но очень медленно?) 1.

В собственных ОС/стандартных библиотеках Apple я знаю несколько источников текстового рендеринга. NB: я использовал большинство из них подробно для проектов 2D-рендеринга, мои заявления о них, выводящие разные рендеринг, основаны на непосредственном опыте

  • CoreGraphics с NSString
    • Самый простой из всех: сделать "в CGRect"
    • Похоже, что это немного более быстрая версия "фиксированной строки", которую люди рекомендуют (хотя вы ожидаете, что она будет почти такой же)
  • UILabel и UITextArea с открытым текстом
    • NB: они не то же самое! Небольшие различия в том, как они отображают текст smae.
  • NSAttributedString, отображаемый в одном из указанных выше
    • Снова: проявляется по-разному (различия, которые я знаю, довольно тонкие и классифицируются как "ошибки", различные SO-вопросы об этом)
  • CATextLayer
    • Гибрид между шрифтами iOS и старым C-рендерингом. Использует "не полностью" бесплатный CFFont/UIFont, который показывает еще несколько различий/странностей рендеринга
  • CoreText
    • ... окончательное решение? Но это зверь...

Ответ 1

Я сделал еще несколько экспериментов, и кажется, что CoreText может сделать идеальное решение в сочетании с текстурным атласом и текстурами с разметкой Valve (которые могут превратить глифовый растровый рисунок в не зависящую от разрешения hi-res текстуру).

... но у меня пока нет работы, все еще экспериментируя.


ОБНОВЛЕНИЕ. Документы Apple говорят, что они дают вам доступ ко всему, кроме последней детали: какой глиф + глиф-макет для рендеринга (вы можете получить макет строки и количество глифов, но не самого глифа, согласно документам), По всей видимости, эта основная часть информации, по-видимому, отсутствует в CoreText (если это так, что делает CT почти бесполезным. Я все еще охочусь, могу ли я найти способ получить фактические данные glpyhs + per-glyph)


UPDATE2: теперь у меня это работает правильно с Apple CT (но не с разными текстурами), но оно заканчивается как 3 файла классов, 10 структур данных, около 300 строк кода, плюс код OpenGL для его рендеринга. Слишком много для ответа SO: (.

Короткий ответ: да, вы можете это сделать, и он работает, если вы:

  • Создать CTFrameSetter
  • Создать CTFrame для теоретического 2D-кадра
  • Создайте CGContext, который вы преобразуете в текстуру GL.
  • Пройдите глиф-глиф, позволяя Apple отображать в CGContext
  • Каждый раз, когда Apple отображает глиф, вычисляем ограничивающий бокс (это HARD) и сохраняем его где-то
  • И сохраните уникальный идентификатор глифа (это будет отличаться для, например, "o", "f" и "of" (один символ!))
  • Наконец, отправьте свой CGContext до GL как текстуру.

При рендеринге используйте список идентификаторов символов, созданных Apple, и для каждого из них используйте сохраненную информацию и текстуру для рендеринга квадрантов с координатами текстуры, которые вытаскивают отдельные глифы из загруженной вами текстуры.

Это работает, быстро, работает со всеми шрифтами, получает все макет шрифта и кернинг, и т.д.

Ответ 2

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

http://metalbyexample.com/rendering-text-in-metal-with-signed-distance-fields/

Это идеальная реализация CoreText с OpenGL, использующая методы похудения текстур и знаковых полей расстояния. Это очень помогло мне достичь желаемых результатов. Надеюсь, это поможет кому-то еще.

Ответ 3

1.

Создайте любую строку с помощью NSMutableAttributedString.

let mabstring = NSMutableAttributedString(string: "This is a test of characterAttribute.")
mabstring.beginEditing()
var matrix = CGAffineTransform(rotationAngle: CGFloat(GLKMathDegreesToRadians(0)))
let font = CTFontCreateWithName("Georgia" as CFString?, 40, &matrix)
mabstring.addAttribute(kCTFontAttributeName as String, value: font, range: NSRange(location: 0, length: 4))
var number: Int8 = 2
let kdl = CFNumberCreate(kCFAllocatorDefault, .sInt8Type, &number)!
mabstring.addAttribute(kCTStrokeWidthAttributeName as String, value: kdl, range: NSRange(location: 0, length: mabstring.length))
mabstring.endEditing()

2.

Создать CTFrame. rect вычисляет из mabstring по CoreText. CTFramesetterSuggestFrameSizeWithConstraints

let framesetter = CTFramesetterCreateWithAttributedString(mabstring)
let path = CGMutablePath()
path.addRect(rect)
let frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, nil)

3.

Создать растровый контекст.

let imageWidth = Int(rect.width)
let imageHeight = Int(rect.height)
var rawData = [UInt8](repeating: 0, count: Int(imageWidth * imageHeight * 4))
let bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo.byteOrder32Big.rawValue | CGImageAlphaInfo.premultipliedLast.rawValue)
let rgbColorSpace = CGColorSpaceCreateDeviceRGB()
let bitsPerComponent = 8
let bytesPerRow = Int(rect.width) * 4
let context = CGContext(data: &rawData, width: imageWidth, height: imageHeight, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: rgbColorSpace, bitmapInfo: bitmapInfo.rawValue)!

4.

Нарисуйте CTFrame в растровом контексте.

CTFrameDraw(frame, context)

Теперь мы получили необработанные пиксельные данные rawData. Создайте OpenGL Texture, MTLTexture, UIImage с помощью rawData в порядке.


Пример

В OpenGL Texture: Преобразование UIImage в текстуру

Настройка текстуры:

GLuint textureID;    
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glGenTextures(1, &textureID);

glBindTexture(GL_TEXTURE_2D, textureID);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, textureData);

//to MTLTexture
let textureDescriptor = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: .rgba8Unorm, width: Int(imageWidth), height: Int(imageHeight), mipmapped: true)
let device = MTLCreateSystemDefaultDevice()!
let texture = device.makeTexture(descriptor: textureDescriptor)
let region = MTLRegionMake2D(0, 0, Int(imageWidth), Int(imageHeight))
texture.replace(region: region, mipmapLevel: 0, withBytes: &rawData, bytesPerRow: imageRef.bytesPerRow)

//to UIImage
  let providerRef = CGDataProvider(data: NSData(bytes: &rawData, length: rawData.count * MemoryLayout.size(ofValue: UInt8(0))))
  let renderingIntent =  CGColorRenderingIntent.defaultIntent
  let imageRef = CGImage(width: imageWidth, height: imageHeight, bitsPerComponent: 8, bitsPerPixel: 32, bytesPerRow: bytesPerRow, space: rgbColorSpace, bitmapInfo: bitmapInfo, provider: providerRef!, decode: nil, shouldInterpolate: false, intent: renderingIntent)!
  let image = UIImage.init(cgImage: imageRef)