Shader特效——“水彩画”的实现【GLSL】
发表于2018-02-06
在用shader实现水彩画之前,对算法进行了改进
原图:
效果图:
原图:
效果图:
(以下为简化代码)
片元着色器代码1:
uniform sampler2D OpenGL; uniform sampler2D noiseTexture; uniform float uQuantLevel; // 2-6 uniform float uWaterPower; // 8-64 const vec2 texSize = vec2(256., 256.); varying vec2 vUV; vec4 quant(vec4 cl, float n) { cl.x = floor(cl.x * 255./n)*n/255.; cl.y = floor(cl.y * 255./n)*n/255.; cl.z = floor(cl.z * 255./n)*n/255.; return cl; } void main(void) { vec4 noiseColor = uWaterPower * texture2D(noiseTexture, vUV); vec2 newUV = vec2(vUV.x + noiseColor.x / texSize.x, vUV.y + noiseColor.y / texSize.y); vec4 fColor = texture2D(OpenGL, newUV); vec4 color = quant(fColor, 255./pow(2., uQuantLevel)); //vec4 color = vec4(1., 1., .5, 1.); gl_FragColor = color; }
片元着色器代码2:
uniform sampler2D renderTexture; const vec2 texSize = vec2(256., 256.); varying vec2 vUV; vec4 dip_filter(mat3 filter, vec2 filter_pos_delta[9], sampler2D image, vec2 xy, vec2 texSize) { vec4 final_color = vec4(0., 0., 0., 0.); for(int i=0; i<3; i++) { for(int j=0; j<3; j++) { vec2 new_xy = vec2(xy.x + filter_pos_delta[3*i+j].x, xy.y + filter_pos_delta[3*i+j].y); vec2 new_uv = vec2(new_xy.x / texSize.x, new_xy.y / texSize.y); final_color += texture2D(renderTexture, new_uv) * filter[i][j]; } } return final_color; } void main(void) { //vec2(-1., -1.), vec2(0., -1.), vec2(1., -1.), //vec2(-1., 0.), vec2(0., 0.), vec2(1., 0.), //vec2(-1., 1.), vec2(0., 1.), vec2(1., 1.) vec2 filter_pos_delta[9] = vec2[]( vec2(-1., -1.), vec2(0., -1.), vec2(1., -1.), vec2(-1., 0.), vec2(0., 0.), vec2(1., 0.), vec2(-1., 1.), vec2(0., 1.), vec2(1., 1.)); mat3 filter = mat3(1./16., 1./8.,1./16., 1./8.,1./4.,1./8., 1./16.,1./8.,1./16.); vec2 xy = vec2(vUV.x * texSize.x, vUV.y * texSize.y); vec4 color = dip_filter(filter, filter_pos_delta, renderTexture, xy, texSize); gl_FragColor = color; }
还有就是Candy Cat的博客,虽然看起来效果比较好,不过她是用Distance Field画出来的,而且颜色还得根据 Kubelka-Munk 模型计算(K 、S系数都来自论文提供的数据),比较适合交互式的应用
效果 2-1:
效果 2-2:
片元着色器代码:
#version 330 uniform sampler2D iChannel0; // Table of pigments // from Computer-Generated Watercolor. Cassidy et al. // K is absortion. S is scattering vec3 K_QuinacridoneRose = vec3(0.22, 1.47, 0.57); vec3 S_QuinacridoneRose = vec3(0.05, 0.003, 0.03); vec3 K_FrenchUltramarine = vec3(0.86, 0.86, 0.06); vec3 S_FrenchUltramarine = vec3(0.005, 0.005, 0.09); vec3 K_CeruleanBlue = vec3(1.52, 0.32, 0.25); vec3 S_CeruleanBlue = vec3(0.06, 0.26, 0.40); vec3 K_HookersGreen = vec3(1.62, 0.61, 1.64); vec3 S_HookersGreen = vec3(0.01, 0.012, 0.003); vec3 K_HansaYellow = vec3(0.06, 0.21, 1.78); vec3 S_HansaYellow = vec3(0.50, 0.88, 0.009); // Math functions not available in webgl vec3 cosh(vec3 val) { vec3 e = exp(val); return (e + vec3(1.0) / e) / vec3(2.0); } vec3 tanh(vec3 val) { vec3 e = exp(val); return (e - vec3(1.0) / e) / (e + vec3(1.0) / e); } vec3 sinh(vec3 val) { vec3 e = exp(val); return (e - vec3(1.0) / e) / vec3(2.0); } // Kubelka-Munk reflectance and transmitance model void KM(vec3 k, vec3 s, float x, out vec3 refl, out vec3 trans) { // 5.1 Specifying the optical properties of pigments vec3 a = (k+s)/s; // K = S(a - 1) vec3 b = sqrt(a*a - vec3(1.0)); vec3 bsx = b*s*vec3(x); vec3 sinh_bsx = sinh(bsx); vec3 denom = b*cosh(bsx)+a*sinh_bsx; refl = sinh_bsx/denom; trans = b/denom; } // The watercolours tends to dry first in the center // and accumulate more pigment in the corners float BrushEffect(float dist, float h_avg, float h_var) { float h = max(0.0,1.0-10.0*abs(dist)); h = pow(h, 4); return (h_avg + h_var*h) * smoothstep(-0.01, 0.002, dist); } // Kubelka-Munk model for layering void CompositeLayers(vec3 r0, vec3 t0, vec3 r1, vec3 t1, out vec3 r, out vec3 t) { // 5.2 Optical compositing of layers r = r0 + t0*t0*r1 / (vec3(1.0)-r0*r1); t = t0*t1 / (vec3(1.0)-r0*r1); } // Simple 2d noise fbm with 3 octaves float Noise2d(vec2 p) { float t = texture2D(iChannel0, p).x; t += 0.5 * texture2D(iChannel0, p * 2.0).x; t += 0.25 * texture2D(iChannel0, p * 4.0).x; return t / 1.75; } float DistanceCircle(vec2 pos, vec2 center, float radius) { vec2 offset = pos - center; return 1.2 * radius - length(offset); //return 1.0 - distance(pos, center) / radius } float DistanceSegment(vec2 pos, vec2 pt1, vec2 pt2, float delta) { float dist = 0; vec2 a = pos - pt1; vec2 b = pt2 - pt1; // "clamp" restrain the length of the vector "c" // |a|*cos(theta) / |b| vec2 c = b * clamp(dot(a, b)/dot(b, b), 0.0, 1.0); vec2 e = c - a; // delta control the width of the line dist = 0.5*delta - length(e); return dist; } void main(void) { vec2 iResolution = textureSize(iChannel0, 0); vec2 uv = gl_FragCoord.xy / iResolution.xy; vec3 R0,T0,R1,T1; // Background float background = 0.1 + 0.2 * Noise2d(uv * vec2(1.0)); KM(K_HansaYellow, S_HansaYellow, background, R0, T0); // Edge roughness: 0.04 vec2 pos = uv * vec2(1.0, iResolution.y / iResolution.x) + vec2(0.04 * Noise2d(uv * vec2(0.1))); float dist = DistanceCircle(pos, vec2(0.5, 0.5), 0.15); // Average thickness: 0.2, edge varing thickness: 0.2 float circle = BrushEffect(dist, 0.2, 0.2); // Granulation: 0.85 circle *= 0.15 + 0.85 * Noise2d(uv * vec2(0.2)); KM(K_QuinacridoneRose, S_QuinacridoneRose, circle, R1, T1); // 将上次计算的R0 T0与新计算的R1 T1 进行合成,并生成新的R0 T0 CompositeLayers(R0, T0, R1, T1, R0, T0); // Edge roughness: 0.03 pos = uv * vec2(1.0, iResolution.y / iResolution.x) + vec2(0.03 * Noise2d(uv * vec2(0.1))); dist = DistanceCircle(pos, vec2(0.4, 0.3), 0.15); // Average thickness: 0.3, edge varing thickness: 0.1 circle = BrushEffect(dist, 0.3, 0.1); // Granulation: 0.65 circle *= 0.35 + 0.65 * Noise2d(uv * vec2(0.2)); KM(K_CeruleanBlue, S_CeruleanBlue, circle, R1, T1); CompositeLayers(R0, T0, R1, T1, R0, T0); // Edge roughness: 0.02 pos = uv * vec2(1.0, iResolution.y / iResolution.x) + vec2(0.02 * Noise2d(uv * vec2(0.1))); dist = DistanceCircle(pos, vec2(0.6, 0.3), 0.15); // Average thickness: 0.3, edge varing thickness: 0.2 circle = BrushEffect(dist, 0.3, 0.2); // Granulation: 0.45 circle *= 0.55 + 0.45 * Noise2d(uv * vec2(0.2)); KM(K_FrenchUltramarine, S_FrenchUltramarine, circle, R1, T1); CompositeLayers(R0, T0, R1, T1, R0, T0); // Opaque paints, e.g. Indian Red pos = uv * vec2(1.0, iResolution.y / iResolution.x) + vec2(0.02 * Noise2d(uv * vec2(0.3))); dist = DistanceSegment(pos, vec2(0.2, 0.1), vec2(0.4, 0.25), 0.03); float line = BrushEffect(dist, 0.2, 0.1); KM(K_HansaYellow, S_HansaYellow, line, R1, T1); CompositeLayers(R0, T0, R1, T1, R0, T0); // Transparent paints, e.g. Quinacridone Rose pos = uv * vec2(1.0, iResolution.y / iResolution.x) + vec2(0.02 * Noise2d(uv * vec2(0.2))); dist = DistanceSegment(pos, vec2(0.2, 0.5), vec2(0.4, 0.55), 0.03);; line = BrushEffect(dist, 0.2, 0.1); KM(K_QuinacridoneRose, S_QuinacridoneRose, line, R1, T1); CompositeLayers(R0, T0, R1, T1, R0, T0); // Interference paints, e.g. Interference Lilac pos = uv * vec2(1.0, iResolution.y / iResolution.x) + vec2(0.02 * Noise2d(uv * vec2(0.1))); dist = DistanceSegment(pos, vec2(0.6, 0.55), vec2(0.8, 0.4), 0.03); line = BrushEffect(dist, 0.2, 0.1); KM(K_HookersGreen, S_HookersGreen, line, R1, T1); CompositeLayers(R0, T0, R1, T1, R0, T0); gl_FragColor = vec4(R0+T0, 1.0); }