[WebGL shader]磨皮油画等多种高级滤镜

美颜滤镜都用过,用webgl写一个呢?点就来就学如何用shader实现一个柔光磨皮效果滤镜,自己动手丰衣足食,从此再也不用担心照片处理!

1.webgl加载图片

顶点着色器

c++ 复制代码
precision mediump float;
attribute vec2 aPosition;
attribute vec2 aTexCoord;
varying vec2 vTexCoord;

void main() {
    gl_Position = vec4(aPosition, 0.0, 1);
    vTexCoord = aTexCoord;
}

片元着色器

c++ 复制代码
precision mediump float;
varying vec2 vTexCoord;
uniform sampler2D uTex;
void main() {
    gl_FragColor = texture2D(uTex, vTexCoord).ggba;
}
js 复制代码
  async function init(imageUrl) {
        var gl = initGl('webgl');
        const vs = document.getElementById('vertexShader').innerText;

        const fs = document.getElementById('fragmentShader').innerText;

        var program = initShaderProgram(gl, vs, fs);

        const textureCoord = [
          [0.0, 0.0],
          [1.0, 0.0],
          [0.0, 1.0],
          [0.0, 1.0],
          [1.0, 0.0],
          [1.0, 1.0]
        ].reverse();
        //贴图uv
        initArrBuffer(gl, 'aTexCoord', new Float32Array(flatArr(textureCoord)), 2);
        //设置贴图
        var { texture, image } = await initTexture(gl, imageUrl);

        const positions = [
          [-1.0, 1.0],
          [1.0, 1.0],
          [-1.0, -1.0],
          [-1.0, -1.0],
          [1.0, 1.0],
          [1.0, -1.0]
        ].reverse();
        //形状坐标点
        initArrBuffer(gl, 'aPosition', new Float32Array(flatArr(positions)), 2);

        //开启正反面
        gl.enable(gl.CULL_FACE);
        function drawScene() {
          cleanGl(gl);
          //画三角形
          gl.drawArrays(gl.TRIANGLES, 0, positions.length);
        }
        drawScene();
      }
html 复制代码
 <div>
      <img id="picture" />
      <canvas id="webgl"></canvas>
    </div>
    <div>
      <input type="file" placeholder="选择图片" onchange="changeFile(this)" />
      <button onclick="savePic()">保存图片</button>
    </div>

上传图片,按比例缩放,另存为webgl图片

js 复制代码
function updatePic(url) {
        const picture = document.getElementById('picture');
        picture.src = url;
        picture.onload = () => {
        //按比例缩放
          const s = 500 / picture.naturalWidth;
          const w = 500;
          const h = picture.naturalHeight * s;
          webgl.width = 500;
          webgl.height = h;
          webgl.style.width = w + 'px';
          webgl.style.height = h + 'px';
          picture.style.width = w + 'px';
          picture.style.height = h + 'px';
          init(picture.src);
        };
      }
      //
      function changeFile(dom) {
        if (dom.files.length) {
          updatePic(window.URL.createObjectURL(dom.files[0]));
        }
      }
      //保存图片
      function savePic() {
        const a = document.createElement('a');

        const fileName = new Date().getTime() + '.png';
        let file = convertBase64UrlToFile(webgl.toDataURL('image/png'), fileName);
        a.download = fileName;
        a.href = window.URL.createObjectURL(file);
        a.click();
      }
      updatePic('pic1.jpg');

左侧是原始图,右侧是wegbl效果图

2.shader美白效果滤镜

美白的公式: 白色 -(人像颜色的反色)^n,当n为1时原图颜色,当n<1时肤色变暗,当n>1时肤色变亮。

c++ 复制代码
precision mediump float;
      varying  vec2 vTexCoord;
      uniform float light;
      uniform sampler2D uTex;
      void main() {
        vec3 texColor=texture2D(uTex, vTexCoord).rgb;        
        vec3 c1= vec3(1.0)-texColor;
        gl_FragColor =  vec4(vec3(1.0)- pow(c1,vec3(light)) ,1.0);
      }

添加dat.GUI控制

js 复制代码
const range={
light:[0.5, 3.0, 0.5]
}
const uniforms = {
          light: 2
        };
        var gui = new dat.GUI();
        //监听uniform值改变
        for (let k in uniforms) {
          gui.add(uniforms, k, ...range[k]).onChange(drawScene);
        }
        
function drawScene() {
          cleanGl(gl);
          //传入uniform值
          for (const k in uniforms) {
            gl.uniform1f(gl.getUniformLocation(gl.program, k), uniforms[k]);
          }
          //画三角形
          gl.drawArrays(gl.TRIANGLES, 0, positions.length);
        }

基于人像颜色计算出来的美白效果比单纯调整亮度曝光之类要更有质感,更为柔和。

3.shader模糊效果滤镜

