相机之使用OpenGL预览
相机之使用OpenGL拍照
相机之使用OpenGL录像
相机之为录像添加音频
相机之大眼
相机之贴纸
相机之口红
相机之美颜磨皮
相机之LUT滤镜
相机廋脸效果
瘦脸效果实现:将指定圆形区域内的像素按照一定的规则进行整体偏移,越靠近圆心,像素偏移的强度越大,示意图:
片段着色器
precision highp float;
varying vec2 v_TextureCoord;
uniform sampler2D vTexture;
// 圆半径
uniform float leftRadius;
// 拉伸中心坐标
uniform vec2 leftOrigin;
// 拉伸目标坐标
uniform vec2 leftTarget;
uniform float rightRadius;
uniform vec2 rightOrigin;
uniform vec2 rightTarget;
// textureCoord:采样坐标;originPosition:拉伸中心坐标;targetPosition:拉伸目标坐标;radius:拉伸半径;curve:拉伸算法参数
vec2 stretch(vec2 textureCoord, vec2 originPosition, vec2 targetPosition, float radius, float curve){
// 存储当前采样点到应该采样的点的偏移值
vec2 offset = vec2(0.0);
// 应该采样的点
vec2 result = vec2(0.0);
// 偏移的方向
vec2 direction = targetPosition - originPosition;
// 采样坐标距离拉伸中心坐标越近,偏移越远
float infect = distance(textureCoord, originPosition) / radius;
// 放大infect的影响,默认curve为1,即不放大影响,这个值越大,拉伸到指定点越圆润,越小越尖
infect = pow(infect, curve);
infect = 1.0 - infect;
infect = clamp(infect, 0.0, 1.0);
// 要偏移的值
offset = direction * infect;
result = textureCoord - offset;
return result;
}
void main(){
vec2 stretchedCoord = stretch(v_TextureCoord, leftOrigin, leftTarget, leftRadius, 1.0);
stretchedCoord = stretch(stretchedCoord, rightOrigin, rightTarget, rightRadius, 1.0);
gl_FragColor = texture2D(vTexture, stretchedCoord);
}
着色器程序
class FaceLiftFilter(context: Context, width: Int, height: Int) :
FboFilter(context, R.raw.base_vertex, R.raw.face_lift_frag, width, height) {
private var faceLiftRatio: Float = 0f
private lateinit var matrix: FloatArray
private var facePosition: FloatArray = FloatArray(84 * 2)
// 圆的半径,只有在圆内才会进行变形
private val leftRadiusLocation = GLES20.glGetUniformLocation(mProgram, "leftRadius")
// 拉伸中心坐标,相当于PS中使用液化功能时,鼠标点下的地方
private val leftOriginLocation = GLES20.glGetUniformLocation(mProgram, "leftOrigin")
// 拉伸目标坐标,相当于PS中使用液化功能时,鼠标抬起的地方
private val leftTargetLocation = GLES20.glGetUniformLocation(mProgram, "leftTarget")
private val rightRadiusLocation = GLES20.glGetUniformLocation(mProgram, "rightRadius")
private val rightOriginLocation = GLES20.glGetUniformLocation(mProgram, "rightOrigin")
private val rightTargetLocation = GLES20.glGetUniformLocation(mProgram, "rightTarget")
// 左太阳穴索引
private val leftTempleIndex = 63
// 右太阳穴索引
private val rightTempleIndex = 62
// 下巴索引
private val chinIndex = 64
override fun onDrawInFBO(textureId: Int) {
// 先将textureId的图像画到这一个FBO中
//激活纹理单元0
GLES20.glActiveTexture(GLES20.GL_TEXTURE0)
//将textureId纹理绑定到纹理单元0
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId)
//将纹理单元0传给vTexture,告诉vTexture采样器从纹理单元0读取数据
GLES20.glUniform1i(vTexture, 0)
//在textureId纹理上画出图像
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4)
// 右太阳穴
val rightTemple = getPos(rightTempleIndex)
// 左太阳穴
val leftTemple = getPos(leftTempleIndex)
// 下巴
val chin = getPos(chinIndex)
// 圆的半径
var leftRadius = sqrt(
pow(chin[0] - leftTemple[0], 2f) + pow(chin[1] - leftTemple[1], 2f)
) / 2f
GLES20.glUniform1f(leftRadiusLocation, leftRadius)
var rightRadius = sqrt(
pow(chin[0] - rightTemple[0], 2f) + pow(chin[1] - rightTemple[1], 2f)
) / 2f
GLES20.glUniform1f(rightRadiusLocation, rightRadius)
// 左边鼠标最远的抬起坐标,即左边太阳穴与下巴中点
val leftMaxUp = floatArrayOf(
(leftTemple[0] + chin[0]) / 2f,
(leftTemple[1] + chin[1]) / 2f
)
// 右边鼠标最远的抬起坐标,即右边太阳穴与下巴中点
val rightMaxUp = floatArrayOf(
(rightTemple[0] + chin[0]) / 2f,
(rightTemple[1] + chin[1]) / 2f
)
// 左边鼠标点下坐标,为左边太阳穴 绕着 左边最远的抬起坐标,逆时针旋转 90
val leftDown = floatArrayOf(
leftMaxUp[0] - (leftTemple[1] - leftMaxUp[1]) * sin(90f),
leftMaxUp[1] + (leftTemple[0] - leftMaxUp[0]) * sin(90f)
)
GLES20.glUniform2f(leftOriginLocation, leftDown[0], leftDown[1])
// 右边鼠标点下坐标,为下巴 绕着 右边最远的抬起坐标,逆时针旋转 90
val rightDown = floatArrayOf(
rightMaxUp[0] - (chin[1] - rightMaxUp[1]) * sin(90f),
rightMaxUp[1] + (chin[0] - rightMaxUp[0]) * sin(90f)
)
GLES20.glUniform2f(rightOriginLocation, rightDown[0], rightDown[1])
// 以右边点下坐标为坐标系,y轴和右边鼠标最远的抬起坐标的夹角
val rightTheta = atan(abs(rightDown[0] - rightMaxUp[0]) / abs(rightDown[1] - rightMaxUp[1]))
// 以左边点下坐标为坐标系,y轴和左边鼠标最远的抬起坐标的夹角
val leftTheta = atan(abs(leftDown[0] - leftMaxUp[0]) / abs(leftDown[1] - leftMaxUp[1]))
// 左边抬起坐标
val leftUp = floatArrayOf(
leftDown[0] + leftRadius * faceLiftRatio * sin(leftTheta),
leftDown[1] + leftRadius * faceLiftRatio * cos(leftTheta)
)
GLES20.glUniform2f(leftTargetLocation, leftUp[0], leftUp[1])
// 右边抬起坐标
val rightUp = floatArrayOf(
rightDown[0] - rightRadius * faceLiftRatio * sin(rightTheta),
rightDown[1] + rightRadius * faceLiftRatio * cos(rightTheta)
)
GLES20.glUniform2f(rightTargetLocation, rightUp[0], rightUp[1])
//解除绑定
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0)
}
/**
* 获取纹理坐标系中,index索引对应的位置
* @param index Int
* @return FloatArray
*/
private fun getPos(index: Int): FloatArray {
val pos =
floatArrayOf(facePosition[index * 2], facePosition[index * 2 + 1], 0f, 1f)
val posResult = FloatArray(4)
// 因为facePosition中的人脸数据是向左侧着的,因此位置信息需要旋转90度
Matrix.multiplyMV(posResult, 0, matrix, 0, pos, 0)
// 现在坐标是在归一化坐标系中的值,而OpenGL程序中是在texture2D函数中使用,需要转换为纹理坐标
posResult[0] = (posResult[0] + 1f) / 2f
posResult[1] = (posResult[1] + 1f) / 2f
return posResult
}
/**
* 更新人脸顶点位置
* @param facePosition FloatArray
*/
fun setFacePosition(facePosition: FloatArray) {
this.facePosition = facePosition
}
/**
* 设置瘦脸程度
* @param ratio Float
*/
fun setFaceLiftRatio(ratio: Float) {
// 因为faceLiftRatio过大,会变得很畸形,因此在这里控制faceLiftRatio在[0,1/4]之间
faceLiftRatio = ratio / 4f
}
/**
* 设置变换矩阵,否则人脸位置是旋转90的
* @param matrix FloatArray
*/
fun setUniforms(matrix: FloatArray) {
this.matrix = matrix
}
}