【Android 美颜相机】第十五天:GPUImage3x3TextureSamplingFilter 解析

GPUImage3x3TextureSamplingFilter 代码全解析

GPUImage3x3TextureSamplingFilter 是 Android 平台 GPUImage 框架中用于3x3 纹理邻域采样的滤镜基类。

其核心作用是在顶点着色器中计算当前纹理坐标周围 8 个邻域纹理坐标(上下左右、左上/右上/左下/右下),为后续片段着色器实现卷积类滤镜(如高斯模糊、边缘检测、锐化等)提供基础的纹理坐标数据。

代码逐段解析(含行注释)

1. 版权与许可声明

java 复制代码
/*
 * Copyright (C) 2018 CyberAgent, Inc.  // 版权归属声明
 *
 * Licensed under the Apache License, Version 2.0 (the "License");  // 开源协议声明(Apache 2.0)
 * you may not use this file except in compliance with the License.  // 使用约束:必须遵循协议
 * You may obtain a copy of the License at  // 协议获取地址
 *
 * http://www.apache.org/licenses/LICENSE-2.0  // Apache 2.0 协议官方地址
 *
 * Unless required by applicable law or agreed to in writing, software  // 免责声明:无法律强制/书面约定时
 * distributed under the License is distributed on an "AS IS" BASIS,  // 软件按"现状"分发
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  // 无明示/默示担保
 * See the License for the specific language governing permissions and  // 协议定义了权限和限制的具体规则
 * limitations under the License.  // 遵循协议中的限制条款
 */

说明:这是开源项目标准的版权和许可声明,明确代码的使用规则,避免法律风险。

2. 包名与导入类

java 复制代码
// 声明当前类所属的包路径,属于GPUImage框架的滤镜模块
package jp.co.cyberagent.android.gpuimage.filter;

// 导入Android OpenGL ES 2.0的核心类,用于操作GLSL着色器、获取统一变量位置等
import android.opengl.GLES20;

说明 :包名遵循 Java 命名规范,导入 GLES20 是为了使用 OpenGL ES 2.0 的 API 操作着色器程序。

3. 类定义与核心顶点着色器常量

