[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

参考

相关推荐
abc800211703412 分钟前
前端Bug 修复手册
前端·bug
Best_Liu~15 分钟前
el-table实现固定列,及解决固定列导致部分滚动条无法拖动的问题
前端·javascript·vue.js
_斯洛伐克1 小时前
下降npm版本
前端·vue.js
苏十八2 小时前
前端进阶:Vue.js
前端·javascript·vue.js·前端框架·npm·node.js·ecmascript
st紫月3 小时前
用MySQL+node+vue做一个学生信息管理系统(四):制作增加、删除、修改的组件和对应的路由
前端·vue.js·mysql
乐容3 小时前
vue3使用pinia中的actions,需要调用接口的话
前端·javascript·vue.js
似水明俊德4 小时前
ASP.NET Core Blazor 5:Blazor表单和数据
java·前端·javascript·html·asp.net
至天5 小时前
UniApp 中 Web/H5 正确使用反向代理解决跨域问题
前端·uni-app·vue3·vue2·vite·反向代理
与墨学长5 小时前
Rust破界:前端革新与Vite重构的深度透视(中)
开发语言·前端·rust·前端框架·wasm
H-J-L5 小时前
Web基础与HTTP协议
前端·http·php