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/[email protected]/build/three.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/[email protected]/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/[email protected]/build/three.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/[email protected]/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