相机之廋脸

相机之使用OpenGL预览
相机之使用OpenGL拍照
相机之使用OpenGL录像
相机之为录像添加音频
相机之大眼
相机之贴纸
相机之口红
相机之美颜磨皮
相机之LUT滤镜

相机廋脸效果

瘦脸效果实现:将指定圆形区域内的像素按照一定的规则进行整体偏移,越靠近圆心,像素偏移的强度越大,示意图:


jialing.jpeg

片段着色器

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
    }
}

你可能感兴趣的:(相机之廋脸)