[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

参考

相关推荐
逐·風7 分钟前
unity关于自定义渲染、内存管理、性能调优、复杂物理模拟、并行计算以及插件开发
前端·unity·c#
Devil枫36 分钟前
Vue 3 单元测试与E2E测试
前端·vue.js·单元测试
尚梦1 小时前
uni-app 封装刘海状态栏(适用小程序, h5, 头条小程序)
前端·小程序·uni-app
_oP_i2 小时前
Unity Addressables 系统处理 WebGL 打包本地资源的一种高效方式
unity·游戏引擎·webgl
GIS程序媛—椰子2 小时前
【Vue 全家桶】6、vue-router 路由(更新中)
前端·vue.js
前端青山2 小时前
Node.js-增强 API 安全性和性能优化
开发语言·前端·javascript·性能优化·前端框架·node.js
毕业设计制作和分享3 小时前
ssm《数据库系统原理》课程平台的设计与实现+vue
前端·数据库·vue.js·oracle·mybatis
从兄3 小时前
vue 使用docx-preview 预览替换文档内的特定变量
javascript·vue.js·ecmascript
清灵xmf4 小时前
在 Vue 中实现与优化轮询技术
前端·javascript·vue·轮询
大佩梨4 小时前
VUE+Vite之环境文件配置及使用环境变量
前端