【问题标题】:Text/font rendering in OpenGLES 2 (iOS - CoreText?) - options and best practice?OpenGLES 2(iOS - CoreText?)中的文本/字体渲染 - 选项和最佳实践?
【发布时间】:2013-09-04 15:17:16
【问题描述】:

关于 OpenGL 字体渲染有很多问题,其中很多问题都可以通过纹理图集(快速但错误)或字符串纹理(仅限固定文本)来解决。

但是,这些方法很差,并且似乎已经过时了(使用着色器来更好/更快地做到这一点怎么样?)。对于 OpenGL 4.1,有一个很好的问题是关于“你今天应该使用什么?”:

What is state-of-the-art for text rendering in OpenGL as of version 4.1?

那么,我们今天应该在 iOS GL ES 2 上使用什么?

我很失望似乎没有开源(甚至商业解决方案)。我知道很多团队都把它搞砸了,花费数周的开发时间重新发明这个轮子,逐渐学习如何调整字距和空间等(呃)——但肯定有比重写整个“字体”更好的方法从头开始?


据我所知,这有两个部分:

  1. 我们如何使用字体呈现文本?
  2. 我们如何显示输出?

对于 1(如何渲染),Apple 提供了许多方法来获得“正确”的渲染输出 - 但“简单”的方法不支持 OpenGL(也许其他一些支持 - 例如有一种简单的方法可以将 CoreText 输出映射到 OpenGL?)。

对于 2(如何显示),我们有着色器、VBO、字形纹理、查找纹理和其他技术(例如上面链接的 OpenGL 4.1 东西?)

以下是我所知道的两种常见的 OpenGL 方法:

  1. 纹理图集(渲染所有字形一次,然后从共享纹理中为每个字符渲染 1 x 纹理四边形)
    1. 这是错误的,除非您使用的是 1980 年代的“位图字体”(即便如此:纹理图集需要比看起来更多的工作,如果您需要正确的字体)
    2. (字体不是“字形的集合”,有大量的定位、布局、环绕、间距、字距调整、样式、着色、权重等。纹理图集失败)
  2. 修复字符串(使用任何 Apple 类正确渲染,然后截取背景图像数据,并作为纹理上传)
    1. 在人类方面,这很快。在帧渲染中,这非常非常慢。如果您使用大量不断变化的文本来执行此操作,那么您的帧速率会下降
    2. 从技术上讲,它大部分是正确的(不完全正确:这样会丢失一些信息),但效率极低

我也见过,但也听说过:

  1. Imagination/PowerVR “Print3D”(链接断开)(来自制造 GPU 的人!但他们的网站已移动/删除了文本渲染页面)
  2. FreeType(需要预处理、解释、大量代码、额外库?)
  3. ...和/或 FTGL http://sourceforge.net/projects/ftgl/(谣言:速度慢?有问题?很久没更新了?)
  4. Font-Stash http://digestingduck.blogspot.co.uk/2009/08/font-stash.html(高质量,但速度很慢?)
  5. 1.

在 Apple 自己的操作系统/标准库中,我知道几种文本渲染来源。注意:我已经在 2D 渲染项目中详细使用了其中的大部分,我关于它们输出不同渲染的陈述是基于直接经验的

  1. 带有 NSString 的 CoreGraphics
    1. 最简单的:渲染“成 CGRect”
    2. 似乎是人们推荐的“固定字符串”方法的一个稍快的版本(尽管您希望它大致相同)
  2. 带有纯文本的 UILabel 和 UITextArea
    1. 注意:它们不一样!它们呈现 smae 文本的方式略有不同
  3. NSAttributedString,呈现为上述之一
    1. 再次:呈现方式不同(我所知道的差异相当微妙,被归类为“错误”,关于此的各种 SO 问题)
  4. CATextLayer
    1. iOS 字体和旧 C 渲染的混合体。使用“不完全”的免费桥接 CFFont / UIFont,这揭示了更多的渲染差异/奇怪
  5. 核心文本
    1. ... 最终解决方案?但它自己的野兽......

