在OpenGLES 2中进行文本/字体渲染(iOS-CoreText?)-选项和最佳实践?

19
在OpenGL字体渲染方面,有许多问题,其中许多问题可以通过纹理图集(快速但错误)或字符串纹理(仅限固定文本)来解决。
然而,这些方法很差,并且似乎已经过时数年了(使用着色器是否可以更好/更快地完成?)。对于OpenGL 4.1,有一个非常好的问题,看一下“今天应该使用什么?”: OpenGL中文网-OpenGL 4.1的文本渲染现状 那么,在iOS GL ES 2上,我们今天应该使用什么呢?
我很失望,似乎没有开源(甚至商业)的解决方案。我知道很多团队会花费几周的时间重新发明这个轮子,逐渐学习如何调整和间隔等等(呃)-但一定有比从头开始重写“字体”更好的方法吧?
据我所见,这个问题可以分为两部分:
  1. 如何使用字体来呈现文本?
  2. 如何显示输出结果?
对于第一部分(如何呈现),苹果提供了许多方法来获取“正确”的呈现效果,但是“简单”的方法不支持OpenGL(也许其他一些方法可以 - 例如是否有一种简单的方法将CoreText输出映射到OpenGL?)。
对于第二部分(如何显示),我们有着色器、VBOs、字形纹理、查找纹理和其他技术(例如上面链接的OpenGL 4.1?)。
以下是我所知道的两种常见的OpenGL方法:
  1. 纹理集(仅渲染一次所有字形,然后从共享纹理中为每个字符渲染1个带纹理的矩形)
    1. 这是错误的,除非您使用的是1980年代的“位图字体”(即使是这样:纹理集需要更多的工作量,如果您需要对非常规字体进行正确处理)
    2. (字体不仅仅是“一组字形”,还涉及大量的定位、布局、换行、间距、字距、样式、着色、加权等。纹理集会失败)
  2. 固定字符串(使用任何苹果类进行正确渲染,然后截取背景图像数据并上传为纹理)
    1. 从人类的角度来看,这很快。但在帧渲染方面则非常缓慢,如果您使用大量变化的文本来做这件事,则帧率会急剧下降。
    2. 技术上,这基本上是正确的(不完全正确:这样您会失去一些信息),但效率极低。

我也看到过有关这两种方法的好的和坏的反馈:

  1. Imagination/PowerVR "Print3D"(链接失效)(来自制造GPU的公司!但他们的网站已经移动/删除了文本渲染页面)
  2. FreeType(需要预处理、解释、很多代码和额外的库?)
  3. ...和/或FTGLhttp://sourceforge.net/projects/ftgl/ (传言:慢?有漏洞?很长时间没有更新?)
  4. Font-Stash http://digestingduck.blogspot.co.uk/2009/08/font-stash.html(高质量,但非常缓慢?)

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

  1. 使用NSString和CoreGraphics
    1. 最简单的方式:在CGRect中渲染
    2. 似乎比人们推荐的“固定字符串”方法稍微快一些(尽管你可能认为它们应该大致相同)
  2. 使用纯文本的UILabel和UITextArea
    1. 注意:它们不是完全相同的!在呈现相同文本方面有细微差异
  3. NSAttributedString,呈现为上述之一
    1. 再次强调:呈现方式不同(我知道的差异相当微小,被归类为“错误”,有关此问题的各种SO问题)
  4. CATextLayer
    1. iOS字体和旧版C渲染之间的混合。使用“未完全”桥接的CFFont / UIFont,这将显示出一些更多的渲染差异/奇怪之处
  5. CoreText
    1. ......最终解决方案?但是它有自己的难点......