模糊效果逻辑: 去上下左右四周的像素,按照一定比重相加,取到的颜色值与周围相近,即得到模糊的效果。

c++ 复制代码
precision mediump float;
varying vec2 vTexCoord; 
uniform sampler2D uTex;
uniform vec2 texSize;
uniform float offset1;
float offset2 =3.2307692308 ;
float weight=0.1362162162;
float weight1 =0.18972972972 ;
 float weight2 =0.04216216218;
//计算模糊颜色
vec3 getBlur(vec3 c0, float f, float w) {
    vec3 c = c0;
        //取下offset像素的颜色
    c += texture2D(uTex, vTexCoord + vec2(0.0, f) / texSize).rgb * w;
        //取上offset像素的颜色
    c += texture2D(uTex, vTexCoord - vec2(0.0, f) / texSize).rgb * w;
        //取右offset像素的颜色
    c += texture2D(uTex, vTexCoord + vec2(f, 0.0) / texSize).rgb * w;
        //取左offset像素的颜色
    c += texture2D(uTex, vTexCoord - vec2(f, 0.0) / texSize).rgb * w;
    return c;
}
void main() {
    vec3 texColor = texture2D(uTex, vTexCoord).rgb;
    //texColor * weight颜色的一定权重值作为初始值
    vec3 c1 = getBlur(texColor * weight, offset1, weight1);
    c1 = getBlur(c1, offset2, weight2);
    //clamp(x,0.0,1.0)将值规整到0.0-1.0之间
    gl_FragColor = vec4(clamp(c1, 0.0, 1.0), 1.0);
}

添加offset1的模糊程度控制

js 复制代码
const uniforms = {
        offset1: 1.3846153846
      };
      const range = {
        offset1: [0.0, 3.2, 0.1]
      };

可以看到offset1越大,模糊效果越厉害。

4.美白磨皮滤镜效果

将美白效果+模糊效果就是美白磨皮滤镜效果了。

c++ 复制代码
 precision mediump float;
            varying  vec2 vTexCoord;
            uniform sampler2D uTex;
            uniform vec2 texSize;
            uniform float light;
            uniform  float offset1 ;

              float offset2 =3.2307692308 ;
              float weight=0.1362162162;
              float weight1 =0.18972972972 ;
              float weight2 =0.04216216218;

      //模糊
      vec3 getBlur(vec3 c0, float f, float w) {
        vec3 c = c0;

        //取下offset[i]像素的颜色
        c += texture2D(uTex, vTexCoord + vec2(0.0, f) / texSize).rgb * w;
        //取上offset[i]像素的颜色
        c += texture2D(uTex, vTexCoord - vec2(0.0, f) / texSize).rgb * w;
        //取右offset[i]像素的颜色
        c += texture2D(uTex, vTexCoord + vec2(f, 0.0) / texSize).rgb * w;
        //取左offset[i]像素的颜色
        c += texture2D(uTex, vTexCoord - vec2(f, 0.0) / texSize).rgb * w;
        return c;
      }
            void main() {
              vec3 texColor=texture2D(uTex, vTexCoord).rgb;

              vec3 c1=  getBlur(texColor*weight,offset1,weight1);
                c1=  clamp(getBlur(c1,offset2,weight2),0.0,1.0);
              //美白
                vec3 c2=pow((1.0-texColor),vec3(light));

              gl_FragColor =vec4(1.0-(1.0-c1)*c2,1.0);
            }

磨皮程度offset1和亮度light的范围控制

js 复制代码
const uniforms = {
        offset1: 1.3846153846,
        light: 1
      };
      const range = {
        offset1: [0.0, 3.2, 0.1],

        light: [0.5, 3.0, 0.1]
      };

加了美白磨皮后有种朦胧美的感觉。

5.shader对比度效果滤镜

对比度计算(rgb颜色值-128)*增强程度+128,导致的结果就是亮色更亮,暗色更暗。颜色值128对应着色器里面的0.5

c++ 复制代码
precision mediump float;
      varying  vec2 vTexCoord;
      uniform float contract;
      uniform sampler2D uTex;
      vec3 getColor(vec3 c) {//增加对比度
        return (c - 0.5) * contract + 0.5;
      }
      void main() {
        vec3 texColor=texture2D(uTex, vTexCoord).rgb;

        vec3 c1=  getColor(texColor);
        gl_FragColor =vec4(c1,1.0);
      }

添加contract的对比度控制

js 复制代码
const uniforms = {
        contract: 1.2
      };
      const range = {
        contract: [1.0, 2.0, 0.1]
      };

6.柔光磨皮效果

将美白+对比度+模糊度就是柔光磨皮效果了

c++ 复制代码
precision mediump float;
varying vec2 vTexCoord;
uniform sampler2D uTex;
uniform vec2 texSize;
uniform float contract;
uniform float offset1;

float offset2 = 3.2307692308;
float weight = 0.1362162162;
float weight1 = 0.18972972972;
float weight2 = 0.04216216218;

vec3 getColor(vec3 c) {//增加对比度
    return (c - 0.5) * contract + 0.5;
}

vec3 getBlur(vec3 c0, float f, float w) {
    vec3 c = c0;

        //取下offset[i]像素的颜色
    c += texture2D(uTex, vTexCoord + vec2(0.0, f) / texSize).rgb * w;
        //取上offset[i]像素的颜色
    c += texture2D(uTex, vTexCoord - vec2(0.0, f) / texSize).rgb * w;
        //取右offset[i]像素的颜色
    c += texture2D(uTex, vTexCoord + vec2(f, 0.0) / texSize).rgb * w;
        //取左offset[i]像素的颜色
    c += texture2D(uTex, vTexCoord - vec2(f, 0.0) / texSize).rgb * w;
    return c;
}
void main() {
    vec3 texColor = texture2D(uTex, vTexCoord).rgb;
    //模糊
    vec3 c1 = getBlur(texColor * weight, offset1, weight1);
    c1 = clamp(getBlur(c1, offset2, weight2), 0.0, 1.0);
    //对比度
    vec3 c2 = getColor(texColor);
    //美白
    vec3 c3=pow(1.0-(1.0-c1)*(1.0-c2),vec3(2.0-light));
    gl_FragColor = vec4(c3, 1.0);
}

照片风格从冷色调到暖色调就这么简单。

7.仙气模糊效果滤镜

另一种blur模糊,有方向的模糊。

c++ 复制代码
precision mediump float;
varying vec2 vTexCoord;
uniform sampler2D uTex;
uniform vec2 texSize;
uniform float radius;
uniform float contract;
#define DIST 4.0
vec3 getColor(vec3 c) {//增加对比度
    return (c - 0.5) * contract + 0.5;
}
//模糊
vec3 getBlur() {
    vec2 delta = vec2(radius / texSize.x, radius / texSize.y);
    vec3 c = vec3(0.0);
    float total = 0.0, percent = 0.0, weight = 0.0, offset = clamp(fract(sin(dot(vTexCoord * texSize, vec2(12.9898, 78.233))) * 437.5) - 0.5, -0.5, 0.5);
    for(float t = -DIST; t <= DIST; t++) {
        percent = (t + offset) / DIST;
        weight = 1.0 - abs(percent);
        c += texture2D(uTex, vTexCoord + delta * percent).rgb * weight;
        total += weight;
    }
    vec4 c0 = vec4(clamp(c / total, 0.0, 1.0), 1.0);
    return c0.rgb;
}

void main() {
    vec3 texColor = texture2D(uTex, vTexCoord).rgb;

    vec3 c1 = getBlur();
    c1 = getColor(c1);
    vec3 c2 = getColor(texColor);
    gl_FragColor = vec4(1.0 - (1.0 - c2) * (1.0 - c1), 1.0);
}

添加contract对比度和radius模糊半径控制

js 复制代码
  const uniforms = {
        contract: 1.2,
        radius: 32
      };
      const range = {
        contract: [1.0, 2.0, 0.1],
        radius: [1.0, 32, 1]
      };

从左上到右下的斜方向模糊

8.Lomo风格效果滤镜

以一个点为中心,向四周逐渐变暗,并且高饱和度。

c++ 复制代码
precision mediump float;
     //颜色RGB饱和度的权重
     #define R_LU 0.3
     #define G_LU 0.59
     #define B_LU 0.11
     #define SAT 2.2//饱和度

     varying vec2 vTexCoord;
     uniform sampler2D uTex;
     uniform vec2 texSize;
     uniform float centerX;
     uniform float centerY;
     uniform float radius;

     uniform float exposure;//曝光

     void main() {
         vec2 center = vec2(centerX, centerY);          

         vec3 c0 = texture2D(uTex, vTexCoord).rgb, c;
         //颜色饱和度
         c.r = ((R_LU + (1.0 - R_LU) * SAT) * c0.r) + ((G_LU - G_LU * SAT) * c0.g) + ((B_LU - B_LU * SAT) * c0.b);
         c.g = ((R_LU - R_LU * SAT) * c0.r) + ((G_LU + (1.0 - G_LU) * SAT) * c0.g) + ((B_LU - B_LU * SAT) * c0.b);
         c.b = ((R_LU - R_LU * SAT) * c0.r) + ((G_LU - G_LU * SAT) * c0.g) + ((B_LU + (1.0 - B_LU) * SAT) * c0.b);
        
         gl_FragColor = vec4((1.0 - distance(vTexCoord * texSize, center) / radius) * c * exposure , 1.0);
     }
  • vTexCoord* texSize当前像素实际位置xy
  • distance(vTexCoord * texSize, center)距离中心点位置
  • (1.0 - distance(vTexCoord * texSize, center) / radius) 离中心点越远,则值越小

centerX,centerY控制中心点位置,radius控制渐变暗的半径,exposure控制曝光情况。

js 复制代码
uniforms = {
           radius: webgl.width * 0.85,
           exposure: 2.25,
           centerX: webgl.width * 0.5,
           centerY: webgl.height * 0.5
         };
         range = {
           radius: [0.0, webgl.width, 1],
           exposure: [0, 5, 0.1],
           centerX: [0.0, webgl.width, 1],
           centerY: [0.0, webgl.height, 1]
         };

9.油画风格滤镜

c++ 复制代码
precision mediump float;
      //颜色RGB饱和度的权重
      #define R_LU 0.3
      #define G_LU 0.59
      #define B_LU 0.11
      #define SAT 2.2//饱和度
      
       varying vec2 vTexCoord;
       uniform sampler2D uTex;
       uniform vec2 texSize;
       uniform float contract;

       vec3 getColor(vec3 c) {//增加对比度
        return (c - 0.5) * contract + 0.8;
      }

       void main() {
        //对比度
        vec3 c = getColor(texture2D(uTex, vTexCoord).rgb  )  ;
        //减少色彩细节
        float lu = c.r * R_LU + c.g * G_LU + c.b * B_LU;
        if(lu > 0.8)
            c *= 1.1;
        else if(lu > 0.6)
            c *= 0.8;
        else if(lu > 0.4)
            c *= 0.6;
        else if(lu > 0.2)
            c *= 0.4;
        else
            c = vec3(0.0);
            //颜色饱和度
        gl_FragColor.r = ((R_LU + (1.0 - R_LU) * SAT) * c.r) + ((G_LU - G_LU * SAT) * c.g) + ((B_LU - B_LU * SAT) * c.b);
        gl_FragColor.g = ((R_LU - R_LU * SAT) * c.r) + ((G_LU + (1.0 - G_LU) * SAT) * c.g) + ((B_LU - B_LU * SAT) * c.b);
        gl_FragColor.b = ((R_LU - R_LU * SAT) * c.r) + ((G_LU - G_LU * SAT) * c.g) + ((B_LU + (1.0 - B_LU) * SAT) * c.b);
        gl_FragColor.a = 1.0;

          }

contract控制对比度

js 复制代码
const uniforms = {
        contract: 1.5
      };
      const range = {
        contract: [1.0, 2.0, 0.1]
      };

10.素描风格滤镜

c++ 复制代码
     precision mediump float;
      varying vec2 vTexCoord;
      uniform sampler2D uTex;
      uniform vec2 texSize;
      uniform float lineRate;

      void main() {
          vec2 size = vec2( 2.5/ texSize.x, 2.5/ texSize.y);
          float big = 0.0, small = 0.0, c;
          //big取周围颜色绿色值之和
          for(float x = -1.0; x <= 1.0; x += 1.0) {
              for(float y = -1.0; y <= 1.0; y += 1.0) {
                  c = texture2D(uTex, vTexCoord + size * vec2(x, y)).g;
                  big += c;
                  //small取当前颜色绿色值
                  if(x == 0.0 || y == 0.0)
                      small += c;
              }
          }
          //计算边缘
          float edge = max(0.0, big / 9.0 - small / 5.0);
          gl_FragColor = vec4(1.0 - max(vec3(0.0), (edge * edge * lineRate * 1000.0)), 1.0);
      }

lineRate控制素描细节程度,值越大,细节越多

js 复制代码
const uniforms = {
        lineRate: 3.0
      };
      const range = {
        lineRate: [2, 6, 0.1]
      };

Github地址

https://github.com/xiaolidan00/my-webgl

参考

相关推荐
加班是不可能的,除非双倍日工资2 小时前
css预编译器实现星空背景图
前端·css·vue3
wyiyiyi3 小时前
【Web后端】Django、flask及其场景——以构建系统原型为例
前端·数据库·后端·python·django·flask
gnip3 小时前
vite和webpack打包结构控制
前端·javascript
excel4 小时前
在二维 Canvas 中模拟三角形绕 X、Y 轴旋转
前端
阿华的代码王国4 小时前
【Android】RecyclerView复用CheckBox的异常状态
android·xml·java·前端·后端
一条上岸小咸鱼4 小时前
Kotlin 基本数据类型(三):Booleans、Characters
android·前端·kotlin
Jimmy4 小时前
AI 代理是什么,其有助于我们实现更智能编程
前端·后端·ai编程
ZXT4 小时前
promise & async await总结
前端
Jerry说前后端4 小时前
RecyclerView 性能优化:从原理到实践的深度优化方案
android·前端·性能优化
画个太阳作晴天4 小时前
A12预装app
linux·服务器·前端