You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
569 lines
14 KiB
569 lines
14 KiB
2 years ago
|
<!DOCTYPE html>
|
||
|
<html lang="en">
|
||
|
<head>
|
||
|
<title>three.js webgl - gpgpu - protoplanet</title>
|
||
|
<meta charset="utf-8">
|
||
|
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
|
||
|
<link type="text/css" rel="stylesheet" href="main.css">
|
||
|
</head>
|
||
|
<body>
|
||
|
|
||
|
<div id="info">
|
||
|
<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - <span id="protoplanets"></span> webgl gpgpu debris
|
||
|
</div>
|
||
|
|
||
|
<!-- Fragment shader for protoplanet's position -->
|
||
|
<script id="computeShaderPosition" type="x-shader/x-fragment">
|
||
|
|
||
|
#define delta ( 1.0 / 60.0 )
|
||
|
|
||
|
void main() {
|
||
|
|
||
|
vec2 uv = gl_FragCoord.xy / resolution.xy;
|
||
|
|
||
|
vec4 tmpPos = texture2D( texturePosition, uv );
|
||
|
vec3 pos = tmpPos.xyz;
|
||
|
|
||
|
vec4 tmpVel = texture2D( textureVelocity, uv );
|
||
|
vec3 vel = tmpVel.xyz;
|
||
|
float mass = tmpVel.w;
|
||
|
|
||
|
if ( mass == 0.0 ) {
|
||
|
vel = vec3( 0.0 );
|
||
|
}
|
||
|
|
||
|
// Dynamics
|
||
|
pos += vel * delta;
|
||
|
|
||
|
gl_FragColor = vec4( pos, 1.0 );
|
||
|
|
||
|
}
|
||
|
|
||
|
</script>
|
||
|
|
||
|
<!-- Fragment shader for protoplanet's velocity -->
|
||
|
<script id="computeShaderVelocity" type="x-shader/x-fragment">
|
||
|
|
||
|
// For PI declaration:
|
||
|
#include <common>
|
||
|
|
||
|
#define delta ( 1.0 / 60.0 )
|
||
|
|
||
|
uniform float gravityConstant;
|
||
|
uniform float density;
|
||
|
|
||
|
const float width = resolution.x;
|
||
|
const float height = resolution.y;
|
||
|
|
||
|
float radiusFromMass( float mass ) {
|
||
|
// Calculate radius of a sphere from mass and density
|
||
|
return pow( ( 3.0 / ( 4.0 * PI ) ) * mass / density, 1.0 / 3.0 );
|
||
|
}
|
||
|
|
||
|
void main() {
|
||
|
|
||
|
vec2 uv = gl_FragCoord.xy / resolution.xy;
|
||
|
float idParticle = uv.y * resolution.x + uv.x;
|
||
|
|
||
|
vec4 tmpPos = texture2D( texturePosition, uv );
|
||
|
vec3 pos = tmpPos.xyz;
|
||
|
|
||
|
vec4 tmpVel = texture2D( textureVelocity, uv );
|
||
|
vec3 vel = tmpVel.xyz;
|
||
|
float mass = tmpVel.w;
|
||
|
|
||
|
if ( mass > 0.0 ) {
|
||
|
|
||
|
float radius = radiusFromMass( mass );
|
||
|
|
||
|
vec3 acceleration = vec3( 0.0 );
|
||
|
|
||
|
// Gravity interaction
|
||
|
for ( float y = 0.0; y < height; y++ ) {
|
||
|
|
||
|
for ( float x = 0.0; x < width; x++ ) {
|
||
|
|
||
|
vec2 secondParticleCoords = vec2( x + 0.5, y + 0.5 ) / resolution.xy;
|
||
|
vec3 pos2 = texture2D( texturePosition, secondParticleCoords ).xyz;
|
||
|
vec4 velTemp2 = texture2D( textureVelocity, secondParticleCoords );
|
||
|
vec3 vel2 = velTemp2.xyz;
|
||
|
float mass2 = velTemp2.w;
|
||
|
|
||
|
float idParticle2 = secondParticleCoords.y * resolution.x + secondParticleCoords.x;
|
||
|
|
||
|
if ( idParticle == idParticle2 ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if ( mass2 == 0.0 ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
vec3 dPos = pos2 - pos;
|
||
|
float distance = length( dPos );
|
||
|
float radius2 = radiusFromMass( mass2 );
|
||
|
|
||
|
if ( distance == 0.0 ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// Checks collision
|
||
|
|
||
|
if ( distance < radius + radius2 ) {
|
||
|
|
||
|
if ( idParticle < idParticle2 ) {
|
||
|
|
||
|
// This particle is aggregated by the other
|
||
|
vel = ( vel * mass + vel2 * mass2 ) / ( mass + mass2 );
|
||
|
mass += mass2;
|
||
|
radius = radiusFromMass( mass );
|
||
|
|
||
|
}
|
||
|
else {
|
||
|
|
||
|
// This particle dies
|
||
|
mass = 0.0;
|
||
|
radius = 0.0;
|
||
|
vel = vec3( 0.0 );
|
||
|
break;
|
||
|
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
float distanceSq = distance * distance;
|
||
|
|
||
|
float gravityField = gravityConstant * mass2 / distanceSq;
|
||
|
|
||
|
gravityField = min( gravityField, 1000.0 );
|
||
|
|
||
|
acceleration += gravityField * normalize( dPos );
|
||
|
|
||
|
}
|
||
|
|
||
|
if ( mass == 0.0 ) {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Dynamics
|
||
|
vel += delta * acceleration;
|
||
|
|
||
|
}
|
||
|
|
||
|
gl_FragColor = vec4( vel, mass );
|
||
|
|
||
|
}
|
||
|
|
||
|
</script>
|
||
|
|
||
|
<!-- Particles vertex shader -->
|
||
|
<script type="x-shader/x-vertex" id="particleVertexShader">
|
||
|
|
||
|
// For PI declaration:
|
||
|
#include <common>
|
||
|
|
||
|
uniform sampler2D texturePosition;
|
||
|
uniform sampler2D textureVelocity;
|
||
|
|
||
|
uniform float cameraConstant;
|
||
|
uniform float density;
|
||
|
|
||
|
varying vec4 vColor;
|
||
|
|
||
|
float radiusFromMass( float mass ) {
|
||
|
// Calculate radius of a sphere from mass and density
|
||
|
return pow( ( 3.0 / ( 4.0 * PI ) ) * mass / density, 1.0 / 3.0 );
|
||
|
}
|
||
|
|
||
|
|
||
|
void main() {
|
||
|
|
||
|
|
||
|
vec4 posTemp = texture2D( texturePosition, uv );
|
||
|
vec3 pos = posTemp.xyz;
|
||
|
|
||
|
vec4 velTemp = texture2D( textureVelocity, uv );
|
||
|
vec3 vel = velTemp.xyz;
|
||
|
float mass = velTemp.w;
|
||
|
|
||
|
vColor = vec4( 1.0, mass / 250.0, 0.0, 1.0 );
|
||
|
|
||
|
vec4 mvPosition = modelViewMatrix * vec4( pos, 1.0 );
|
||
|
|
||
|
// Calculate radius of a sphere from mass and density
|
||
|
//float radius = pow( ( 3.0 / ( 4.0 * PI ) ) * mass / density, 1.0 / 3.0 );
|
||
|
float radius = radiusFromMass( mass );
|
||
|
|
||
|
// Apparent size in pixels
|
||
|
if ( mass == 0.0 ) {
|
||
|
gl_PointSize = 0.0;
|
||
|
}
|
||
|
else {
|
||
|
gl_PointSize = radius * cameraConstant / ( - mvPosition.z );
|
||
|
}
|
||
|
|
||
|
gl_Position = projectionMatrix * mvPosition;
|
||
|
|
||
|
}
|
||
|
|
||
|
</script>
|
||
|
|
||
|
<!-- Particles fragment shader -->
|
||
|
<script type="x-shader/x-fragment" id="particleFragmentShader">
|
||
|
|
||
|
varying vec4 vColor;
|
||
|
|
||
|
void main() {
|
||
|
|
||
|
if ( vColor.y == 0.0 ) discard;
|
||
|
|
||
|
float f = length( gl_PointCoord - vec2( 0.5, 0.5 ) );
|
||
|
if ( f > 0.5 ) {
|
||
|
discard;
|
||
|
}
|
||
|
gl_FragColor = vColor;
|
||
|
|
||
|
}
|
||
|
|
||
|
</script>
|
||
|
|
||
|
<!-- Import maps polyfill -->
|
||
|
<!-- Remove this when import maps will be widely supported -->
|
||
|
<script async src="https://unpkg.com/es-module-shims@1.3.6/dist/es-module-shims.js"></script>
|
||
|
|
||
|
<script type="importmap">
|
||
|
{
|
||
|
"imports": {
|
||
|
"three": "../build/three.module.js",
|
||
|
"three/addons/": "./jsm/"
|
||
|
}
|
||
|
}
|
||
|
</script>
|
||
|
|
||
|
<script type="module">
|
||
|
|
||
|
import * as THREE from 'three';
|
||
|
|
||
|
import Stats from 'three/addons/libs/stats.module.js';
|
||
|
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
|
||
|
|
||
|
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
||
|
import { GPUComputationRenderer } from 'three/addons/misc/GPUComputationRenderer.js';
|
||
|
|
||
|
// Texture width for simulation (each texel is a debris particle)
|
||
|
const WIDTH = 64;
|
||
|
|
||
|
let container, stats;
|
||
|
let camera, scene, renderer, geometry;
|
||
|
|
||
|
const PARTICLES = WIDTH * WIDTH;
|
||
|
|
||
|
let gpuCompute;
|
||
|
let velocityVariable;
|
||
|
let positionVariable;
|
||
|
let velocityUniforms;
|
||
|
let particleUniforms;
|
||
|
let effectController;
|
||
|
|
||
|
init();
|
||
|
animate();
|
||
|
|
||
|
function init() {
|
||
|
|
||
|
container = document.createElement( 'div' );
|
||
|
document.body.appendChild( container );
|
||
|
|
||
|
camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 5, 15000 );
|
||
|
camera.position.y = 120;
|
||
|
camera.position.z = 400;
|
||
|
|
||
|
scene = new THREE.Scene();
|
||
|
|
||
|
renderer = new THREE.WebGLRenderer();
|
||
|
renderer.setPixelRatio( window.devicePixelRatio );
|
||
|
renderer.setSize( window.innerWidth, window.innerHeight );
|
||
|
container.appendChild( renderer.domElement );
|
||
|
|
||
|
const controls = new OrbitControls( camera, renderer.domElement );
|
||
|
controls.minDistance = 100;
|
||
|
controls.maxDistance = 1000;
|
||
|
|
||
|
effectController = {
|
||
|
// Can be changed dynamically
|
||
|
gravityConstant: 100.0,
|
||
|
density: 0.45,
|
||
|
|
||
|
// Must restart simulation
|
||
|
radius: 300,
|
||
|
height: 8,
|
||
|
exponent: 0.4,
|
||
|
maxMass: 15.0,
|
||
|
velocity: 70,
|
||
|
velocityExponent: 0.2,
|
||
|
randVelocity: 0.001
|
||
|
};
|
||
|
|
||
|
initComputeRenderer();
|
||
|
|
||
|
stats = new Stats();
|
||
|
container.appendChild( stats.dom );
|
||
|
|
||
|
window.addEventListener( 'resize', onWindowResize );
|
||
|
|
||
|
initGUI();
|
||
|
|
||
|
initProtoplanets();
|
||
|
|
||
|
dynamicValuesChanger();
|
||
|
|
||
|
}
|
||
|
|
||
|
function initComputeRenderer() {
|
||
|
|
||
|
gpuCompute = new GPUComputationRenderer( WIDTH, WIDTH, renderer );
|
||
|
|
||
|
if ( renderer.capabilities.isWebGL2 === false ) {
|
||
|
|
||
|
gpuCompute.setDataType( THREE.HalfFloatType );
|
||
|
|
||
|
}
|
||
|
|
||
|
const dtPosition = gpuCompute.createTexture();
|
||
|
const dtVelocity = gpuCompute.createTexture();
|
||
|
|
||
|
fillTextures( dtPosition, dtVelocity );
|
||
|
|
||
|
velocityVariable = gpuCompute.addVariable( 'textureVelocity', document.getElementById( 'computeShaderVelocity' ).textContent, dtVelocity );
|
||
|
positionVariable = gpuCompute.addVariable( 'texturePosition', document.getElementById( 'computeShaderPosition' ).textContent, dtPosition );
|
||
|
|
||
|
gpuCompute.setVariableDependencies( velocityVariable, [ positionVariable, velocityVariable ] );
|
||
|
gpuCompute.setVariableDependencies( positionVariable, [ positionVariable, velocityVariable ] );
|
||
|
|
||
|
velocityUniforms = velocityVariable.material.uniforms;
|
||
|
|
||
|
velocityUniforms[ 'gravityConstant' ] = { value: 0.0 };
|
||
|
velocityUniforms[ 'density' ] = { value: 0.0 };
|
||
|
|
||
|
const error = gpuCompute.init();
|
||
|
|
||
|
if ( error !== null ) {
|
||
|
|
||
|
console.error( error );
|
||
|
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
function restartSimulation() {
|
||
|
|
||
|
const dtPosition = gpuCompute.createTexture();
|
||
|
const dtVelocity = gpuCompute.createTexture();
|
||
|
|
||
|
fillTextures( dtPosition, dtVelocity );
|
||
|
|
||
|
gpuCompute.renderTexture( dtPosition, positionVariable.renderTargets[ 0 ] );
|
||
|
gpuCompute.renderTexture( dtPosition, positionVariable.renderTargets[ 1 ] );
|
||
|
gpuCompute.renderTexture( dtVelocity, velocityVariable.renderTargets[ 0 ] );
|
||
|
gpuCompute.renderTexture( dtVelocity, velocityVariable.renderTargets[ 1 ] );
|
||
|
|
||
|
}
|
||
|
|
||
|
function initProtoplanets() {
|
||
|
|
||
|
geometry = new THREE.BufferGeometry();
|
||
|
|
||
|
const positions = new Float32Array( PARTICLES * 3 );
|
||
|
let p = 0;
|
||
|
|
||
|
for ( let i = 0; i < PARTICLES; i ++ ) {
|
||
|
|
||
|
positions[ p ++ ] = ( Math.random() * 2 - 1 ) * effectController.radius;
|
||
|
positions[ p ++ ] = 0; //( Math.random() * 2 - 1 ) * effectController.radius;
|
||
|
positions[ p ++ ] = ( Math.random() * 2 - 1 ) * effectController.radius;
|
||
|
|
||
|
}
|
||
|
|
||
|
const uvs = new Float32Array( PARTICLES * 2 );
|
||
|
p = 0;
|
||
|
|
||
|
for ( let j = 0; j < WIDTH; j ++ ) {
|
||
|
|
||
|
for ( let i = 0; i < WIDTH; i ++ ) {
|
||
|
|
||
|
uvs[ p ++ ] = i / ( WIDTH - 1 );
|
||
|
uvs[ p ++ ] = j / ( WIDTH - 1 );
|
||
|
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
geometry.setAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) );
|
||
|
geometry.setAttribute( 'uv', new THREE.BufferAttribute( uvs, 2 ) );
|
||
|
|
||
|
particleUniforms = {
|
||
|
'texturePosition': { value: null },
|
||
|
'textureVelocity': { value: null },
|
||
|
'cameraConstant': { value: getCameraConstant( camera ) },
|
||
|
'density': { value: 0.0 }
|
||
|
};
|
||
|
|
||
|
// THREE.ShaderMaterial
|
||
|
const material = new THREE.ShaderMaterial( {
|
||
|
uniforms: particleUniforms,
|
||
|
vertexShader: document.getElementById( 'particleVertexShader' ).textContent,
|
||
|
fragmentShader: document.getElementById( 'particleFragmentShader' ).textContent
|
||
|
} );
|
||
|
|
||
|
material.extensions.drawBuffers = true;
|
||
|
|
||
|
const particles = new THREE.Points( geometry, material );
|
||
|
particles.matrixAutoUpdate = false;
|
||
|
particles.updateMatrix();
|
||
|
|
||
|
scene.add( particles );
|
||
|
|
||
|
}
|
||
|
|
||
|
function fillTextures( texturePosition, textureVelocity ) {
|
||
|
|
||
|
const posArray = texturePosition.image.data;
|
||
|
const velArray = textureVelocity.image.data;
|
||
|
|
||
|
const radius = effectController.radius;
|
||
|
const height = effectController.height;
|
||
|
const exponent = effectController.exponent;
|
||
|
const maxMass = effectController.maxMass * 1024 / PARTICLES;
|
||
|
const maxVel = effectController.velocity;
|
||
|
const velExponent = effectController.velocityExponent;
|
||
|
const randVel = effectController.randVelocity;
|
||
|
|
||
|
for ( let k = 0, kl = posArray.length; k < kl; k += 4 ) {
|
||
|
|
||
|
// Position
|
||
|
let x, z, rr;
|
||
|
|
||
|
do {
|
||
|
|
||
|
x = ( Math.random() * 2 - 1 );
|
||
|
z = ( Math.random() * 2 - 1 );
|
||
|
rr = x * x + z * z;
|
||
|
|
||
|
} while ( rr > 1 );
|
||
|
|
||
|
rr = Math.sqrt( rr );
|
||
|
|
||
|
const rExp = radius * Math.pow( rr, exponent );
|
||
|
|
||
|
// Velocity
|
||
|
const vel = maxVel * Math.pow( rr, velExponent );
|
||
|
|
||
|
const vx = vel * z + ( Math.random() * 2 - 1 ) * randVel;
|
||
|
const vy = ( Math.random() * 2 - 1 ) * randVel * 0.05;
|
||
|
const vz = - vel * x + ( Math.random() * 2 - 1 ) * randVel;
|
||
|
|
||
|
x *= rExp;
|
||
|
z *= rExp;
|
||
|
const y = ( Math.random() * 2 - 1 ) * height;
|
||
|
|
||
|
const mass = Math.random() * maxMass + 1;
|
||
|
|
||
|
// Fill in texture values
|
||
|
posArray[ k + 0 ] = x;
|
||
|
posArray[ k + 1 ] = y;
|
||
|
posArray[ k + 2 ] = z;
|
||
|
posArray[ k + 3 ] = 1;
|
||
|
|
||
|
velArray[ k + 0 ] = vx;
|
||
|
velArray[ k + 1 ] = vy;
|
||
|
velArray[ k + 2 ] = vz;
|
||
|
velArray[ k + 3 ] = mass;
|
||
|
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
function onWindowResize() {
|
||
|
|
||
|
camera.aspect = window.innerWidth / window.innerHeight;
|
||
|
camera.updateProjectionMatrix();
|
||
|
|
||
|
renderer.setSize( window.innerWidth, window.innerHeight );
|
||
|
|
||
|
particleUniforms[ 'cameraConstant' ].value = getCameraConstant( camera );
|
||
|
|
||
|
}
|
||
|
|
||
|
function dynamicValuesChanger() {
|
||
|
|
||
|
velocityUniforms[ 'gravityConstant' ].value = effectController.gravityConstant;
|
||
|
velocityUniforms[ 'density' ].value = effectController.density;
|
||
|
particleUniforms[ 'density' ].value = effectController.density;
|
||
|
|
||
|
}
|
||
|
|
||
|
function initGUI() {
|
||
|
|
||
|
const gui = new GUI( { width: 280 } );
|
||
|
|
||
|
const folder1 = gui.addFolder( 'Dynamic parameters' );
|
||
|
|
||
|
folder1.add( effectController, 'gravityConstant', 0.0, 1000.0, 0.05 ).onChange( dynamicValuesChanger );
|
||
|
folder1.add( effectController, 'density', 0.0, 10.0, 0.001 ).onChange( dynamicValuesChanger );
|
||
|
|
||
|
const folder2 = gui.addFolder( 'Static parameters' );
|
||
|
|
||
|
folder2.add( effectController, 'radius', 10.0, 1000.0, 1.0 );
|
||
|
folder2.add( effectController, 'height', 0.0, 50.0, 0.01 );
|
||
|
folder2.add( effectController, 'exponent', 0.0, 2.0, 0.001 );
|
||
|
folder2.add( effectController, 'maxMass', 1.0, 50.0, 0.1 );
|
||
|
folder2.add( effectController, 'velocity', 0.0, 150.0, 0.1 );
|
||
|
folder2.add( effectController, 'velocityExponent', 0.0, 1.0, 0.01 );
|
||
|
folder2.add( effectController, 'randVelocity', 0.0, 50.0, 0.1 );
|
||
|
|
||
|
const buttonRestart = {
|
||
|
restartSimulation: function () {
|
||
|
|
||
|
restartSimulation();
|
||
|
|
||
|
}
|
||
|
};
|
||
|
|
||
|
folder2.add( buttonRestart, 'restartSimulation' );
|
||
|
|
||
|
folder1.open();
|
||
|
folder2.open();
|
||
|
|
||
|
}
|
||
|
|
||
|
function getCameraConstant( camera ) {
|
||
|
|
||
|
return window.innerHeight / ( Math.tan( THREE.MathUtils.DEG2RAD * 0.5 * camera.fov ) / camera.zoom );
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
function animate() {
|
||
|
|
||
|
requestAnimationFrame( animate );
|
||
|
|
||
|
render();
|
||
|
stats.update();
|
||
|
|
||
|
}
|
||
|
|
||
|
function render() {
|
||
|
|
||
|
gpuCompute.compute();
|
||
|
|
||
|
particleUniforms[ 'texturePosition' ].value = gpuCompute.getCurrentRenderTarget( positionVariable ).texture;
|
||
|
particleUniforms[ 'textureVelocity' ].value = gpuCompute.getCurrentRenderTarget( velocityVariable ).texture;
|
||
|
|
||
|
renderer.render( scene, camera );
|
||
|
|
||
|
}
|
||
|
|
||
|
</script>
|
||
|
</body>
|
||
|
</html>
|