OpenGL ES API(绑定shader)

本文介绍如何使用OpenGLES创建和管理Shader,包括创建内存、编写GLSL shader、触发GPU编译、创建和使用program等关键步骤。此外还介绍了如何处理编译错误和优化加载流程。
部署运行你感兴趣的模型镜像

OpenGL ES创建一块内存,使用GLSL编写shader,然后触发GPU的shader编译器进行编译。编译好的shader由OpenGL ES控制输入,然后经过shader生成的结果,会被OpenGL ES pipeline的后半段操作使用,最终结果保存在绘制buffer中。

OpenGL ES API

只有当EGL给GL 创建好环境,也就是创建好一套适合 GL API 运行的 surface 和 context,并将它们 makecurrent 之后,GL 的 API 才可以被正常 调用

GLenum glGetError(void);

返回当前thread如果GL的Api出错,最近一个错误对应的错误代码。
输入:空
输出:错误代码
(GL_NO_ERROR :说明自上次执行glGetError以来,所有的GL API运行正常,只要当glGetError返回GL_OUT_OF_MEMORY ,即内存不够,剩下的GL结果会被认为undefine。其他时候,剩余api会忽略错误,继续执行)

GLuint glCreateShader(GLenum shaderType);

创建一个shader的handle(然后把GLSL编写的shader source传入这个handle,才能触发GPU编译shader等后续操作)
输入:shader type(只支持GL_VERTEX_SHADER 和GL_FRAGMENT_SHADER,使用其他token,会报GL_INVALID_ENUM 错误)
输出:非负数字。指定返回的shader object(0为失败,刚被创建的shader object 为空,可存放构建shader所需的shader source)

void glShaderSource(GLuint shader, GLsizei count, const GLchar **string, const GLint *length);

往一个shader的handle(shader object)传递shader source
输入:第一个参数shader object(glCreateShader创建的非0值),第二个和第三个参数是:string是个由count个指针元素组成的数组,每个数组元素都是一个无终结的字符串。(GLSL编写的shader将写在这些字符串中。可以把一些格式化的内容如precision lowp float等保存在一个字符串中国,其他内容保存在另一个字符串中,这样无需在每个fragment shader中在编写一边格式化内容)第四个参数也是一个数组,对应string,用于限制string数组中对应的数组元素的字符串长度。(若为负,对应的字符串长度无限长,若数组为NULL吗,则string中所有字符串无限长)
输出:空

void glCompileShader(GLuint shader);

把一个已经包含shader source内容的shader发给GPU进行编译(一般程序是在CPU进行编译)
输入:shader object
输出:空 (但每个shader object有个bool值Compile_status,该值会根据编译结果进行修改,编译成功为true,否则false。该值可通过GetShaderiv这个api查询。每个shader object有个information log,是string,在每次编译会被重写,包含编译信息,可通过glGetShaderInfoLog查看)

void glShaderBinary(GLsizei n, const GLuint *shaders, GLenum binaryformat, const void *binary, GLsizei length)

某些 GPU 并不支持 shader compiler,可通过 glGet 输入 GL_SHADER_COMPILER 这个参数来查询。若不支持的话,那 么 glshadersource 、 glCompilerShader 、 glGetShaderPrecisionFormat 、 glReleaseShaderCompiler 会报 错。这时,通常GPU会支持glShaderBinary,来load一个编译好的shader binary。节省GPU编写shader的时间
输入:第一个和第二个参数:shaders是包含n个shader object的数组。若存放了两个相同类型的shader(如两个VS shader),会报错。第三个参数:预编译shader的格式。(支持的格式可由参数NUM_SHADER_BINARY_FORMATS 和 SHADER_BINARY_FORMATS 查询到,若GPU不支持glshaderbinary,则查询会报错)shader将会格局binaryformat来进行解码成一个类似编译好的shader,交个GPU。第四个参数:指向预编译shader binary的指针,第五个参数:限定shader binary的长度。
输出:空

GLuint glCreateProgram(void);

创建一个program object。(在一个完整的OpenGL ES的pipeline中,最终交给GPU使用的,一次只能是一组VS和PS,把两个shader放入program对象,再将该对象交给GPU)
输入:空
输出:非负数,指定返回的program object,失败为0 。(与shader object使用同一个namespace,即可被多个共享的context共享)

void glAttachShader(GLuint program,GLuint shader);

把shader关联到program对象
输入:program对象,shader对象(glCreateShader创建的非0值)
输出:空
(一个program只能关联一个同类型的shader,否则报错)

void glDetachShader(GLuint program, GLuint shader);

把shader从program上解绑,以此来替换同类型的shader
输入:program对象和shader对象
输出:空
(如果传入的shader未绑定在传入的program,也会报错)
若一个shader已被被绑定到program上,删除shader,不会被立即删除,而是做一个标记,直到该shader从program解绑才会执行删除操作

