opengl实现水墨画效果

1,789 阅读3分钟

非常感谢大飞哥路很长OoO分享的文章《JetPack-Compose 水墨画效果👍👍👍》。在这篇文章中,大飞哥使用了JetPack实现了水墨画效果。

原理呢,是使用两张事先准备好的图片,一张是原图,一张是黑白图,然后使用PorterDuffXfermode将两张图片按一定的形状混合,就得到了这样的水墨画效果。 原理很简单,但是呢,点子和效果很棒。而且大佬写的文章也很棒,讲的很细致。如果大家觉得这个效果很棒,可以去飞哥的博客点赞。
在大飞哥这篇文章的下面,有一条这样的评论:

image.png

评论里说直接处理原图色值,比搞两张图片更通用吧。我也觉得直接处理色值更加的通用一点。而无论java,kotlin,c还是c++等CPU类型的语言,都不适合进行大量的运算,尤其图形处理。所以大佬想要在Android中使用cpu语言逐像素处理来做到这样的水墨画效果会很难,也不是不能做,就是慢。
那么可以考虑使用GPU来做这件事,使用opengl或者renderscript来做。考虑到opengl在Android平台似乎比renderscript的前景要好。我就决定使用opengl来做,来验证一下。因为我只是验证,我没必要写全部的demo了,也没必要像飞哥那样对demo的效果和文章进行仔细的打磨。
为了快速验证,我直接在shadertoy上进行验证:

chrome-capture.gif
如上面的gif所示,我选用的是一个圆的形状,慢慢的去扩大这个圆,然后在圆内,图片是彩色的【即原图】,而在圆外面,图片是黑白色的。代码如下【ps:你也可以直接去shadertoy查看效果】:

#define END_TIME 10.0
#define RATIO 1.5*max(iResolution.x,iResolution.y)/min(iResolution.x,iResolution.y)
float sdCircle(vec2 p,float r){
    return length(p)-r;
}

float rgb2gray(vec3 col){
    return 0.2989 * col.x + 0.5870 * col.y + 0.1140 * col.z;
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec2 p = (2.0*fragCoord-iResolution.xy)/iResolution.y;
    vec2 uv = fragCoord/iResolution.xy;
    vec3 col = texture(iChannel0,uv).xyz;
    vec3 black = vec3(rgb2gray(col));
    
    float r = sin(iTime)*RATIO+RATIO;
    col = mix(col,black,smoothstep(0.0,0.01,sdCircle(p,r)));
    fragColor = vec4(col,1.0);
}

下面我分别简单解释一下opengl&&opengles,shadertoy,以及我的代码:

opengl:

opengl是Open Graphics Library的缩写,即开源图形库;使用glsl作为开发语言,这是一种类似c的语言,但它可以使用gpu进行运算,进而操作图形。

opengles:

opengles是Open Graphics Library Embedded System的缩写,即开源图形库嵌入式系统,是针对嵌入式设备专门优化的图形库。而Android使用的就是opengles,ios也可以使用。

shadertoy:

当你了解opengl以后,你会慢慢的接触到vertex shader, fragment shader...一些渲染器脚本,其中fragment shader就是在vertex shader渲染完成以后,对于vertex shader生成的图元进行逐像素的处理的渲染器脚本。shadertoy是一个可以浏览和创作fragment shader的在线网站。虽然shadertoy只有fragment shader,但是这不影响各位大佬使用它进行各种魔术表演。想观看表演的同学可以去shadertoy大饱眼福。

我的代码:

sdCircle函数:当点在圆内时,返回负数,当点在圆上时返回0,当点在圆外时,返回正数
rgb2gray函数:把彩色像素转为灰度,输入像素点的rgb值,输出像素点转为灰度图的值
mainImage:

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec2 p = (2.0*fragCoord-iResolution.xy)/iResolution.y;
    vec2 uv = fragCoord/iResolution.xy;
    vec3 col = texture(iChannel0,uv).xyz;//获得某一像素的颜色
    vec3 black = vec3(rgb2gray(col));//将颜色转为灰度图的值
    
    float r = sin(iTime)*RATIO+RATIO;//随着时间的流逝周期性的从[0->2*RATIO]的圆的半径
    col = mix(col,black,smoothstep(0.0,0.01,sdCircle(p,r)));//在圆内,使用原图的颜色,在圆外使用灰度颜色
    fragColor = vec4(col,1.0);
}

写在最后

我在这里做的只是验证的工作,希望能有大佬在看到我这篇文章后有些启发,使用opengles完成水墨画效果的制作【抛砖引玉】。最后的最后,希望大家使用各式语言做出更多的优秀的创作,或许你就是下一个写出"自如裸眼3D"的大佬!!!