OpenGL-ES选择性混合:基于GPUImage实现相机帧角点Sparkle渲染咨询
嘿,我来帮你搞定这个在GPUImage检测到的角点上渲染Sparkle效果的需求,结合OpenGL ES的选择性混合技术,咱们一步一步来:
实现方案:GPUImage角点检测 + OpenGL ES选择性混合渲染Sparkle效果
1. 先搞懂选择性混合的核心逻辑
咱们要的是只有Sparkle的发光部分和原相机帧混合,透明区域完全不干扰原画面,所以首先得把OpenGL ES的混合配置弄对:
- 先启用混合:
glEnable(GL_BLEND) - 选合适的混合函数:
- 要是想做常规的半透明叠加,用
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA),Sparkle的alpha通道会控制混合程度,透明部分完全不影响原帧; - 要是Sparkle是发光效果,推荐用加法混合
glBlendFunc(GL_SRC_ALPHA, GL_ONE),这样光斑会更亮,符合闪烁的视觉效果。
- 要是想做常规的半透明叠加,用
2. 坐标转换:把GPUImage的角点坐标转成OpenGL能用的
GPUImage输出的角点Position一般是归一化坐标(范围01,对应图像的左上角到右下角),但OpenGL渲染需要转成设备坐标或者归一化设备坐标(NDC,范围-11),这里给你两种转换方式:
- 转成像素坐标(适合基于屏幕尺寸控制点大小):
let frameSize = imageFramebuffer.size let pixelX = position.x * frameSize.width let pixelY = frameSize.height - position.y * frameSize.height // 注意OpenGL Y轴和UIKit相反,要翻转 - 转成归一化设备坐标(适合直接传入Shader):
let ndcX = position.x * 2.0 - 1.0 let ndcY = -(position.y * 2.0 - 1.0) // 翻转Y轴适配OpenGL坐标系
3. 完整的Sparkle渲染函数实现
结合混合配置和坐标转换,把你的renderSparkles函数补全调整成这样:
public func renderSparkles(_ positions:[Position]) { // 1. 激活当前帧缓冲区,准备渲染 imageFramebuffer.activateFramebufferForRendering() imageFramebuffer.timingStyle = .stillImage // 2. 开启混合和点精灵(用点来渲染Sparkle) glEnable(GLenum(GL_BLEND)) // 这里用加法混合,适合发光闪烁的效果,你可以根据需求换混合函数 glBlendFunc(GLenum(GL_SRC_ALPHA), GLenum(GL_ONE)) glEnable(GLenum(GL_POINT_SPRITE_OES)) glEnable(GLenum(GL_VERTEX_PROGRAM_POINT_SIZE_OES)) // 允许Shader控制点的大小 // 3. 启用Sparkle着色器,恢复之前的Uniform设置 sparklesShader.use() uniformSettings.restoreUniformSettings() // 4. 把帧缓冲区尺寸传给Shader,用来适配不同分辨率下的Sparkle大小 let frameSize = imageFramebuffer.size sparklesShader.setUniformValue(GLfloat(frameSize.width), forKey: "u_frameWidth") sparklesShader.setUniformValue(GLfloat(frameSize.height), forKey: "u_frameHeight") // 5. 准备顶点数据:把所有角点坐标转成OpenGL能用的格式 var vertices = [GLfloat]() for position in positions { // 转成归一化设备坐标(NDC) let ndcX = GLfloat(position.x * 2.0 - 1.0) let ndcY = GLfloat(-(position.y * 2.0 - 1.0)) vertices.append(ndcX) vertices.append(ndcY) } // 6. 绑定顶点缓冲区并绘制 var vertexBuffer: GLuint = 0 glGenBuffers(1, &vertexBuffer) glBindBuffer(GLenum(GL_ARRAY_BUFFER), vertexBuffer) glBufferData(GLenum(GL_ARRAY_BUFFER), vertices.count * MemoryLayout<GLfloat>.stride, vertices, GLenum(GL_STATIC_DRAW)) // 设置顶点属性(对应Shader里的a_position变量) let positionAttrib = sparklesShader.attributeIndex(forAttributeName: "a_position") glEnableVertexAttribArray(GLuint(positionAttrib)) glVertexAttribPointer(GLuint(positionAttrib), 2, GLenum(GL_FLOAT), GLboolean(GL_FALSE), 0, nil) // 绘制所有Sparkle点 glDrawArrays(GLenum(GL_POINTS), 0, GLsizei(positions.count)) // 7. 清理状态,避免影响后续GPUImage的渲染流程 glDisableVertexAttribArray(GLuint(positionAttrib)) glBindBuffer(GLenum(GL_ARRAY_BUFFER), 0) glDeleteBuffers(1, &vertexBuffer) glDisable(GLenum(GL_POINT_SPRITE_OES)) glDisable(GLenum(GL_VERTEX_PROGRAM_POINT_SIZE_OES)) glDisable(GLenum(GL_BLEND)) }
4. Sparkle着色器的关键配置
你的Sparkle顶点和片段着色器要配合上面的代码,才能实现正确的选择性混合:
- 顶点着色器:主要用来控制点的大小,适配不同分辨率:
attribute vec2 a_position; uniform float u_frameWidth; uniform float u_frameHeight; void main() { gl_Position = vec4(a_position, 0.0, 1.0); // 设置点大小,这里以640px宽度为基准,缩放适配其他分辨率 gl_PointSize = 20.0 * (u_frameWidth / 640.0); }
- 片段着色器:生成Sparkle的形状,并输出正确的alpha值,这样混合时只有形状内的部分会生效:
precision highp float; uniform float u_time; // 可选,用来做闪烁动画 void main() { // 计算当前像素到点中心的距离 vec2 center = vec2(0.5, 0.5); float distanceToCenter = length(gl_PointCoord - center); // 生成圆形渐变的Sparkle,边缘透明 float alpha = 1.0 - smoothstep(0.0, 0.5, distanceToCenter); // 可选:添加闪烁动画,用时间变量控制alpha alpha *= 0.5 + 0.5 * sin(u_time * 10.0); // 输出颜色,这里用黄色,你可以换成其他颜色 gl_FragColor = vec4(1.0, 1.0, 0.0, alpha); }
5. 几个要注意的细节
- 确认GPUImage输出的
Position坐标是归一化的(0~1范围),如果不是的话,要先做坐标归一化处理; - 要是你想用纹理作为Sparkle(比如一张闪烁的图片),只需要在片段着色器里采样纹理,把纹理的alpha通道和上面的渐变alpha相乘就行,混合逻辑不变;
- 渲染完Sparkle后一定要恢复OpenGL的状态(比如禁用混合、点精灵),不然会干扰GPUImage后续的帧处理。
内容的提问来源于stack exchange,提问作者Farid A.
相关产品推荐
相关产品推荐