java 复制代码
// 继承自GPUImageFilter(GPUImage框架的基础滤镜类),实现3x3纹理采样的核心逻辑
public class GPUImage3x3TextureSamplingFilter extends GPUImageFilter {
    // 定义3x3纹理采样的顶点着色器字符串,核心作用是计算当前纹理坐标的3x3邻域坐标
    public static final String THREE_X_THREE_TEXTURE_SAMPLING_VERTEX_SHADER = "" +
            "attribute vec4 position;\n" +  // 顶点位置属性(由外部传入,如矩形的四个顶点)
            "attribute vec4 inputTextureCoordinate;\n" +  // 原始纹理坐标属性(对应顶点的纹理坐标)
            "\n" +
            "uniform highp float texelWidth; \n" +  // 统一变量:单个纹理像素的宽度(1/纹理宽度)
            "uniform highp float texelHeight; \n" +  // 统一变量:单个纹理像素的高度(1/纹理高度)
            "\n" +
            "varying vec2 textureCoordinate;\n" +  // 易变变量:传递给片段着色器的当前纹理坐标
            "varying vec2 leftTextureCoordinate;\n" +  // 易变变量:左侧纹理坐标
            "varying vec2 rightTextureCoordinate;\n" +  // 易变变量:右侧纹理坐标
            "\n" +
            "varying vec2 topTextureCoordinate;\n" +  // 易变变量:顶部纹理坐标
            "varying vec2 topLeftTextureCoordinate;\n" +  // 易变变量:左上纹理坐标
            "varying vec2 topRightTextureCoordinate;\n" +  // 易变变量:右上纹理坐标
            "\n" +
            "varying vec2 bottomTextureCoordinate;\n" +  // 易变变量:底部纹理坐标
            "varying vec2 bottomLeftTextureCoordinate;\n" +  // 易变变量:左下纹理坐标
            "varying vec2 bottomRightTextureCoordinate;\n" +  // 易变变量:右下纹理坐标
            "\n" +
            "void main()\n" +  // 顶点着色器主函数(每个顶点都会执行)
            "{\n" +
            "    gl_Position = position;\n" +  // 设置顶点最终输出位置(传递给光栅化阶段)
            "\n" +
            "    // 定义纹理坐标偏移步长:单像素宽度、单像素高度、宽高步长、宽负高步长\n" +
            "    vec2 widthStep = vec2(texelWidth, 0.0);\n" +  // 水平方向(左右)偏移步长
            "    vec2 heightStep = vec2(0.0, texelHeight);\n" +  // 垂直方向(上下)偏移步长
            "    vec2 widthHeightStep = vec2(texelWidth, texelHeight);\n" +  // 右下/左上偏移步长
            "    vec2 widthNegativeHeightStep = vec2(texelWidth, -texelHeight);\n" +  // 右上/左下偏移步长
            "\n" +
            "    // 赋值当前纹理坐标(传递给片段着色器)\n" +
            "    textureCoordinate = inputTextureCoordinate.xy;\n" +
            "    // 计算左侧纹理坐标:当前坐标 - 水平步长\n" +
            "    leftTextureCoordinate = inputTextureCoordinate.xy - widthStep;\n" +
            "    // 计算右侧纹理坐标:当前坐标 + 水平步长\n" +
            "    rightTextureCoordinate = inputTextureCoordinate.xy + widthStep;\n" +
            "\n" +
            "    // 计算顶部纹理坐标:当前坐标 - 垂直步长(GL纹理坐标系y轴向下,所以"上"是减)\n" +
            "    topTextureCoordinate = inputTextureCoordinate.xy - heightStep;\n" +
            "    // 计算左上纹理坐标:当前坐标 - 宽高步长\n" +
            "    topLeftTextureCoordinate = inputTextureCoordinate.xy - widthHeightStep;\n" +
            "    // 计算右上纹理坐标:当前坐标 + 宽负高步长\n" +
            "    topRightTextureCoordinate = inputTextureCoordinate.xy + widthNegativeHeightStep;\n" +
            "\n" +
            "    // 计算底部纹理坐标:当前坐标 + 垂直步长\n" +
            "    bottomTextureCoordinate = inputTextureCoordinate.xy + heightStep;\n" +
            "    // 计算左下纹理坐标:当前坐标 - 宽负高步长\n" +
            "    bottomLeftTextureCoordinate = inputTextureCoordinate.xy - widthNegativeHeightStep;\n" +
            "    // 计算右下纹理坐标:当前坐标 + 宽高步长\n" +
            "    bottomRightTextureCoordinate = inputTextureCoordinate.xy + widthHeightStep;\n" +
            "}";

核心说明

  • 顶点着色器是该类的核心,通过 texelWidth/texelHeight(单像素纹理尺寸)计算当前纹理坐标的 8 个邻域坐标;
  • GLSL 中 varying 变量会在顶点着色器和片段着色器之间做插值,因此片段着色器能拿到每个像素的 3x3 邻域纹理坐标;
  • 注意 GL 纹理坐标系的 y 轴向下,因此"顶部"纹理坐标是当前坐标减去 texelHeight

4. 成员变量定义

java 复制代码
    // 统一变量位置:texelWidth(对应顶点着色器中的uniform变量)
    private int uniformTexelWidthLocation;
    // 统一变量位置:texelHeight(对应顶点着色器中的uniform变量)
    private int uniformTexelHeightLocation;

    // 标记:是否手动覆盖了纹理像素尺寸(true=手动设置,false=自动计算)
    private boolean hasOverriddenImageSizeFactor = false;
    // 单个纹理像素的宽度(1/纹理宽度 * 线宽系数)
    private float texelWidth;
    // 单个纹理像素的高度(1/纹理高度 * 线宽系数)
    private float texelHeight;
    // 线宽系数(用于调整采样步长,默认1.0,越大采样范围越宽)
    private float lineSize = 1.0f;

说明

  • uniform 变量位置需要在着色器程序初始化后通过 glGetUniformLocation 获取,后续通过该位置更新变量值;
  • lineSize 是扩展参数,用于动态调整采样步长(比如实现"模糊强度"调节)。

5. 构造方法

java 复制代码
    // 无参构造:使用默认的顶点着色器(NO_FILTER_VERTEX_SHADER)+ 空片段着色器
    public GPUImage3x3TextureSamplingFilter() {
        this(NO_FILTER_VERTEX_SHADER);
    }

