图像滤镜本质上是一种像素级的变换操作。在OpenGL ES
中,我们通过编写片段着色器(Fragment Shader)
来实现这些变换。片段着色器会对每个像素点进行处理,根据特定的算法改变其颜色值,从而实现各种视觉效果。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.example.myapplication.MyGLSurfaceView
android:id="@+id/gl_surface_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="选择滤镜:"
android:textSize="16sp"
android:textStyle="bold" />
<RadioGroup
android:id="@+id/filter_radio_group"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<RadioButton
android:id="@+id/radio_normal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true"
android:text="原图" />
<RadioButton
android:id="@+id/radio_gray"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="灰度" />
<RadioButton
android:id="@+id/radio_invert"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="反色" />
<RadioButton
android:id="@+id/radio_sepia"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="棕褐色" />
<RadioButton
android:id="@+id/horizontal_radio_blur"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="水平高斯模糊" />
<RadioButton
android:id="@+id/vertical_radio_blur"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="竖直高斯模糊" />
<RadioButton
android:id="@+id/radio_sharpen"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="锐化" />
<RadioButton
android:id="@+id/radio_edge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="边缘检测" />
<RadioButton
android:id="@+id/radio_saturation"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="饱和度调整" />
RadioGroup>
<LinearLayout
android:id="@+id/saturation_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginTop="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="饱和度调整:"
android:textSize="16sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<SeekBar
android:id="@+id/saturation_seek_bar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:max="200" />
<TextView
android:id="@+id/saturation_value_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="40dp"
android:text="1.0"
android:gravity="center" />
LinearLayout>
LinearLayout>
LinearLayout>
ScrollView>
LinearLayout>
Activity
代码class MainActivity : AppCompatActivity() {
private lateinit var glSurfaceView: MyGLSurfaceView
private lateinit var filterRadioGroup: RadioGroup
private lateinit var saturationLayout: LinearLayout
private lateinit var saturationSeekBar: SeekBar
private lateinit var saturationValueText: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initView()
initListener()
}
fun initView() {
glSurfaceView = findViewById(R.id.gl_surface_view)
filterRadioGroup = findViewById(R.id.filter_radio_group)
saturationLayout = findViewById(R.id.saturation_layout)
saturationSeekBar = findViewById(R.id.saturation_seek_bar)
saturationValueText = findViewById(R.id.saturation_value_text)
// 初始隐藏饱和度调整控件
saturationLayout.visibility = View.GONE
// 设置初始饱和度为100%
saturationSeekBar.progress = 100
saturationValueText.text = "1.0"
}
fun initListener() {
// 设置滤镜选择监听
filterRadioGroup.setOnCheckedChangeListener { _, checkedId ->
when (checkedId) {
R.id.radio_normal -> {
applyFilter(FilterType.NORMAL)
saturationLayout.visibility = View.GONE
}
R.id.radio_gray -> {
applyFilter(FilterType.GRAY)
saturationLayout.visibility = View.GONE
}
R.id.radio_invert -> {
applyFilter(FilterType.INVERT)
saturationLayout.visibility = View.GONE
}
R.id.radio_sepia -> {
applyFilter(FilterType.SEPIA)
saturationLayout.visibility = View.GONE
}
R.id.horizontal_radio_blur -> {
glSurfaceView?.apply {
queueEvent {
getRenderer()?.getDrawData()?.apply {
setFilterType(FilterType.GAUSSIAN_BLUR)
setGaussianHorizontal(true, 10f)
}
requestRender()
}
}
saturationLayout.visibility = View.GONE
}
R.id.vertical_radio_blur -> {
glSurfaceView?.apply {
queueEvent {
getRenderer()?.getDrawData()?.apply {
setFilterType(FilterType.GAUSSIAN_BLUR)
setGaussianHorizontal(false, 10f)
}
requestRender()
}
}
saturationLayout.visibility = View.GONE
}
R.id.radio_sharpen -> {
applyFilter(FilterType.SHARPEN)
saturationLayout.visibility = View.GONE
}
R.id.radio_edge -> {
applyFilter(FilterType.EDGE_DETECTION)
saturationLayout.visibility = View.GONE
}
R.id.radio_saturation -> {
applyFilter(FilterType.SATURATION)
saturationLayout.visibility = View.VISIBLE
}
}
// 设置饱和度调整监听
saturationSeekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
val saturation = progress / 100f
saturationValueText.text = String.format("%.1f", saturation)
// 在OpenGL线程中更新饱和度参数
glSurfaceView?.apply {
queueEvent {
getRenderer()?.getDrawData()?.setSaturation(saturation)
requestRender()
}
}
}
override fun onStartTrackingTouch(seekBar: SeekBar) {}
override fun onStopTrackingTouch(seekBar: SeekBar) {}
})
}
}
private fun applyFilter(filterType: FilterType) {
// 在OpenGL线程中设置滤镜类型
glSurfaceView?.apply {
queueEvent {
getRenderer()?.getDrawData()?.setFilterType(filterType)
requestRender()
}
}
}
}
GLSurfaceView
代码class MyGLSurfaceView(context: Context, attrs: AttributeSet) : GLSurfaceView(context, attrs) {
private var mRenderer = MyGLRenderer(context)
init {
// 设置 OpenGL ES 3.0 版本
setEGLContextClientVersion(3)
setRenderer(mRenderer)
// 设置渲染模式, 仅在需要重新绘制时才进行渲染,以节省资源
renderMode = RENDERMODE_WHEN_DIRTY
}
fun getRenderer(): MyGLRenderer {
return mRenderer
}
}
GLSurfaceView.Renderer
代码class MyGLRenderer(private val mContext: Context) : GLSurfaceView.Renderer {
private var mDrawData: DrawData? = null
override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
// 当 Surface 创建时调用, 进行 OpenGL ES 环境的初始化操作, 设置清屏颜色为青蓝色 (Red=0, Green=0.5, Blue=0.5, Alpha=1)
GLES30.glClearColor(0.0f, 0.5f, 0.5f, 1.0f)
mDrawData = DrawData().apply {
initVertexBuffer()
initShader()
mTextureID[0] = loadTexture(mContext, R.drawable.pic)
}
}
override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
// 当 Surface 尺寸发生变化时调用,例如设备的屏幕方向发生改变, 设置视口为新的尺寸,视口是指渲染区域的大小
GLES30.glViewport(0, 0, width, height)
mDrawData?.computeMVPMatrix(width.toFloat(), height.toFloat())
}
override fun onDrawFrame(gl: GL10?) {
// 每一帧绘制时调用, 清除颜色缓冲区
GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)
mDrawData?.enableTexture()
mDrawData?.drawSomething()
}
fun getDrawData(): DrawData? {
return mDrawData
}
}
GLSurfaceView.Renderer
需要的绘制数据class DrawData {
private var mProgram: Int = -1
private var NO_OFFSET = 0
private val VERTEX_POS_DATA_SIZE = 3
private val TEXTURE_POS_DATA_SIZE = 2
// VBO IDs
private var mVertexVBO = 0
private var mTexCoordVBO = 0
// 最终变化矩阵
private val mMVPMatrix = FloatArray(16)
// 投影矩阵
private val mProjectionMatrix = FloatArray(16)
// 相机矩阵
private val mViewMatrix = FloatArray(16)
private var mViewPortRatio = 1f
// 当前滤镜类型
private var mCurrentFilterType = FilterType.NORMAL
// 饱和度参数,默认为1.0(原始饱和度)
private var mSaturation: Float = 1.0f
// 水平模糊标志,用于高斯模糊滤镜
private var mHorizontalBlur: Boolean = true
// 纹理ID
var mTextureID = IntArray(1)
// 1. 准备顶点坐标,分配直接内存
// OpenGL ES坐标系:原点在中心,X轴向右为正,Y轴向上为正,Z轴向外为正
val vertex = floatArrayOf(
-1.0f, 1.0f, 0.0f, // 左上
-1.0f, -1.0f, 0.0f, // 左下
1.0f, 1.0f, 0.0f, // 右上
1.0f, -1.0f, 0.0f, // 右下
)
val vertexBuffer = ByteBuffer.allocateDirect(vertex.size * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
// 2. 准备纹理坐标,分配直接内存
// 纹理坐标系:原点在左下角,X轴向右为正,Y轴向上为正
val textureCoords = floatArrayOf(
0.0f, 1.0f, // 左上
0.0f, 0.0f, // 左下
1.0f, 1.0f, // 右上
1.0f, 0.0f, // 右下
)
val textureBuffer = ByteBuffer.allocateDirect(textureCoords.size * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
// 3. 创建顶点缓冲区对象
fun initVertexBuffer() {
// 初始化顶点坐标缓冲区
vertexBuffer.put(vertex)
vertexBuffer.position(NO_OFFSET)
// 初始化纹理坐标缓冲区
textureBuffer.put(textureCoords)
textureBuffer.position(NO_OFFSET)
// 创建两个VBO,一个用于顶点坐标,一个用于纹理坐标
val vbo = IntArray(2)
GLES30.glGenBuffers(vbo.size, vbo, NO_OFFSET) // 生成一个缓冲区对象ID,并存储在数组 vbo 中,存放位置为0
// 绑定顶点缓冲区
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, vbo[0])
GLES30.glBufferData(
GLES30.GL_ARRAY_BUFFER,
vertex.size * 4, // 数据总字节数 = 顶点数 * Float占4字节
vertexBuffer,
GLES30.GL_STATIC_DRAW
)
// 绑定纹理缓冲区
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, vbo[1])
GLES30.glBufferData(
GLES30.GL_ARRAY_BUFFER,
textureCoords.size * 4, // 数据总字节数 = 顶点数 * Float占4字节
textureBuffer,
GLES30.GL_STATIC_DRAW
)
mVertexVBO = vbo[0]
mTexCoordVBO = vbo[1]
}
// 4. 初始化着色器程序
fun initShader() {
val vertexShaderCode = """#version 300 es
uniform mat4 uMVPMatrix; // 变换矩阵
in vec4 aPosition; // 顶点坐标
in vec2 aTexCoord; // 纹理坐标
out vec2 vTexCoord;
void main() {
// 输出顶点坐标和纹理坐标到片段着色器
gl_Position = uMVPMatrix * aPosition;
vTexCoord = aTexCoord;
}""".trimIndent() // 顶点着色器代码
val fragmentShaderCode = when (mCurrentFilterType) {
FilterType.NORMAL -> getNormalShader() // 无滤镜
FilterType.GRAY -> getGrayFilterShader() // 灰度滤镜
FilterType.INVERT -> getInvertFilterShader() // 反色滤镜
FilterType.SEPIA -> getSepiaFilterShader() // 棕褐色滤镜
FilterType.SHARPEN -> getSharpenFilterShader() // 锐化滤镜
FilterType.EDGE_DETECTION -> getEdgeDetectionFilterShader() // 边缘检测滤镜
FilterType.SATURATION -> getSaturationFilterShader() // 饱和度滤镜
FilterType.GAUSSIAN_BLUR -> getGaussianBlurFilterShader() // 高斯模糊滤镜
else -> {}
}.toString()
// 加载顶点着色器和片段着色器, 并创建着色器程序
val vertexShader = LoadShaderUtil.loadShader(GLES30.GL_VERTEX_SHADER, vertexShaderCode)
val fragmentShader =
LoadShaderUtil.loadShader(GLES30.GL_FRAGMENT_SHADER, fragmentShaderCode)
mProgram = GLES30.glCreateProgram()
GLES30.glAttachShader(mProgram, vertexShader)
GLES30.glAttachShader(mProgram, fragmentShader)
GLES30.glLinkProgram(mProgram)
GLES30.glUseProgram(mProgram)
GLES30.glDeleteShader(vertexShader)
GLES30.glDeleteShader(fragmentShader)
}
// 5. 计算变换矩阵
fun computeMVPMatrix(width: Float, height: Float) {
// 正交投影矩阵
takeIf { width > height }?.let {
mViewPortRatio = width / height
Matrix.orthoM(
mProjectionMatrix, // 正交投影矩阵
NO_OFFSET, // 偏移量
-mViewPortRatio, // 近平面的坐标系左边界
mViewPortRatio, // 近平面的坐标系右边界
-1f, // 近平面的坐标系的下边界
1f, // 近平面坐标系的上边界
0f, // 近平面距离相机距离
1f // 远平面距离相机距离
)
} ?: run {
mViewPortRatio = height / width
Matrix.orthoM(
mProjectionMatrix, // 正交投影矩阵
NO_OFFSET, // 偏移量
-1f, // 近平面坐标系左边界
1f, // 近平面坐标系右边界
-mViewPortRatio, // 近平面坐标系下边界
mViewPortRatio, // 近平面坐标系上边界
0f, // 近平面距离相机距离
1f // 远平面距离相机距离
)
}
// 设置相机矩阵
// 相机位置(0f, 0f, 1f)
// 物体位置(0f, 0f, 0f)
// 相机方向(0f, 1f, 0f)
Matrix.setLookAtM(
mViewMatrix, // 相机矩阵
NO_OFFSET, // 偏移量
0f, // 相机位置x
0f, // 相机位置y
1f, // 相机位置z
0f, // 物体位置x
0f, // 物体位置y
0f, // 物体位置z
0f, // 相机上方向x
1f, // 相机上方向y
0f // 相机上方向z
)
// 最终变化矩阵
Matrix.multiplyMM(
mMVPMatrix, // 最终变化矩阵
NO_OFFSET, // 偏移量
mProjectionMatrix, // 投影矩阵
NO_OFFSET, // 投影矩阵偏移量
mViewMatrix, // 相机矩阵
NO_OFFSET // 相机矩阵偏移量
)
// 纹理坐标系为(0, 0), (1, 0), (1, 1), (0, 1)的正方形逆时针坐标系,从Bitmap生成纹理,即像素拷贝到纹理坐标系
// 变换矩阵需要加上一个y方向的翻转, x方向和z方向不改变
Matrix.scaleM(
mMVPMatrix,
NO_OFFSET,
1f,
-1f,
1f,
)
}
// 6. 使用着色器程序绘制图形
fun drawSomething() {
// 激活变换矩阵
val matrixHandle = GLES30.glGetUniformLocation(mProgram, "uMVPMatrix")
GLES30.glUniformMatrix4fv(matrixHandle, 1, false, mMVPMatrix, NO_OFFSET)
// 输入顶点数据
val positionHandle = GLES30.glGetAttribLocation(mProgram, "aPosition")
GLES30.glEnableVertexAttribArray(positionHandle)
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, mVertexVBO)
GLES30.glVertexAttribPointer(
positionHandle,
VERTEX_POS_DATA_SIZE,
GLES30.GL_FLOAT,
false,
0,
NO_OFFSET
)
// 绑定纹理数据
val textureHandle = GLES30.glGetAttribLocation(mProgram, "aTexCoord")
GLES30.glEnableVertexAttribArray(textureHandle)
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, mTexCoordVBO)
GLES30.glVertexAttribPointer(
textureHandle,
TEXTURE_POS_DATA_SIZE,
GLES30.GL_FLOAT,
false,
0,
NO_OFFSET
)
// 绘制纹理
GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, NO_OFFSET, vertex.size / VERTEX_POS_DATA_SIZE)
// 解绑顶点数据
GLES30.glDisableVertexAttribArray(positionHandle)
// 解绑纹理数据
GLES30.glDisableVertexAttribArray(textureHandle)
}
fun enableTexture() {
// 激活纹理编号
GLES30.glActiveTexture(GLES30.GL_TEXTURE0)
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mTextureID[0])
// 激活纹理取样器
val textureSampleHandle = GLES30.glGetUniformLocation(mProgram, "uTexture")
GLES30.glUniform1i(textureSampleHandle, 0)
}
// 加载纹理
fun loadTexture(context: Context, resourceId: Int) : Int {
val textureId = IntArray(1)
// 生成纹理
GLES30.glGenTextures(1, textureId, 0)
// 绑定纹理
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureId[0])
// 设置纹理参数
GLES30.glTexParameteri(
GLES30.GL_TEXTURE_2D,
GLES30.GL_TEXTURE_MIN_FILTER,
GLES30.GL_LINEAR
) // 纹理缩小时使用线性插值
GLES30.glTexParameteri(
GLES30.GL_TEXTURE_2D,
GLES30.GL_TEXTURE_MAG_FILTER,
GLES30.GL_LINEAR
) // 纹理放大时使用线性插值
GLES30.glTexParameteri(
GLES30.GL_TEXTURE_2D,
GLES30.GL_TEXTURE_WRAP_S,
GLES30.GL_CLAMP_TO_EDGE
) // 纹理坐标超出范围时,超出部分使用最边缘像素进行填充
GLES30.glTexParameteri(
GLES30.GL_TEXTURE_2D,
GLES30.GL_TEXTURE_WRAP_T,
GLES30.GL_CLAMP_TO_EDGE
) // 纹理坐标超出范围时,超出部分使用最边缘像素进行填充
// 加载图片
val options = BitmapFactory.Options().apply {
inScaled = false // 不进行缩放
}
val bitmap = BitmapFactory.decodeResource(context.resources, resourceId, options)
// 将图片数据加载到纹理中
GLUtils.texImage2D(GLES30.GL_TEXTURE_2D, 0, bitmap, 0)
// 释放资源
bitmap.recycle()
// 解绑纹理
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0)
return textureId[0]
}
/**
* 设置滤镜类型
*/
fun setFilterType(filterType: FilterType) {
if (mCurrentFilterType != filterType) {
mCurrentFilterType = filterType
// 重新初始化着色器
initShader()
}
}
/**
* 设置饱和度
*/
fun setSaturation(saturation: Float) {
if (mSaturation != saturation){
mSaturation = saturation
if (mCurrentFilterType == FilterType.SATURATION && mProgram > 0) {
val saturationHandle = GLES30.glGetUniformLocation(mProgram, "uSaturation")
GLES30.glUniform1f(saturationHandle, mSaturation)
}
}
}
/**
* 设置高斯模糊方向
*/
fun setGaussianHorizontal(horizontal: Boolean, strength: Float) {
if (mHorizontalBlur != horizontal){
mHorizontalBlur = horizontal
if (mCurrentFilterType == FilterType.GAUSSIAN_BLUR && mProgram > 0) {
val directionHandle = GLES30.glGetUniformLocation(mProgram, "uHorizontal")
GLES30.glUniform1i(directionHandle, if (mHorizontalBlur) 1 else 0)
val strengthHandle = GLES30.glGetUniformLocation(mProgram, "uStrength")
GLES30.glUniform1f(strengthHandle, strength)
}
}
}
object LoadShaderUtil {
// 创建着色器对象
fun loadShader(type: Int, source: String): Int {
val shader = GLES30.glCreateShader(type)
GLES30.glShaderSource(shader, source)
GLES30.glCompileShader(shader)
return shader
}
}
object FilterFactory {
enum class FilterType {
NORMAL,
GRAY,
INVERT,
SEPIA,
GAUSSIAN_BLUR,
SHARPEN,
EDGE_DETECTION,
SATURATION
}
// 1. 无滤镜
fun getNormalShader(): String {
return """#version 300 es
precision mediump float; // 定义float 精度为 mediump
uniform sampler2D uTexture; // 纹理取样器
in vec2 vTexCoord; // 接收顶点着色器传递过来的纹理坐标
out vec4 fragColor; // 输出片段颜色
void main() {
fragColor = texture(uTexture, vTexCoord);
}""".trimIndent()
}
// 2. 灰度滤镜
fun getGrayFilterShader(): String {
return """#version 300 es
precision mediump float; // 定义float 精度为 mediump
uniform sampler2D uTexture; // 纹理取样器
in vec2 vTexCoord; // 接收顶点着色器传递过来的纹理坐标
out vec4 fragColor; // 输出片段颜色
void main() {
// 获取纹理颜色
vec4 textureColor = texture(uTexture, vTexCoord);
// 加权平均计算灰度值
float gray = dot(textureColor.rgb, vec3(0.299, 0.587, 0.114));
// 输出灰度颜色,保留原始alpha值
fragColor = vec4(vec3(gray), textureColor.a);
}""".trimIndent()
}
// 3. 反色滤镜
fun getInvertFilterShader(): String {
return """#version 300 es
precision mediump float; // 定义float 精度为 mediump
uniform sampler2D uTexture; // 纹理取样器
in vec2 vTexCoord; // 接收顶点着色器传递过来的纹理坐标
out vec4 fragColor; // 输出片段颜色
void main() {
// 获取纹理颜色
vec4 textureColor = texture(uTexture, vTexCoord);
// 计算反色
fragColor = vec4(vec3(1.0) - textureColor.rgb, textureColor.a);
}""".trimIndent()
}
// 4. 棕褐色滤镜
fun getSepiaFilterShader(): String {
return """#version 300 es
precision mediump float; // 定义float 精度为 mediump
uniform sampler2D uTexture; // 纹理取样器
in vec2 vTexCoord; // 接收顶点着色器传递过来的纹理坐标
out vec4 fragColor; // 输出片段颜色
void main() {
// 获取纹理颜色
vec4 textureColor = texture(uTexture, vTexCoord);
// 加权平均计算灰度值
float gray = dot(textureColor.rgb, vec3(0.299, 0.587, 0.114));
// 应用棕褐色
vec3 sepia = vec3(
gray * 1.2, // 红色通道增强
gray * 0.9, // 绿色通道略微减弱
gray * 0.6 // 蓝色通道大幅减弱
);
// 输出棕褐色
fragColor = vec4(sepia, textureColor.a);
}""".trimIndent()
}
// 5. 高斯模糊滤镜
fun getGaussianBlurFilterShader(): String {
return """#version 300 es
precision mediump float; // 定义float 精度为 mediump
uniform sampler2D uTexture; // 纹理取样器
uniform bool uHorizontal; // 是否水平方向高斯模糊
uniform float uStrength; // 模糊强度参数,默认为1.0
in vec2 vTexCoord; // 接收顶点着色器传递过来的纹理坐标
out vec4 fragColor; // 输出片段颜色
// 扩展高斯权重数组 (增加采样点数量)
const float weight[9] = float[](
0.22, 0.19, 0.12, 0.08, 0.05, 0.03, 0.02, 0.01, 0.005
);
void main() {
// 获取纹理大小并计算一个像素的偏移量
vec2 texSize = vec2(textureSize(uTexture, 0));
vec2 texelSize = 1.0 / texSize;
// 应用模糊强度调整偏移量
float blurStrength = max(1.0, uStrength); // 确保至少为1.0
texelSize *= blurStrength;
// 初始化结果颜色,中心像素权重最大
vec4 result = texture(uTexture, vTexCoord) * weight[0];
float totalWeight = weight[0];
// 根据模糊方向选择水平或垂直模糊
if(uHorizontal) {
// 水平方向模糊
for(int i = 1; i < 9; ++i) {
// 向右采样 - 使用更大的范围
result += texture(uTexture, vTexCoord + vec2(texelSize.x * float(i), 0.0)) * weight[i];
// 向左采样
result += texture(uTexture, vTexCoord - vec2(texelSize.x * float(i), 0.0)) * weight[i];
totalWeight += 2.0 * weight[i];
}
} else {
// 垂直方向模糊
for(int i = 1; i < 9; ++i) {
// 向上采样 - 使用更大的范围
result += texture(uTexture, vTexCoord + vec2(0.0, texelSize.y * float(i))) * weight[i];
// 向下采样
result += texture(uTexture, vTexCoord - vec2(0.0, texelSize.y * float(i))) * weight[i];
totalWeight += 2.0 * weight[i];
}
}
// 归一化结果,确保亮度保持不变
fragColor = result / totalWeight;
}""".trimIndent()
}
// 6. 锐化滤镜
fun getSharpenFilterShader(): String {
return """#version 300 es
precision mediump float; // 定义float 精度为 mediump
uniform sampler2D uTexture; // 纹理取样器
in vec2 vTexCoord; // 接收顶点着色器传递过来的纹理坐标
out vec4 fragColor; // 输出片段颜色
void main() {
// 获取纹理大小并计算一个像素的偏移量
vec2 texSize = vec2(textureSize(uTexture, 0));
vec2 texelSize = 1.0 / texSize;
// 中心像素
vec4 center = texture(uTexture, vTexCoord);
// 周围像素
vec4 top = texture(uTexture, vTexCoord + vec2(0.0, -texelSize.y));
vec4 bottom = texture(uTexture, vTexCoord + vec2(0.0, texelSize.y));
vec4 left = texture(uTexture, vTexCoord + vec2(-texelSize.x, 0.0));
vec4 right = texture(uTexture, vTexCoord + vec2(texelSize.x, 0.0));
vec4 topLeft = texture(uTexture, vTexCoord + vec2(-texelSize.x, -texelSize.y));
vec4 topRight = texture(uTexture, vTexCoord + vec2(texelSize.x, -texelSize.y));
vec4 bottomLeft = texture(uTexture, vTexCoord + vec2(-texelSize.x, texelSize.y));
vec4 bottomRight = texture(uTexture, vTexCoord + vec2(texelSize.x, texelSize.y));
// 应用锐化卷积核
vec4 result = center * 9.0 - (top + bottom + left + right + topLeft + topRight + bottomLeft + bottomRight);
// 确保结果在有效范围内
result = clamp(result, 0.0, 1.0);
// 输出锐化结果
fragColor = result;
}""".trimIndent()
}
// 7. 边缘检测滤镜
fun getEdgeDetectionFilterShader(): String {
return """#version 300 es
precision mediump float; // 定义float 精度为 mediump
uniform sampler2D uTexture; // 纹理取样器
in vec2 vTexCoord; // 接收顶点着色器传递过来的纹理坐标
out vec4 fragColor; // 输出片段颜色
void main() {
// 获取纹理大小并计算一个像素的偏移量
vec2 texSize = vec2(textureSize(uTexture, 0));
vec2 texelSize = 1.0 / texSize;
// 采样周围像素
vec4 topLeft = texture(uTexture, vTexCoord + vec2(-texelSize.x, -texelSize.y));
vec4 top = texture(uTexture, vTexCoord + vec2(0.0, -texelSize.y));
vec4 topRight = texture(uTexture, vTexCoord + vec2(texelSize.x, -texelSize.y));
vec4 left = texture(uTexture, vTexCoord + vec2(-texelSize.x, 0.0));
vec4 right = texture(uTexture, vTexCoord + vec2(texelSize.x, 0.0));
vec4 bottomLeft = texture(uTexture, vTexCoord + vec2(-texelSize.x, texelSize.y));
vec4 bottom = texture(uTexture, vTexCoord + vec2(0.0, texelSize.y));
vec4 bottomRight = texture(uTexture, vTexCoord + vec2(texelSize.x, texelSize.y));
// 计算水平和垂直梯度
vec4 gx = -topLeft - 2.0 * left - bottomLeft + topRight + 2.0 * right + bottomRight;
vec4 gy = -topLeft - 2.0 * top - topRight + bottomLeft + 2.0 * bottom + bottomRight;
// 计算梯度幅值
vec4 gradient = sqrt(gx * gx + gy * gy);
// 调整边缘强度,使其更加明显
float strength = 1.0;
gradient *= strength;
// 输出边缘检测结果
fragColor = gradient;
}""".trimIndent()
}
// 8. 饱和度滤镜
fun getSaturationFilterShader(): String {
return """#version 300 es
precision mediump float; // 定义float 精度为 mediump
uniform sampler2D uTexture; // 纹理取样器
uniform float uSaturation;
in vec2 vTexCoord; // 接收顶点着色器传递过来的纹理坐标
out vec4 fragColor; // 输出片段颜色
void main() {
// 获取纹理颜色
vec4 textureColor = texture(uTexture, vTexCoord);
// 加权平均计算灰度值
float gray = dot(textureColor.rgb, vec3(0.299, 0.587, 0.114));
// 插值得到调整饱和度后的颜色
vec3 result = mix(vec3(gray), textureColor.rgb, uSaturation);
// 应用饱和度
fragColor = vec4(result, textureColor.a);
}""".trimIndent()
}
}
}