void glLinkProgram(GLuint program);

封住program,使其中的VS和PS组成一对,成为一个完整的新个体。
输入:program对象
输出:空
(每个program对象都有一个bool值Link_status,根据link的结果进行修改,链接成功,且创建了一个有效可执行文件这被设为true,否则false,可通过glGetProgramiv查询,每个program object有个information log在每次link会被重写,包含链接信息,可通过glGetProgramInfoLog查看)
link成功后,所有shader中开发者定义的active的uniform都会被初始化为0,然后被分配一个地址(可通过glGetUniformLocation 获取地址)shader中attribute若为被指定的index绑定,也会分配一个index
当 program 被 link 之后,该 program 对应的 shader 可以被修改、重新编译、 detach、attach 其他 shader 等操作,而这些操作不会影响 link 的 log 以及 program 的可执行文件。

void glUseProgram(GLuint program);

指定使用program(一个程序同一时间只能使用一个program),即把program的可执行文件当做当前rendering state的一部分。
输入:program对象。
输出:空
如果被使用的program被relink成功,则新生村的可执行文件会替换掉当前rendering state中使用的可执行文件。若relink失败,做linkstatus为false,当前rendering state使用之前可用的可执行文件,知道另一个program被使用。

void glDeleteProgram(GLuint program);

删除program
输入:program对象
输出:空
(若program不被任何GL Context使用,则立即删除,否则,做标记,知道GL context不在使用,执行删除操作。可通过glGetProgramiv 传入GL_DELETE_STATUS 查询delete标记,删除后,所有的shader都会被detach)

void glDeleteShader(GLuint shader);

删除shader
输入:shader对象
输出:空
(删除机制同解绑,通过glGetShaderiv 传入GL_DELETE_STATUS 查询delete标记)

void glReleaseShaderCompiler(void);

释放ShaderCompiler相关资源。在一段时间内不会编译shader,可以释放shader编译器相关资源。
输入:空
输出:空

3.0

void glGetProgramBinary(GLuint program, GLsizei bufsize, GLsizei *length, GLenum *binaryFormat, void *binary);

将已经生成的program生成program binary。下次使用,不需要在生成shader,编译shader,link program等,可直接通过program binary生成program
输入:第一个参数:已经生成program。第二个参数:存放binary的最大尺寸。第五个参数:存放binary。第三个参数:存放实际存放的binary的尺寸。第四个参数:存放实际存放binary的格式
输出:空

void glProgramBinary(GLuint program, GLenum binaryFormat, const void *binary, GLsizei length);

