Android RenderScript 实现 LowPoly 效果(一)

***本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布 **

前言

之前在知乎看到一篇关于 LowPoly js 实现的贴,觉得效果很棒,就打算在 Android 上实现看看。本来以为一两天就可以搞定,没想到不知不觉耗了一周多,踏坑不少。鉴于内容可能会比较多,所以暂时打算分篇幅介绍实现过程。
LowPoly (低多边形) 这种风格在前两年十分风靡,比较有名的《纪念碑谷》就是采用这种美术风格的游戏。

纪念碑谷

效果

LowPolyAndroid

java result

java cost time

这个是由纯 java 实现的版本,在查找资料时找到的,应该是国内某人写的。用 Nexus5 测试,处理尺寸为 900 * 900 的 bitmap 图片耗时基本在 60~90 s 左右,可见效率确实比较不能忍受。从 Log 的计时器看,在处理两千不到的取样点的情况下,三角化和绘图耗时都在 1s 以内,所以估计主要耗时应该在 Sobel 边缘处理和点列采样的过程中,没有验证,有心的同学可以试试看。需要说明的是,我的实现使用了这位同学的 Delaunay 算法和 canvas 部分的代码(表示感谢!),所以在取样点差不多时,我的效果应该和这个差不多。

LowPoly

jni result

jni cost time

这个是国外某位使用 jni 实现的版本,是在着手写该篇时才找到的,看到其介绍由 jni 实现,想必肯定会比 java 快,担心如果比 rs 实现快很多,也没必要写出来了。根据测试效果,明显比 java 实现快很多,处理同样的图片,用了7秒多,比 java 快了十倍左右。从效果图上来看,其边缘更加清晰,尖锐三角形也很多,风格与 java 实现的稍有不同,不过这些应该是因为使用不同的三角化算法导致,对耗时影响应该不大。

rs process
rs result

rs cost time

最后是使用 RenderScript 实现的,为了方便描述实现过程 Demo 分步骤展示了主要的过程。处理同样的图片,通过计时器可以看到,总耗时大概在 2s以内,效率应该说还是比较能够让人接受的。正如前文提到的,使用了前文同学的 Delaunay 算法和 canvas 部分代码,所以风格应该差不多。不同的是,用 rs 处理了灰度、边缘化和采样的过程,大大节省了时间。

实现原理

根据知乎那个答案,这种效果主要有那么几个步骤:

  1. 首先将原图转为灰度图(不是必须);
  2. 使用灰度图(或原图)查找边缘,获得边缘线图;
  3. 从边缘线上采样,采样率决定后期三角的数量与图片精度;
  4. 使用上步骤得到的采样点列,生成三角化序列;
  5. 遍历三角形,使用原图得到的色值填充三角形。

其中,在边缘检测上一般采用 Sobel 算法,即通过 sobel 算子计算某一点的梯度,可以简单理解为某一点与周围点灰度差异度;然后,在三角化处理上,一般采用 Delaunay 算法。

在实现方面,首先 jni 并不是一定会比 java 快的,使用 jni 一般是为了某些特定场景,其中图形处理所需要的大量数学运算就是其一,在 LowPoly 的代码中:

LowPoly.java

...
static {
        System.loadLibrary("lowpoly");
    }

    public static native int[] getTriangles(int[] pixels,int width,int height,int pointNum);
    
    /**
     *
     * @param input
     * @param blur
     * @return
     */
    public static Bitmap generate(Bitmap input,int blur){
        int width = input.getWidth();
        int height = input.getHeight();
        Bitmap newImage = Bitmap.createBitmap(input.getWidth(),input.getHeight(),Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(newImage);
        Paint paint = new Paint();
        paint.setAntiAlias(false);
        paint.setStyle(Paint.Style.FILL);
        int x1, x2, x3, y1, y2, y3;
        int pixels[] = new int[width*height];
        for (int i=0;i<height;i++){
            for(int j=0;j<width;j++){
                pixels[i*width+j] = input.getPixel(j,i);
            }
        }
        int[] triangles = getTriangles(pixels,width,height,blur);
        for (int i=0;i<triangles.length;i=i+6){
            x1 = triangles[i];
            y1 = triangles[i+1];
            x2 = triangles[i+2];
            y2 =  triangles[i+3];
            x3 =  triangles[i+4];
            y3 = triangles[i+5];

            int color = input.getPixel((x1 + x2 + x3) / 3, (y1 + y2 + y3) / 3);
            Path path = new Path();
            path.moveTo(x1,y1);
            path.lineTo(x2,y2);
            path.lineTo(x3,y3);
            path.close();
            paint.setColor(color);
            canvas.drawPath(path,paint);
        }
        return newImage;
    }
...

可见,除了取色、上色两个步骤,其他步骤都通过调用 nativegetTriangles 方法处理,其中应该就是负责图形处理的大量运算。

对于 RenderScript,Google 这样描述:
RenderScript

RenderScript is a framework for running computationally intensive tasks at high performance on Android. 
RenderScript is primarily oriented for use with data-parallel computation, although serial workloads can 
benefit as well. The RenderScript runtime parallelizes work across processors available on a device,
such as multi-core CPUs and GPUs. This allows you to focus on expressing algorithms rather than 
scheduling work. RenderScript is especially useful for applications performing image processing, 
computational photography, or computer vision.

介绍了 RenderScript 主要基于多核 CPU 或者 GPU,主要是为了处理图形运算。早些时候,在手机处理器和图形处理器的性能普遍不高的情况下, rs 似乎并不太引人注意,从网络上少得可怜的资料就可以看出。不过,时至今日,各大手机厂商在硬件的堆砌上似乎一点也没有节制,以及 VR、 AR 等视觉技术的兴起,相信 rs 技术会有更多应用场景。

这篇就到这里,下一篇将会介绍一下 RenderScript 的简单基础和常用函数。

示例

Github-LowPoly

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,014评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,796评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,484评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,830评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,946评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,114评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,182评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,927评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,369评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,678评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,832评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,533评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,166评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,885评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,128评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,659评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,738评论 2 351

推荐阅读更多精彩内容