用 Vulkan 渲染写一个 Android GPUImage

2021-03-08 20:38:05 +08:00
 glumess

说的 GPUImage 相信大家都不陌生,GPUImage 是做滤镜、渲染、特效最主流的框架之一,被广泛应用在短视频应用中。

GPUImage 目前还是采用 OpenGL 进行渲染的,可随着技术的发展进步,iOS 系统都开始抛弃 OpenGL 拥抱 Metal 了,Android 也推出了 Vulkan 渲染机制。

关于 Vulkan,大家可能会有点陌生,它和 OpenGL 一样也是跨平台的渲染接口,就是学习成本高了一点,调用流程麻烦了一点,但还是可以掌握的~~~

而且一旦掌握了 Vulkan, 再去看 Metal,或者 Windows 平台下的 Direct3D,就会发现它们有很多共通之处的,很多概念都是可以互相借鉴的,就好比编程语言一样,掌握了 Kotlin 再去看 Swift 感觉就傻傻分不清楚了。

这次要介绍的就是用 Vulkan 实现一个 Android GPUImage 了。

我已经实现了大部分的代码,组建了基本的渲染链机制,复刻了一些特效,具体可以看代码详情:

代码地址如下,欢迎 Star !!!

https://github.com/glumes/Vulkan-GPUImage

想要后续添加特效也是非常方便的,以曝光特效为例:

static const char *shader =
        "#version 400\n"
        "#extension GL_ARB_separate_shader_objects : enable\n"
        "#extension GL_ARB_shading_language_420pack : enable\n"
        "layout (binding = 0) uniform sampler2D tex;\n"
        "layout (location = 0) in vec2 texcoord;\n"
        "layout (location = 0) out vec4 uFragColor;\n"
        "layout (push_constant) uniform exposure {\n"
        " float exposure;\n"
        "} pushVals;\n"
        "void main() {\n"
        "    vec4 textureColor = texture(tex,  texcoord); \n"
        "   uFragColor = vec4(textureColor.rgb * pow(2.0,pushVals.exposure),textureColor.w);\n"
        "}";

class ExposureFilter : public VulkanFilter {

public:
    ExposureFilter() : VulkanFilter() {
        pFragShader = shader;
        pushConstant.resize(1);
        pushConstant[0] = exposure;
    }

    virtual void setProcess(uint32_t process);

protected:

private:
    float exposure = 1.0f;
};

首先实现对应的 Shader,然后对应特效类继承自 VulkanFilter,如果特效没有需要更新的参数,可以不用 pushConstant 。

如果有的话把 pushConstant 的长度修改为参数的个数,每个参数对应 shader 中推送常数的值即可。

然后在 setProcess 里面修改对应的 pushConstant 值:

void ExposureFilter::setProcess(uint32_t process) {
    pushConstant[0] = FilterUtil::getProcess(process,-10.0f,10.0f);
}

在应用中拖动 SeekBar,就可以更改对应的常量值了,从而修改 Shader 效果,对于简单的特效,基本上不用五分钟就可以添加一个新的效果了,很多封装工作都放在 VulkanFilter 里面了。

在实现上采用的是多 RenderPass 的方式,其实也可以用多 Subpass 的方式,但是不好做效果切换,干脆就多 RenderPass 了,其实也可以两者混合。

代码写的不是特别优雅,后面持续优化了,各位走过路过不要错过,目前全网还搜不到 Vulkan 做 GPUImage 相关的实现呢,算是开张了,觉得有帮助就点个 Star 呀~~~

9336 次点击
所在节点    Android
12 条回复
visionsmile
2021-03-08 20:47:00 +08:00
老哥牛皮,已 star
glumess
2021-03-08 20:47:57 +08:00
@visionsmile 感谢老铁~~
codehz
2021-03-08 20:48:56 +08:00
Vulkan 在移动平台 /linux 桌面海星,windows 上驱动还是问题很大。。。
neoblackcap
2021-03-08 23:42:44 +08:00
@codehz windows 平台就老实用 DX 啦,用 vulkan 找不自在么?
MCVector
2021-03-09 00:11:58 +08:00
@codehz @neoblackcap Vulkan 在 Windows 下的驱动似乎还行呀。我用的 GTX 1070 基本上 Vulkan 1.2 的 feature 都能用,Ray tracing pipeline 的大部分 extensions 都支持
MCVector
2021-03-09 00:24:08 +08:00
如果用 subpass 的话像 Gaussian blur 这种需要读周围 pixel 的 pass 应该就用不了了吧?这样直接用 renderpass 可能会更灵活一些
neoblackcap
2021-03-09 00:26:35 +08:00
@MCVector 可能看是哪一家吧,不过 Vulkan 是二等公民,肯定不如 DX 好使。
MCVector
2021-03-09 01:33:52 +08:00
@neoblackcap 我不认为 DX12 在 Windows 下能做的 Vulkan 做不了。据我所知基本上支持 DX12 的显卡在 Windows 下都支持 Vulkan 。所以我认为 Vulkan 是二等公民的说法是不成立的。
TSai2019
2021-03-09 08:37:41 +08:00
filter-> filter 没有?
two input 也没有,离完整 GPUImage 还差不少
glumess
2021-03-09 13:22:21 +08:00
@TSai2019 filter -> fitler 应该有了啊,采用两个 renderpass 的方式实现的,two input 还在想怎么搞更好...
glumess
2021-03-09 13:23:10 +08:00
@MCVector 高斯模糊的 shader 应该可以吧,就是 shader 里面的操作呀
MCVector
2021-03-09 22:56:23 +08:00
@glumess 哈哈我之前也是这么想的。
如果用 subpass 的话在当前的 fragment shader 里只能读前一个 subpass 输出的同一个 fragment (subpassLoad()), 而不是像传统的 renderpass 一样可以用 uv 读。高斯模糊需要周围像素的信息所以在这种情况下会有限制。
Subpass 存在的原因是告诉 GPU 不需要等到整张图片都处理完了再开始下一个 pass 的处理,而是当前 fragment 处理完了下一个 pass 就可以操作这个 fragment 了。这样可以减少一些 overhead,同时也限制了只能操作当前 fragment 。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/759756

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX