美颜滤镜都用过,用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
当前像素实际位置xydistance(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
参考
- webcamtoy
- 照片来源于微博