GLES2.0中文API-glUniform

本文深入解析OpenGL中的统一变量,包括glUniform系列函数的使用方法,参数说明,以及注意事项。了解如何通过这些函数修改统一变量或统一变量数组的值,掌握OpenGL着色器编程的关键技巧。

名称

glUniform - 指定当前程序对象的统一变量的值

C规范

void glUniform1f(    GLint location, GLfloat v0);
void glUniform2f(    GLint location,  GLfloat v0, GLfloat v1);
void glUniform3f(    GLint location, GLfloat v0, GLfloat v1, GLfloat v2);
void glUniform4f(    GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3);
void glUniform1i(    GLint location, GLint v0);
void glUniform2i(    GLint location,GLint v0,GLint v1);
void glUniform3i(    GLint location,GLint v0,GLint v1,GLint v2);
void glUniform4i(    GLint location,GLint v0,GLint v1,GLint v2,GLint v3);

void glUniform1fv(    GLint location,GLsizei count,const GLfloat *value);
void glUniform2fv(    GLint location,GLsizei count,const GLfloat *value);
void glUniform3fv(    GLint location,GLsizei count,const GLfloat *value);
void glUniform4fv(    GLint location,GLsizei count,const GLfloat *value);
void glUniform1iv(    GLint location,GLsizei count,const GLint *value);
void glUniform2iv(    GLint location,GLsizei count,const GLint *value);
void glUniform3iv(    GLint location, GLsizei count, const GLint *value);
void glUniform4iv(    GLint location, GLsizei count, const GLint *value);

void glUniformMatrix2fv(    GLint location,GLsizei countM,GLboolean transpose,const GLfloat *valueM);
void glUniformMatrix3fv(    GLint location,GLsizei countM,GLboolean transpose,const GLfloat *valueM);
void glUniformMatrix4fv(    GLint location,GLsizei countM,GLboolean transpose, const GLfloat *valueM);

参数

location

指定要修改的统一变量的位置。

v0,v1,v2,v3

指定要用于指定统一变量的新值。

count

指定要修改的元素数。 如果目标统一变量不是数组,则此值应为1;如果是数组,则应为1或更大。

value

指定指向将用于更新指定统一变量的count值数组的指针。

countM

指定要修改的矩阵数。 如果目标统一变量不是矩阵数组,则该值应为1;如果是矩阵数组,则应为1或更大。

transpose

指定在将值加载到统一变量时是否转置矩阵。 必须是GL_FALSE

valueM

指定指向将用于更新指定统一变量的countM值数组的指针。

描述

glUniform修改统一变量或统一变量数组的值。要被修改值的location是由glGetUniformLocation返回的值。glUniform通过调用glUseProgram对程序对象进行操作,该程序对象是当前状态的一部分。

命令glUniform {1 | 2 | 3 | 4} {f | i}用于使用作为参数传递的值来更改由location指定的统一变量的值。命令中指定的数字应该与指定的统一变量的数据类型中的组件数相匹配(例如,1表示float,int,bool; 2表示vec2,ivec2,bvec2等)。后缀f表示正在传递浮点值;后缀i表示正在传递整数值,并且此类型还应与指定的统一变量的数据类型匹配。应使用此函数的i变体为定义为int,ivec2,ivec3,ivec4或这些变量的数组提供统一变量的值。f变量应该用于为float,vec2,vec3,vec4或这些类型的数组的统一变量提供值。 i或f变体可用于为bool,bvec2,bvec3,bvec4或这些的数组的均匀变量提供值。如果输入值为0或0.0f,则uniform变量将设置为false,否则将设置为true。

程序对象成功链接时,程序对象中定义的所有活动统一变量都初始化为0。它们保留通过调用glUniform分配给它们的值,直到程序对象上的下一个成功链接操作再次初始化为0时为止。

