<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>一只滑冰兔子</title>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.min.css"
/>
<style>
@import url(https://fonts.googleapis.com/css?family=Open+Sans:600);
.webgl {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
outline: none;
background-color: #000000;
cursor: move;
cursor: grabbing;
cursor: -webkit-grabbing;
}
#credits {
position: absolute;
width: 100%;
margin: auto;
bottom: 0;
margin-bottom: 20px;
font-family: 'Open Sans', sans-serif;
color: #544027;
font-size: 0.7em;
text-transform: uppercase;
text-align: center;
}
#credits a {
color: #7beeff;
}
#credits a:hover {
color: #ff3434;
}
#instructions {
position: absolute;
width: 100%;
margin: auto;
bottom: 60px;
font-family: 'Open Sans', sans-serif;
color: #ff3434;
font-size: 0.7em;
text-transform: uppercase;
text-align: center;
}
</style>
</head>
<body>
<canvas class="webgl"></canvas>
<div id="instructions">- 点击跳跃 -</div>
<script type="x-shader/x-vertex" id="reflectorVertexShader">
uniform mat4 textureMatrix;
varying vec4 vUvReflection;
varying vec2 vUv;
#include <common>
#include <shadowmap_pars_vertex>
#include <logdepthbuf_pars_vertex>
void main() {
#include <beginnormal_vertex>
#include <defaultnormal_vertex>
#include <begin_vertex>
vUvReflection = textureMatrix * vec4( position, 1.0 );
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
#include <logdepthbuf_vertex>
#include <worldpos_vertex>
#include <shadowmap_vertex>
}
</script>
<script type="x-shader/x-fragment" id="reflectorFragmentShader">
uniform vec3 color;
uniform sampler2D tDiffuse;
uniform sampler2D tScratches;
varying vec4 vUvReflection;
varying vec2 vUv;
#include <common>
#include <packing>
#include <lights_pars_begin>
#include <shadowmap_pars_fragment>
#include <shadowmask_pars_fragment>
#include <logdepthbuf_pars_fragment>
vec4 blur9(sampler2D image, vec4 uv, vec2 resolution, vec2 direction) {
vec4 color = vec4(0.0);
vec2 off1 = vec2(1.3846153846) * direction;
vec2 off2 = vec2(3.2307692308) * direction;
color += texture2DProj(image, uv) * 0.2270270270;
color += texture2DProj(image, uv + vec4(off1 / resolution, off1 / resolution)) * 0.3162162162;
color += texture2DProj(image, uv - vec4(off1 / resolution, off1 / resolution)) * 0.3162162162;
color += texture2DProj(image, uv + vec4(off2 / resolution, off2 / resolution)) * 0.0702702703;
color += texture2DProj(image, uv - vec4(off2 / resolution, off2 / resolution)) * 0.0702702703;
return color;
}
float blendOverlay( float base, float blend ) {
return( base < 0.5 ? ( 2.0 * base * blend ) : ( 1.0 - 2.0 * ( 1.0 - base ) * ( 1.0 - blend ) ) );
}
vec3 blendOverlay( vec3 base, vec3 blend ) {
return vec3( blendOverlay( base.r, blend.r ), blendOverlay( base.g, blend.g ), blendOverlay( base.b, blend.b ) );
}
void main() {
#include <logdepthbuf_fragment>
vec4 displacement = vec4( sin(vUvReflection.y * 3.) * .05, sin(vUvReflection.x * 3.) * .05, 0.0, 0.0);
vec2 resolution = vec2(30., 30.);
vec4 base = blur9( tDiffuse, vUvReflection + displacement, resolution, vec2(1., 0.) ) * .25;
base += blur9( tDiffuse, vUvReflection + displacement, resolution, vec2(-1., 0.) ) * .25;
base += blur9( tDiffuse, vUvReflection + displacement, resolution, vec2(0, 1.) ) * .25;
base += blur9( tDiffuse, vUvReflection + displacement, resolution, vec2(0, -1.) ) * .25;
vec4 scratchesCol = texture2D( tScratches, vUv);
vec3 col = mix(color, base.rgb, .5);
col.rgb += scratchesCol.r * .02;
col.gb -= scratchesCol.g * .01;
col.gb -= (1.0 - getShadowMask() ) * .015;
gl_FragColor = vec4(col, 1.0);
#include <tonemapping_fragment>
#include <colorspace_fragment>
}
</script>
<script type="x-shader/x-vertex" id="simulationVertexShader">
precision highp float;
uniform float time;
varying vec2 vUv;
void main() {
vUv = uv;
vec4 modelViewPosition = modelViewMatrix * vec4(position, 1.0);
gl_Position = projectionMatrix * modelViewPosition;
}
</script>
<script type="x-shader/x-fragment" id="simulationFragmentShader">
precision highp float;
uniform sampler2D inputTexture;
uniform vec2 blade1PosOld;
uniform vec2 blade1PosNew;
uniform float strength;
uniform float time;
varying vec2 vUv;
float lineSegment(vec2 p, vec2 a, vec2 b, float thickness) {
vec2 pa = p - a;
vec2 ba = b - a;
float h = clamp( dot(pa,ba)/dot(ba,ba), 0.0, 1.0 );
float idk = length(pa - ba*h);
return smoothstep(thickness, .2 * thickness, idk);
}
void main(void) {
vec4 prevTexture = texture2D(inputTexture, vUv);
vec3 col = prevTexture.rgb * .999;
if (strength>0.){
float space = .001;
float crease = .001;
float thickness = .001 + strength * .001;
float leftRed = lineSegment(vUv + space, blade1PosOld, blade1PosNew, thickness);
float leftGreen = lineSegment(vUv + space + crease, blade1PosOld, blade1PosNew, thickness);
float rightRed = lineSegment(vUv - space - crease, blade1PosOld, blade1PosNew, thickness);
float rightGreen = lineSegment(vUv - space, blade1PosOld, blade1PosNew, thickness);
col.r += ( leftRed + rightRed ) * strength * 3.0;
col.g += ( leftGreen + rightGreen) * strength * 3.0;
col.r = clamp(col.r, .0, 1.0);
col.g = clamp(col.g, .0, 1.0);
}
gl_FragColor = vec4(col, 1.0);
}
</script>
<script type="x-shader/x-fragment" id="outlineFragmentShader">
uniform vec3 color;
void main(void) {
gl_FragColor = vec4( color, 1.0);
}
</script>
<script type="x-shader/x-vertex" id="outlineVertexShader">
uniform float size;
uniform float time;
void main() {
vec3 transformed = position + normal * size * (1.0 + abs( sin ( position.y * time * .02 ) * 2.0 ));
vec4 modelViewPosition = modelViewMatrix * vec4(transformed, 1.0);
gl_Position = projectionMatrix * modelViewPosition;
}
</script>
<script type="module">
import * as THREE from 'https://esm.sh/three@0.156.1';
import { OrbitControls } from 'https://esm.sh/three@0.156.1/addons/controls/OrbitControls.js';
import { GLTFLoader } from 'https://esm.sh/three@0.156.1/examples/jsm/loaders/GLTFLoader';
import gsap from 'https://esm.sh/gsap';
import { Reflector } from 'https://esm.sh/three@0.156.1/examples/jsm/objects/Reflector';
document.addEventListener('DOMContentLoaded', () => new App());
class App {
constructor() {
this.winWidth = window.innerWidth;
this.winHeight = window.innerHeight;
this.gltfFile = 'https://assets.codepen.io/264161/rabbit6.glb';
this.loadAssets();
}
loadAssets() {
const loaderModel = new GLTFLoader();
loaderModel.load(this.gltfFile, (gltf) => {
this.model = gltf.scene;
this.setUpScene();
});
}
setUpScene() {
this.scene = new THREE.Scene();
this.bgrColor = 0x332e2e;
this.fog = new THREE.Fog(this.bgrColor, 13, 20);
this.scene.fog = this.fog;
this.camera = new THREE.PerspectiveCamera(
60,
this.winWidth / this.winHeight,
1,
100
);
this.camera.position.set(0, 4, 8);
this.camera.lookAt(new THREE.Vector3());
this.scene.add(this.camera);
this.heroAngularSpeed = 0;
this.heroOldRot = 0;
this.heroDistance = 0;
this.heroOldUVPos = new THREE.Vector2(0.5, 0.5);
this.heroNewUVPos = new THREE.Vector2(0.5, 0.5);
this.heroSpeed = new THREE.Vector2(0, 0);
this.heroAcc = new THREE.Vector2(0, 0);
this.targetHeroUVPos = new THREE.Vector2(0.5, 0.5);
this.targetHeroAbsMousePos = new THREE.Vector2(0, 0);
this.raycaster = new THREE.Raycaster();
this.mouse = new THREE.Vector2();
this.isJumping = this.isLanding = false;
this.jumpParams = { jumpProgress: 0, landProgress: 0 };
this.clock = new THREE.Clock();
this.time = 0;
this.deltaTime = 0;
this.createRenderer();
this.createSim();
this.createListeners();
this.floorSize = 30;
this.createMaterials();
this.processModel();
this.createFloor();
this.createLine();
this.createLight();
this.createParticles();
this.draw();
}
processModel() {
this.rabbit = this.model.getObjectByName('Rabbit');
this.rabbitBody = this.model.getObjectByName('body');
this.earRight = this.model.getObjectByName('earRight');
this.earLeft = this.model.getObjectByName('earLeft');
this.tail = this.model.getObjectByName('tail');
this.footLeft = this.model.getObjectByName('footLeft');
this.footRight = this.model.getObjectByName('footRight');
this.eyeLeft = this.model.getObjectByName('eyeLeft');
this.eyeRight = this.model.getObjectByName('eyeRight');
this.carrot = this.model.getObjectByName('carrot');
this.carrotLeaf = this.model.getObjectByName('carrotLeaf');
this.carrotLeaf2 = this.model.getObjectByName('carrotLeaf2');
this.carrot.rotation.z = 0.2;
this.carrot.rotation.x = 0.2;
this.rabbitBody.material = this.primMat;
this.earRight.material = this.primMat;
this.earLeft.material = this.primMat;
this.tail.material = this.primMat;
this.footLeft.material = this.secMat;
this.footRight.material = this.secMat;
this.eyeLeft.material = this.secMat;
this.eyeRight.material = this.secMat;
this.carrot.material = this.bonusMat;
this.carrotLeaf.material = this.primMat;
this.carrotLeaf2.material = this.primMat;
this.addOutline(this.rabbitBody);
this.addOutline(this.earRight);
this.addOutline(this.earLeft);
this.addOutline(this.tail);
this.addOutline(this.carrot);
this.rabbit.traverse((object) => {
if (object.isMesh) {
object.castShadow = true;
object.receiveShadow = true;
}
});
this.carrot.traverse((object) => {
if (object.isMesh) {
object.castShadow = true;
}
});
this.scene.add(this.rabbit);
this.scene.add(this.carrot);
}
createFloor() {
this.floor = new Reflector(
new THREE.PlaneGeometry(this.floorSize, this.floorSize),
{
color: new THREE.Color(this.bgrColor),
textureWidth: 1024,
textureHeight: 1024,
}
);
this.floor.rotation.x = -Math.PI / 2;
this.floor.receiveShadow = true;
this.modifyFloorShader();
this.scene.add(this.floor);
}
createLine() {
const material = new THREE.LineDashedMaterial({
color: 0x7beeff,
linewidth: 1,
scale: 1,
dashSize: 0.2,
gapSize: 0.1,
});
const points = [];
points.push(new THREE.Vector3(0, 0.2, 0));
points.push(new THREE.Vector3(3, 0.2, 3));
const geometry = new THREE.BufferGeometry().setFromPoints(points);
this.line = new THREE.Line(geometry, material);
this.scene.add(this.line);
}
createParticles() {
let bodyCount = 20;
let tailCount = 5;
let particleGeom = new THREE.BoxGeometry(0.2, 0.2, 0.2, 1, 1, 1);
this.particles1 = [];
this.particles2 = [];
let i = 0;
for (i = 0; i < bodyCount; i++) {
let m = new THREE.Mesh(particleGeom, this.bonusMat);
this.particles1.push(m);
m.scale.set(0, 0, 0);
this.scene.add(m);
}
for (i = 0; i < tailCount; i++) {
let m = new THREE.Mesh(particleGeom, this.primMat);
this.particles2.push(m);
m.scale.set(0, 0, 0);
this.scene.add(m);
}
}
createLight() {
this.ambientLight = new THREE.AmbientLight(0xffffff);
this.scene.add(this.ambientLight);
this.light = new THREE.DirectionalLight(0xffffff, 1);
this.light.position.set(1, 5, 1);
this.light.castShadow = true;
this.light.shadow.mapSize.width = 512;
this.light.shadow.mapSize.height = 512;
this.light.shadow.camera.near = 0.5;
this.light.shadow.camera.far = 12;
this.light.shadow.camera.left = -12;
this.light.shadow.camera.right = 12;
this.light.shadow.camera.bottom = -12;
this.light.shadow.camera.top = 12;
this.light.shadow.radius = 3;
this.light.shadow.blurSamples = 4;
this.scene.add(this.light);
const helper = new THREE.CameraHelper(this.light.shadow.camera);
}
createRenderer() {
const canvas = document.querySelector('canvas.webgl');
this.renderer = new THREE.WebGLRenderer({
canvas,
antialias: true,
preserveDrawingBuffer: true,
});
this.renderer.setClearColor(new THREE.Color(this.bgrColor));
this.renderer.setPixelRatio(
(this.pixelRatio = window.devicePixelRatio)
);
this.renderer.setSize(window.innerWidth, window.innerHeight);
this.renderer.toneMapping = THREE.LinearToneMapping;
this.renderer.toneMappingExposure = 1;
this.renderer.shadowMap.enabled = true;
this.renderer.shadowMap.type = THREE.VSMShadowMap;
this.renderer.localClippingEnabled = true;
}
createSim() {
const fragmentShader = document.getElementById(
'simulationFragmentShader'
).textContent;
const vertexShader = document.getElementById(
'simulationVertexShader'
).textContent;
this.floorSimMat = new THREE.ShaderMaterial({
uniforms: {
inputTexture: { type: 't', value: null },
time: { value: 0.0 },
blade1PosOld: { value: new THREE.Vector2(0.5, 0.5) },
blade1PosNew: { value: new THREE.Vector2(0.5, 0.5) },
strength: { value: 0.0 },
},
vertexShader,
fragmentShader,
});
this.bufferSim = new BufferSim(
this.renderer,
1024,
1024,
this.floorSimMat
);
}
createMaterials() {
this.primMat = new THREE.MeshToonMaterial({ color: 0x7beeff });
this.secMat = new THREE.MeshToonMaterial({ color: this.bgrColor });
this.bonusMat = new THREE.MeshToonMaterial({ color: 0xff3434 });
const fragmentShader = document.getElementById(
'outlineFragmentShader'
).textContent;
const vertexShader = document.getElementById(
'outlineVertexShader'
).textContent;
this.outlineMat = new THREE.ShaderMaterial({
uniforms: {
color: { value: new THREE.Color(0x000000) },
size: { type: 'f', value: 0.02 },
},
vertexShader,
fragmentShader,
side: THREE.BackSide,
});
}
addOutline(origin) {
let outline = origin.clone();
outline.children = [];
outline.position.set(0, 0, 0);
outline.rotation.x = 0;
outline.rotation.y = 0;
outline.rotation.z = 0;
outline.scale.set(1, 1, 1);
outline.material = this.outlineMat;
origin.add(outline);
return outline;
}
createControls() {
this.controls = new OrbitControls(
this.camera,
this.renderer.domElement
);
this.controls.minDistance = 0;
this.controls.maxDistance = 20;
this.controls.enabled = true;
}
createListeners() {
window.addEventListener('resize', this.onWindowResize.bind(this));
document.addEventListener(
'mousemove',
this.onMouseMove.bind(this),
false
);
document.addEventListener(
'touchmove',
this.onTouchMove.bind(this),
false
);
document.addEventListener(
'mousedown',
this.onMouseDown.bind(this),
false
);
}
draw() {
this.updateGame();
this.renderer.render(this.scene, this.camera);
if (this.controls) this.controls.update();
window.requestAnimationFrame(this.draw.bind(this));
}
updateGame() {
this.dt = Math.min(this.clock.getDelta(), 0.3);
this.time += this.dt;
if (this.rabbit && this.line) {
let constrainUVPosX = this.constrain(
this.targetHeroUVPos.x - 0.5,
-0.3,
0.3
);
let constrainUVPosY = this.constrain(
this.targetHeroUVPos.y - 0.5,
-0.3,
0.3
);
this.targetHeroAbsMousePos.x = constrainUVPosX * this.floorSize;
this.targetHeroAbsMousePos.y = -constrainUVPosY * this.floorSize;
let dx = this.targetHeroAbsMousePos.x - this.rabbit.position.x;
let dy = this.targetHeroAbsMousePos.y - this.rabbit.position.z;
let angle = Math.atan2(dy, dx);
this.heroDistance = Math.sqrt(dx * dx + dy * dy);
let ax = dx * this.dt * 0.5;
let ay = dy * this.dt * 0.5;
this.heroSpeed.x += ax;
this.heroSpeed.y += ay;
this.heroSpeed.x *= Math.pow(this.dt, 0.005);
this.heroSpeed.y *= Math.pow(this.dt, 0.005);
this.rabbit.position.x += this.heroSpeed.x;
this.rabbit.position.z += this.heroSpeed.y;
let targetRot = -angle + Math.PI / 2;
if (this.heroDistance > 0.3)
this.rabbit.rotation.y +=
this.getShortestAngle(targetRot - this.rabbit.rotation.y) *
3 *
this.dt;
this.heroAngularSpeed = this.getShortestAngle(
this.rabbit.rotation.y - this.heroOldRot
);
this.heroOldRot = this.rabbit.rotation.y;
if (!this.isJumping) {
this.earLeft.rotation.x = this.earRight.rotation.x =
-this.heroSpeed.length() * 2;
}
let p = this.line.geometry.attributes.position.array;
p[0] = this.targetHeroAbsMousePos.x;
p[2] = this.targetHeroAbsMousePos.y;
p[3] = this.rabbit.position.x;
p[4] = this.rabbit.position.y;
p[5] = this.rabbit.position.z;
this.line.geometry.attributes.position.needsUpdate = true;
this.line.computeLineDistances();
this.heroNewUVPos = new THREE.Vector2(
0.5 + this.rabbit.position.x / this.floorSize,
0.5 - this.rabbit.position.z / this.floorSize
);
this.floorSimMat.time += this.dt;
this.floorSimMat.uniforms.blade1PosNew.value = this.heroNewUVPos;
this.floorSimMat.uniforms.blade1PosOld.value = this.heroOldUVPos;
this.floorSimMat.uniforms.strength.value = this.isJumping
? 0
: 1 / (1 + this.heroSpeed.length() * 10);
this.bufferSim.render();
this.renderer.setRenderTarget(null);
this.floor.material.uniforms.tScratches.value =
this.bufferSim.output.texture;
this.heroOldUVPos = this.heroNewUVPos.clone();
this.carrot.rotation.y += this.dt;
this.testCollision();
}
}
onWindowResize() {
this.winWidth = window.innerWidth;
this.winHeight = window.innerHeight;
this.camera.aspect = this.winWidth / this.winHeight;
this.camera.updateProjectionMatrix();
this.renderer.setSize(this.winWidth, this.winHeight);
}
onMouseMove(event) {
const x = (event.clientX / this.winWidth) * 2 - 1;
const y = -((event.clientY / this.winHeight) * 2 - 1);
this.updateMouse(x, y);
}
onTouchMove(event) {
if (event.touches.length == 1) {
event.preventDefault();
const x = (event.touches[0].pageX / this.winWidth) * 2 - 1;
const y = -((event.touches[0].pageY / this.winHeight) * 2 - 1);
this.updateMouse(x, y);
}
}
updateMouse(x, y) {
this.mouse.x = x;
this.mouse.y = y;
if (this.floor) this.raycast();
}
onMouseDown() {
if (this.rabbit && !this.isJumping) this.jump();
}
jump() {
this.isJumping = true;
let turns = Math.floor(this.heroSpeed.length() * 5) + 1;
let jumpDuration = 0.5 + turns * 0.2;
let targetRot =
this.heroAngularSpeed > 0
? Math.PI * 2 * turns
: -Math.PI * 2 * turns;
gsap.to(this.rabbitBody.rotation, {
duration: jumpDuration,
ease: 'linear.none',
y: targetRot,
onComplete: () => {
this.rabbitBody.rotation.y = 0;
},
});
gsap.to([this.earLeft.rotation, this.earRight.rotation], {
duration: jumpDuration * 0.8,
ease: 'power4.out',
x: Math.PI / 4,
});
gsap.to([this.earLeft.rotation, this.earRight.rotation], {
duration: jumpDuration * 0.2,
delay: jumpDuration * 0.8,
ease: 'power4.in',
x: 0,
});
gsap.to(this.jumpParams, {
duration: jumpDuration * 0.5,
ease: 'power2.out',
jumpProgress: 0.5,
onUpdate: () => {
let sin = Math.sin(this.jumpParams.jumpProgress * Math.PI);
this.rabbit.position.y = Math.pow(sin, 4) * turns;
},
});
gsap.to(this.jumpParams, {
duration: jumpDuration * 0.5,
ease: 'power2.in',
delay: jumpDuration * 0.5,
jumpProgress: 1,
onUpdate: () => {
let sin = Math.sin(this.jumpParams.jumpProgress * Math.PI);
this.rabbit.position.y = Math.pow(sin, 1) * turns;
},
onComplete: () => {
this.rabbit.position.y = 0;
this.jumpParams.jumpProgress = 0;
this.isJumping = false;
},
});
}
raycast() {
this.raycaster.setFromCamera(this.mouse, this.camera);
var intersects = this.raycaster.intersectObjects([this.floor]);
if (intersects.length > 0) {
this.targetHeroUVPos.x = intersects[0].uv.x;
this.targetHeroUVPos.y = intersects[0].uv.y;
}
}
getShortestAngle(v) {
let a = v % (Math.PI * 2);
if (a < -Math.PI) a += Math.PI * 2;
else if (a > Math.PI) a -= Math.PI * 2;
return a;
}
constrain(v, vMin, vMax) {
return Math.min(vMax, Math.max(vMin, v));
}
testCollision() {
if (this.isExploding) return;
let distVec = this.rabbit.position.clone();
distVec.sub(this.carrot.position);
let l = distVec.length();
if (l <= 1) {
this.carrot.visible = false;
this.explode(this.carrot.position);
}
}
explode(pos) {
this.isExploding = true;
let p1Count = this.particles1.length;
let p2Count = this.particles2.length;
let i = 0;
for (i = 0; i < p1Count; i++) {
let m = this.particles1[i];
m.position.x = pos.x;
m.position.y = pos.y;
m.position.z = pos.z;
m.scale.set(2, 2, 2);
gsap.to(m.position, {
x: pos.x + (-0.5 + Math.random()) * 1.5,
y: pos.y + (0.5 + Math.random()) * 1.5,
z: pos.z + (-0.5 + Math.random()) * 1.5,
duration: 1,
ease: 'power4.out',
});
gsap.to(m.scale, {
x: 0,
y: 0,
z: 0,
duration: 1,
ease: 'power4.out',
onComplete: () => {
this.spawnCarrot();
this.isExploding = false;
},
});
}
for (i = 0; i < p2Count; i++) {
let m = this.particles2[i];
m.position.x = pos.x;
m.position.y = pos.y;
m.position.z = pos.z;
m.scale.set(2, 2, 2);
gsap.to(m.position, {
x: pos.x + (-0.5 + Math.random()) * 1.5,
y: pos.y + (0.5 + Math.random()) * 1.5,
z: pos.z + (-0.5 + Math.random()) * 1.5,
duration: 1,
ease: 'power4.out',
});
gsap.to(m.scale, {
x: 0,
y: 0,
z: 0,
duration: 1,
ease: 'power4.out',
onComplete: () => {
this.spawnCarrot();
this.isExploding = false;
},
});
}
}
spawnCarrot() {
let px = (Math.random() - 0.5) * 0.3;
let py = (Math.random() - 0.5) * 0.3;
let h = 0.2 + Math.random() * 1;
this.carrot.position.x = px * this.floorSize;
this.carrot.position.z = py * this.floorSize;
this.carrot.position.y = -1;
this.carrot.scale.set(0, 0, 0);
this.carrot.visible = true;
gsap.to(this.carrot.scale, {
duration: 1.5,
ease: 'elastic.out',
x: 1,
y: 1,
z: 1,
});
gsap.to(this.carrot.position, {
duration: 1.5,
ease: 'elastic.out',
y: h,
});
}
modifyFloorShader() {
let renderTarget = this.floor.getRenderTarget();
const textureMatrix = this.floor.material.uniforms.textureMatrix;
const fragmentShader = document.getElementById(
'reflectorFragmentShader'
).textContent;
const vertexShader = document.getElementById(
'reflectorVertexShader'
).textContent;
const uniforms = THREE.UniformsUtils.merge([
THREE.UniformsLib['common'],
THREE.UniformsLib['shadowmap'],
THREE.UniformsLib['lights'],
this.floor.material.uniforms,
{
tScratches: { value: this.bufferSim.output.texture },
},
]);
this.floor.material.lights = true;
this.floor.material.uniforms = uniforms;
this.floor.material.uniforms.tDiffuse.value = renderTarget.texture;
this.floor.material.uniforms.textureMatrix.value =
textureMatrix.value;
this.floor.material.vertexShader = vertexShader;
this.floor.material.fragmentShader = fragmentShader;
}
}
class BufferSim {
constructor(renderer, width, height, shader) {
this.renderer = renderer;
this.shader = shader;
this.orthoScene = new THREE.Scene();
var fbo = new THREE.WebGLRenderTarget(width, height, {
wrapS: THREE.ClampToEdgeWrapping,
wrapT: THREE.ClampToEdgeWrapping,
minFilter: THREE.LinearFilter,
magFilter: THREE.LinearFilter,
format: THREE.RGBAFormat,
type: THREE.FloatType,
stencilBuffer: false,
depthBuffer: false,
});
fbo.texture.generateMipmaps = false;
this.fbos = [fbo, fbo.clone()];
this.current = 0;
this.output = this.fbos[0];
this.orthoCamera = new THREE.OrthographicCamera(
width / -2,
width / 2,
height / 2,
height / -2,
0.00001,
1000
);
this.orthoQuad = new THREE.Mesh(
new THREE.PlaneGeometry(width, height),
this.shader
);
this.orthoScene.add(this.orthoQuad);
}
render() {
this.shader.uniforms.inputTexture.value =
this.fbos[this.current].texture;
this.input = this.fbos[this.current];
this.current = 1 - this.current;
this.output = this.fbos[this.current];
this.renderer.setRenderTarget(this.output);
this.renderer.render(this.orthoScene, this.orthoCamera);
this.renderer.setRenderTarget(null);
}
}
</script>
</body>
</html>