    // 带参构造:自定义片段着色器,顶点着色器固定为3x3采样的顶点着色器
    public GPUImage3x3TextureSamplingFilter(final String fragmentShader) {
        super(THREE_X_THREE_TEXTURE_SAMPLING_VERTEX_SHADER, fragmentShader);
    }

说明

  • 该类是基类,通常需要子类传入具体的片段着色器(如边缘检测、模糊的片段着色器);
  • NO_FILTER_VERTEX_SHADER 是 GPUImageFilter 中定义的默认顶点着色器,无参构造仅用于基础初始化。

6. 重写初始化方法(onInit)

java 复制代码
    // 重写GPUImageFilter的onInit:着色器程序初始化时调用
    @Override
    public void onInit() {
        super.onInit();  // 调用父类初始化逻辑(创建着色器程序、编译着色器等)
        // 获取texelWidth统一变量的位置(关联着色器程序和Java层变量)
        uniformTexelWidthLocation = GLES20.glGetUniformLocation(getProgram(), "texelWidth");
        // 获取texelHeight统一变量的位置
        uniformTexelHeightLocation = GLES20.glGetUniformLocation(getProgram(), "texelHeight");
    }

说明glGetUniformLocation 必须在着色器程序链接成功后调用,因此放在 onInit 中(父类 onInit 会完成程序编译链接)。

7. 重写初始化完成方法(onInitialized)

java 复制代码
    // 重写GPUImageFilter的onInitialized:着色器程序初始化完成后调用
    @Override
    public void onInitialized() {
        super.onInitialized();  // 调用父类逻辑
        // 如果texelWidth已手动设置(非0),更新纹理像素尺寸到着色器
        if (texelWidth != 0) {
            updateTexelValues();
        }
    }

说明 :确保手动设置的 texelWidth/texelHeight 能在着色器初始化完成后立即生效。

8. 重写输出尺寸变化方法(onOutputSizeChanged)

java 复制代码
    // 重写GPUImageFilter的onOutputSizeChanged:滤镜输出尺寸(纹理/屏幕)变化时调用
    @Override
    public void onOutputSizeChanged(final int width, final int height) {
        super.onOutputSizeChanged(width, height);  // 调用父类逻辑(更新输出尺寸)
        // 如果未手动覆盖纹理像素尺寸,重新计算texelWidth/texelHeight(基于新尺寸和lineSize)
        if (!hasOverriddenImageSizeFactor) {
            setLineSize(lineSize);
        }
    }

说明:当屏幕旋转、纹理尺寸变化时,自动重新计算采样步长,保证3x3采样的准确性。

9. 手动设置纹理像素宽度/高度

java 复制代码
    // 手动设置texelWidth(覆盖自动计算)
    public void setTexelWidth(final float texelWidth) {
        hasOverriddenImageSizeFactor = true;  // 标记为手动覆盖
        this.texelWidth = texelWidth;  // 赋值
        // 将新值传递给着色器的uniform变量
        setFloat(uniformTexelWidthLocation, texelWidth);
    }

    // 手动设置texelHeight(覆盖自动计算)
    public void setTexelHeight(final float texelHeight) {
        hasOverriddenImageSizeFactor = true;  // 标记为手动覆盖
        this.texelHeight = texelHeight;  // 赋值
        // 将新值传递给着色器的uniform变量
        setFloat(uniformTexelHeightLocation, texelHeight);
    }

说明:提供手动控制采样步长的接口,适用于特殊场景(如非等比例缩放的纹理采样)。

10. 设置线宽系数(核心对外接口)

java 复制代码
    // 设置线宽系数,动态调整采样步长
    public void setLineSize(final float size) {
        lineSize = size;  // 更新线宽系数
        // 计算新的texelWidth:线宽系数 / 输出宽度(单像素宽度 * 系数)
        texelWidth = size / getOutputWidth();
        // 计算新的texelHeight:线宽系数 / 输出高度(单像素高度 * 系数)
        texelHeight = size / getOutputHeight();
        // 更新着色器中的uniform变量
        updateTexelValues();
    }

说明

