2021年05月01日 - WebVR・Three.js
シェーダで画像エフェクト

「PlaneGeometryをシェーダで波形アニメーション」に続き、「WebGLのシェーダーGLSLでの画像処理の作り方」を参考に、PlaneGeometryのテクスチャ画像にモザイク、すりガラス、モノトーンのエフェクトをかけました。※Three.jsはr128を使用しています。
シェーダで画像エフェクト
● モザイク
モザイクは、スクリーン(PlaneGeometry)を縦横に任意の大きさのブロックで分割し、分割したブロックの中央ピクセルの色をブロックの色に設定することで実装します。
const fragmentShader =`
precision highp float;
~ 略 ~
void main(void){
~ 略 ~
//UV座標
vec2 vUv2 = vUv;
//ブロックサイズのピクセル数
float mosaicScale = 20.0;
//スクリーンサイズのピクセル数
vec2 screenSize = vec2(1000.0,1000.0);
//モザイク処理
vUv2.x = floor(vUv2.x * screenSize.x / mosaicScale) / (screenSize.x / mosaicScale) + (mosaicScale / 2.0) / screenSize.x;
vUv2.y = floor(vUv2.y * screenSize.y / mosaicScale) / (screenSize.y / mosaicScale) + (mosaicScale / 2.0) / screenSize.y;
//テクスチャを設定
vec4 color = texture2D(texture, vUv2);
//ライトを設定
float r = color.r * outgoingLight.r;
float g = color.g * outgoingLight.g;
float b = color.b * outgoingLight.b;
gl_FragColor = vec4(r,g,b,diffuseColor.a);
}
`;
● すりガラス
すりガラスは、各ピクセルの周辺からランダムで色を取得し、表示するピクセルの色に設定することで実装します。
※絵が綺麗に見えるようにモザイクのブロックサイズを調整しました。
const fragmentShader =`
precision highp float;
~ 略 ~
//ランダム値生成
float random(vec2 co) {
float a = fract(dot(co,vec2(2.067390879775102,12.451168662908249))) - 0.5;
float s = a * (6.182785114200511 + a * a * (-38.026512460676566 + a * a * 53.392573080032137));
float t = fract(s * 43758.5453);
return t;
}
void main(void){
~ 略 ~
//モザイク
vec2 vUv2 = vUv;
//モザイクのピクセル数を調整
float mosaicScale = 1.0;
vec2 screenSize = vec2(1000.0,1000.0);
vUv2.x = floor(vUv2.x * screenSize.x / mosaicScale) / (screenSize.x / mosaicScale) + (mosaicScale / 2.0) / screenSize.x;
vUv2.y = floor(vUv2.y * screenSize.y / mosaicScale) / (screenSize.y / mosaicScale) + (mosaicScale / 2.0) / screenSize.y;
//すりガラス
float radius = 5.0;
//周辺からランダムで色を取得
float x = (vUv2.x * screenSize.x) + random(vUv2) * radius * 2.0 - radius;
float y = (vUv2.y * screenSize.y) + random(vec2(vUv2.y, vUv2.x)) * radius * 2.0 - radius;
vec4 color = texture2D(texture, vec2(x,y)/screenSize);
float r = color.r * outgoingLight.r;
float g = color.g * outgoingLight.g;
float b = color.b * outgoingLight.b;
gl_FragColor = vec4(r,g,b,diffuseColor.a);
}
`;
● モノクローム
モノクロームは、描画色とNTSC系加重平均法と呼ばれるモノクローム変換する係数との内積をとって実装します。
const fragmentShader =`
precision highp float;
~ 略 ~
float random(vec2 co) {
float a = fract(dot(co,vec2(2.067390879775102,12.451168662908249))) - 0.5;
float s = a * (6.182785114200511 + a * a * (-38.026512460676566 + a * a * 53.392573080032137));
float t = fract(s * 43758.5453);
return t;
}
void main(void){
~ 略 ~
//モザイク
vec2 vUv2 = vUv;
float mosaicScale = 1.0;
vec2 screenSize = vec2(1000.0,1000.0);
vUv2.x = floor(vUv2.x * screenSize.x / mosaicScale) / (screenSize.x / mosaicScale) + (mosaicScale / 2.0) / screenSize.x;
vUv2.y = floor(vUv2.y * screenSize.y / mosaicScale) / (screenSize.y / mosaicScale) + (mosaicScale / 2.0) / screenSize.y;
//すりガラス
float radius = 5.0;
float x = (vUv2.x * screenSize.x) + random(vUv2) * radius * 2.0 - radius;
float y = (vUv2.y * screenSize.y) + random(vec2(vUv2.y, vUv2.x)) * radius * 2.0 - radius;
vec4 color = texture2D(texture, vec2(x,y)/screenSize);
//モノクローム
const vec3 monochromeScale = vec3(0.298912,0.5866111,0.114478);
//描画色とNTSC系加重平均法との内積
float grayColor = dot(color.rgb,monochromeScale);
float r = grayColor * outgoingLight.r;
float g = grayColor * outgoingLight.g;
float b = grayColor * outgoingLight.b;
gl_FragColor = vec4(r,g,b,diffuseColor.a);
}
`;
● フラグメントシェーダ
フラグメントシェーダ以外は「PlaneGeometryをシェーダで波形アニメーション」と同じです。
const fragmentShader =`
precision highp float;
uniform float time;
uniform vec3 diffuse;
uniform vec3 emissive;
uniform sampler2D texture;
uniform mat4 viewMatrix;
varying vec2 vUv;
varying vec3 vViewPosition;
varying vec3 vNormal;
#include <common>
#include <bsdfs>
#include <lights_pars_begin>
float random(vec2 co) {
float a = fract(dot(co,vec2(2.067390879775102,12.451168662908249))) - 0.5;
float s = a * (6.182785114200511 + a * a * (-38.026512460676566 + a * a * 53.392573080032137));
float t = fract(s * 43758.5453);
return t;
}
void main(void){
vec3 mvPosition = vViewPosition;
vec3 transformedNormal = vNormal;
GeometricContext geometry;
geometry.position = mvPosition.xyz;
geometry.normal = normalize(transformedNormal);
geometry.viewDir = (normalize(-mvPosition.xyz));
vec3 lightFront = vec3(0.0);
vec3 indirectFront = vec3(0.0);
IncidentLight directLight;
float dotNL;
vec3 directLightColor_Diffuse;
#if NUM_POINT_LIGHTS > 0
#pragma unroll_loop_start
for (int i = 0; i < NUM_POINT_LIGHTS; i++) {
getPointDirectLightIrradiance(pointLights[ i ], geometry, directLight);
dotNL = dot(geometry.normal, directLight.direction);
directLightColor_Diffuse = PI * directLight.color;
lightFront += saturate(dotNL) * directLightColor_Diffuse;
}
#pragma unroll_loop_end
#endif
vec4 diffuseColor = vec4(diffuse, 1.0);
ReflectedLight reflectedLight = ReflectedLight(vec3(0.0),vec3(0.0),vec3(0.0),vec3(0.0));
vec3 totalEmissiveRadiance = emissive;
reflectedLight.indirectDiffuse = getAmbientLightIrradiance(ambientLightColor);
reflectedLight.indirectDiffuse += indirectFront;
reflectedLight.indirectDiffuse *= BRDF_Diffuse_Lambert(diffuseColor.rgb);
reflectedLight.directDiffuse = lightFront;
reflectedLight.directDiffuse *= BRDF_Diffuse_Lambert(diffuseColor.rgb);
vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;
vec2 vUv2 = vUv;
float mosaicScale = 1.0;
vec2 screenSize = vec2(1000.0,1000.0);
vUv2.x = floor(vUv2.x * screenSize.x / mosaicScale) / (screenSize.x / mosaicScale) + (mosaicScale / 2.0) / screenSize.x;
vUv2.y = floor(vUv2.y * screenSize.y / mosaicScale) / (screenSize.y / mosaicScale) + (mosaicScale / 2.0) / screenSize.y;
float radius = 5.0;
float x = (vUv2.x * screenSize.x) + random(vUv2) * radius * 2.0 - radius;
float y = (vUv2.y * screenSize.y) + random(vec2(vUv2.y, vUv2.x)) * radius * 2.0 - radius;
vec4 color = texture2D(texture, vec2(x,y)/screenSize);
const vec3 monochromeScale = vec3(0.298912,0.5866111,0.114478);
float grayColor = dot(color.rgb,monochromeScale);
float r = grayColor * outgoingLight.r;
float g = grayColor * outgoingLight.g;
float b = grayColor * outgoingLight.b;
gl_FragColor = vec4(r,g,b,diffuseColor.a);
}
`;
完成したデモになります。シェーダでテクスチャ画像にエフェクトをかけました。

