表达爱意的代码

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style type="text/css">
        body {
            margin: 0;
            overflow: hidden;
        }

        #sketch {
            width: 100vw;
            height: 100vh;
            background: black;
        }

        .loading span {
            animation: blur 1.5s calc(var(--i) / 5 * 1s) alternate infinite;
        }

        @keyframes blur {
            to {
                filter: blur(2px);
            }
        }
    </style>
    <script src="https://unpkg.com/[email protected]/build/three.min.js"></script>
    <script src="https://unpkg.com/kokomi.js/build/kokomi.umd.js"></script>
    <script src="https://unpkg.com/gsap/dist/gsap.min.js"></script>
    <script src="https://unpkg.com/[email protected]/build/postprocess"></script>
</head>

<body>
    <div id="sketch"></div>
    <div class="fixed z-5 top-0 left-0 loader-screen w-screen h-screen transition-all duration-300 bg-black">
        <div class="absolute hv-center">
            <div class="loading text-white text-3xl tracking-widest">
                <span style="--i: 0">L</span>
                <span style="--i: 1">O</span>
                <span style="--i: 2">A</span>
                <span style="--i: 3">D</span>
                <span style="--i: 4">I</span>
                <span style="--i: 5">N</span>
                <span style="--i: 6">G</span>
            </div>
        </div>
    </div>
    <script>
        const vertexShader = `
uniform mat4 textureMatrix;

varying vec2 vUv;
varying vec4 vMirrorCoord;
varying vec3 vWorldPosition;

// https://tympanus.net/codrops/2019/10/29/real-time-multiside-refraction-in-three-steps/
vec4 getWorldPosition(mat4 modelMat,vec3 pos){
    vec4 worldPosition=modelMat*vec4(pos,1.);
    return worldPosition;
}

void main(){
    vec3 p=position;
    
    gl_Position=projectionMatrix*modelViewMatrix*vec4(p,1.);
    
    vUv=uv;
    vMirrorCoord=textureMatrix*vec4(p,1.);
    vWorldPosition=getWorldPosition(modelMatrix,p).xyz;
}
`;

        const fragmentShader = `
// https://stackoverflow.com/questions/13501081/efficient-bicubic-filtering-code-in-glsl
vec4 sampleBicubic(float v){
    vec4 n=vec4(1.,2.,3.,4.)-v;
    vec4 s=n*n*n;
    vec4 o;
    o.x=s.x;
    o.y=s.y-4.*s.x;
    o.z=s.z-4.*s.y+6.*s.x;
    o.w=6.-o.x-o.y-o.z;
    return o;
}

vec4 sampleBicubic(sampler2D tex,vec2 st,vec2 texResolution){
    vec2 pixel=1./texResolution;
    st=st*texResolution-.5;
    
    vec2 fxy=fract(st);
    st-=fxy;
    
    vec4 xcubic=sampleBicubic(fxy.x);
    vec4 ycubic=sampleBicubic(fxy.y);
    
    vec4 c=st.xxyy+vec2(-.5,1.5).xyxy;
    
    vec4 s=vec4(xcubic.xz+xcubic.yw,ycubic.xz+ycubic.yw);
    vec4 offset=c+vec4(xcubic.yw,ycubic.yw)/s;
    
    offset*=pixel.xxyy;
    
    vec4 sample0=texture(tex,offset.xz);
    vec4 sample1=texture(tex,offset.yz);
    vec4 sample2=texture(tex,offset.xw);
    vec4 sample3=texture(tex,offset.yw);
    
    float sx=s.x/(s.x+s.y);
    float sy=s.z/(s.z+s.w);
    
    return mix(mix(sample3,sample2,sx),mix(sample1,sample0,sx),sy);
}

// With original size argument
vec4 packedTexture2DLOD(sampler2D tex,vec2 uv,int level,vec2 originalPixelSize){
    float floatLevel=float(level);
    vec2 atlasSize;
    atlasSize.x=floor(originalPixelSize.x*1.5);
    atlasSize.y=originalPixelSize.y;
    // we stop making mip maps when one dimension == 1
    float maxLevel=min(floor(log2(originalPixelSize.x)),floor(log2(originalPixelSize.y)));
    floatLevel=min(floatLevel,maxLevel);
    // use inverse pow of 2 to simulate right bit shift operator
    vec2 currentPixelDimensions=floor(originalPixelSize/pow(2.,floatLevel));
    vec2 pixelOffset=vec2(
        floatLevel>0.?originalPixelSize.x:0.,
        floatLevel>0.?currentPixelDimensions.y:0.
    );
    // "minPixel / atlasSize" samples the top left piece of the first pixel
    // "maxPixel / atlasSize" samples the bottom right piece of the last pixel
    vec2 minPixel=pixelOffset;
    vec2 maxPixel=pixelOffset+currentPixelDimensions;
    vec2 samplePoint=mix(minPixel,maxPixel,uv);
    samplePoint/=atlasSize;
    vec2 halfPixelSize=1./(2.*atlasSize);
    samplePoint=min(samplePoint,maxPixel/atlasSize-halfPixelSize);
    samplePoint=max(samplePoint,minPixel/atlasSize+halfPixelSize);
    return sampleBicubic(tex,samplePoint,originalPixelSize);
}

vec4 packedTexture2DLOD(sampler2D tex,vec2 uv,float level,vec2 originalPixelSize){
    float ratio=mod(level,1.);
    int minLevel=int(floor(level));
    int maxLevel=int(ceil(level));
    vec4 minValue=packedTexture2DLOD(tex,uv,minLevel,originalPixelSize);
    vec4 maxValue=packedTexture2DLOD(tex,uv,maxLevel,originalPixelSize);
    return mix(minValue,maxValue,ratio);
}

// https://www.shadertoy.com/view/4djSRW
float hash12(vec2 p){
    vec3 p3=fract(vec3(p.xyx)*.1031);
    p3+=dot(p3,p3.yzx+19.19);
    return fract((p3.x+p3.y)*p3.z);
}

vec2 hash22(vec2 p){
    vec3 p3=fract(vec3(p.xyx)*vec3(.1031,.1030,.0973));
    p3+=dot(p3,p3.yzx+19.19);
    return fract((p3.xx+p3.yz)*p3.zy);
}

// https://gist.github.com/companje/29408948f1e8be54dd5733a74ca49bb9
float map(float value,float min1,float max1,float min2,float max2){
    return min2+(value-min1)*(max2-min2)/(max1-min1);
}

uniform vec3 color;
uniform sampler2D tDiffuse;
varying vec2 vUv;
varying vec4 vMirrorCoord;
varying vec3 vWorldPosition;

uniform sampler2D uRoughnessTexture;
uniform sampler2D uNormalTexture;
uniform sampler2D uOpacityTexture;
uniform vec2 uTexScale;
uniform vec2 uTexOffset;
uniform float uDistortionAmount;
uniform float uBlurStrength;
uniform float iTime;
uniform float uRainCount;
uniform vec2 uMipmapTextureSize;

#define MAX_RADIUS 1
#define DOUBLE_HASH 0

void main(){
    vec2 p=vUv;
    vec2 texUv=p*uTexScale;
    texUv+=uTexOffset;
    float floorOpacity=texture(uOpacityTexture,texUv).r;
    vec3 floorNormal=texture(uNormalTexture,texUv).rgb*2.-1.;
    floorNormal=normalize(floorNormal);
    float roughness=texture(uRoughnessTexture,texUv).r;
    
    vec2 reflectionUv=vMirrorCoord.xy/vMirrorCoord.w;
    
    // https://www.shadertoy.com/view/ldfyzl
    vec2 rippleUv=75.*p*uTexScale;
    
    vec2 p0=floor(rippleUv);
    
    float rainStrength=map(uRainCount,0.,10000.,3.,.5);
    if(rainStrength==3.){
        rainStrength=50.;
    }
    
    vec2 circles=vec2(0.);
    for(int j=-MAX_RADIUS;j<=MAX_RADIUS;++j)
    {
        for(int i=-MAX_RADIUS;i<=MAX_RADIUS;++i)
        {
            vec2 pi=p0+vec2(i,j);
            #if DOUBLE_HASH
            vec2 hsh=hash22(pi);
            #else
            vec2 hsh=pi;
            #endif
            vec2 p=pi+hash22(hsh);
            
            float t=fract(.8*iTime+hash12(hsh));
            vec2 v=p-rippleUv;
            float d=length(v)-(float(MAX_RADIUS)+1.)*t+(rainStrength*.1*t);
            
            float h=1e-3;
            float d1=d-h;
            float d2=d+h;
            float p1=sin(31.*d1)*smoothstep(-.6,-.3,d1)*smoothstep(0.,-.3,d1);
            float p2=sin(31.*d2)*smoothstep(-.6,-.3,d2)*smoothstep(0.,-.3,d2);
            circles+=.5*normalize(v)*((p2-p1)/(2.*h)*(1.-t)*(1.-t));
        }
    }
    circles/=float((MAX_RADIUS*2+1)*(MAX_RADIUS*2+1));
    
    float intensity=.05*floorOpacity;
    vec3 n=vec3(circles,sqrt(1.-dot(circles,circles)));
    
    vec2 rainUv=intensity*n.xy;
    
    vec2 finalUv=reflectionUv+floorNormal.xy*uDistortionAmount-rainUv;
    
    float level=roughness*uBlurStrength;
    
    vec3 col=packedTexture2DLOD(tDiffuse,finalUv,level,uMipmapTextureSize).rgb;
    
    gl_FragColor=vec4(col,1.);
    
    // vec4 base=texture2DProj(tDiffuse,vec4(finalUv,1.,1.));
    // gl_FragColor=vec4(base.rgb,1.);
}
`;

        const vertexShader2 = `
uniform float iTime;
uniform vec2 iResolution;
uniform vec2 iMouse;

varying vec2 vUv;

attribute float aProgress;
attribute float aSpeed;

uniform float uSpeed;
uniform float uHeightRange;

varying vec2 vScreenspace;

// https://stackoverflow.com/questions/55582846/how-do-i-implement-an-instanced-billboard-in-three-js
vec3 billboard(vec3 v,mat4 view){
    vec3 up=vec3(view[0][1],view[1][1],view[2][1]);
    vec3 right=vec3(view[0][0],view[1][0],view[2][0]);
    vec3 pos=right*v.x+up*v.y;
    return pos;
}

vec3 distort(vec3 p){
    float y=mod(aProgress-iTime*aSpeed*.25*uSpeed,1.)*uHeightRange-(uHeightRange*.5);
    p.y+=y;
    return p;
}

// https://github.com/Samsy/glsl-screenspace
vec2 screenspace(mat4 projectionmatrix,mat4 modelviewmatrix,vec3 position){
    vec4 temp=projectionmatrix*modelviewmatrix*vec4(position,1.);
    temp.xyz/=temp.w;
    temp.xy=(.5)+(temp.xy)*.5;
    return temp.xy;
}

void main(){
    #include <begin_vertex>
    
    vec3 billboardPos=billboard(transformed,modelViewMatrix);
    transformed=billboardPos;
    
    transformed=distort(transformed);
    
    #include <project_vertex>
    
    vUv=uv;
    
    vScreenspace=screenspace(projectionMatrix,modelViewMatrix,transformed);
}
`;

        const fragmentShader2 = `
uniform float iTime;
uniform vec2 iResolution;
uniform vec2 iMouse;

varying vec2 vUv;

uniform sampler2D uNormalTexture;
uniform sampler2D uBgRt;
uniform float uRefraction;
uniform float uBaseBrightness;

varying vec2 vScreenspace;

void main(){
    vec2 p=vUv;
    
    vec4 normalColor=texture(uNormalTexture,p);
    
    if(normalColor.a<.5){
        discard;
    }
    
    vec3 normal=normalize(normalColor.rgb);
    
    vec2 bgUv=vScreenspace+normal.xy*uRefraction;
    vec4 bgColor=texture(uBgRt,bgUv);
    
    float brightness=uBaseBrightness*pow(normal.b,10.);
    
    vec3 col=bgColor.rgb+vec3(brightness);
    
    gl_FragColor=vec4(col,1.);
}
`;


        class RainFloor extends kokomi.Component {
            constructor(base, config = {}) {
                super(base);

                const { count = 1000 } = config;

                const am = this.base.am;

                // floor
                const fNormalTex = am.items["floor-normal"];
                const fOpacityTex = am.items["floor-opacity"];
                const fRoughnessTex = am.items["floor-roughness"];
                fNormalTex.wrapS = fNormalTex.wrapT = THREE.MirroredRepeatWrapping;
                fOpacityTex.wrapS = fOpacityTex.wrapT = THREE.MirroredRepeatWrapping;
                fRoughnessTex.wrapS = fRoughnessTex.wrapT = THREE.MirroredRepeatWrapping;

                // custom reflector
                const uj = new kokomi.UniformInjector(this.base);
                this.uj = uj;
                const mirror = new kokomi.Reflector(new THREE.PlaneGeometry(25, 100));
                this.mirror = mirror;
                mirror.position.z = -25;
                mirror.rotation.x = -Math.PI / 2;

                mirror.material.uniforms = {
                    ...mirror.material.uniforms,
                    ...uj.shadertoyUniforms,
                    ...{
                        uNormalTexture: {
                            value: fNormalTex,
                        },
                        uOpacityTexture: {
                            value: fOpacityTex,
                        },
                        uRoughnessTexture: {
                            value: fRoughnessTex,
                        },
                        uRainCount: {
                            value: count,
                        },
                        uTexScale: {
                            value: new THREE.Vector2(1, 4),
                        },
                        uTexOffset: {
                            value: new THREE.Vector2(1, -0.5),
                        },
                        uDistortionAmount: {
                            value: 0.25,
                        },
                        uBlurStrength: {
                            value: 8,
                        },
                        uMipmapTextureSize: {
                            value: new THREE.Vector2(window.innerWidth, window.innerHeight),
                        },
                    },
                };
                mirror.material.vertexShader = vertexShader;
                mirror.material.fragmentShader = fragmentShader;

                const mipmapper = new kokomi.PackedMipMapGenerator();
                this.mipmapper = mipmapper;
                const mirrorFBO = mirror.getRenderTarget();
                this.mirrorFBO = mirrorFBO;
                const mipmapFBO = new kokomi.FBO(this.base);
                this.mipmapFBO = mipmapFBO;

                mirror.material.uniforms.tDiffuse.value = mipmapFBO.rt.texture;
            }
            addExisting() {
                this.base.scene.add(this.mirror);
            }
            update() {
                this.uj.injectShadertoyUniforms(this.mirror.material.uniforms);

                this.mipmapper.update(
                    this.mirrorFBO.texture,
                    this.mipmapFBO.rt,
                    this.base.renderer
                );
            }
        }

        class Rain extends kokomi.Component {
            constructor(base, config = {}) {
                super(base);

                const { count = 1000, speed = 1.5, debug = false } = config;

                const am = this.base.am;

                // rain
                const rNormalTex = am.items["rain-normal"];
                rNormalTex.flipY = false;

                const uj = new kokomi.UniformInjector(this.base);
                this.uj = uj;
                const rainMat = new THREE.ShaderMaterial({
                    vertexShader: vertexShader2,
                    fragmentShader: fragmentShader2,
                    uniforms: {
                        ...uj.shadertoyUniforms,
                        ...{
                            uSpeed: {
                                value: speed,
                            },
                            uHeightRange: {
                                value: 20,
                            },
                            uNormalTexture: {
                                value: rNormalTex,
                            },
                            uBgRt: {
                                value: null,
                            },
                            uRefraction: {
                                value: 0.1,
                            },
                            uBaseBrightness: {
                                value: 0.1,
                            },
                        },
                    },
                });
                this.rainMat = rainMat;

                const rain = new THREE.InstancedMesh(
                    new THREE.PlaneGeometry(),
                    rainMat,
                    count
                );
                this.rain = rain;
                rain.instanceMatrix.needsUpdate = true;

                const dummy = new THREE.Object3D();

                const progressArr = [];
                const speedArr = [];

                for (let i = 0; i < rain.count; i++) {
                    dummy.position.set(
                        THREE.MathUtils.randFloat(-10, 10),
                        0,
                        THREE.MathUtils.randFloat(-20, 10)
                    );
                    dummy.scale.set(0.03, THREE.MathUtils.randFloat(0.3, 0.5), 0.03);
                    if (debug) {
                        dummy.scale.setScalar(1);
                        rainMat.uniforms.uSpeed.value = 0;
                    }
                    dummy.updateMatrix();
                    rain.setMatrixAt(i, dummy.matrix);

                    progressArr.push(Math.random());
                    speedArr.push(dummy.scale.y * 10);
                }
                rain.rotation.set(-0.1, 0, 0.1);
                rain.position.set(0, 4, 4);

                rain.geometry.setAttribute(
                    "aProgress",
                    new THREE.InstancedBufferAttribute(new Float32Array(progressArr), 1)
                );
                rain.geometry.setAttribute(
                    "aSpeed",
                    new THREE.InstancedBufferAttribute(new Float32Array(speedArr), 1)
                );

                const bgFBO = new kokomi.FBO(this.base, {
                    width: window.innerWidth * 0.1,
                    height: window.innerHeight * 0.1,
                });
                this.bgFBO = bgFBO;
                rainMat.uniforms.uBgRt.value = bgFBO.rt.texture;

                const fboCamera = this.base.camera.clone();
                this.fboCamera = fboCamera;
            }
            addExisting() {
                this.base.scene.add(this.rain);
            }
            update() {
                this.uj.injectShadertoyUniforms(this.rainMat.uniforms);

                this.rain.visible = false;
                this.base.renderer.setRenderTarget(this.bgFBO.rt);
                this.base.renderer.render(this.base.scene, this.fboCamera);
                this.base.renderer.setRenderTarget(null);
                this.rain.visible = true;
            }
        }

        class Sketch extends kokomi.Base {
            create() {
                this.camera.position.set(0, 2, 9);

                const lookAt = new THREE.Vector3(0, 2, 0);
                this.camera.lookAt(lookAt);

                const controls = new kokomi.OrbitControls(this);
                controls.controls.target = lookAt;

                // config
                const config = {
                    text: "love",
                    color: "#ef77eb",
                    rain: {
                        count: 1000,
                        speed: 1.5,
                        debug: false,
                    },
                };

                // asphalt: https://3dtextures.me/2017/04/05/asphalt-001/
                // floor: https://3dtextures.me/2019/10/22/ground-wet-002/
                // rain normal: https://www.shadertoy.com/view/XsfXDr
                const am = new kokomi.AssetManager(this, [
                    {
                        name: "asphalt-normal",
                        type: "texture",
                        path: "https://s2.loli.net/2023/02/09/4FkJryn78ZhQBqj.jpg",
                    },
                    {
                        name: "floor-normal",
                        type: "texture",
                        path: "https://s2.loli.net/2023/02/15/GcWBptwDKn8b2dU.jpg",
                    },
                    {
                        name: "floor-opacity",
                        type: "texture",
                        path: "https://s2.loli.net/2023/02/15/E5dajTYIucWL1vy.jpg",
                    },
                    {
                        name: "floor-roughness",
                        type: "texture",
                        path: "https://s2.loli.net/2023/02/15/aWeN6ED4mbpZGLs.jpg",
                    },
                    {
                        name: "rain-normal",
                        type: "texture",
                        path: "https://s2.loli.net/2023/01/31/qT2vC8G71UtMXeb.png",
                    },
                ]);
                this.am = am;

                am.on("ready", async () => {
                    const font = await kokomi.loadFont();

                    document.querySelector(".loader-screen").classList.add("hollow");

                    // lights
                    const pointLight1 = new THREE.PointLight(config.color, 0.5, 17, 0.8);
                    pointLight1.position.set(0, 2, 0);
                    this.scene.add(pointLight1);

                    const pointLight2 = new THREE.PointLight("#81C8F2", 2, 30);
                    pointLight2.position.set(0, 25, 0);
                    this.scene.add(pointLight2);

                    const rectLight1 = new THREE.RectAreaLight("#89D7FF", 66, 19.1, 0.2);
                    rectLight1.position.set(0, 8, -10);
                    rectLight1.rotation.set(
                        THREE.MathUtils.degToRad(90),
                        THREE.MathUtils.degToRad(180),
                        0
                    );
                    this.scene.add(rectLight1);

                    const rectLight1Helper = new kokomi.RectAreaLightHelper(rectLight1);
                    this.scene.add(rectLight1Helper);

                    // wall
                    const aspTex = am.items["asphalt-normal"];
                    aspTex.rotation = THREE.MathUtils.degToRad(90);
                    aspTex.wrapS = aspTex.wrapT = THREE.RepeatWrapping;
                    aspTex.repeat.set(5, 8);

                    const wallMat = new THREE.MeshPhongMaterial({
                        color: new THREE.Color("#111111"),
                        normalMap: aspTex,
                        normalScale: new THREE.Vector2(0.5, 0.5),
                        shininess: 200,
                    });

                    const wall = new THREE.Mesh(new THREE.BoxGeometry(25, 20, 0.5), wallMat);
                    this.scene.add(wall);
                    wall.position.y = 10;
                    wall.position.z = -10.3;

                    const wall2 = new THREE.Mesh(new THREE.BoxGeometry(0.5, 20, 20), wallMat);
                    this.scene.add(wall2);
                    wall2.position.y = 10;
                    wall2.position.x = -12;

                    const wall3 = new THREE.Mesh(new THREE.BoxGeometry(0.5, 20, 20), wallMat);
                    this.scene.add(wall3);
                    wall3.position.y = 10;
                    wall3.position.x = 12;

                    // text
                    const t3d = new kokomi.Text3D(this, config.text, font, {

                        size: 3,
                        height: 0.2,
                        curveSegments: 120,
                        bevelEnabled: false,
                    });
                    t3d.mesh.geometry.center();

                    const tm = new THREE.Mesh(
                        t3d.mesh.geometry,
                        new THREE.MeshBasicMaterial({
                            color: config.color,
                        })
                    );
                    this.scene.add(tm);
                    tm.position.y = 1.54;

                    // rain floor
                    const rainFloor = new RainFloor(this, {
                        count: config.rain.count,
                    });
                    rainFloor.addExisting();

                    // rain
                    const rain = new Rain(this, config.rain);
                    rain.addExisting();

                    rainFloor.mirror.ignoreObjects.push(rain.rain);

                    // flicker
                    const turnOffLight = () => {
                        tm.material.color.copy(new THREE.Color("black"));
                        pointLight1.color.copy(new THREE.Color("black"));
                    };

                    const turnOnLight = () => {
                        tm.material.color.copy(new THREE.Color(config.color));
                        pointLight1.color.copy(new THREE.Color(config.color));
                    };

                    let flickerTimer = null;

                    const flicker = () => {
                        flickerTimer = setInterval(async () => {
                            const rate = Math.random();
                            if (rate < 0.5) {
                                turnOffLight();
                                await kokomi.sleep(200 * Math.random());
                                turnOnLight();
                                await kokomi.sleep(200 * Math.random());
                                turnOffLight();
                                await kokomi.sleep(200 * Math.random());
                                turnOnLight();
                            }
                        }, 3000);
                    };

                    flicker();

                    // postprocessing
                    const composer = new POSTPROCESSING.EffectComposer(this.renderer);
                    this.composer = composer;

                    composer.addPass(new POSTPROCESSING.RenderPass(this.scene, this.camera));

                    // bloom
                    const bloom = new POSTPROCESSING.BloomEffect({
                        luminanceThreshold: 0.4,
                        luminanceSmoothing: 0,
                        mipmapBlur: true,
                        intensity: 2,
                        radius: 0.4,
                    });
                    composer.addPass(new POSTPROCESSING.EffectPass(this.camera, bloom));

                    // antialiasing
                    const smaa = new POSTPROCESSING.SMAAEffect();
                    composer.addPass(new POSTPROCESSING.EffectPass(this.camera, smaa));
                });
            }
        }

        const createSketch = () => {
            const sketch = new Sketch();
            sketch.create();
            return sketch;
        };

        createSketch();

    </script>
</body>

</html>

看效果:

 非原创,但是没有找到原文链接。

猜你喜欢

转载自blog.csdn.net/huihui_999/article/details/129955510