如何显示按比例缩小的 GL ES 纹理而不夹紧或在 android 上重复

How to display a scaled down GL ES texture without clamp or repeat on android

我目前正在 android 上使用 GL ES 2.0 将相机预览渲染到 SurfaceTexture,使用 opengl 渲染它,然后将其传输到媒体编解码器的输入表面以进行记录。它在表面视图中显示给用户,通过设置该表面视图的纵横比,相机预览不会根据屏幕尺寸失真。

录音是纵向的,但在某些时候传入的纹理将开始横向出现,此时我想缩小并将其显示为 "movie" 拉伸宽度以适应屏幕边缘水平,顶部和底部有 黑条 以保持纹理的纵横比。

drawing code in onDrawFrame is pretty simple。 link 包含着色器等的其余设置代码,但它只是设置要绘制的三角形带。

private final float[] mTriangleVerticesData = {
        // X, Y, Z, U, V
        -1.f, -1.f, 0, 0.f, 0.f,
        1.f, -1.f, 0, 1.f, 0.f,
        -1.f,  1.f, 0, 0.f, 1.f,
        1.f,  1.f, 0, 1.f, 1.f,
};
    public static final String VERTEX_SHADER =
        "uniform mat4 uMVPMatrix;\n" +
                "uniform mat4 uSTMatrix;\n" +
                "attribute vec4 aPosition;\n" +
                "attribute vec4 aTextureCoord;\n" +
                "varying vec2 vTextureCoord;\n" +
                "void main() {\n" +
                "  gl_Position = uMVPMatrix * aPosition;\n" +
                "  vTextureCoord = (uSTMatrix * aTextureCoord).xy;\n" +
                "}\n";

private float[] mMVPMatrix = new float[16];
private float[] mSTMatrix = new float[16];


    public TextureManager() {
    mTriangleVertices = ByteBuffer.allocateDirect(
            mTriangleVerticesData.length * FLOAT_SIZE_BYTES)
            .order(ByteOrder.nativeOrder()).asFloatBuffer();
    mTriangleVertices.put(mTriangleVerticesData).position(0);

    mTriangleHalfVertices = ByteBuffer.allocateDirect(
            mTriangleVerticesHalfData.length * FLOAT_SIZE_BYTES)
            .order(ByteOrder.nativeOrder()).asFloatBuffer();
    mTriangleHalfVertices.put(mTriangleVerticesHalfData).position(0);

    Matrix.setIdentityM(mSTMatrix, 0);
}

    onDrawFrame(){
    mSurfaceTexture.getTransformMatrix(mSTMatrix);

    GLES20.glUseProgram(mProgram);
    checkGlError("glUseProgram");

    GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
    GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0);
    GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureID);

    mTriangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET);
    GLES20.glVertexAttribPointer(maPositionHandle, 3, GLES20.GL_FLOAT, false,
            TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices);
    checkGlError("glVertexAttribPointer maPosition");
    GLES20.glEnableVertexAttribArray(maPositionHandle);
    checkGlError("glEnableVertexAttribArray maPositionHandle");

    mTriangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET);
    GLES20.glVertexAttribPointer(maTextureHandle, 2, GLES20.GL_FLOAT, false,
            TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices);
    checkGlError("glVertexAttribPointer maTextureHandle");
    GLES20.glEnableVertexAttribArray(maTextureHandle);
    checkGlError("glEnableVertexAttribArray maTextureHandle");

    Matrix.setIdentityM(mMVPMatrix, 0);
    GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, mMVPMatrix, 0);
    GLES20.glUniformMatrix4fv(muSTMatrixHandle, 1, false, mSTMatrix, 0);

    GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
    checkGlError("glDrawArrays");
    GLES20.glFinish();`
}

我尝试过但不太奏效的方法:缩放 mMVPMatrix 或 mSTMatrix 以放大。我可以放大,所以我可以让风景视频的 "center slice" 显示而不会失真,但是这会切断视频的 40% 的巨大部分,因此这不是一个很好的解决方案。由于钳位到边缘行为,通过缩放这些矩阵来缩小会导致纹理重复边缘上的像素。

将 mTriangleVerticesData 的 x、y、z 部分减半给出 一些 所需的行为,如下面的屏幕截图所示,除了精确的宽高比。正如预期的那样,图片的中心部分被减半并居中。但是,纹理向左、右和底部重复,并且左上角出现扭曲。我想要的是中心保持原样,black/nothing围绕着它。

我可以横向扩展,然后转换 mMVPMatrix 或 mSTMatrix,然后更改我的着色器以对 (0,1) 之外的任何内容显示黑色,但最终我想将多个纹理叠加在另一个之上,例如全尺寸背景和部分大小的前景纹理。要做到这一点,我必须最终弄清楚如何只在可用部分 space 中显示纹理,而不仅仅是操纵纹理,所以它看起来就像正在发生的事情。

感谢您阅读所有内容。任何帮助、建议或大胆的猜测都将不胜感激。

您正在寻找的似乎是一个 "fit" 系统来获取元素的正确框架。这意味着例如您有一个 100x200 图像并且您想要在 50x50 的框架中显示它。结果应该是在矩形 (25, 0, 25, 50) 中看到整个图像。因此生成的帧必须遵守图像比例 (25/50 = 100/200) 并且必须遵守原始帧边界。

要实现这一点,通常需要比较图像比例和目标帧比例:imageRatio = imageWidth/imageHeightframeRatio = frameWidth/frameHeight。然后,如果图像比例大于帧比例,则意味着您需要在顶部和底部出现黑色边框,而如果帧比例较大,则您将在左侧和右侧看到黑色边框。

所以要计算目标帧:

imageRatio = imageWidth/imageHeight
frameRatio = frameWidth/frameHeight
if(imageRatio > frameRatio) {
    targetFrame = {0, (frameHeight-frameWidth/imageRatio)*.5, frameWidth, frameWidth/imageRatio} // frame as: {x, y, width, height}
}
else {
    targetFrame = {(frameWidth-frameHeight*imageRatio)*.5, 0, frameHeight*imageRatio, frameHeight} // frame as: {x, y, width, height}
}

在你的例子中,图像的宽度和高度是从流中接收到的;帧宽度和高度来自您的目标帧,这似乎是矩阵的结果,但对于全屏情况,如果您使用它,它只是来自 glOrtho 的值。然后应使用目标帧来构造顶点位置,以便获得完全正确的顶点数据以显示完整纹理。

我看到您在您的案例中使用矩阵进行所有计算,并且可以使用相同的算法将其转换为矩阵,但我不鼓励您这样做。您似乎过度滥用矩阵,这使您的代码完全无法维护。在你的情况下,我建议你保持 "ortho" 投影矩阵,使用帧来绘制纹理,并且只在有意义的地方使用矩阵比例和平移。

重复的图像块看起来像 GPU 平铺伪影,而不是纹理重复。添加 glClear() 调用以擦除背景。