UNPKG

@needle-tools/facefilter

Version:

Needle Engine FaceFilter

200 lines (181 loc) 8.07 kB
import { Behaviour, Context, getParam, makeIdFromRandomWords, serializable, setParam, setParamWithoutReload, showBalloonMessage, showBalloonWarning, syncField } from "@needle-tools/engine"; import { FaceMeshBehaviour } from "../facemesh/FaceMeshBehaviour"; import { Material, ShaderMaterial, ShaderMaterialParameters, Texture, Vector3, Vector4 } from "three"; /* Shader Inputs uniform vec3 iResolution; // viewport resolution (in pixels) uniform float iTime; // shader playback time (in seconds) uniform float iTimeDelta; // render time (in seconds) uniform float iFrameRate; // shader frame rate uniform int iFrame; // shader playback frame uniform float iChannelTime[4]; // channel playback time (in seconds) uniform vec3 iChannelResolution[4]; // channel resolution (in pixels) uniform vec4 iMouse; // mouse pixel coords. xy: current (if MLB down), zw: click uniform samplerXX iChannel0..3; // input channel. XX = 2D/Cube uniform vec4 iDate; // (year, month, day, time in seconds) */ const inputsChunk = ` uniform vec3 iResolution; // viewport resolution (in pixels) uniform float iTime; // shader playback time (in seconds) uniform float iTimeDelta; // render time (in seconds) uniform float iFrameRate; // shader frame rate uniform int iFrame; // shader playback frame // channelPlaybackTime uniform vec3 iChannelResolution[4]; // channel resolution (in pixels) uniform vec4 iMouse; // mouse pixel coords. xy: current (if MLB down), zw: click uniform vec4 iDate; // (year, month, day, time in seconds) uniform sampler2D iChannel0; ` const mainChunk = ` uniform sampler2D mask; varying vec2 vUv; void main() { gl_FragColor = vec4(0.3, 1.0, 0.4, 1.0); mainImage(gl_FragColor, gl_FragCoord.xy); #ifdef USE_MASK vec4 maskColor = texture2D(mask, vUv); gl_FragColor.a *= maskColor.r; #endif } ` const fragmentShader = ` void mainImage( out vec4 fragColor, in vec2 fragCoord ) { vec2 uv = fragCoord/iResolution.xy; vec3 col = 0.5 + 0.5*cos(iTime+uv.xyx+vec3(0,2,4)); fragColor = vec4(col,1.0); } `; class ShaderToyMaterial extends ShaderMaterial { constructor(args?: ShaderMaterialParameters) { super(args); if (!args?.fragmentShader) { let shader = inputsChunk; shader += fragmentShader; shader += mainChunk; this.fragmentShader = shader; } this.vertexShader = ` varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); } ` this.transparent = true; this.uniforms = { iResolution: { value: new Vector3(100, 100, 1) }, iTime: { value: 0 }, iTimeDelta: { value: 0 }, iFrameRate: { value: 0 }, iFrame: { value: 0 }, iChannelTime: { value: [0, 0, 0, 0] }, iChannelResolution: { value: [new Vector3(1, 1, 1), new Vector3(1, 1, 1), new Vector3(1, 1, 1), new Vector3(1, 1, 1)] }, iMouse: { value: new Vector4(0, 0, 0, 0) }, iDate: { value: new Vector4(0, 0, 0, 0) }, iChannel0: { value: null }, iChannel1: { value: null }, iChannel2: { value: null }, iChannel3: { value: null }, ...this.uniforms, } } update(filter: ShaderToyFaceFilter) { const context = filter.context; this.uniforms.iResolution.value.set(context.domWidth * window.devicePixelRatio, context.domHeight * window.devicePixelRatio, 1); this.uniforms.iTime.value = context.time.realtimeSinceStartup; this.uniforms.iTimeDelta.value = context.time.deltaTime; this.uniforms.iFrameRate.value = context.time.smoothedFps; this.uniforms.iFrame.value = context.time.frameCount; this.uniforms.iChannelTime.value[0] = context.time.realtimeSinceStartup; // this.uniforms.iChannelResolution.value[0].set(context.domWidth, context.domHeight, 1); const pointerPosition = context.input.getPointerPosition(0); if (pointerPosition) this.uniforms.iMouse.value.set(pointerPosition.x, pointerPosition.y, context.input.getPointerDown(0), 0); const time = new Date(); this.uniforms.iDate.value.set(time.getFullYear(), time.getMonth(), time.getDate(), time.getTime()); } } export class ShaderToyFaceFilter extends FaceMeshBehaviour { constructor(args?: { shader: string }) { super(); if (args?.shader) { this._networkedShader = args.shader; } } @serializable() allowPaste: boolean = true; @serializable(Texture) mask: Texture | null = null; protected createMaterial(): Material | null { const mat = new ShaderToyMaterial({ uniforms: { mask: { value: this.mask } }, defines: { USE_MASK: this.mask ? true : false } }); if (this._networkedShader) { this.trySetShader(this._networkedShader, mat); } return mat; } awake() { const info = `Copy paste <a href=\"https://shadertoy.com\" target=\"_blank\">shadertoy</a> shaders (the whole code) to use as a face filter.<br/>For example <a href=\"https://www.shadertoy.com/view/tlVGDt\" target=\"_blank\">this one</a> or <a href=\"https://www.shadertoy.com/view/ftSSRR\" target=\"_blank\">this one</a> or <a href=\"https://www.shadertoy.com/new\" target=\"_blank\">create your own</a>.` if (!this._networkedShader) showBalloonMessage(info); console.debug(info); } onEnable(): void { super.onEnable(); window.addEventListener("paste", this.onPaste); let shaderRoomName = getParam("shader") as string; if (typeof shaderRoomName != "string" || shaderRoomName.length < 1) { shaderRoomName = makeIdFromRandomWords(); setParamWithoutReload("shader", shaderRoomName); } this.context.connection.joinRoom(shaderRoomName); } onDisable(): void { super.onDisable(); window.removeEventListener("paste", this.onPaste); const room = getParam("shader") as string; if (room) { this.context.connection.leaveRoom(room); } } update(): void { const material = this.material as ShaderToyMaterial; if (material) { material.update(this) } } @syncField(ShaderToyFaceFilter.prototype.onShaderChanged) private _networkedShader: string | null = null; private onPaste = (e: ClipboardEvent) => { if (!e.clipboardData) return; if (!this.allowPaste) return; const text = e.clipboardData.getData("text"); if (text) { this.trySetShader(text); } } private trySetShader(shader: string, target?: Material) { if (shader.includes("void mainImage")) { const material = (target || this.material) as ShaderToyMaterial; if (material) { material.fragmentShader = inputsChunk + shader + mainChunk; material.needsUpdate = true; if (shader != this._networkedShader) { this._networkedShader = shader; } } } else { showBalloonWarning("The pasted text does not contain a mainImage function / is not a ShaderToy shader.") } } private onShaderChanged() { if (this._networkedShader) this.trySetShader(this._networkedShader); } }