Floating point texture test...
A Pen by Kilian K Lindberg on CodePen.
Floating point texture test...
A Pen by Kilian K Lindberg on CodePen.
<canvas id="canvas"></canvas> |
function App() { | |
const conf = { | |
el: 'canvas', | |
fov: 75, | |
cameraZ: 100, | |
background: 0x000000, | |
}; | |
let renderer, scene, camera, cameraCtrl; | |
let width, height, cx, cy, wWidth, wHeight; | |
const { randFloat: rnd, randFloatSpread: rndFS } = THREE.Math; | |
let ripple; | |
let gridWWidth, gridWHeight, 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() { | |
renderer = new THREE.WebGLRenderer({ canvas: document.getElementById(conf.el), antialias: true }); | |
// if (!renderer.extensions.get('OES_texture_float')) { | |
// alert('no floating point texture support'); | |
// return; | |
// } | |
camera = new THREE.PerspectiveCamera(conf.fov); | |
camera.position.z = conf.cameraZ; | |
updateSize(); | |
window.addEventListener('resize', updateSize, false); | |
gridWWidth = wWidth; | |
gridWHeight = wHeight; | |
gridWidth = gridWWidth * width / wWidth; | |
gridHeight = gridWHeight * height / wHeight; | |
ripple = new RippleEffect(renderer, gridWidth, gridHeight, 1 / 5); | |
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('mouseleave', e => { mouseOver = false; }); | |
renderer.domElement.addEventListener('mousemove', e => { | |
mouseOver = true; | |
const gp = getGridMP(e); | |
ripple.addDrop(gp.x, gp.y, 0.05, 0.1); | |
}); | |
renderer.domElement.addEventListener('mouseup', e => { | |
const gp = getGridMP(e); | |
ripple.addDrop(gp.x, gp.y, 0.1, -1.5); | |
}); | |
initScene(); | |
animate(); | |
} | |
function initScene() { | |
scene = new THREE.Scene(); | |
if (conf.background) scene.background = new THREE.Color(conf.background); | |
const material = new THREE.MeshBasicMaterial({ | |
color: 0xFFFFFF, side: THREE.DoubleSide, 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); | |
transformed.z = 20. * info.r; | |
`; | |
shader.vertexShader = shader.vertexShader.replace(token, customTransform); | |
} | |
}); | |
const pSize = 1; | |
let nx = Math.round(gridWidth / 5), ny = Math.round(gridHeight / 50); | |
let dy = gridWHeight / ny; | |
for (let j = 0; j <= ny; j++) { | |
const geometry = new THREE.PlaneBufferGeometry(gridWWidth, pSize, nx, 1); | |
geometry.translate(0, - gridWHeight / 2 + j * dy, 0); | |
const uvH = pSize / gridWHeight; | |
const uvY = j / ny; | |
const uvs = geometry.attributes.uv.array; | |
for (let i = 0; i < uvs.length; i += 2) { | |
uvs[i + 1] = (uvs[i + 1] == 0) ? uvY - uvH : uvY + uvH; | |
} | |
scene.add(new THREE.Mesh(geometry, material)); | |
} | |
nx = Math.round(gridWidth / 50); ny = Math.round(gridHeight / 5); | |
let dx = gridWWidth / nx; | |
for (let i = 0; i <= nx; i++) { | |
const geometry = new THREE.PlaneBufferGeometry(pSize, gridWHeight, 1, ny); | |
geometry.translate(- gridWWidth / 2 + i * dx, 0, 0); | |
const uvW = pSize / gridWWidth; | |
const uvX = i / nx; | |
const uvs = geometry.attributes.uv.array; | |
for (let i = 0; i < uvs.length; i += 2) { | |
uvs[i] = (uvs[i] == 0) ? uvX - uvW : uvX + uvW; | |
} | |
scene.add(new THREE.Mesh(geometry, material)); | |
} | |
camera.position.set(0, -gridWHeight/1.6, 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.4; | |
const y = Math.sin(time) * 0.4; | |
ripple.addDrop(x, y, 0.05, -0.05); | |
} | |
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, ratio) { | |
this.renderer = renderer; | |
this.width = Math.round(ratio * width); | |
this.height = Math.round(ratio * height); | |
this.delta = new THREE.Vector2(ratio / width, ratio / 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(); | |
} | |
// From https://github.com/evanw/webgl-water | |
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.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(); | |
}; | |
RippleEffect.prototype.updateHMap = function () { | |
this.updateMat.uniforms.tDiffuse.value = this.hMap.texture; | |
this.renderShaderMat(this.updateMat, 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(); |
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/110/three.js"></script> | |
<script src="https://klevron.github.io/codepen/three.js/OrbitControls.110.js"></script> |
html, body { | |
margin: 0; | |
} | |
canvas { | |
display: block; | |
} |