有任何进展吗?我正在追随你的脚步。但是... - Mehmet Emre Portakal
3
我已经让它能够工作了,但还有一些小问题。我可以正确处理一些非常复杂的字体和多字母字形,但仍然存在一些小故障。我正在逐步撰写博客文章,但文本布局部分要等到2013年圣诞节或2014年春季才能完成,抱歉。博客文章在这里:http://t-machine.org/index.php/2013/09/08/opengl-es-2-basic-drawing/ - Adam
是的,我很想听听你的文本布局部分。到目前为止,它的工作效果如何? - kamziro
这是很多代码。而且我已经修复了CoreText中一些苹果的bug :). 但是我仍然有一些bug(例如,在某些特定大小的点上,某些字符/字形与真实值相差2-3像素:(). 我考虑开放git repo作为捐赠软件(50美元或其他金额),因为研究和开发需要太多时间! - Adam
使用OpenGL方法绘制文本的方法:https://dev59.com/wWox5IYBdhLWcg3w_JLd - Ciro Santilli OurBigBook.com
3个回答

8
我进行了更多实验,发现CoreText与纹理地图和Valve的签名差异纹理(可以将位图字形转换为分辨率无关的高分辨率纹理)结合使用可能是完美解决方案。
但我还没有使它工作,仍在实验中。
更新:苹果的文档表示,他们提供除最细节之外的所有内容:要呈现哪个字形+字形布局(您可以获取行布局和字形数量,但根据文档无法获取字形本身)。由于没有明显的原因,这个核心信息似乎缺失于CoreText中(如果是这样,那么CT几乎毫无价值)。我仍在寻找一种获取实际glpyhs和每个字形数据的方法。
更新2:我现在已经成功使用苹果的CT(但没有不同纹理),但它最终变成了3个类文件、10个数据结构、约300行代码以及用于渲染的OpenGL代码。对于SO答案来说太多了 :(。
简短的答案是:是的,您可以这样做,并且它有效,只要您:
1. 创建CTFrameSetter 2. 为理论上的2D框架创建CTFrame 3. 创建一个CGContext,您将将其转换为GL纹理 4. 逐个字形地进行操作,允许Apple渲染到CGContext 5. 每次Apple呈现字形时,计算边界框(这很难),并将其保存在某个地方 6. 并保存唯一的glyph-ID(对于例如“o”、“f”和“of”(一个字形!)将不同) 7. 最后,将您的CGContext作为纹理发送到GL
在渲染时,使用Apple创建的字形ID列表,对于每个字形ID,使用已保存的信息和纹理来呈现四边形,以从您上传的纹理中提取单个字形。
这有效,速度快,适用于所有字体,可以正确获取所有字体布局和字距等。

我正在寻找类似于这样的东西。如果您不介意开源它,那么它听起来会是一个很棒的Github项目! - David
@David 抱歉,不行 - 这已经花费了我大量的未付报酬时间(超过一个月的全职工作)。如果我发布任何东西,那将是商业性质的。更有可能的是,我会为特定的公司/项目提供咨询服务 - 对他们来说更便宜,对我来说也更轻松(我不想经营网站/公司等)。抱歉! - Adam
好的,没问题!如果还有其他人在寻找方向,我认为我们公司可以基于FreeType构建我们的系统,这似乎不会太费力,并且跨平台。 - David
有时候开源项目是为了真正帮助人们而创建的。如果你不想并且不介意将某些东西商业化,那么就帮助那些研究同样事物的人。 - Петър Петров
@ПетърПетров,你的态度很糟糕;你在回答中侮辱我,因为我给出了其他人可以遵循的逐步算法。如果你继续这样做,你会教导人们停止回答StackOverflow - 停止“帮助人们”,正如你所说的那样。不要自私,不要贪婪:如果你想要三个月的编码,请不要免费要求它。 - Adam

3
我知道这篇帖子很老了,但是我在尝试在我的应用程序中做到这一点时遇到了它。在我的搜索中,我找到了这个示例项目。

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。通过CoreText中的CTFramesetterSuggestFrameSizeWithConstraints函数,从mabstring计算出rect

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 TextureMTLTextureUIImage是可以的。


例如,

对于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)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接