更多有趣示例 尽在小红砖社区
示例
HTML
<input type="file" id="thefile" accept="audio/*" />
<audio id="audio" controls></audio>
<script id="vertex" type="x-shader/x-fragment">
#define PI 3.14159265358979323846
uniform float u_time;
uniform sampler2D uAudio;
uniform float uMove;
uniform float uAverage;
varying vec2 vUv;
varying float height;
void main(){
float a = texture2D(uAudio, vec2(0.5,0. )).r;
float doubleX = position.x * position.x ;
float distanceSquared = doubleX + position.z * position.z;
vec3 pos = position;
// 0 instead of pos.y to get noise on 2d space
float n = snoise(vec3(pos.x,0.,pos.z- u_time*(2. ) - uMove )/20.);
// pos.y +=1.*sin(distanceSquared*sin(u_time/143.0)/1000.);
height = n *( sin(doubleX/1000.) );
pos.y += height*(10. + 15. * uAverage / 255. );
// pos.z += pos.z * ((sin(u_time/256.0) + 1.75) / 2.);
float diagonalSin = sin(u_time/200.0);
float frequency = 5000.;
float om = (sin(distanceSquared*sin(u_time/10.0)/frequency) * diagonalSin) /1.5;
pos.y = pos.x*sin(om)+pos.y*cos(om);
pos.x = pos.x*cos(om)-pos.y*sin(om);
gl_Position = projectionMatrix * modelViewMatrix * vec4(pos,1.);
vUv = uv;
}
</script>
<script id="fragment" type="x-shader/x-fragment">
#define PI 3.14159265358979323846
uniform float u_time;
varying vec2 vUv;
varying float height;
uniform float uAverage;
uniform sampler2D uAudio;
uniform vec3 fogColor;
uniform float fogNear;
uniform float fogFar;
vec3 palette( in float t, in vec3 a, in vec3 b, in vec3 c, in vec3 d )
{
return a + b*cos( 6.28318*(c*t+d) );
}
void main(){
float a = texture2D(uAudio, vec2(height/2.+0.5,0. )).r;
float b = texture2D(uAudio, vec2(vUv.y, 0. )).r;
float roadSize = 0.03;
float road = smoothstep(0.-roadSize,0.,vUv.x -0.5) - smoothstep(0.,0.+roadSize,vUv.x -0.5 );
gl_FragColor = vec4(palette(
height/2.+0.5,
vec3(0.5, 0.5, 0.5),
vec3(0.5, 0.5*a, 0.5*b),
vec3(2.0, 1.0*a, 0.0),
vec3(0.50*b, 0.20, 0.25) )+road,1.);
// gl_FragColor = vec4(vec3(noise),1.);
#ifdef USE_FOG
#ifdef USE_LOGDEPTHBUF_EXT
float depth = gl_FragDepthEXT / gl_FragCoord.w;
#else
float depth = gl_FragCoord.z / gl_FragCoord.w;
#endif
float fogFactor = smoothstep( fogNear, fogFar, depth );
gl_FragColor.rgb = mix( gl_FragColor.rgb, fogColor, fogFactor );
#endif
}
</script>
<script id="noise" type="x-shader/x-fragment">
//
// Description : Array and textureless GLSL 2D/3D/4D simplex
// noise functions.
// Author : Ian McEwan, Ashima Arts.
// Maintainer : ijm
// Lastmod : 20110822 (ijm)
// License : Copyright (C) 2011 Ashima Arts. All rights reserved.
// Distributed under the MIT License. See LICENSE file.
// https://github.com/ashima/webgl-noise
//
vec3 mod289(vec3 x) {
return x - floor(x * (1.0 / 289.0)) * 289.0;
}
vec4 mod289(vec4 x) {
return x - floor(x * (1.0 / 289.0)) * 289.0;
}
vec4 permute(vec4 x) {
return mod289(((x*34.0)+1.0)*x);
}
vec4 taylorInvSqrt(vec4 r){
return 1.79284291400159 - 0.85373472095314 * r;
}
float snoise(vec3 v) {
const vec2 C = vec2(1.0/6.0, 1.0/3.0) ;
const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);
// First corner
vec3 i = floor(v + dot(v, C.yyy) );
vec3 x0 = v - i + dot(i, C.xxx) ;
// Other corners
vec3 g = step(x0.yzx, x0.xyz);
vec3 l = 1.0 - g;
vec3 i1 = min( g.xyz, l.zxy );
vec3 i2 = max( g.xyz, l.zxy );
// x0 = x0 - 0.0 + 0.0 * C.xxx;
// x1 = x0 - i1 + 1.0 * C.xxx;
// x2 = x0 - i2 + 2.0 * C.xxx;
// x3 = x0 - 1.0 + 3.0 * C.xxx;
vec3 x1 = x0 - i1 + C.xxx;
vec3 x2 = x0 - i2 + C.yyy; // 2.0*C.x = 1/3 = C.y
vec3 x3 = x0 - D.yyy; // -1.0+3.0*C.x = -0.5 = -D.y
// Permutations
i = mod289(i);
vec4 p = permute( permute( permute(
i.z + vec4(0.0, i1.z, i2.z, 1.0 ))
+ i.y + vec4(0.0, i1.y, i2.y, 1.0 ))
+ i.x + vec4(0.0, i1.x, i2.x, 1.0 ));
// Gradients: 7x7 points over a square, mapped onto an octahedron.
// The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294)
float n_ = 0.142857142857; // 1.0/7.0
vec3 ns = n_ * D.wyz - D.xzx;
vec4 j = p - 49.0 * floor(p * ns.z * ns.z); // mod(p,7*7)
vec4 x_ = floor(j * ns.z);
vec4 y_ = floor(j - 7.0 * x_ ); // mod(j,N)
vec4 x = x_ *ns.x + ns.yyyy;
vec4 y = y_ *ns.x + ns.yyyy;
vec4 h = 1.0 - abs(x) - abs(y);
vec4 b0 = vec4( x.xy, y.xy );
vec4 b1 = vec4( x.zw, y.zw );
//vec4 s0 = vec4(lessThan(b0,0.0))*2.0 - 1.0;
//vec4 s1 = vec4(lessThan(b1,0.0))*2.0 - 1.0;
vec4 s0 = floor(b0)*2.0 + 1.0;
vec4 s1 = floor(b1)*2.0 + 1.0;
vec4 sh = -step(h, vec4(0.0));
vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy ;
vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww ;
vec3 p0 = vec3(a0.xy,h.x);
vec3 p1 = vec3(a0.zw,h.y);
vec3 p2 = vec3(a1.xy,h.z);
vec3 p3 = vec3(a1.zw,h.w);
//Normalise gradients
vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3)));
p0 *= norm.x;
p1 *= norm.y;
p2 *= norm.z;
p3 *= norm.w;
// Mix final noise value
vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);
m = m * m;
return 42.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1), dot(p2,x2), dot(p3,x3) ) );
}
// via: https://petewerner.blogspot.jp/2015/02/intro-to-curl-noise.html
vec3 curlNoise( vec3 p ){
const float e = 0.1;
float n1 = snoise(vec3(p.x, p.y + e, p.z));
float n2 = snoise(vec3(p.x, p.y - e, p.z));
float n3 = snoise(vec3(p.x, p.y, p.z + e));
float n4 = snoise(vec3(p.x, p.y, p.z - e));
float n5 = snoise(vec3(p.x + e, p.y, p.z));
float n6 = snoise(vec3(p.x - e, p.y, p.z));
float x = n2 - n1 - n4 + n3;
float y = n4 - n3 - n6 + n5;
float z = n6 - n5 - n2 + n1;
const float divisor = 1.0 / ( 2.0 * e );
return normalize( vec3( x , y , z ) * divisor );
}
</script>
CSS
body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
background-color: #DDA0DD;
}
#thefile{
position: fixed;
top: 15px;
left: 100px;
}
audio {
position: fixed;
width: 100%;
bottom: 5px;
}
.stats {
position: absolute;
top: 5px;
left: 5px;
}
JS
// GIF Generation: https://github.com/antimatter15/jsgif
class ThreeBasic {
constructor(withControls = false){
this.hasControls = withControls;
this.useControls = false;
this.renderer = null;
this.camera = null;
this.scene = null;
this.controls = null;
this.getClampedData = this.getClampedData.bind(this);
}
init(){
const VIEW_ANGLE = 45,
ASPECT = window.innerWidth / window.innerHeight,
NEAR = 0.1,
FAR = 10000;
const camera = new THREE.PerspectiveCamera(VIEW_ANGLE, ASPECT, NEAR, FAR);
camera.position.z = 20;
camera.rotation.x = 20 * Math.PI / 180;
camera.updateProjectionMatrix();
const scene = new THREE.Scene();
scene.background = new THREE.Color( 0xDDA0DD );
scene.fog = new THREE.Fog( 0x800080, 0.2,350. );
const renderer = new THREE.WebGLRenderer({ antialias: true});
document.body.appendChild(renderer.domElement);
if(this.hasControls){
this.controls = new THREE.OrbitControls(camera, renderer.domElement);
}
this.camera = camera;
this.scene = scene;
this.renderer = renderer;
this.onResize();
}
add(mesh){
this.scene.add(mesh);
}
getClampedData (width,height){
// change size if its bigger that gl canvas
const gl = this.renderer.context;
var pixels = new Uint8Array(width * height * 4);
gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
const clamped = Uint8ClampedArray.from(pixels);
return clamped;
}
onStart(){
}
onResize(){
console.log('resize')
this.camera.aspect = window.innerWidth / window.innerHeight;
this.camera.updateProjectionMatrix();
this.renderer.setSize(window.innerWidth, window.innerHeight);
// uniforms.u_res.value.x = renderer.domElement.width;
// uniforms.u_res.value.y = renderer.domElement.height;
this.render(this.scene,this.camera);
}
render(){
this.renderer.render( this.scene, this.camera );
}
}
class Giffer {
constructor(width,height,totalFrames = 5){
this.encoder = new GIFEncoder();
this.encoder.setSize(width,height);
this.encoder.setRepeat(0);
this.encoder.setDelay(1000/60);
this.width = width;
this.height = height;
this.totalFrames = totalFrames;
this.frames= 0;
this.started = false;
this.done = false;
this.isActive = false;
}
initializeEncoder(){
this.encoder = new GIFEncoder();
this.encoder.setSize(width,height);
this.encoder.setRepeat(0);
this.encoder.setDelay(100);
}
start(totalFrames = 1, width, height){
this.started = true;
this.frames = 0;
this.done = false;
this.totalFrames = totalFrames;
if(width && height) {
this.width = width;
this.height = height;
}
}
onInitialFrame(){
this.encoder.setSize(this.width,this.height);
this.encoder.start();
}
capture(getData) {
if(this.done || !this.started ) return;
if(this.frames === 0) {
this.onInitialFrame();
}
if(this.frames >= this.totalFrames){
this.onFinish();
return;
}
const clampedData = getData(this.width,this.height);
this.encoder.addFrame(clampedData,true);
this.frames++;
}
onFinish(){
this.encoder.finish();
this.encoder.download('download.gif');
this.done = true;
}
}
const getShaders = (name, options) =>{
const shaders = {
fragmentShader: (document.getElementById(name+'-fragment') || document.getElementById('fragment')).textContent,
vertexShader: (document.getElementById(name+'-vertex') || document.getElementById('vertex')).textContent
}
if(options){
if(options.noise === true){
const noise = document.getElementById('noise');
if(noise){
shaders.fragmentShader = noise.textContent + shaders.fragmentShader;
shaders.vertexShader = noise.textContent + shaders.vertexShader;
} else {
console.error('NOISE NOT FOUND');
}
}
}
return shaders;
}
const app = new ThreeBasic(false);
app.init();
const framesPerLoop = 60;
const config = {
framesPerLoop: framesPerLoop,
timeSpeed: Math.PI*2./framesPerLoop *0.1
}
// CODE GOES HERE
var file = document.getElementById("thefile");
var audioElement = document.getElementById("audio");
file.onchange = function(){
audioElement.classList.add('active');
var files = this.files;
audioElement.src = URL.createObjectURL(files[0]);
audioElement.load();
audioElement.addEventListener('play', ()=>{
config.timeSpeed = Math.PI*2./framesPerLoop;
play()
})
}
function play(){
audio.setMediaElementSource( audioElement );
}
let listener = new THREE.AudioListener();
let audio = new THREE.Audio(listener);
var fftSize = 128;
let analyser = new THREE.AudioAnalyser(audio, fftSize);
const uniforms = {
u_time: {type:'f', value: 0},
uAudio: { value: new THREE.DataTexture( analyser.data, fftSize / 2, 1, THREE.LuminanceFormat ) },
fogColor: { type: "c", value: app.scene.fog.color },
fogNear: { type: "f", value: app.scene.fog.near },
fogFar: { type: "f", value: app.scene.fog.far },
uMove: {type:'f', value: 0.},
uAverage: {type: 'f', value: 0.}
}
let segments = 200;
let height = 500;
let width = 200;
let geometry = new THREE.PlaneBufferGeometry(width,height,segments,segments);
geometry.rotateX(-20);
const material = new THREE.ShaderMaterial({
uniforms,
side: THREE.DoubleSide,
...getShaders('',{noise: true}),
fog: true
});
console.log(material);
const mesh = new THREE.Mesh(geometry, material);
// ADD TO SCENE
app.add(mesh);
// STATS
let stats = new Stats();
stats.showPanel(0);
stats.domElement.className = "stats"
document.body.appendChild( stats.domElement );
// GUI
const gui = new dat.GUI()
// GUI controls go here
const gui_time = gui.add(config,'timeSpeed',Math.PI*2./240,Math.PI*2./60, Math.PI/120).name('Frame speed');
gui_time.onChange((r)=>{
config.framesPerLoop = Math.floor((Math.PI * 2)/r);
})
// gui_time.onFinishChange(()=>{})
// GIF maker stuff
const giffy = new Giffer(window.innerWidth,window.innerHeight, config.framesPerLoop);
const gui_functions = {
start_gif: ()=>{
giffy.start(config.framesPerLoop,app.renderer.context.drawingBufferWidth,app.renderer.context.drawingBufferHeight);
},
randomize: ()=>{
// Generate a new seed or something
}
}
gui.add(gui_functions,'randomize').name("Randomize");
gui.add(gui_functions,'start_gif').name("Generate GIF");
// Life loop
// var capturer = new CCapture( { format: 'gif', workersPath: 'https://cdn.jsdelivr.net/gh/spite/ccapture.js/src/' } );
let frames = 0;
var bufferLength = analyser.frequencyBinCount;
var dataArray = new Uint8Array(bufferLength);
const update = ()=>{
const data = analyser.getFrequencyData();
const average = avg(data);
uniforms.uAudio.value.needsUpdate = true;
uniforms.u_time.value += config.timeSpeed ;
uniforms.uMove.value += (average * 0.005);
uniforms.uAverage.value = analyser.getAverageFrequency();
// mesh.rotation.y=uniforms.u_time.value;
}
function draw(){
stats.begin();
app.render();
update();
stats.end();
requestAnimationFrame(draw)
giffy.capture(app.getClampedData);
// if(frames < 10){
// capturer.capture(app.renderer.domElement);
// } else if(frames === 10){
// capturer.stop();
// capturer.save();
// }
// frames++;
}
function init(){
// capturer.start()
app.onStart();
requestAnimationFrame(draw)
}
window.addEventListener('resize', ()=>{
app.onResize();
});
window.addEventListener('mousemove',(e)=>{
// uniforms.u_mouse.value.x = e.clientX/window.innerWidth;
// uniforms.u_mouse.value.y = e.clientY/window.innerHeight;
})
init();
function avg(arr){
var sum = 0;
for(var i = arr.length-1.;i>-1;i--){
sum += arr[i];
}
return (sum / arr.length) || 0;
}
function max(arr){
var max = arr[0];
for(var i = arr.length-1.;i>-1;i--){
max = arr[i] > max ? arr[i] : max;
}
return max;
}