[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

参考

相关推荐
NiNg_1_23412 分钟前
Echarts连接数据库,实时绘制图表详解
前端·数据库·echarts
m0_7482480221 分钟前
WebAssembly与WebGL结合:高性能图形处理
webgl·wasm
如若1231 小时前
对文件内的文件名生成目录,方便查阅
java·前端·python
滚雪球~2 小时前
npm error code ETIMEDOUT
前端·npm·node.js
沙漏无语2 小时前
npm : 无法加载文件 D:\Nodejs\node_global\npm.ps1,因为在此系统上禁止运行脚本
前端·npm·node.js
supermapsupport2 小时前
iClient3D for Cesium在Vue中快速实现场景卷帘
前端·vue.js·3d·cesium·supermap
brrdg_sefg2 小时前
WEB 漏洞 - 文件包含漏洞深度解析
前端·网络·安全
胡西风_foxww2 小时前
【es6复习笔记】rest参数(7)
前端·笔记·es6·参数·rest
m0_748254882 小时前
vue+elementui实现下拉表格多选+搜索+分页+回显+全选2.0
前端·vue.js·elementui
星就前端叭3 小时前
【开源】一款基于Vue3 + WebRTC + Node + SRS + FFmpeg搭建的直播间项目
前端·后端·开源·webrtc