V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
glumess
V2EX  ›  Android

用 Vulkan 渲染写一个 Android GPUImage

  •  
  •   glumess · 323 天前 · 1830 次点击
    这是一个创建于 323 天前的主题,其中的信息可能已经有所发展或是发生改变。

    说的 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 呀~~~

    12 条回复    2021-03-09 22:56:23 +08:00
    visionsmile
        1
    visionsmile  
       323 天前
    老哥牛皮,已 star
    glumess
        2
    glumess  
    OP
       323 天前
    @visionsmile 感谢老铁~~
    codehz
        3
    codehz  
       323 天前 via Android
    Vulkan 在移动平台 /linux 桌面海星,windows 上驱动还是问题很大。。。
    neoblackcap
        4
    neoblackcap  
       323 天前
    @codehz windows 平台就老实用 DX 啦,用 vulkan 找不自在么?
    MCVector
        5
    MCVector  
       323 天前
    @codehz @neoblackcap Vulkan 在 Windows 下的驱动似乎还行呀。我用的 GTX 1070 基本上 Vulkan 1.2 的 feature 都能用,Ray tracing pipeline 的大部分 extensions 都支持
    MCVector
        6
    MCVector  
       323 天前
    如果用 subpass 的话像 Gaussian blur 这种需要读周围 pixel 的 pass 应该就用不了了吧?这样直接用 renderpass 可能会更灵活一些
    neoblackcap
        7
    neoblackcap  
       323 天前
    @MCVector 可能看是哪一家吧,不过 Vulkan 是二等公民,肯定不如 DX 好使。
    MCVector
        8
    MCVector  
       323 天前   ❤️ 1
    @neoblackcap 我不认为 DX12 在 Windows 下能做的 Vulkan 做不了。据我所知基本上支持 DX12 的显卡在 Windows 下都支持 Vulkan 。所以我认为 Vulkan 是二等公民的说法是不成立的。
    TSai2019
        9
    TSai2019  
       323 天前 via Android
    filter-> filter 没有?
    two input 也没有,离完整 GPUImage 还差不少
    glumess
        10
    glumess  
    OP
       323 天前
    @TSai2019 filter -> fitler 应该有了啊,采用两个 renderpass 的方式实现的,two input 还在想怎么搞更好...
    glumess
        11
    glumess  
    OP
       323 天前
    @MCVector 高斯模糊的 shader 应该可以吧,就是 shader 里面的操作呀
    MCVector
        12
    MCVector  
       322 天前
    @glumess 哈哈我之前也是这么想的。
    如果用 subpass 的话在当前的 fragment shader 里只能读前一个 subpass 输出的同一个 fragment (subpassLoad()), 而不是像传统的 renderpass 一样可以用 uv 读。高斯模糊需要周围像素的信息所以在这种情况下会有限制。
    Subpass 存在的原因是告诉 GPU 不需要等到整张图片都处理完了再开始下一个 pass 的处理,而是当前 fragment 处理完了下一个 pass 就可以操作这个 fragment 了。这样可以减少一些 overhead,同时也限制了只能操作当前 fragment 。
    关于   ·   帮助文档   ·   API   ·   FAQ   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   4323 人在线   最高记录 5497   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 33ms · UTC 08:48 · PVG 16:48 · LAX 00:48 · JFK 03:48
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.