mirror of
https://github.com/tiennm99/webgl-heart.git
synced 2026-10-11 03:13:54 +00:00
init commit
This commit is contained in:
commit
6bc375f4c7
4 files changed
+364
No files matched your search
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Phuc Tran
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,8 @@
|
||||
# WebGL Heart
|
||||
|
||||
Just a small toy project. Nothing special here.
|
||||
|
||||
**Demo 1:** https://snowflk.github.io/webgl-heart/
|
||||
**Demo 2:** https://snowflk.github.io/webgl-heart/staticpoints/
|
||||
|
||||
Three.js setup inspired by https://codepen.io/prisoner849
|
||||
Executable
+168
@@ -0,0 +1,168 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" >
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Heartbeat | WebGL</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto+Condensed&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
html, body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
background-color: #000;
|
||||
font-family: 'Roboto Condensed', sans-serif;
|
||||
}
|
||||
canvas {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.footer{
|
||||
position:fixed;
|
||||
width:100%;
|
||||
text-align: center;
|
||||
font-size: 8pt;
|
||||
color: #999;
|
||||
opacity: 0.5;
|
||||
bottom: 24px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<script src="https://cdn.jsdelivr.net/npm/three@0.100.0/build/three.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/three@0.100.0/examples/js/controls/OrbitControls.js"></script>
|
||||
|
||||
<div class="footer">
|
||||
P. Tran
|
||||
</div>
|
||||
<script>
|
||||
const scene = new THREE.Scene();
|
||||
const camera = new THREE.PerspectiveCamera(60, 1, 1, 1000);
|
||||
camera.position.set(0, 12, 100).setLength(150);
|
||||
const renderer = new THREE.WebGLRenderer({
|
||||
antialias: true
|
||||
});
|
||||
renderer.setPixelRatio( window.devicePixelRatio );
|
||||
//renderer.setPixelRatio(1);
|
||||
const canvas = renderer.domElement;
|
||||
document.body.appendChild(canvas);
|
||||
|
||||
const controls = new THREE.OrbitControls(camera, renderer.domElement);
|
||||
controls.enableZoom = false;
|
||||
controls.enablePan = false;
|
||||
controls.update();
|
||||
|
||||
const xSize = 50;
|
||||
const ySize = 50;
|
||||
const zSize = 50;
|
||||
const density = 3;
|
||||
const nParticles = xSize * ySize * zSize * density;
|
||||
|
||||
const positions = [];
|
||||
const speed = [];
|
||||
const sign = [];
|
||||
|
||||
for (let i = 0; i < nParticles; i++) {
|
||||
positions.push(new THREE.Vector3(Math.random(), Math.random(), Math.random()).multiplyScalar(100));
|
||||
speed.push(Math.random() * 10 + 2);
|
||||
}
|
||||
|
||||
const pointsGeometry = new THREE.BufferGeometry().setFromPoints(positions);
|
||||
pointsGeometry.addAttribute("speed", new THREE.BufferAttribute(new Float32Array(speed), 1));
|
||||
pointsGeometry.center();
|
||||
|
||||
const points = new THREE.Points(
|
||||
pointsGeometry,
|
||||
new THREE.ShaderMaterial({
|
||||
uniforms: {
|
||||
time: {
|
||||
value: 0
|
||||
},
|
||||
size: {
|
||||
value: 0.9
|
||||
},
|
||||
ratio: {
|
||||
value: window.devicePixelRatio
|
||||
}
|
||||
},
|
||||
vertexShader: `
|
||||
#define PI 3.1415926
|
||||
uniform float time;
|
||||
uniform float size;
|
||||
uniform float ratio;
|
||||
attribute float speed;
|
||||
varying vec3 vC;
|
||||
varying float vDiscard;
|
||||
|
||||
void main(){
|
||||
vec3 pos = position;
|
||||
pos.y = mod(pos.y + speed * time, 100.) - 50.;
|
||||
|
||||
|
||||
vec3 h = pos / 2.5;
|
||||
h.y = 4. + 1.2 * h.y - abs(h.x) * sqrt(max((20. - abs(h.x)) / 15., 0.));
|
||||
h.z = h.z * (2. - h.y / 15.);
|
||||
float pLimit = 0.675;
|
||||
float nLimit = -pLimit;
|
||||
float nullPoint = 0.5;
|
||||
float scaledT = time * 1.3;
|
||||
float dt = scaledT - pLimit * ( 2. * floor( scaledT / (pLimit* 2.)) + 1.);
|
||||
float r = 15. + pow(sin(2. * PI * dt), 4.);
|
||||
if (dt < -nullPoint || dt > nullPoint) {
|
||||
r = 15.;
|
||||
}
|
||||
float dCell = length(h) - r;
|
||||
vec3 c = vec3(247. / 255., 89. / 255., 183. / 255.);
|
||||
if (dCell > -0.1) c = vec3(250. / 255., 182. / 255., 223. / 255.);;
|
||||
vC = c;
|
||||
|
||||
vec3 vPos = pos;
|
||||
vDiscard = dCell > 0. || dCell < -1.0 ? 1. : 0.;
|
||||
vec4 mvPosition = modelViewMatrix * vec4( pos, 1.0 );
|
||||
gl_PointSize = sqrt(length(pos)/30.) * size * ( 300.0 / -mvPosition.z ) * ratio;
|
||||
gl_Position = projectionMatrix * mvPosition;
|
||||
}
|
||||
`,
|
||||
fragmentShader: `
|
||||
|
||||
varying vec3 vC;
|
||||
varying float vDiscard;
|
||||
|
||||
void main(){
|
||||
|
||||
if ( vDiscard >= 0.5 ) {discard;}
|
||||
if (length(gl_PointCoord - 0.5) > 0.5) {discard;}
|
||||
gl_FragColor = vec4( vC, 1.0);
|
||||
}
|
||||
`
|
||||
})
|
||||
);
|
||||
scene.add(points);
|
||||
const clock = new THREE.Clock();
|
||||
let time = 0;
|
||||
|
||||
renderer.setAnimationLoop(() => {
|
||||
if (resize(renderer)) {
|
||||
camera.aspect = canvas.clientWidth / canvas.clientHeight;
|
||||
camera.updateProjectionMatrix();
|
||||
}
|
||||
time += clock.getDelta();
|
||||
//scene.rotation.y = time * 0.25;
|
||||
points.material.uniforms.time.value = time;
|
||||
renderer.render(scene, camera);
|
||||
});
|
||||
|
||||
function resize(renderer) {
|
||||
const canvas = renderer.domElement;
|
||||
const width = canvas.clientWidth;
|
||||
const height = canvas.clientHeight;
|
||||
const needResize = canvas.width !== width || canvas.height !== height;
|
||||
if (needResize) {
|
||||
renderer.setSize(width, height, false);
|
||||
}
|
||||
return needResize;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Executable
+167
@@ -0,0 +1,167 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" >
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Heartbeat | WebGL</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto+Condensed&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
html, body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
background-color: #000;
|
||||
font-family: 'Roboto Condensed', sans-serif;
|
||||
}
|
||||
canvas {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.footer{
|
||||
position:fixed;
|
||||
width:100%;
|
||||
text-align: center;
|
||||
font-size: 8pt;
|
||||
color: #999;
|
||||
opacity: 0.5;
|
||||
bottom: 24px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<script src="https://cdn.jsdelivr.net/npm/three@0.100.0/build/three.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/three@0.100.0/examples/js/controls/OrbitControls.js"></script>
|
||||
|
||||
<div class="footer">
|
||||
P. Tran
|
||||
</div>
|
||||
<script>
|
||||
const scene = new THREE.Scene();
|
||||
const camera = new THREE.PerspectiveCamera(60, 1, 1, 1000);
|
||||
camera.position.set(0, 12, 100).setLength(150);
|
||||
const renderer = new THREE.WebGLRenderer({
|
||||
antialias: true
|
||||
});
|
||||
renderer.setPixelRatio( window.devicePixelRatio );
|
||||
//renderer.setPixelRatio(1);
|
||||
const canvas = renderer.domElement;
|
||||
document.body.appendChild(canvas);
|
||||
|
||||
const controls = new THREE.OrbitControls(camera, renderer.domElement);
|
||||
controls.enableZoom = false;
|
||||
controls.enablePan = false;
|
||||
controls.update();
|
||||
|
||||
const xSize = 50;
|
||||
const ySize = 50;
|
||||
const zSize = 50;
|
||||
const density = 3;
|
||||
const nParticles = xSize * ySize * zSize * density;
|
||||
|
||||
const positions = [];
|
||||
const speed = [];
|
||||
const sign = [];
|
||||
|
||||
for (let i = 0; i < nParticles; i++) {
|
||||
positions.push(new THREE.Vector3(Math.random(), Math.random(), Math.random()).multiplyScalar(100));
|
||||
speed.push(Math.random() * 10 + 2);
|
||||
}
|
||||
|
||||
const pointsGeometry = new THREE.BufferGeometry().setFromPoints(positions);
|
||||
pointsGeometry.addAttribute("speed", new THREE.BufferAttribute(new Float32Array(speed), 1));
|
||||
pointsGeometry.center();
|
||||
|
||||
const points = new THREE.Points(
|
||||
pointsGeometry,
|
||||
new THREE.ShaderMaterial({
|
||||
uniforms: {
|
||||
time: {
|
||||
value: 0
|
||||
},
|
||||
size: {
|
||||
value: 0.9
|
||||
},
|
||||
ratio: {
|
||||
value: window.devicePixelRatio
|
||||
}
|
||||
},
|
||||
vertexShader: `
|
||||
#define PI 3.1415926
|
||||
uniform float time;
|
||||
uniform float size;
|
||||
uniform float ratio;
|
||||
attribute float speed;
|
||||
varying vec3 vC;
|
||||
varying float vDiscard;
|
||||
|
||||
void main(){
|
||||
vec3 pos = position;
|
||||
|
||||
vec3 h = pos / 2.5;
|
||||
h.y = 4. + 1.2 * h.y - abs(h.x) * sqrt(max((20. - abs(h.x)) / 15., 0.));
|
||||
h.z = h.z * (2. - h.y / 15.);
|
||||
float pLimit = 0.675;
|
||||
float nLimit = -pLimit;
|
||||
float nullPoint = 0.5;
|
||||
float scaledT = time * 1.3;
|
||||
float dt = scaledT - pLimit * ( 2. * floor( scaledT / (pLimit* 2.)) + 1.);
|
||||
float r = 15. + 1.25 * pow(sin(2. * PI * dt), 4.);
|
||||
if (dt < -nullPoint || dt > nullPoint) {
|
||||
r = 15.;
|
||||
}
|
||||
float dCell = length(h) - r;
|
||||
vec3 c = vec3(247. / 255., 89. / 255., 183. / 255.);
|
||||
if (dCell > 0.) c = vec3(250. / 255., 182. / 255., 223. / 255.);;
|
||||
vC = c;
|
||||
pos = pos - pos / length(pos) * (dCell) * 2.5;
|
||||
|
||||
vec3 vPos = pos;
|
||||
vDiscard = dCell > 0. || dCell < -1.0 ? 1. : 0.;
|
||||
vec4 mvPosition = modelViewMatrix * vec4( pos, 1.0 );
|
||||
gl_PointSize = sqrt(length(pos)/30.) * size * ( 300.0 / -mvPosition.z ) * ratio;
|
||||
gl_Position = projectionMatrix * mvPosition;
|
||||
}
|
||||
`,
|
||||
fragmentShader: `
|
||||
|
||||
varying vec3 vC;
|
||||
varying float vDiscard;
|
||||
|
||||
void main(){
|
||||
|
||||
if ( vDiscard >= 0.5 ) {discard;}
|
||||
if (length(gl_PointCoord - 0.5) > 0.5) {discard;}
|
||||
gl_FragColor = vec4( vC, 1.0);
|
||||
}
|
||||
`
|
||||
})
|
||||
);
|
||||
scene.add(points);
|
||||
const clock = new THREE.Clock();
|
||||
let time = 0;
|
||||
|
||||
renderer.setAnimationLoop(() => {
|
||||
if (resize(renderer)) {
|
||||
camera.aspect = canvas.clientWidth / canvas.clientHeight;
|
||||
camera.updateProjectionMatrix();
|
||||
}
|
||||
time += clock.getDelta();
|
||||
//scene.rotation.y = time * 0.25;
|
||||
points.material.uniforms.time.value = time;
|
||||
renderer.render(scene, camera);
|
||||
});
|
||||
|
||||
function resize(renderer) {
|
||||
const canvas = renderer.domElement;
|
||||
const width = canvas.clientWidth;
|
||||
const height = canvas.clientHeight;
|
||||
const needResize = canvas.width !== width || canvas.height !== height;
|
||||
if (needResize) {
|
||||
renderer.setSize(width, height, false);
|
||||
}
|
||||
return needResize;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in new issue
Block a user