【问题讨论】:

  • 有进展吗?我正在追随你的脚步。但是...
  • 我已经让它工作了,但有一些错误。我正确处理了一些非常花哨的字体和多字母字形,但仍然存在一些小故障。我一步一步地把它写成博客文章,但文本布局部分要到 2013 年圣诞节或 2014 年春季才能完成,对不起。博客在这里t-machine.org/index.php/2013/09/08/opengl-es-2-basic-drawing
  • 是的,很想听听您的文本布局位。到目前为止,它是否发挥了应有的作用?
  • 代码很多。而且我已经修复了一些 Apple 在 CoreText 中的错误 :)。但是我仍然有一些错误(例如,某些字符/字形,在某些点大小上,距离真实 :() 2-3 像素。我正在考虑将 git repo 的访问权限作为捐赠软件(50 美元或一些东西),b/c 研究 + 开发这需要很多时间!

标签: ios opengl-es fonts core-animation core-text


【解决方案1】:

我做了一些更多的试验,似乎 CoreText 与纹理图集和 Valve 的符号差分纹理(可以将位图字形转换为与分辨率无关的高分辨率纹理)结合使用时可能是一个完美的解决方案。

...但我还没有让它工作,还在试验中。


更新:Apple 的文档说他们可以让您访问除最终细节之外的所有内容:要渲染的字形 + 字形布局(根据文档,您可以获得行布局和字形数量,但不能获得字形本身) .没有明显的原因,CoreText 中显然缺少这条核心信息(如果是这样,那 CT 几乎一文不值。我仍在寻找是否能找到一种方法来获取实际的 glpyhs + per-glyph 数据)


UPDATE2:我现在可以在 Apple 的 CT 上正常工作(但没有不同的纹理),但它最终是 3 个类文件、10 个数据结构、大约 300 行代码,加上渲染它的 OpenGL 代码。 SO答案太多了:(。

简短的回答是:是的,你可以做到,而且它有效,如果你:

  1. 创建 CTFrameSetter
  2. 为理论上的二维帧创建 CTFrame
  3. 创建一个将转换为 GL 纹理的 CGContext
  4. 逐个字形检查,允许 Apple 渲染到 CGContext
  5. 每次 Apple 呈现字形时,计算边界框(这是 HARD),并将其保存在某处
  6. 并保存唯一的字形 ID(这对于例如“o”、“f”和“of”(一个字形!)会有所不同)
  7. 最后,将 CGContext 作为纹理发送到 GL

渲染时,请使用 Apple 创建的字形 ID 列表,并为每个字形 ID 使用保存的信息和纹理,以渲染具有纹理坐标的四边形,从而将单个字形从您上传的纹理中提取出来.

这很有效,速度很快,它适用于所有字体,它可以让所有字体布局和字距调整正确,等等。

【讨论】:

  • 我正在寻找这样的东西。如果你不介意开源它,听起来这将是一个很棒的 Github 项目!
  • @David 对不起,不——这让我花费了大量的无偿时间(超过一个月的全职工作)。如果我发布任何东西,那将是商业性的。我更有可能咨询特定的公司/项目——对他们来说更便宜,对我来说麻烦更少(我不想经营网站/公司/等)。对不起!
  • 好的,没问题!对于其他正在寻找方向的人,我认为我的公司可能会在 FreeType 的基础上构建我们的系统,这看起来不会太多工作并且是跨平台的。
  • 有时开源项目是为了真正帮助人们。如果您不想也不介意在商业上发布某些东西,那么请帮助其他研究相同内容的人。
  • @ПетърПетров 你的态度很糟糕;您正在回复一个答案,其中我通过侮辱我不帮助别人而为其他人提供了逐步算法。继续这样做,你会教人们停止回复 StackOverflow——停止“帮助人们”,就像你所说的那样。不要自私,不要贪婪:如果你想要 3 个月的编码,不要免费要求。
【解决方案2】:

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 由 CoreText 从 mabstring 计算得出。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。用rawData创建OpenGL Texture、MTLTexture、UIImage就可以了。


例子,

到 OpenGL 纹理:Convert an UIImage in a texture

设置你的纹理:

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)

【讨论】:

    【解决方案3】:

    我知道这篇文章很旧,但我在尝试在我的应用程序中完全执行此操作时遇到了它。在我的搜索中,我遇到了这个示例项目

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

    它是使用纹理图集和有符号距离场技术的 OpenGL 的 CoreText 的完美实现。它极大地帮助我达到了我想要的结果。希望这对其他人有帮助。

    【讨论】:

      猜你喜欢
      • 2014-07-14
      • 2015-08-21
      • 1970-01-01
      • 2020-10-23
      • 2021-04-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-12-10
      相关资源
      最近更新 更多