371 lines
12 KiB
JavaScript
371 lines
12 KiB
JavaScript
function App() {
|
|
const conf = {
|
|
el: 'canvas',
|
|
fov: 75,
|
|
cameraZ: 100,
|
|
};
|
|
|
|
let renderer, scene, camera, cameraCtrl;
|
|
let width, height, cx, cy, wWidth, wHeight;
|
|
|
|
let ripple;
|
|
let gridWWidth, gridWHeight;
|
|
let gridWidth, gridHeight;
|
|
|
|
const mouse = new THREE.Vector2();
|
|
const mousePlane = new THREE.Plane(new THREE.Vector3(0, 0, 1), 0);
|
|
const mousePosition = new THREE.Vector3();
|
|
const raycaster = new THREE.Raycaster();
|
|
let mouseOver = false;
|
|
|
|
init();
|
|
|
|
function init() {
|
|
// const gl = renderer.getContext();
|
|
// const floatTextures = gl.getExtension('OES_texture_float');
|
|
// if (!floatTextures) {
|
|
// alert('no floating point texture support');
|
|
// return;
|
|
// }
|
|
|
|
renderer = new THREE.WebGLRenderer({ canvas: document.getElementById(conf.el), antialias: true });
|
|
camera = new THREE.PerspectiveCamera(conf.fov);
|
|
camera.position.z = conf.cameraZ;
|
|
|
|
updateSize();
|
|
window.addEventListener('resize', updateSize, false);
|
|
|
|
// gridWHeight = wHeight - 20;
|
|
// gridWWidth = gridWHeight;
|
|
gridWHeight = wHeight;
|
|
gridWWidth = wWidth;
|
|
gridWidth = gridWWidth * width / wWidth;
|
|
gridHeight = gridWHeight * height / wHeight;
|
|
|
|
ripple = new RippleEffect(renderer, width, height);
|
|
|
|
const getGridMP = function (e) {
|
|
const v = new THREE.Vector3();
|
|
camera.getWorldDirection(v);
|
|
v.normalize();
|
|
mouse.x = ((e.clientX / width) * 2 - 1);
|
|
mouse.y = (-(e.clientY / height) * 2 + 1);
|
|
raycaster.setFromCamera(mouse, camera);
|
|
raycaster.ray.intersectPlane(mousePlane, mousePosition);
|
|
return { x: 2 * mousePosition.x / gridWWidth, y: 2 * mousePosition.y / gridWHeight };
|
|
};
|
|
|
|
renderer.domElement.addEventListener('mousemove', e => {
|
|
mouseOver = true;
|
|
const gp = getGridMP(e);
|
|
ripple.addDrop(gp.x, gp.y, 0.05, 0.1);
|
|
});
|
|
renderer.domElement.addEventListener('mouseleave', e => { mouseOver = false; });
|
|
|
|
// renderer.domElement.addEventListener('mouseup', e => {
|
|
// const gp = getGridMP(e);
|
|
// ripple.addDrop(gp.x, gp.y, 0.2, -3.0);
|
|
// });
|
|
|
|
initScene();
|
|
animate();
|
|
}
|
|
|
|
function initScene() {
|
|
scene = new THREE.Scene();
|
|
|
|
let pointLight1 = new THREE.PointLight(0xFFFF80);
|
|
pointLight1.position.set(-wWidth / 2, wHeight / 2, 50);
|
|
scene.add(pointLight1);
|
|
|
|
let pointLight2 = new THREE.PointLight(0xde3578);
|
|
pointLight2.position.set(wWidth / 2, wHeight / 2, 50);
|
|
scene.add(pointLight2);
|
|
|
|
let pointLight3 = new THREE.PointLight(0xFF4040);
|
|
pointLight3.position.set(-wWidth / 2, -wHeight / 2, 50);
|
|
scene.add(pointLight3);
|
|
|
|
let pointLight4 = new THREE.PointLight(0x0247e5);
|
|
pointLight4.position.set(wWidth / 2, -wHeight / 2, 50);
|
|
scene.add(pointLight4);
|
|
|
|
renderer.domElement.addEventListener('mouseup', e => {
|
|
pointLight1.color = new THREE.Color(chroma.random().hex());
|
|
pointLight2.color = new THREE.Color(chroma.random().hex());
|
|
pointLight3.color = new THREE.Color(chroma.random().hex());
|
|
pointLight4.color = new THREE.Color(chroma.random().hex());
|
|
});
|
|
|
|
const material = new THREE.MeshStandardMaterial({ color: 0xffffff, side: THREE.DoubleSide, metalness: 0.5, roughness: 0.5, onBeforeCompile: shader => {
|
|
shader.uniforms.hmap = { value: ripple.hMap.texture };
|
|
shader.vertexShader = "uniform sampler2D hmap;\n" + shader.vertexShader;
|
|
const token = '#include <begin_vertex>';
|
|
const customTransform = `
|
|
vec3 transformed = vec3(position);
|
|
vec4 info = texture2D(hmap, uv);
|
|
vNormal = vec3(info.b, sqrt(1.0 - dot(info.ba, info.ba)), info.a).xzy;
|
|
transformed.z = 20. * info.r;
|
|
`;
|
|
shader.vertexShader = shader.vertexShader.replace(token, customTransform);
|
|
} });
|
|
|
|
let nx = Math.round(gridWidth / 5), ny = Math.round(gridHeight / 40);
|
|
let dx = gridWWidth / nx, dy = gridWHeight / ny;
|
|
for (let j = 0; j <= ny; j++) {
|
|
const geometry = new THREE.BufferGeometry();
|
|
const positions = [], uvs = [];
|
|
const y = - gridWHeight / 2 + j * dy;
|
|
for (let i = 0; i <= nx; i++) {
|
|
positions.push(- gridWWidth / 2 + i * dx, y, 0);
|
|
uvs.push(i / nx, j / ny);
|
|
}
|
|
geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
|
|
geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, 2));
|
|
geometry.computeBoundingSphere();
|
|
scene.add(new THREE.Line(geometry, material));
|
|
}
|
|
|
|
nx = Math.round(gridWidth / 40); ny = Math.round(gridHeight / 5);
|
|
dx = gridWWidth / nx; dy = gridWHeight / ny;
|
|
for (let i = 0; i <= nx; i++) {
|
|
const geometry = new THREE.BufferGeometry();
|
|
const positions = [], uvs = [];
|
|
const x = - gridWWidth / 2 + i * dx;
|
|
for (let j = 0; j <= ny; j++) {
|
|
positions.push(x, - gridWHeight / 2 + j * dy, 0);
|
|
uvs.push(i / nx, j / ny);
|
|
}
|
|
geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
|
|
geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, 2));
|
|
geometry.computeBoundingSphere();
|
|
scene.add(new THREE.Line(geometry, material));
|
|
}
|
|
camera.position.set(0, -gridWHeight/2, 40);
|
|
camera.lookAt(new THREE.Vector3(0, -gridWHeight/6, 0));
|
|
|
|
cameraCtrl = new THREE.OrbitControls(camera, renderer.domElement);
|
|
cameraCtrl.enableDamping = true;
|
|
cameraCtrl.dampingFactor = 0.1;
|
|
cameraCtrl.rotateSpeed = 0.5;
|
|
}
|
|
|
|
function animate() {
|
|
if (!mouseOver) {
|
|
const time = Date.now() * 0.001;
|
|
const x = Math.cos(time) * 0.2;
|
|
const y = Math.sin(time) * 0.2;
|
|
ripple.addDrop(x, y, 0.05, -0.04);
|
|
}
|
|
|
|
ripple.update();
|
|
renderer.render(scene, camera);
|
|
requestAnimationFrame(animate);
|
|
}
|
|
|
|
function updateSize() {
|
|
width = window.innerWidth; cx = width / 2;
|
|
height = window.innerHeight; cy = height / 2;
|
|
renderer.setSize(width, height);
|
|
camera.aspect = width / height;
|
|
camera.updateProjectionMatrix();
|
|
const wsize = getRendererSize();
|
|
wWidth = wsize[0]; wHeight = wsize[1];
|
|
}
|
|
|
|
function getRendererSize() {
|
|
const cam = new THREE.PerspectiveCamera(camera.fov, camera.aspect);
|
|
const vFOV = (cam.fov * Math.PI) / 180;
|
|
const height = 2 * Math.tan(vFOV / 2) * Math.abs(conf.cameraZ);
|
|
const width = height * cam.aspect;
|
|
return [width, height];
|
|
}
|
|
}
|
|
|
|
const RippleEffect = (function () {
|
|
function RippleEffect(renderer, width, height) {
|
|
this.renderer = renderer;
|
|
this.width = 512;
|
|
this.height = 512;
|
|
// this.delta = new THREE.Vector2(this.width / Math.pow(width, 2), this.height / Math.pow(height, 2));
|
|
this.delta = new THREE.Vector2(1 / this.width, 1 / this.height);
|
|
|
|
this.hMap = new THREE.WebGLRenderTarget(this.width, this.height, { type: THREE.FloatType, depthBuffer: false, stencilBuffer: false });
|
|
this.hMap1 = new THREE.WebGLRenderTarget(this.width, this.height, { type: THREE.FloatType, depthBuffer: false, stencilBuffer: false });
|
|
this.fsQuad = new FullScreenQuad();
|
|
|
|
this.initShaders();
|
|
}
|
|
|
|
RippleEffect.prototype.initShaders = function () {
|
|
// default vertex shader
|
|
const defaultVertexShader = `
|
|
varying vec2 vUv;
|
|
void main() {
|
|
vUv = uv;
|
|
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
}
|
|
`;
|
|
|
|
this.copyMat = new THREE.ShaderMaterial({
|
|
uniforms: { 'tDiffuse': { value: null } },
|
|
vertexShader: defaultVertexShader,
|
|
fragmentShader: `
|
|
uniform sampler2D tDiffuse;
|
|
varying vec2 vUv;
|
|
void main() {
|
|
gl_FragColor = texture2D(tDiffuse, vUv);
|
|
}
|
|
`,
|
|
});
|
|
|
|
this.updateMat = new THREE.ShaderMaterial({
|
|
uniforms: {
|
|
'tDiffuse': { value: null },
|
|
'delta': new THREE.Uniform(this.delta),
|
|
},
|
|
vertexShader: defaultVertexShader,
|
|
fragmentShader: `
|
|
uniform sampler2D tDiffuse;
|
|
uniform vec2 delta;
|
|
varying vec2 vUv;
|
|
void main() {
|
|
vec4 texel = texture2D(tDiffuse, vUv);
|
|
|
|
vec2 dx = vec2(delta.x, 0.0);
|
|
vec2 dy = vec2(0.0, delta.y);
|
|
float average = (
|
|
texture2D(tDiffuse, vUv - dx).r +
|
|
texture2D(tDiffuse, vUv - dy).r +
|
|
texture2D(tDiffuse, vUv + dx).r +
|
|
texture2D(tDiffuse, vUv + dy).r
|
|
) * 0.25;
|
|
texel.g += (average - texel.r) * 2.0;
|
|
texel.g *= 0.995;
|
|
texel.r += texel.g;
|
|
|
|
gl_FragColor = texel;
|
|
}
|
|
`,
|
|
});
|
|
|
|
this.normalsMat = new THREE.ShaderMaterial({
|
|
uniforms: {
|
|
'tDiffuse': { value: null },
|
|
'delta': new THREE.Uniform(this.delta),
|
|
},
|
|
vertexShader: defaultVertexShader,
|
|
fragmentShader: `
|
|
uniform sampler2D tDiffuse;
|
|
uniform vec2 delta;
|
|
varying vec2 vUv;
|
|
void main() {
|
|
vec4 texel = texture2D(tDiffuse, vUv);
|
|
vec3 dx = vec3(delta.x, texture2D(tDiffuse, vec2(vUv.x + delta.x, vUv.y)).r - texel.r, 0.0);
|
|
vec3 dy = vec3(0.0, texture2D(tDiffuse, vec2(vUv.x, vUv.y + delta.y)).r - texel.r, delta.y);
|
|
texel.ba = normalize(cross(dy, dx)).xz;
|
|
gl_FragColor = texel;
|
|
}
|
|
`,
|
|
});
|
|
|
|
this.dropMat = new THREE.ShaderMaterial({
|
|
uniforms: {
|
|
'tDiffuse': { value: null },
|
|
'center': new THREE.Uniform(new THREE.Vector2()),
|
|
'radius': { value: 0.05 },
|
|
'strength': { value: 0.5 },
|
|
},
|
|
vertexShader: defaultVertexShader,
|
|
fragmentShader: `
|
|
const float PI = 3.1415926535897932384626433832795;
|
|
uniform sampler2D tDiffuse;
|
|
uniform vec2 center;
|
|
uniform float radius;
|
|
uniform float strength;
|
|
varying vec2 vUv;
|
|
void main() {
|
|
vec4 texel = texture2D(tDiffuse, vUv);
|
|
float drop = max(0.0, 1.0 - length(center * 0.5 + 0.5 - vUv) / radius);
|
|
drop = 0.5 - cos(drop * PI) * 0.5;
|
|
texel.r += drop * strength;
|
|
// texel.r = clamp(texel.r, -2.0, 2.0);
|
|
gl_FragColor = texel;
|
|
}
|
|
`,
|
|
});
|
|
};
|
|
|
|
RippleEffect.prototype.update = function () {
|
|
this.updateHMap();
|
|
this.updateHMapNormals();
|
|
};
|
|
|
|
RippleEffect.prototype.updateHMap = function () {
|
|
this.updateMat.uniforms.tDiffuse.value = this.hMap.texture;
|
|
this.renderShaderMat(this.updateMat, this.hMap1);
|
|
this.swapBuffers();
|
|
};
|
|
|
|
RippleEffect.prototype.updateHMapNormals = function () {
|
|
this.normalsMat.uniforms.tDiffuse.value = this.hMap.texture;
|
|
this.renderShaderMat(this.normalsMat, this.hMap1);
|
|
this.swapBuffers();
|
|
};
|
|
|
|
RippleEffect.prototype.addDrop = function (x, y, radius, strength) {
|
|
this.dropMat.uniforms.tDiffuse.value = this.hMap.texture;
|
|
this.dropMat.uniforms.center.value.set(x, y);
|
|
this.dropMat.uniforms.radius.value = radius;
|
|
this.dropMat.uniforms.strength.value = strength;
|
|
this.renderShaderMat(this.dropMat, this.hMap1);
|
|
this.swapBuffers();
|
|
};
|
|
|
|
RippleEffect.prototype.renderBuffer = function (buffer, target) {
|
|
target = target ? target : null;
|
|
this.copyMat.uniforms.tDiffuse.value = buffer.texture;
|
|
this.renderShaderMat(this.copyMat, target);
|
|
};
|
|
|
|
RippleEffect.prototype.renderShaderMat = function (mat, target) {
|
|
this.fsQuad.material = mat;
|
|
const oldTarget = this.renderer.getRenderTarget();
|
|
this.renderer.setRenderTarget(target);
|
|
this.fsQuad.render(this.renderer);
|
|
this.renderer.setRenderTarget(oldTarget);
|
|
};
|
|
|
|
RippleEffect.prototype.swapBuffers = function () {
|
|
const temp = this.hMap;
|
|
this.hMap = this.hMap1;
|
|
this.hMap1 = temp;
|
|
};
|
|
|
|
// from https://threejs.org/examples/js/postprocessing/EffectComposer.js
|
|
const FullScreenQuad = (function () {
|
|
const camera = new THREE.OrthographicCamera(- 1, 1, 1, - 1, 0, 1);
|
|
const geometry = new THREE.PlaneBufferGeometry(2, 2);
|
|
|
|
const FullScreenQuad = function (material) {
|
|
this._mesh = new THREE.Mesh(geometry, material);
|
|
};
|
|
|
|
Object.defineProperty(FullScreenQuad.prototype, 'material', {
|
|
get: function () { return this._mesh.material; },
|
|
set: function (value) { this._mesh.material = value; }
|
|
});
|
|
|
|
Object.assign(FullScreenQuad.prototype, {
|
|
render: function (renderer) {
|
|
renderer.render(this._mesh, camera);
|
|
}
|
|
});
|
|
|
|
return FullScreenQuad;
|
|
})();
|
|
|
|
return RippleEffect;
|
|
})();
|
|
|
|
App(); |