Shader特效——“水彩画”的实现【GLSL】

发表于2018-02-06
评论0 1.5k浏览

想免费获取内部独家PPT资料库?观看行业大牛直播?点击加入腾讯游戏学院游戏程序行业精英群

711501594
在用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);  
}  

原文链接

著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引

游戏学院公众号二维码
腾讯游戏学院
微信公众号

提供更专业的游戏知识学习平台