将上个函数获取到的program binary生成program
输入:第一个参数:指定将生成的program,第二三四为glGetProgramBinary获取到的参数。(length也可通过glGetProgramiv传入PROGRAM_BINARY_LENGTH 获取在这里插入代码片

您可能感兴趣的与本文相关的镜像

Wan2.2-I2V-A14B

Wan2.2-I2V-A14B

图生视频
Wan2.2

Wan2.2是由通义万相开源高效文本到视频生成模型,是有​50亿参数的轻量级视频生成模型,专为快速内容创作优化。支持480P视频生成,具备优秀的时序连贯性和运动推理能力

<think> 嗯,用户需要的是在OpenGL ES 3.0的shader中将NV12格式转换为RGBA的具体实现方法。参考站内提供的引用资料,虽然给出了NV21和NV12的部分shader代码,但用户明确需要的是NV12转RGBA的完整方案。 用户可能正在开发移动端的视频处理应用,因为NV12是安卓平台常见的摄像机输出格式,而RGBA是OpenGL渲染需要的输入格式。从技术细节来看,NV12属于YUV420SP格式,其内存布局特点是一个平面存储Y分量,另一个平面交错存储U/V分量(先U后V)。 参考引用[3]中的NV12采样代码很有价值,但需要反向转换。关键点在于:1)YUV到RGB的色彩空间转换公式要准确;2)双平面纹理的正确采样方式。OpenGL ES 3.0支持采样器类型精确指定纹理格式,这点比ES 2.0更方便。 考虑到性能优化,应该采用双纹理对象分别绑定Y和UV平面,避免在shader中做坐标偏移计算。色彩转换矩阵采用BT.601标准(移动设备常用),但实际可能需要根据输入源调整。另外注意到用户提供的引用[2]提到"可能不是最优方式",所以实现时要考虑fragment shader的指令优化。 需要特别提醒用户注意UV平面的采样精度问题,因为UV分量在NV12中是下采样存储的,采样时需要处理半个像素偏移。引用[3]代码里"vTextureCoord + vec2(0.0, 0.5)"这种写法在ES 3.0里可能需要调整,建议改用textureLod控制细节。 </think> 在 OpenGL ES 3.0 中实现 NV12 转 RGBA 的 Shader,核心在于正确采样 Y 分量和 UV 分量平面,并应用 YUV 到 RGB 的色彩空间转换公式。以下是完整实现方法: --- ### **1. 核心步骤** #### **1.1 纹理准备** * **Y 平面:** 绑定为 `GL_LUMINANCE` 格式的 **2D 纹理** (`sTextureY`),存储亮度信息。 * **UV 平面 (CrCb):** 绑定为 `GL_LUMINANCE_ALPHA` 格式的 **2D 纹理** (`sTextureUV`),存储色度信息(U 在 R/G 通道,V 在 B/A 通道)。 #### **1.2 Shader 实现 (Fragment Shader)** ```glsl #version 300 es precision mediump float; in vec2 vTextureCoord; // 纹理坐标 (0.0 - 1.0) out vec4 fragColor; // 输出 RGBA uniform sampler2D sTextureY; // Y 分量纹理 uniform sampler2D sTextureUV; // UV 分量纹理 // YUV 转 RGB 矩阵 (BT.601 标准) const mat3 yuv2rgb = mat3( 1.164, 1.164, 1.164, // 第一列 (影响 R) 0.0, -0.392, 2.017, // 第二列 (影响 G) 1.596, -0.813, 0.0 // 第三列 (影响 B) ); void main() { // 1. 采样 Y 分量 (单通道) float y = texture(sTextureY, vTextureCoord).r; // 2. 采样 UV 分量 (U->r, V->a) vec2 uv = texture(sTextureUV, vTextureCoord).ra; // 根据纹理实际存储方式调整 float u = uv.x - 0.5; // 归一化到 [-0.5, 0.5] float v = uv.y - 0.5; // 3. 应用转换矩阵 (缩放 Y 并偏移 UV) vec3 yuv = vec3(y - 0.0625, u, v); // Y 范围 [0,1] -> [0.0625, 0.9375] vec3 rgb = yuv2rgb * yuv; // 4. 输出 RGBA fragColor = vec4(rgb, 1.0); } ``` > **关键说明:** > * `yuv2rgb` 矩阵基于 BT.601 标准(适用于标清/SDR)。 > * Y 值需要减去 `0.0625` 以补偿范围偏移(YUV 通常范围是 [16, 235])。 > * UV 采样后需减去 `0.5` 使其居中于零(实际范围 [-0.5, 0.5])。 > * `uv` 的 `.ra` 通道需根据 `sTextureUV` 的实际数据布局调整(如 UV 存储在 `.rg`,则改为 `.rg`)。 #### **1.3 CPU 端绑定纹理** ```cpp // 绑定 Y 平面 (Luminance) glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, textureYId); glUniform1i(glGetUniformLocation(program, "sTextureY"), 0); // 绑定 UV 平面 (Luminance_Alpha) glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, textureUVId); glUniform1i(glGetUniformLocation(program, "sTextureUV"), 1); ``` --- ### **2. 完整流程概述** 1. **初始化:** 创建两个 2D 纹理对象 (`textureYId`, `textureUVId`)。 2. **上传数据:** * 将 NV12 的 Y 平面数据传入 `textureYId`(格式 `GL_LUMINANCE`)。 * 将 NV12 的 UV 交错平面数据传入 `textureUVId`(格式 `GL_LUMINANCE_ALPHA`)。 3. **渲染:** 使用包含上述 Fragment Shader 的 GLSL 程序绘制矩形。 4. **纹理采样:** Shader 同时采样 Y 和 UV 纹理,计算 RGB 值。 --- ### **3. 注意事项** 1. **UV 采样坐标缩放:** * UV 平面的宽高通常是 Y 平面的一半(420 下采样)。 * 在 CPU 端上传 UV 数据时,需设置正确的纹理宽高(如 Y 为 1920x1080,UV 应为 960x540)。 * **不需在 Shader 中缩放坐标**,OpenGL 会自动线性采样(`GL_LINEAR` 过滤时)。 2. **性能优化:** * 使用 `GL_R8` 格式存储 Y 平面(ES 3.0 支持)。 * 使用 `GL_RG8` 格式存储 UV 平面(效率高于 `GL_LUMINANCE_ALPHA`)。修改 Shader 中的采样为 `.rg`。 3. **色彩标准:** * 移动端视频常用 **BT.601**(如示例),高清内容可能用 **BT.709**,需替换矩阵系数: ```glsl // BT.709 矩阵 const mat3 yuv2rgb = mat3( 1.164, 1.164, 1.164, 0.0, -0.213, 2.112, 1.793, -0.533, 0.0 ); ``` 4. **精度问题:** * 使用 `mediump` 可能引入色带,对高质量场景可尝试 `highp`(确保设备支持)。 --- ### **示例代码参考** 基于引用资料的优化实现: > 引用[2]提到通过 `sampler2D` 分离采样 Y 和 UV 是可靠方案[^2],引用[3]展示了 UV 交错平面的采样技巧[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值