  • 这是最常用的对外接口,通过调整 lineSize 可以改变3x3采样的范围;
  • 例如:lineSize=2.0f 时,采样步长是2个像素,适用于更宽的卷积核(如强模糊)。

11. 私有更新方法(统一更新uniform变量)

java 复制代码
    // 私有方法:更新texelWidth/texelHeight到着色器
    private void updateTexelValues() {
        setFloat(uniformTexelWidthLocation, texelWidth);
        setFloat(uniformTexelHeightLocation, texelHeight);
    }
}

说明:封装uniform变量的更新逻辑,避免代码冗余。

3x3纹理邻域坐标的计算逻辑(一)

3x3纹理采样是图像滤波(如高斯模糊、边缘检测、锐化)的基础------对每个像素,需要采样其周围8个邻域像素+自身,共9个像素参与计算。

而顶点着色器的核心作用是:为每个顶点计算出「当前纹理坐标」及其8个邻域的纹理坐标,并传递给片段着色器(Fragment Shader),让片段着色器能采样这9个坐标的颜色值做滤波。

java 复制代码
    public static final String THREE_X_THREE_TEXTURE_SAMPLING_VERTEX_SHADER = "" +
            "attribute vec4 position;\n" +
            "attribute vec4 inputTextureCoordinate;\n" +
            "\n" +
            "uniform highp float texelWidth; \n" +
            "uniform highp float texelHeight; \n" +
            "\n" +
            "varying vec2 textureCoordinate;\n" +
            "varying vec2 leftTextureCoordinate;\n" +
            "varying vec2 rightTextureCoordinate;\n" +
            "\n" +
            "varying vec2 topTextureCoordinate;\n" +
            "varying vec2 topLeftTextureCoordinate;\n" +
            "varying vec2 topRightTextureCoordinate;\n" +
            "\n" +
            "varying vec2 bottomTextureCoordinate;\n" +
            "varying vec2 bottomLeftTextureCoordinate;\n" +
            "varying vec2 bottomRightTextureCoordinate;\n" +
            "\n" +
            "void main()\n" +
            "{\n" +
            "    gl_Position = position;\n" +
            "\n" +
            "    vec2 widthStep = vec2(texelWidth, 0.0);\n" +
            "    vec2 heightStep = vec2(0.0, texelHeight);\n" +
            "    vec2 widthHeightStep = vec2(texelWidth, texelHeight);\n" +
            "    vec2 widthNegativeHeightStep = vec2(texelWidth, -texelHeight);\n" +
            "\n" +
            "    textureCoordinate = inputTextureCoordinate.xy;\n" +
            "    leftTextureCoordinate = inputTextureCoordinate.xy - widthStep;\n" +
            "    rightTextureCoordinate = inputTextureCoordinate.xy + widthStep;\n" +
            "\n" +
            "    topTextureCoordinate = inputTextureCoordinate.xy - heightStep;\n" +
            "    topLeftTextureCoordinate = inputTextureCoordinate.xy - widthHeightStep;\n" +
            "    topRightTextureCoordinate = inputTextureCoordinate.xy + widthNegativeHeightStep;\n" +
            "\n" +
            "    bottomTextureCoordinate = inputTextureCoordinate.xy + heightStep;\n" +
            "    bottomLeftTextureCoordinate = inputTextureCoordinate.xy - widthNegativeHeightStep;\n" +
            "    bottomRightTextureCoordinate = inputTextureCoordinate.xy + widthHeightStep;\n" +
            "}";

OpenGL ES纹理坐标系(UV坐标系)

纹理坐标用vec2(uv)表示,范围通常是[0,1](左上角是(0,0),右下角是(1,1)),核心特点:y轴向下 (和屏幕像素坐标系相反)。

这是理解「上下偏移」的关键------比如"向上偏移一个像素",在纹理坐标系中是y -= 单个纹理像素高度,而非y +=

关键变量解析(着色器部分)

先明确顶点着色器中变量的作用,才能理解计算逻辑:

变量类型 变量名 作用
attribute position 顶点位置(如矩形的四个顶点坐标,由外部传入,最终赋值给gl_Position
attribute inputTextureCoordinate 顶点对应的原始纹理坐标(UV),每个顶点对应一个纹理坐标
uniform texelWidth 单个纹理像素的宽度(1/纹理宽度,Java层传递,如纹理宽1080则为1/1080)
uniform texelHeight 单个纹理像素的高度(1/纹理高度,同理)
varying textureCoordinate 传递给片段着色器的变量(顶点着色器计算后,光栅化阶段会插值给每个像素)

步长(Step)的定义:偏移的"基本单位"

要计算邻域坐标,首先定义「单像素偏移的步长」------即"在UV坐标系中,移动1个像素对应的偏移量"。

代码中定义了4个步长变量,覆盖所有方向的偏移组合:

glsl 复制代码
// 水平步长(左右偏移):x方向偏移1个像素,y方向不变
vec2 widthStep = vec2(texelWidth, 0.0);
// 垂直步长(上下偏移):y方向偏移1个像素,x方向不变
vec2 heightStep = vec2(0.0, texelHeight);
// 对角线步长(右下/左上):x、y各偏移1个像素
vec2 widthHeightStep = vec2(texelWidth, texelHeight);
// 对角线步长(右上/左下):x偏移1个像素,y反向偏移1个像素
vec2 widthNegativeHeightStep = vec2(texelWidth, -texelHeight);
  • texelWidth = 1/纹理宽度:比如纹理宽度是1080,texelWidth = 1/1080,表示UV坐标系中"1个像素的宽度占比";
  • texelHeight = 1/纹理高度:同理,代表UV坐标系中"1个像素的高度占比"。

9个纹理坐标的逐行计算

inputTextureCoordinate.xy(当前顶点的原始纹理坐标)为基准,计算3x3网格的所有坐标:

1. 中心坐标(当前像素)

glsl 复制代码
textureCoordinate = inputTextureCoordinate.xy;

直接将原始纹理坐标赋值给textureCoordinate,作为3x3网格的中心。

2. 水平邻域(左、右)

glsl 复制代码
// 左侧坐标:中心x - 1个像素宽度(y不变)
leftTextureCoordinate = inputTextureCoordinate.xy - widthStep;
// 右侧坐标:中心x + 1个像素宽度(y不变)
rightTextureCoordinate = inputTextureCoordinate.xy + widthStep;

示例:若中心坐标是(0.5, 0.5)texelWidth=1/1080,则左侧坐标是(0.5 - 1/1080, 0.5)

3. 垂直邻域(上、下)+ 对角线邻域(左上、右上、左下、右下)

核心注意:OpenGL纹理坐标系y轴向下 ,因此"向上"是y -= texelHeight,"向下"是y += texelHeight

glsl 复制代码
// 顶部坐标:中心y - 1个像素高度(x不变)→ 对应纹理的"上方"像素
topTextureCoordinate = inputTextureCoordinate.xy - heightStep;
// 左上坐标:中心x-1像素、y-1像素 → 纹理的"左上方"像素
topLeftTextureCoordinate = inputTextureCoordinate.xy - widthHeightStep;
// 右上坐标:中心x+1像素、y-1像素 → 纹理的"右上方"像素
// (widthNegativeHeightStep = (texelWidth, -texelHeight),相加等价于 x+texelWidth, y-texelHeight)
topRightTextureCoordinate = inputTextureCoordinate.xy + widthNegativeHeightStep;

// 底部坐标:中心y + 1个像素高度(x不变)→ 纹理的"下方"像素
bottomTextureCoordinate = inputTextureCoordinate.xy + heightStep;
// 左下坐标:中心x-1像素、y+1像素
// (减去widthNegativeHeightStep = (texelWidth, -texelHeight),等价于 x-texelWidth, y+texelHeight)
bottomLeftTextureCoordinate = inputTextureCoordinate.xy - widthNegativeHeightStep;
// 右下坐标:中心x+1像素、y+1像素 → 纹理的"右下方"像素
bottomRightTextureCoordinate = inputTextureCoordinate.xy + widthHeightStep;
对角线坐标的等价拆解(帮你理解)

topRightTextureCoordinate为例:

glsl 复制代码
// 原代码:
topRightTextureCoordinate = inputTextureCoordinate.xy + widthNegativeHeightStep;
// 等价于:
topRightTextureCoordinate = inputTextureCoordinate.xy + vec2(texelWidth, -texelHeight);
// 再拆解:
topRightTextureCoordinate.x = inputTextureCoordinate.x + texelWidth; // 右移1像素
topRightTextureCoordinate.y = inputTextureCoordinate.y - texelHeight; // 上移1像素

bottomLeftTextureCoordinate为例:

glsl 复制代码
// 原代码:
bottomLeftTextureCoordinate = inputTextureCoordinate.xy - widthNegativeHeightStep;
// 等价于:
bottomLeftTextureCoordinate = inputTextureCoordinate.xy - vec2(texelWidth, -texelHeight);
// 再拆解:
bottomLeftTextureCoordinate.x = inputTextureCoordinate.x - texelWidth; // 左移1像素
bottomLeftTextureCoordinate.y = inputTextureCoordinate.y + texelHeight; // 下移1像素

Java层的辅助:texelWidth/texelHeight的赋值

顶点着色器的texelWidth/texelHeightuniform变量,需要Java层传递值,核心逻辑在setLineSize方法:

java 复制代码
public void setLineSize(final float size) {
    lineSize = size;
    // 计算UV坐标系中"1个像素的宽度/高度占比"
    texelWidth = size / getOutputWidth();
    texelHeight = size / getOutputHeight();
    updateTexelValues(); // 传递给着色器的uniform变量
}
  • getOutputWidth()/getOutputHeight():当前纹理的宽/高(像素);
  • size:偏移倍数(默认1.0,即偏移1个像素;若设为2.0,则偏移2个像素);
  • 最终texelWidth = 偏移倍数 / 纹理宽度,保证偏移量和纹理分辨率适配。

3x3纹理邻域坐标的计算逻辑(二)

前文的GPUImage3x3TextureSamplingFilter顶点着色器配合使用的核心模块,作用是对顶点着色器传递的9个纹理坐标进行颜色采样,并通过3x3卷积矩阵完成加权计算,最终输出单个像素的颜色(实现图像卷积滤波,如边缘检测、高斯模糊、锐化等效果)。

java 复制代码
 public static final String THREE_X_THREE_TEXTURE_SAMPLING_FRAGMENT_SHADER = "" +
            "precision highp float;\n" +
            "\n" +
            "uniform sampler2D inputImageTexture;\n" +
            "\n" +
            "uniform mediump mat3 convolutionMatrix;\n" +
            "\n" +
            "varying vec2 textureCoordinate;\n" +
            "varying vec2 leftTextureCoordinate;\n" +
            "varying vec2 rightTextureCoordinate;\n" +
            "\n" +
            "varying vec2 topTextureCoordinate;\n" +
            "varying vec2 topLeftTextureCoordinate;\n" +
            "varying vec2 topRightTextureCoordinate;\n" +
            "\n" +
            "varying vec2 bottomTextureCoordinate;\n" +
            "varying vec2 bottomLeftTextureCoordinate;\n" +
            "varying vec2 bottomRightTextureCoordinate;\n" +
            "\n" +
            "void main()\n" +
            "{\n" +
            "    mediump vec4 bottomColor = texture2D(inputImageTexture, bottomTextureCoordinate);\n" +
            "    mediump vec4 bottomLeftColor = texture2D(inputImageTexture, bottomLeftTextureCoordinate);\n" +
            "    mediump vec4 bottomRightColor = texture2D(inputImageTexture, bottomRightTextureCoordinate);\n" +
            "    mediump vec4 centerColor = texture2D(inputImageTexture, textureCoordinate);\n" +
            "    mediump vec4 leftColor = texture2D(inputImageTexture, leftTextureCoordinate);\n" +
            "    mediump vec4 rightColor = texture2D(inputImageTexture, rightTextureCoordinate);\n" +
            "    mediump vec4 topColor = texture2D(inputImageTexture, topTextureCoordinate);\n" +
            "    mediump vec4 topRightColor = texture2D(inputImageTexture, topRightTextureCoordinate);\n" +
            "    mediump vec4 topLeftColor = texture2D(inputImageTexture, topLeftTextureCoordinate);\n" +
            "\n" +
            "    mediump vec4 resultColor = topLeftColor * convolutionMatrix[0][0] + topColor * convolutionMatrix[0][1] + topRightColor * convolutionMatrix[0][2];\n" +
            "    resultColor += leftColor * convolutionMatrix[1][0] + centerColor * convolutionMatrix[1][1] + rightColor * convolutionMatrix[1][2];\n" +
            "    resultColor += bottomLeftColor * convolutionMatrix[2][0] + bottomColor * convolutionMatrix[2][1] + bottomRightColor * convolutionMatrix[2][2];\n" +
            "\n" +
            "    gl_FragColor = resultColor;\n" +
            "}";

逐部分拆解代码逻辑

1. 精度声明 & 核心变量定义

glsl 复制代码
precision highp float;  // 声明浮点精度:高精度(保证卷积计算的准确性)

uniform sampler2D inputImageTexture;  // 纹理采样器:绑定输入的纹理(待处理的图像)
uniform mediump mat3 convolutionMatrix;  // 3x3卷积矩阵(中等精度):由Java层传递,定义滤波规则

// 顶点着色器传递过来的9个纹理坐标(已插值到当前像素)
varying vec2 textureCoordinate;
varying vec2 leftTextureCoordinate;
varying vec2 rightTextureCoordinate;
varying vec2 topTextureCoordinate;
varying vec2 topLeftTextureCoordinate;
varying vec2 topRightTextureCoordinate;
varying vec2 bottomTextureCoordinate;
varying vec2 bottomLeftTextureCoordinate;
varying vec2 bottomRightTextureCoordinate;
  • sampler2D:OpenGL ES中用于读取纹理颜色的"句柄",通过texture2D函数可根据纹理坐标采样颜色;
  • mat3 convolutionMatrix:3x3矩阵是卷积滤波的核心(比如边缘检测的Sobel矩阵、高斯模糊的高斯矩阵),每个元素是对应位置像素的权重;
  • varying变量:顶点着色器计算的坐标会在光栅化阶段插值(比如两个顶点之间的像素,坐标会平滑过渡),最终每个像素拿到属于自己的9个邻域坐标。

2. 纹理采样:读取9个邻域像素的颜色

glsl 复制代码
// 对9个纹理坐标分别采样,得到对应的RGBA颜色(vec4:r,g,b,a四个分量,范围0~1)
mediump vec4 bottomColor = texture2D(inputImageTexture, bottomTextureCoordinate);
mediump vec4 bottomLeftColor = texture2D(inputImageTexture, bottomLeftTextureCoordinate);
mediump vec4 bottomRightColor = texture2D(inputImageTexture, bottomRightTextureCoordinate);
mediump vec4 centerColor = texture2D(inputImageTexture, textureCoordinate);
mediump vec4 leftColor = texture2D(inputImageTexture, leftTextureCoordinate);
mediump vec4 rightColor = texture2D(inputImageTexture, rightTextureCoordinate);
mediump vec4 topColor = texture2D(inputImageTexture, topTextureCoordinate);
mediump vec4 topRightColor = texture2D(inputImageTexture, topRightTextureCoordinate);
mediump vec4 topLeftColor = texture2D(inputImageTexture, topLeftTextureCoordinate);
  • texture2D(sampler, uv):OpenGL ES核心纹理采样函数,参数1是纹理采样器,参数2是纹理坐标,返回该坐标对应的RGBA颜色(vec4);
  • 采样结果用mediump(中等精度):平衡计算效率和精度,颜色计算无需超高精度。

3. 卷积计算:加权求和得到最终颜色

glsl 复制代码
// 第一步:计算第一行卷积(左上、上、右上 对应卷积矩阵第一行)
mediump vec4 resultColor = topLeftColor * convolutionMatrix[0][0] + topColor * convolutionMatrix[0][1] + topRightColor * convolutionMatrix[0][2];
// 第二步:累加第二行卷积(左、中、右 对应卷积矩阵第二行)
resultColor += leftColor * convolutionMatrix[1][0] + centerColor * convolutionMatrix[1][1] + rightColor * convolutionMatrix[1][2];
// 第三步:累加第三行卷积(左下、下、右下 对应卷积矩阵第三行)
resultColor += bottomLeftColor * convolutionMatrix[2][0] + bottomColor * convolutionMatrix[2][1] + bottomRightColor * convolutionMatrix[2][2];

这是整个片段着色器的核心,对应3x3卷积的数学公式
result ( x , y ) = ∑ i = − 1 1 ∑ j = − 1 1 color ( x + i , y + j ) × kernel ( i + 1 , j + 1 ) \text{result}(x,y) = \sum_{i=-1}^{1}\sum_{j=-1}^{1} \text{color}(x+i,y+j) \times \text{kernel}(i+1,j+1) result(x,y)=i=−1∑1j=−1∑1color(x+i,y+j)×kernel(i+1,j+1)

  • 卷积矩阵的索引对应关系:

    卷积矩阵索引 对应像素位置
    [0][0] 左上
    [0][1]
    [0][2] 右上
    [1][0]
    [1][1] 中心
    [1][2]
    [2][0] 左下
    [2][1]
    [2][2] 右下
  • 颜色与矩阵相乘:vec4 * float 会对RGBA四个分量分别乘以该权重(比如topLeftColor * 0.1 → r0.1, g 0.1, b0.1, a0.1);

  • 累加:所有加权后的颜色分量相加,得到最终的颜色值。

4. 输出最终颜色

glsl 复制代码
gl_FragColor = resultColor;  // 将卷积计算后的颜色赋值给片段输出变量
  • gl_FragColor:OpenGL ES片段着色器的内置输出变量,代表当前像素最终显示的颜色;
  • 若需要保持Alpha通道不变(比如只处理RGB),可调整为:gl_FragColor = vec4(resultColor.rgb, centerColor.a);

扩展:卷积矩阵的实际应用示例

不同的convolutionMatrix对应不同的滤波效果,比如:

1. 高斯模糊(3x3)

glsl 复制代码
mat3 gaussianBlur = mat3(
    1.0/16.0, 2.0/16.0, 1.0/16.0,
    2.0/16.0, 4.0/16.0, 2.0/16.0,
    1.0/16.0, 2.0/16.0, 1.0/16.0
);

核心:中心像素权重最高,向四周递减,实现模糊效果。

2. 边缘检测(Sobel X轴)

glsl 复制代码
mat3 sobelX = mat3(
    -1.0, 0.0, 1.0,
    -2.0, 0.0, 2.0,
    -1.0, 0.0, 1.0
);

核心:横向像素差值放大,纵向差值抵消,检测垂直边缘。

3. 锐化

glsl 复制代码
mat3 sharpen = mat3(
    0.0, -1.0, 0.0,
    -1.0, 5.0, -1.0,
    0.0, -1.0, 0.0
);

核心:增强中心像素,削弱邻域像素,提升图像锐度。

相关推荐
Whisper_Sy2 小时前
Flutter for OpenHarmony移动数据使用监管助手App实战 - 月报告实现
android·开发语言·javascript·网络·flutter·ecmascript
天才少年曾牛2 小时前
Android 怎么写一个AIDL接口?
android
冬奇Lab3 小时前
【Kotlin系列14】编译器插件与注解处理器开发:在编译期操控Kotlin
android·开发语言·kotlin·状态模式
橘色的喵3 小时前
嵌入式 C++ 高性能流式架构的设计
数码相机·设计模式
橘子133 小时前
MySQL表的约束(五)
android·mysql·adb
2501_915918413 小时前
Wireshark、Fiddler、Charles抓包工具详细使用指南
android·ios·小程序·https·uni-app·iphone·webview
aaa最北边3 小时前
进程间通信-1.管道通信
android·java·服务器
灰灰勇闯IT4 小时前
【Flutter for OpenHarmony--Dart 入门日记】第3篇:基础数据类型全解析——String、数字与布尔值
android·java·开发语言
2501_944521594 小时前
Flutter for OpenHarmony 微动漫App实战:底部导航实现
android·开发语言·前端·javascript·redis·flutter·ecmascript