
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/texelHeight是uniform变量,需要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
);
核心:增强中心像素,削弱邻域像素,提升图像锐度。