命令glUniform {1 | 2 | 3 | 4} {f | i} v可用于修改单个统一变量或统一变量数组。这些命令将个数和指针传递给要加载到统一变量或统一变量数组中的值。如果修改单个统一变量的值,则应使用1的计数,并且可以使用1或更大的计数来修改整个数组或数组的一部分。当从均匀变量数组中的任意位置m开始加载n个元素时,数组中的元素m + n-1将被替换为新值。如果m + n - 1大于统一变量数组的大小,则将忽略超出数组末尾的所有数组元素的值。命令名称中指定的数字表示每个元素的值的组件数,它应该与指定的统一变量的数据类型中的组件数相匹配(例如,1表示float,int,bool; 2表示vec2,ivec2,bvec2等)。命令名称中指定的数据类型必须与指定的统一变量的数据类型匹配,如前面对glUniform {1 | 2 | 3 | 4} {f | i}所述。

对于统一变量数组,数组的每个元素都被认为是命令名称中指示的类型(例如,glUniform3fglUniform3fv可用于加载vec3类型的统一变量数组)。要修改的统一变量数组的元素数由count指定

命令glUniformMatrix {2 | 3 | 4} fv用于修改矩阵或矩阵数组。命令名称中的数字被解释为矩阵的维度。数字2表示2×2矩阵(即4个值),数字3表示3×3矩阵(即9个值),数字4表示4×4矩阵(即16个值)。假设每个矩阵按列主要顺序提供。 countM参数表示要传递的矩阵数。如果修改单个矩阵的值,则应使用计数1,并且可以使用大于1的计数来修改矩阵数组.

注意

glUniform1iglUniform1iv是唯一可用于加载定义为采样器类型的统一变量的两个函数。使用任何其他函数加载采样器将导致GL_INVALID_OPERATION错误。

如果count\countM大于1且指示的统一变量不是数组,则会生成GL_INVALID_OPERATION错误,并且指定的统一变量将保持不变。

除了前面的异常之外,如果着色器中定义的统一变量的类型和大小与用于加载其值的命令名称中指定的类型和大小不匹配,则将生成GL_INVALID_OPERATION错误并生成指定的统一变量将保持不变。

如果location是一个非-1的值,并且它不表示当前程序对象中的有效统一变量位置,则将生成错误,并且不会对当前程序对象的统一变量存储进行任何更改。如果location等于-1,则将默认忽略传入的数据,并且不会更改指定的统一变量。

错误

GL_INVALID_OPERATION:没有当前程序对象

GL_INVALID_OPERATION:着色器中声明的统一变量的大小与glUniform命令指示的大小不匹配

GL_INVALID_OPERATION :如果此函数的整数变量之一用于加载float,vec2,vec3,vec4或其数组的统一变量,或者如果使用此函数的某个浮点变量来加载统一int,ivec2,ivec3或ivec4类型的变量,或者这些变量的数组。

GL_INVALID_VALUEcount<0

GL_INVALID_VALUEtranspose不是GL_FALSE

GL_INVALID_OPERATION:count大于1且指示的统一变量不是数组变量

GL_INVALID_OPERATION:使用glUniform1iglUniform1iv之外的命令加载采样器

相关Gets

glGet 参数GL_CURRENT_PROGRAM

glGetActiveUniform 带有程序对象的句柄和活动的统一变量的索引

glGetUniform 程序对象的句柄和统一变量的位置

glGetUniformLocation 包含程序对象的句柄和统一变量的名称

另见

glLinkProgramglUseProgram

版权

https://www.khronos.org/registry/OpenGL-Refpages/es2.0/xhtml/glUniform.xml

https://blog.csdn.net/flycatdeng

Copyright © 1991-2006 Silicon Graphics, Inc.本文档的许可是根据SGI Free Software B License.详见http://oss.sgi.com/projects/FreeB/.

