Skip to content

Instantly share code, notes, and snippets.

@simondodson
Created October 30, 2020 23:03
Show Gist options
  • Save simondodson/908afd785a04ad24d91c3889143c7130 to your computer and use it in GitHub Desktop.
Save simondodson/908afd785a04ad24d91c3889143c7130 to your computer and use it in GitHub Desktop.
Dodecahedrons
<canvas id="canvas"></canvas>
<p class="collection">
<a href="https://codepen.io/collection/AGZywR" target="_blank">WebGL Collection</a>
</p>
import {
Color,
DodecahedronBufferGeometry,
IcosahedronBufferGeometry,
InstancedBufferAttribute,
InstancedMesh,
MathUtils,
Object3D,
PointLight,
Scene,
ShaderChunk,
ShaderLib,
ShaderMaterial,
TetrahedronBufferGeometry,
UniformsUtils,
Vector2,
Vector3,
} from 'https://unpkg.com/three@0.120.0/build/three.module.js';
import { EffectComposer } from 'https://unpkg.com/three@0.120.0/examples/jsm/postprocessing/EffectComposer.js';
import { RenderPass } from 'https://unpkg.com/three@0.120.0/examples/jsm/postprocessing/RenderPass.js';
import { UnrealBloomPass } from 'https://unpkg.com/three@0.120.0/examples/jsm/postprocessing/UnrealBloomPass.js';
import { ShaderPass } from 'https://unpkg.com/three@0.120.0/examples/jsm/postprocessing/ShaderPass.js';
import { FXAAShader } from 'https://unpkg.com/three@0.120.0/examples/jsm/shaders/FXAAShader.js';
import useThree from 'https://codepen.io/soju22/pen/cb31020fed766eb66bc8ad1879bc3325.js';
App();
function App() {
let three, scene, composer, iMesh;
let pointLight;
let cscale = chroma.scale(['#dd3e1b', '#0b509c']);
const NUM_INSTANCES = 5000;
const instances = [];
const target = new Vector3();
const dummy = new Object3D();
const dummyV = new Vector3();
const { randFloat: rnd, randFloatSpread: rndFS } = MathUtils;
init();
function init() {
three = useThree().init({
canvas: document.getElementById('canvas'),
antialias: false,
mouse_move: true,
mouse_raycast: true,
camera_ctrl: {
enableDamping: true,
dampingFactor: 0.05,
},
camera_fov: 50,
camera_pos: new Vector3(0, 0, 250),
});
initScene();
initPostprocessing();
animate();
}
function initScene() {
scene = new Scene();
pointLight = new PointLight(0xFFC0C0);
scene.add(pointLight);
// const pointLight1 = new PointLight(0x6060FF);
// pointLight1.position.x = -100;
// scene.add(pointLight1);
// const pointLight2 = new PointLight(0xFF6060);
// pointLight2.position.x = 100;
// scene.add(pointLight2);
const geometry = new DodecahedronBufferGeometry(5);
// const geometry = new IcosahedronBufferGeometry(5);
// const geometry = new TetrahedronBufferGeometry(5);
const material = SubSurfaceMaterial();
iMesh = new InstancedMesh(geometry, material, NUM_INSTANCES);
scene.add(iMesh);
for (let i = 0; i < NUM_INSTANCES; i++) {
instances.push({
position: new Vector3(rndFS(200), rndFS(200), rndFS(200)),
scale: rnd(0.2, 1),
velocity: new Vector3(rndFS(2), rndFS(2), rndFS(2)),
attraction: 0.0025 + rnd(0, 0.01),
vlimit: 0.3 + rnd(0, 0.2),
});
}
for (let i = 0; i < NUM_INSTANCES; i++) {
const { position, scale } = instances[i];
dummy.position.copy(position);
dummy.scale.set(scale, scale, scale);
dummy.updateMatrix();
iMesh.setMatrixAt(i, dummy.matrix);
}
iMesh.instanceMatrix.needsUpdate = true;
updateColors();
document.body.addEventListener('click', randomColors);
}
function randomColors() {
const c1 = chroma.random(), c2 = chroma.random();
console.log(c1.hex(), c2.hex());
cscale = chroma.scale([c1, c2]);
updateColors();
}
function updateColors() {
const colors = [];
for (let i = 0; i < NUM_INSTANCES; i++) {
const color = new Color(cscale(rnd(0, 1)).hex());
colors.push(color.r, color.g, color.b);
}
iMesh.geometry.setAttribute('color', new InstancedBufferAttribute(new Float32Array(colors), 3));
}
function initPostprocessing() {
composer = new EffectComposer(three.renderer);
const renderPass = new RenderPass(scene, three.camera);
composer.addPass(renderPass);
const bloomPass = new UnrealBloomPass(new Vector2(three.size.width, three.size.height), 0.5, 0, 0);
composer.addPass(bloomPass);
const aaPass = new ShaderPass(FXAAShader);
composer.addPass(aaPass);
aaPass.material.uniforms.resolution.value.set(1 / three.size.width, 1 / three.size.height);
three.onAfterResize(() => {
composer.setSize(three.size.width, three.size.height);
aaPass.material.uniforms.resolution.value.set(1 / three.size.width, 1 / three.size.height);
});
}
function animate() {
requestAnimationFrame(animate);
const { renderer, camera, cameraCtrl } = three;
target.copy(three.mouseV3);
pointLight.position.copy(target);
for (let i = 0; i < NUM_INSTANCES; i++) {
const { position, scale, velocity, attraction, vlimit } = instances[i];
dummyV.copy(target).sub(position).normalize().multiplyScalar(attraction);
velocity.add(dummyV).clampScalar(-vlimit, vlimit);
position.add(velocity);
dummy.position.copy(position);
dummy.scale.set(scale, scale, scale);
dummy.lookAt(dummyV.copy(position).add(velocity));
dummy.updateMatrix();
iMesh.setMatrixAt(i, dummy.matrix);
}
iMesh.instanceMatrix.needsUpdate = true;
if (cameraCtrl) cameraCtrl.update();
composer.render();
}
}
/**
* ------------------------------------------------------------------------------------------
* Subsurface Scattering shader
* Based on three/examples/jsm/shaders/SubsurfaceScatteringShader.js
* Based on GDC 2011 – Approximating Translucency for a Fast, Cheap and Convincing Subsurface Scattering Look
* https://colinbarrebrisebois.com/2011/03/07/gdc-2011-approximating-translucency-for-a-fast-cheap-and-convincing-subsurface-scattering-look/
*------------------------------------------------------------------------------------------
*/
function SubSurfaceMaterial() {
const meshphongFragHead = ShaderChunk.meshphong_frag.slice(0, ShaderChunk.meshphong_frag.indexOf('void main() {'));
const meshphongFragBody = ShaderChunk.meshphong_frag.slice(ShaderChunk.meshphong_frag.indexOf('void main() {'));
return new ShaderMaterial({
lights: true,
vertexColors: true,
flatShading: true,
uniforms: UniformsUtils.merge([
ShaderLib.phong.uniforms,
{
// diffuse: { value: new Color(0xffffff) },
thicknessColor: { value: new Color(0x808080) },
thicknessDistortion: { value: 0.4 },
thicknessAmbient: { value: 0.01 },
thicknessAttenuation: { value: 0.7 },
thicknessPower: { value: 2.0 },
thicknessScale: { value: 4.0 },
},
]),
vertexShader: `
#define USE_UV
${ShaderChunk.meshphong_vert}
`,
fragmentShader: `
#define USE_UV
#define SUBSURFACE
${meshphongFragHead}
uniform float thicknessPower;
uniform float thicknessScale;
uniform float thicknessDistortion;
uniform float thicknessAmbient;
uniform float thicknessAttenuation;
uniform vec3 thicknessColor;
void RE_Direct_Scattering(const in IncidentLight directLight, const in vec2 uv, const in GeometricContext geometry, inout ReflectedLight reflectedLight) {
#ifdef USE_COLOR
vec3 thickness = vColor * thicknessColor;
#else
vec3 thickness = thicknessColor;
#endif
vec3 scatteringHalf = normalize(directLight.direction + (geometry.normal * thicknessDistortion));
float scatteringDot = pow(saturate(dot(geometry.viewDir, -scatteringHalf)), thicknessPower) * thicknessScale;
vec3 scatteringIllu = (scatteringDot + thicknessAmbient) * thickness;
reflectedLight.directDiffuse += scatteringIllu * thicknessAttenuation * directLight.color;
}
` + meshphongFragBody.replace(
'#include <lights_fragment_begin>',
replaceAll(
ShaderChunk.lights_fragment_begin,
'RE_Direct( directLight, geometry, material, reflectedLight );',
`
RE_Direct( directLight, geometry, material, reflectedLight );
#if defined( SUBSURFACE ) && defined( USE_UV )
RE_Direct_Scattering(directLight, vUv, geometry, reflectedLight);
#endif
`
)
),
});
function replaceAll(string, find, replace) {
return string.split(find).join(replace);
}
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/chroma-js/2.0.3/chroma.min.js"></script>
body {
margin: 0;
width: 100%;
height: 100%;
}
canvas {
display: block;
}
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@900&amp;display=swap" rel="stylesheet" />
<link href="https://codepen.io/soju22/pen/4aeaa568d7a4f3459bcde0e70b10f35a.css" rel="stylesheet" />
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment