[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

参考

相关推荐
前端拾光者17 分钟前
利用D3.js实现数据可视化的简单示例
开发语言·javascript·信息可视化
Json_1817901448035 分钟前
电商拍立淘按图搜索API接口系列,文档说明参考
前端·数据库
风尚云网1 小时前
风尚云网前端学习:一个简易前端新手友好的HTML5页面布局与样式设计
前端·css·学习·html·html5·风尚云网
木子02041 小时前
前端VUE项目启动方式
前端·javascript·vue.js
GISer_Jing1 小时前
React核心功能详解(一)
前端·react.js·前端框架
捂月1 小时前
Spring Boot 深度解析:快速构建高效、现代化的 Web 应用程序
前端·spring boot·后端
深度混淆1 小时前
实用功能,觊觎(Edge)浏览器的内置截(长)图功能
前端·edge
Smartdaili China1 小时前
如何在 Microsoft Edge 中设置代理: 快速而简单的方法
前端·爬虫·安全·microsoft·edge·社交·动态住宅代理
秦老师Q1 小时前
「Chromeg谷歌浏览器/Edge浏览器」篡改猴Tempermongkey插件的安装与使用
前端·chrome·edge
滴水可藏海1 小时前
Chrome离线安装包下载
前端·chrome