Android,OpenGL ES,图形学
package com.android.example.cameraappxjava.util; import android.graphics.ImageFormat; import android.media.Image; import android.opengl.GLES20; import android.opengl.GLSurfaceView; import android.util.Log; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; /** * GLES 2.0 相机渲染器:处理 YUV_420_888 预览 * */ public class CameraGLRenderer implements GLSurfaceView.Renderer { private static final String TAG = "CameraGLRenderer"; private static final int TEXTURE_COUNT = 3; // Y/U/V 3个纹理(GLES 2.0 支持) // -------------------------- 1. GLES 2.0 兼容配置(无任何不支持API-------------------------- /** * 顶点着色器(GLES 2.0 标准语法,必加精度声明) */ private static final String VERTEX_SHADER = "attribute vec4 vPosition;\n" + // 顶点坐标(输入) "attribute vec2 vTexCoord;\n" + // 纹理坐标(输入) "varying vec2 texCoord;\n" + // 传递纹理坐标到片段着色器 "void main() {\n" + " gl_Position = vPosition;\n" + // 全屏顶点位置(-1~1 覆盖屏幕) " texCoord = vTexCoord;\n" + // 传递纹理坐标 "}"; /** * 片段着色器(GLES 2.0 兼容:用 GL_LUMINANCE 单通道格式,无 GL_RED) */ private static final String FRAGMENT_SHADER = "precision mediump float;\n" + // GLES 2.0 必须声明精度(中等精度平衡性能) "varying vec2 texCoord;\n" + // 从顶点着色器接收的纹理坐标 "uniform sampler2D yTex;\n" + // Y通道纹理采样器(纹理单元0) "uniform sampler2D uTex;\n" + // U通道纹理采样器(纹理单元1) "uniform sampler2D vTex;\n" + // V通道纹理采样器(纹理单元2) "void main() {\n" + // GLES 2.0 兼容:读取 GL_LUMINANCE 纹理的 r 通道(亮度值) " float y = texture2D(yTex, texCoord).r;\n" + " float u = texture2D(uTex, texCoord).r - 0.5;\n" + // U/V 偏移 0.5(YUV 标准) " float v = texture2D(vTex, texCoord).r - 0.5;\n" + // BT.601 YUV转RGB 公式(手机相机通用,避免偏色) " float r = y + 1.402 * v;\n" + " float g = y - 0.34414 * u - 0.71414 * v;\n" + " float b = y + 1.772 * u;\n" + // 限制 RGB 范围 0~1(避免颜色溢出,GLES 2.0 支持 clamp 函数) " r = clamp(r, 0.0, 1.0);\n" + " g = clamp(g, 0.0, 1.0);\n" + " b = clamp(b, 0.0, 1.0);\n" + " gl_FragColor = vec4(r, g, b, 1.0);\n" + // 输出 RGB 颜色(不透明) "}"; /** * 全屏顶点坐标(GLES 2.0 标准坐标,顺序:左上→左下→右上→右下) */ private static final float[] VERTEX_COORDS = { -1.0f, 1.0f, 0.0f, // 左上 -1.0f, -1.0f, 0.0f, // 左下 1.0f, 1.0f, 0.0f, // 右上 1.0f, -1.0f, 0.0f // 右下 }; /** * 纹理坐标(GLES 2.0 兼容,适配竖屏预览,解决画面颠倒) * 映射规则:纹理坐标 → 屏幕坐标(确保竖屏显示正常) */ private static final float[] TEX_COORDS = { 0.0f, 1.0f, // 纹理左上 → 屏幕左上 1.0f, 1.0f, // 纹理左下 → 屏幕左下 0.0f, 0.0f, // 纹理右上 → 屏幕右上 1.0f, 0.0f // 纹理右下 → 屏幕右下 }; // -------------------------- 2. 动态变量(新增:手动记录纹理尺寸,替代GL查询) -------------------------- private int mShaderProgram; // GLES 2.0 着色器程序ID private int[] mTextureIds = new int[TEXTURE_COUNT]; // Y/U/V 纹理ID(GPU资源) private FloatBuffer mVertexBuffer; // 顶点坐标缓冲区(GLES 2.0 要求Buffer格式) private FloatBuffer mTexBuffer; // 纹理坐标缓冲区(GLES 2.0 要求Buffer格式) private int mViewWidth, mViewHeight; // GLSurfaceView 宽高(渲染视口尺寸) // 关键:手动记录 Y/U/V 纹理的宽高(替代 GLES 2.0 不支持的 glGetTexLevelParameteriv) private int mYTexWidth = 0, mYTexHeight = 0; // Y纹理尺寸 private int mUTexWidth = 0, mUTexHeight = 0; // U纹理尺寸(Y的1/2) private int mVTexWidth = 0, mVTexHeight = 0; // V纹理尺寸(Y的1/2) // YUV 数据线程安全管理(避免相机线程与渲染线程竞争) private final Object mYuvLock = new Object(); private Image mPendingImage; // 待处理的相机Image(从Camera2接收) private byte[] mYData, mUData, mVData; // 提取后的 Y/U/V 字节数据 private int mYuvWidth, mYuvHeight; // 相机输出的 YUV 帧宽高 // -------------------------- 3. 对外接口(无修改,直接复用) -------------------------- /** * 设置相机预览Image(线程安全,GLES 2.0/3.0 通用) * * @param image 相机输出的 YUV_420_888 格式Image(必须关闭,避免内存泄漏) */ public void setYUVData(Image image) { Log.w(TAG, "调用setYUVData方法"); if (image == null || image.getFormat() != ImageFormat.YUV_420_888) { Log.w(TAG, "无效Image:格式非 YUV_420_888 或 Image为空"); if (image != null) image.close(); // 必须关闭,避免相机缓冲区泄漏 return; } synchronized (mYuvLock) { // 先关闭之前未处理的Image(防止缓冲区堆积导致卡顿) if (mPendingImage != null) { mPendingImage.close(); Log.d(TAG, "关闭未处理的PendingImage,避免内存泄漏"); } mPendingImage = image; // 存储新的待处理Image } } /** * 释放所有资源(Activity/Fragment 销毁时调用,避免内存泄漏) */ public void release() { synchronized (mYuvLock) { // 1. 关闭待处理的Image if (mPendingImage != null) { mPendingImage.close(); mPendingImage = null; } // 2. 释放CPU端 YUV 数据 mYData = null; mUData = null; mVData = null; mYuvWidth = 0; mYuvHeight = 0; // 3. 重置手动记录的纹理尺寸 mYTexWidth = mYTexHeight = 0; mUTexWidth = mUTexHeight = 0; mVTexWidth = mVTexHeight = 0; } // 4. 释放 GLES 2.0 GPU 资源(纹理+着色器程序) if (mTextureIds != null) { GLES20.glDeleteTextures(TEXTURE_COUNT, mTextureIds, 0); mTextureIds = null; } if (mShaderProgram != 0) { GLES20.glDeleteProgram(mShaderProgram); mShaderProgram = 0; } // 5. 释放缓冲区(帮助GC回收) mVertexBuffer = null; mTexBuffer = null; Log.d(TAG, "所有资源释放完成(GLES 2.0 兼容)"); } // -------------------------- 4. GLES 2.0 生命周期回调(无任何不支持API-------------------------- /** * 初始化回调:GLSurfaceView 首次创建时调用(仅1次) * 作用:初始化OpenGL环境、编译着色器、创建纹理、准备坐标缓冲区 */ @Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { Log.d(TAG, "onSurfaceCreated(GLES 2.0):初始化OpenGL环境"); // GLES 2.0 基础配置:禁用混合(避免透明层干扰预览)、黑色背景 GLES20.glDisable(GLES20.GL_BLEND); GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // 准备坐标缓冲区(GLES 2.0 仅支持 Buffer 格式,不支持直接用数组) mVertexBuffer = createFloatBuffer(VERTEX_COORDS); mTexBuffer = createFloatBuffer(TEX_COORDS); // 编译 GLES 2.0 着色器程序(创建渲染"画笔") mShaderProgram = compileShaderProgram(VERTEX_SHADER, FRAGMENT_SHADER); if (mShaderProgram == 0) { Log.e(TAG, "着色器程序创建失败(GLES 2.0),预览不可用"); return; } // 创建 Y/U/V 3个纹理(GLES 2.0 2D纹理),配置基础参数 GLES20.glGenTextures(TEXTURE_COUNT, mTextureIds, 0); initTexture(mTextureIds[0]); // 初始化 Y 纹理 initTexture(mTextureIds[1]); // 初始化 U 纹理 initTexture(mTextureIds[2]); // 初始化 V 纹理 Log.d(TAG, "GLES 2.0 初始化完成,纹理ID:Y=" + mTextureIds[0] + ", U=" + mTextureIds[1] + ", V=" + mTextureIds[2]); } /** * 尺寸变化回调:GLSurfaceView 宽高改变时调用(如屏幕旋转) * 作用:设置渲染视口(画面显示范围),确保全屏渲染 */ @Override public void onSurfaceChanged(GL10 gl, int width, int height) { mViewWidth = width; mViewHeight = height; // GLES 2.0 设置视口:渲染范围 = GLSurfaceView 全屏(左上角(0,0),宽高=View宽高) GLES20.glViewport(0, 0, width, height); Log.d(TAG, "onSurfaceChanged(GLES 2.0):视口尺寸=" + width + "x" + height); } /** * 帧渲染回调:每帧调用1次(渲染线程执行,核心渲染逻辑) * 流程:处理待处理Image → 上传YUV数据到纹理 → 绑定着色器 → 执行渲染 */ @Override public void onDrawFrame(GL10 gl) { Log.e(TAG, "调用onDrawFrame方法"); // 1. 处理待处理的Image(线程安全,提取Y/U/V数据) boolean hasNewData = processPendingImage(); if (!hasNewData) { // 无新数据:清除屏幕为黑色,避免显示上一帧残留 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); return; } // 2. 清除上一帧画面(避免画面重叠) GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); // 3. 激活 GLES 2.0 着色器程序(使用"画笔") GLES20.glUseProgram(mShaderProgram); // 4. 上传 Y/U/V 数据到对应纹理(手动判断纹理尺寸,替代GL查询) uploadTexture(mTextureIds[0], mYData, mYuvWidth, mYuvHeight, true); // Y纹理 uploadTexture(mTextureIds[1], mUData, mYuvWidth / 2, mYuvHeight / 2, false); // U纹理(1/2尺寸) uploadTexture(mTextureIds[2], mVData, mYuvWidth / 2, mYuvHeight / 2, false); // V纹理(1/2尺寸) // 5. 绑定纹理到着色器采样器(让"画笔"找到"画布") bindTextureToSampler(); // 6. 传递顶点/纹理坐标(告诉"画笔"画在哪里) passVertexAndTexCoord(); // 7. 执行渲染:GLES 2.0 支持 GL_TRIANGLE_STRIP,4个顶点画2个三角形覆盖全屏 GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, VERTEX_COORDS.length / 3); // 8. 禁用顶点/纹理坐标输入(避免后续渲染干扰) int vPositionLoc = GLES20.glGetAttribLocation(mShaderProgram, "vPosition"); int vTexCoordLoc = GLES20.glGetAttribLocation(mShaderProgram, "vTexCoord"); GLES20.glDisableVertexAttribArray(vPositionLoc); GLES20.glDisableVertexAttribArray(vTexCoordLoc); } private void passVertexAndTexCoord() { int vPositionLoc = GLES20.glGetAttribLocation(mShaderProgram, "vPosition"); GLES20.glEnableVertexAttribArray(vPositionLoc); GLES20.glVertexAttribPointer( vPositionLoc, 3, GLES20.GL_FLOAT, false, 3 * 4, mVertexBuffer ); int vTexCoordLoc = GLES20.glGetAttribLocation(mShaderProgram, "vTexCoord"); GLES20.glEnableVertexAttribArray(vTexCoordLoc); GLES20.glVertexAttribPointer( vTexCoordLoc, 2, GLES20.GL_FLOAT, false, 2 * 4, mTexBuffer ); } // -------------------------- 5. GLES 2.0 辅助方法(无任何不支持API-------------------------- /** * 创建 FloatBuffer:将 Java float 数组转为 GLES 2.0 支持的 Buffer 格式 * * @param array 原始 float 数组(顶点/纹理坐标) * @return GLES 2.0 可识别的 FloatBuffer */ private FloatBuffer createFloatBuffer(float[] array) { if (array == null || array.length == 0) return null; // 1. 分配直接内存(避免JVM GC移动,提升OpenGL访问效率) ByteBuffer byteBuffer = ByteBuffer.allocateDirect(array.length * 4); // 1float=4字节 // 2. 设置字节序(必须与硬件一致,否则数据错乱) byteBuffer.order(ByteOrder.nativeOrder()); // 3. 转换为 FloatBuffer 并写入数据 FloatBuffer floatBuffer = byteBuffer.asFloatBuffer(); floatBuffer.put(array); // 4. 重置读指针(从缓冲区开头开始读取) floatBuffer.position(0); return floatBuffer; } /** * 编译 GLES 2.0 着色器程序:编译顶点+片段着色器,链接为可执行程序 * * @param vertexCode 顶点着色器代码 * @param fragmentCode 片段着色器代码 * @return 着色器程序ID(0 表示失败) */ private int compileShaderProgram(String vertexCode, String fragmentCode) { // 1. 编译顶点着色器(GLES 2.0) int vertexShader = compileSingleShader(GLES20.GL_VERTEX_SHADER, vertexCode); if (vertexShader == 0) return 0; // 2. 编译片段着色器(GLES 2.0) int fragmentShader = compileSingleShader(GLES20.GL_FRAGMENT_SHADER, fragmentCode); if (fragmentShader == 0) { GLES20.glDeleteShader(vertexShader); // 清理已编译的顶点着色器 return 0; } // 3. 链接着色器程序(GLES 2.0) int program = GLES20.glCreateProgram(); GLES20.glAttachShader(program, vertexShader); // 绑定顶点着色器 GLES20.glAttachShader(program, fragmentShader); // 绑定片段着色器 GLES20.glLinkProgram(program); // 执行链接 // 4. 检查链接结果(GLES 2.0) int[] linkStatus = new int[1]; GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0); if (linkStatus[0] != GLES20.GL_TRUE) { Log.e(TAG, "着色器链接失败(GLES 2.0):" + GLES20.glGetProgramInfoLog(program)); GLES20.glDeleteProgram(program); // 清理无效程序 program = 0; } // 5. 清理中间着色器(程序已链接,单个着色器可删除) GLES20.glDeleteShader(vertexShader); GLES20.glDeleteShader(fragmentShader); return program; } /** * 编译单个 GLES 2.0 着色器:编译顶点/片段着色器代码 * * @param shaderType 着色器类型(GL_VERTEX_SHADER / GL_FRAGMENT_SHADER) * @param shaderCode 着色器代码 * @return 着色器ID(0 表示失败) */ private int compileSingleShader(int shaderType, String shaderCode) { // 1. 创建着色器对象(GLES 2.0) int shader = GLES20.glCreateShader(shaderType); if (shader == 0) { Log.e(TAG, "创建着色器失败(GLES 2.0),类型=" + (shaderType == GLES20.GL_VERTEX_SHADER ? "顶点" : "片段")); return 0; } // 2. 绑定着色器代码并编译(GLES 2.0GLES20.glShaderSource(shader, shaderCode); GLES20.glCompileShader(shader); // 3. 检查编译结果(GLES 2.0) int[] compileStatus = new int[1]; GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compileStatus, 0); if (compileStatus[0] != GLES20.GL_TRUE) { Log.e(TAG, (shaderType == GLES20.GL_VERTEX_SHADER ? "顶点" : "片段") + "着色器编译失败(GLES 2.0):" + GLES20.glGetShaderInfoLog(shader)); GLES20.glDeleteShader(shader); // 清理无效着色器 shader = 0; } return shader; } /** * 初始化 GLES 2.0 纹理参数:配置过滤、边缘处理,确保画面清晰无重复 * * @param textureId 纹理ID(Y/U/V 纹理) */ private void initTexture(int textureId) { if (textureId == 0) return; // 绑定纹理(选中GPU"画布",GLES 2.0 必须先绑定再配置) GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId); // 1. 纹理过滤:缩小时线性过滤(画面平滑,避免锯齿) GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); // 2. 纹理过滤:放大时线性过滤(画面平滑,避免像素块) GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); // 3. 纹理边缘:水平方向超出范围时"夹紧"(不重复显示,避免边缘错乱) GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); // 4. 纹理边缘:垂直方向超出范围时"夹紧" GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); // 解绑纹理(避免后续误操作其他纹理) GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0); } /** * 处理待处理Image:从 mPendingImage 提取 Y/U/V 数据(线程安全) * * @return true=有新数据,false=无新数据 */ private boolean processPendingImage() { Image image = null; synchronized (mYuvLock) { if (mPendingImage == null) { return false; // 无待处理数据 } // 取出待处理Image(释放锁,避免长时间占用) image = mPendingImage; mPendingImage = null; } try { // 1. 提取Image的宽高和Planes(YUV_420_888 格式固定3个Planes) mYuvWidth = image.getWidth(); mYuvHeight = image.getHeight(); Log.e(TAG, "YUV的宽高:"+mYuvWidth+"×"+mYuvHeight); Image.Plane[] planes = image.getPlanes(); if (planes.length < 3) { Log.e(TAG, "Image Planes 数量不足3,无法提取 YUV 数据"); return false; } // 2. 提取 Y 通道数据(Plane[0]:Y通道,无交错) ByteBuffer yBuffer = planes[0].getBuffer(); mYData = byteBufferToByteArray(yBuffer); // 3. 提取 U/V 通道数据(区分 Semi-Planar 和 Planar 模式) if (planes[1].getPixelStride() == 2) { // 模式1:Semi-Planar(UV 交错存储在 Plane[1],Plane[2] 无数据) ByteBuffer uvBuffer = planes[1].getBuffer(); int uvLength = uvBuffer.remaining() / 2; // UV 总长度 = Y 长度 / 2 mUData = new byte[uvLength]; mVData = new byte[uvLength]; // 提取 U(偶数索引)和 V(奇数索引) for (int i = 0; i < uvLength; i++) { mUData[i] = uvBuffer.get(i * 2); // U:第02、4...字节 mVData[i] = uvBuffer.get(i * 2 + 1); // V:第1、3、5...字节 } } else { // 模式2:Planar(UV 分别存储在 Plane[1] 和 Plane[2],无交错) ByteBuffer uBuffer = planes[1].getBuffer(); ByteBuffer vBuffer = planes[2].getBuffer(); mUData = byteBufferToByteArray(uBuffer); mVData = byteBufferToByteArray(vBuffer); } // 4. 验证 YUV 数据长度(避免后续渲染错误) int expectedYLength = mYuvWidth * mYuvHeight; int expectedUVLength = (mYuvWidth / 2) * (mYuvHeight / 2); if (mYData.length != expectedYLength || mUData.length != expectedUVLength || mVData.length != expectedUVLength) { Log.w(TAG, "YUV 数据长度不匹配,重置为正确长度"); mYData = new byte[expectedYLength]; mUData = new byte[expectedUVLength]; mVData = new byte[expectedUVLength]; return false; } Log.d(TAG, "处理 Image 完成(GLES 2.0):YUV 尺寸=" + mYuvWidth + "x" + mYuvHeight + ",数据长度 Y=" + mYData.length + ", U=" + mUData.length); return true; } catch (Exception e) { Log.e(TAG, "处理 Image 异常(GLES 2.0):" + e.getMessage(), e); return false; } finally { // 必须关闭 Image(释放相机缓冲区,避免卡顿的核心!) if (image != null) { image.close(); } } } private byte[] byteBufferToByteArray(ByteBuffer buffer) { if (buffer == null | buffer.remaining() == 0) return new byte[0]; int originalPos = buffer.position(); byte[] data = new byte[buffer.remaining()]; buffer.get(data); buffer.position(originalPos); return data; } /** * 上传数据到 GLES 2.0 纹理(核心:手动记录纹理尺寸,替代 GL 查询) * * @param textureId 纹理ID * @param data 待上传的字节数据(Y/U/V) * @param width 纹理宽度 * @param height 纹理高度 * @param isYTexture 是否为 Y 纹理(用于区分尺寸记录变量) */ private void uploadTexture(int textureId, byte[] data, int width, int height, boolean isYTexture) { if (textureId == 0 || data == null || width <= 0 || height <= 0) { Log.w(TAG, "上传纹理参数无效(GLES 2.0):textureId=" + textureId + ", width=" + width + ", height=" + height); return; } // 绑定纹理(GLES 2.0 必须先绑定再操作) GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId); // 关键:设置像素对齐为 1(YUV 数据无字节对齐,避免数据错位) GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1); // 手动判断纹理是否已创建(替代 GLES 2.0 不支持的 glGetTexLevelParameteriv) boolean isTextureCreated = false; if (isYTexture) { isTextureCreated = (mYTexWidth == width && mYTexHeight == height); } else { // U/V 纹理尺寸相同,共用一套判断 isTextureCreated = (mUTexWidth == width && mUTexHeight == height); } ByteBuffer dataBuffer = ByteBuffer.wrap(data); if (!isTextureCreated) { // 首次创建纹理:调用 glTexImage2D(分配GPU内存) GLES20.glTexImage2D( GLES20.GL_TEXTURE_2D, 0, // 2D纹理,基础层级(固定为0GLES20.GL_LUMINANCE, // GLES 2.0 核心:单通道亮度格式 width, height, 0, // 纹理宽高,边界宽度(必须为0GLES20.GL_LUMINANCE, // 数据格式:与内部格式一致 GLES20.GL_UNSIGNED_BYTE, // 数据类型:无符号字节(YUV 数据类型) dataBuffer // 待上传的 Y/U/V 数据 ); // 更新手动记录的纹理尺寸(下次判断用) if (isYTexture) { mYTexWidth = width; mYTexHeight = height; Log.d(TAG, "创建 Y 纹理(GLES 2.0):尺寸=" + width + "x" + height); } else { mUTexWidth = width; mUTexHeight = height; Log.d(TAG, "创建 U/V 纹理(GLES 2.0):尺寸=" + width + "x" + height); } } else { // 复用纹理:调用 glTexSubImage2D(仅更新数据,不重新分配GPU内存,效率更高) GLES20.glTexSubImage2D( GLES20.GL_TEXTURE_2D, 0, // 2D纹理,基础层级 0, 0, // 数据起始坐标(x=0, y=0,全屏更新) width, height, // 数据宽高(与纹理尺寸一致) GLES20.GL_LUMINANCE, // 数据格式:与创建时一致 GLES20.GL_UNSIGNED_BYTE, // 数据类型:与创建时一致 dataBuffer // 待更新的 Y/U/V 数据 ); } // 解绑纹理(避免后续误操作) GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0); } /** * 绑定纹理到 GLES 2.0 着色器采样器:将 Y/U/V 纹理与着色器的 uniform 变量关联 */ private void bindTextureToSampler() { // 1. 绑定 Y 纹理到采样器 yTex(纹理单元0GLES20.glActiveTexture(GLES20.GL_TEXTURE0); // 激活纹理单元0GLES 2.0 必须先激活) GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIds[0]); // 绑定 Y 纹理 // 关联采样器:将纹理单元0 与 着色器的 yTex 变量绑定 int yTexLoc = GLES20.glGetUniformLocation(mShaderProgram, "yTex"); GLES20.glUniform1i(yTexLoc, 0); // 2. 绑定 U 纹理到采样器 uTex(纹理单元1) GLES20.glActiveTexture(GLES20.GL_TEXTURE1); // 激活纹理单元1 GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIds[1]); // 绑定 U 纹理 int uTexLoc = GLES20.glGetUniformLocation(mShaderProgram, "uTex"); GLES20.glUniform1i(uTexLoc, 1); // 3. 绑定 V 纹理到采样器 vTex(纹理单元2GLES20.glActiveTexture(GLES20.GL_TEXTURE2); //激活纹理单元2 GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIds[2]); int vTexLoc = GLES20.glGetUniformLocation(mShaderProgram, "vTex"); GLES20.glUniform1i(vTexLoc, 2); // 添加错误检查 if (yTexLoc == -1 || uTexLoc == -1 || vTexLoc == -1) { Log.e(TAG, "纹理采样器绑定失败: " + "yTex=" + yTexLoc + " uTex=" + uTexLoc + " vTex=" + vTexLoc); } } } 我给代码发给你,结合这两部分代码你来找出问题
最新发布
09-23
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值