You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 10:54:23