美文网首页
OpenGL 高质量文本渲染

OpenGL 高质量文本渲染

作者: VE视频引擎 | 来源:发表于2021-12-24 14:06 被阅读0次

    OpenGL 高质量文本渲染

    High Quality Text Rendering

    前言

    在实时 3D 图形中保留尽可能高质量的文本具有挑战性。对象可以动态地改变它们的位置、旋转、比例和视角。所有这些都会对质量产生负面影响,因为文本通常只生成一次,而不是在每一帧中生成。根据字体引擎及其性能,为整个文本生成纹理需要很长时间。通常,这段时间足以影响性能。

    本文档介绍了如何在对象为半动态时获得最佳文本质量的方法。半动态对象是既不经常(不是每帧)也不在动画时间内更改的对象。

    此示例描述了如何计算字体大小,这应该使纹理像素与屏幕像素紧密匹配。

    我们将使用作为 Android 一部分的字体引擎。字体引擎生成包含整个文本形状的 RGBA 图像。然后将图像上传到纹理中,然后将纹理映射到矩形上。矩形必须具有根据纹理大小定义的适当宽高比。

    评估字体大小

    要评估对象当前转换的字体大小,我们需要将矩形的四个角从 3D 世界空间转换为 2D 像素屏幕空间。以像素为单位表示角,我们可以计算两个左角之间的距离和两个右角之间的距离。然后根据这些距离计算平均值。平均值就是我们要查找的值,因为这是我们将用于生成图像的字体大小。

    // 1. 使用当前矩阵计算屏幕坐标中的边界框
    Vector4f cLT = new Vector4f(-0.5f,-0.5f, 0.0f, 1.0f);
    Vector4f cLB = new Vector4f(-0.5f, 0.5f, 0.0f, 1.0f);
    Vector4f cRT = new Vector4f( 0.5f,-0.5f, 0.0f, 1.0f);
    Vector4f cRB = new Vector4f( 0.5f, 0.5f, 0.0f, 1.0f);
    
    // 我们重用已经为渲染计算过的矩阵,而不是再次计算矩阵。update() 方法必须在 render() 方法之后调用
    cLT.makePixelCoords(mMVPMatrix, theViewportWidth, theViewportHeight);
    cLB.makePixelCoords(mMVPMatrix, theViewportWidth, theViewportHeight);
    cRT.makePixelCoords(mMVPMatrix, theViewportWidth, theViewportHeight);
    cRB.makePixelCoords(mMVPMatrix, theViewportWidth, theViewportHeight);
    
    // 2. 根据边界框角的高度评估字体大小
    Vector4f vl = Vector4f.sub(cLB, cLT);
    Vector4f vr = Vector4f.sub(cRB, cRT);
    textSize = (vl.length3() + vr.length3()) / 2.0f;
    

    下面是 Vector4f 类中 makePixelCoords 方法的定义。该方法将 3D 顶点位置转换为 2D 像素位置。

    public void makePixelCoords(float[] aMatrix,
                                int aViewportWidth,
                                int aViewportHeight) {
      // 将向量转换为屏幕坐标,我们假设 aMatrix 是 ModelViewProjection 矩阵
      // transform 方法将此向量乘以 aMatrix
      transform(aMatrix);
      
      // 转换为齐次坐标
      x /= w;
      y /= w;
      z /= w;
      w = 1.0f;
      // 现在向量标准化到了 [-1.0, 1.0] 范围
      
      // 转换为标准化设备坐标
      x = 0.5f + x * 0.5f;
      y = 0.5f + y * 0.5f;
      z = 0.5f + z * 0.5f;
      w = 1.0f;
      // 现在值被限制到 [0.0, 1.0] 范围
      
      // 将坐标移动到窗口空间(以像素为单位)
      x *= (float) aViewportWidth;
      y *= (float) aViewportHeight;
    }
    

    纹理生成

    由于我们已经知道字体大小,我们可以估计目标图像的大小。图像必须足够大以存储整个文本而无需任何剪切。另一方面,它不能太大,因为以下几何计算是基于图像大小的。我们希望有一个精确适合字体引擎将要生成的内容的大小。

    高度计算很简单,因为是字体的大小,但是宽度非常复杂。为了正确计算宽度,我们需要使用字体引擎来帮助我们估计它。 Android Java SDK 附带来自 Paint 对象的 measureText 方法。在测量之前,我们需要向对象提供所有必要的数据,例如:字体名称、字体大小(我们已经计算过)、抗锯齿、ARGB 颜色(在我们的例子中它总是白色,因为着色可能是稍后在片段着色器中完成),以及其他不太重要的数据。

    在我们将文本绘制到 Bitmap 对象之前,我们需要使用完全透明的白色 ARGB = (0, 255, 255, 255) 清除其内容。使用此颜色清除背景并将 Paint 颜色也设置为白色,可以防止可能因 alpha 混合而出现的暗纹素。说到混合,GL 混合函数必须在渲染文本之前正确设置,混合函数必须设置为:glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA)

    下面的函数完成了上面提到的所有步骤:

    private void drawCanvasToTexture(
            String aText,
            float aFontSize) {
      
      if (aFontSize < 8.0f)
      aFontSize = 8.0f;
      
      if (aFontSize > 500.0f)
      aFontSize = 500.0f;
      
      Paint textPaint = new Paint();
      textPaint.setTextSize(aFontSize);
      textPaint.setFakeBoldText(false);
      textPaint.setAntiAlias(true);
      textPaint.setARGB(255, 255, 255, 255);
      // 如果支持 hinting,需要启用(取消注释下面一行)
      // textPaint.setHinting(Paint.HINTING_ON);
      textPaint.setSubpixelText(true);
      textPaint.setXfermode(new PorterDuffXfermode(Mode.SCREEN));
      
      float realTextWidth = textPaint.measureText(aText);
      
      // 创建一个新的 bitmap,宽高为128像素
      bitmapWidth = (int)(realTextWidth + 2.0f);
      bitmapHeight = (int)aFontSize + 2;
      
      Bitmap textBitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
      textBitmap.eraseColor(Color.argb(0, 255, 255, 255));
      // 创建一个渲染到 bitmap 的画布
      Canvas bitmapCanvas = new Canvas(textBitmap);
      // 将开始绘图位置设置为 [1, base_line_position]
      // base_line_position 可能因字体而异,但通常等于字体大小(高度)的 75%。
      bitmapCanvas.drawText(aText, 1, 1.0f + aFontSize * 0.75f, textPaint);
      
      GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId[0]);
      HighQualityTextRenderer.checkGLError("glBindTexture");
      // 上传 bitmap 像素到 OpenGL 纹理
      GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, textBitmap, 0);
      // 释放 bitmap
      textBitmap.recycle();
      
      // 图像上传到 texture 后,重新生成 mipmap
      GLES20.glGenerateMipmap(GLES20.GL_TEXTURE_2D);
      HighQualityTextRenderer.checkGLError("glGenerateMipmap");
    }
    

    进一步优化

    • 如果程序中的文本经常更改,那么这个概念可能适合。我们可以创建一个单独的线程,它以一定的间隔连续更新纹理。在大多数情况下,将线程保持在尽可能低的优先级,因为生成文本始终是耗时操作,有可能导致性能中断。更新纹理应该在为 GL 上下文的线程上完成。

    • 如果沿贝塞尔曲线渲染文本或进行一些位移,则需要更精确地估计字体大小。为此,增加矩形宽度分辨率。此时矩形将被分成垂直切片。然后评估所有这些切片的平均高度。平均高度值用于提高字体大小的精度。

    相关文章

      网友评论

          本文标题:OpenGL 高质量文本渲染

          本文链接:https://www.haomeiwen.com/subject/wuvlqrtx.html