Skip to content

Instantly share code, notes, and snippets.

@mutoo
Created August 24, 2018 01:09
Show Gist options
  • Select an option

  • Save mutoo/0c518d1261f3b0bce71948fb7fe5301c to your computer and use it in GitHub Desktop.

Select an option

Save mutoo/0c518d1261f3b0bce71948fb7fe5301c to your computer and use it in GitHub Desktop.
Mobius Silk Strip
<canvas id="stage"></canvas>
let RING_SEGMENT = 200;
let RING_RADIUS = 5;
let RING_KNOTS = 6;
let BAND_SEGMENT = 100;
let BAND_WIDTH = 2;
let BAND_WAVE = 0.25;
let BAND_WAVE_PERIOD = 5;
let light = {
x: 0, y: 0, z: 0
};
let rotation = {
x: 0, y: 0, z: 0
};
// vertex shader source
const vsSource = `
precision lowp float;
//
// Description : Array and textureless GLSL 2D/3D/4D simplex
// noise functions.
// Author : Ian McEwan, Ashima Arts.
// Maintainer : stegu
// 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
// https://github.com/stegu/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) ) );
}
vec3 hsv2rgb(vec3 c)
{
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}
attribute vec4 aVertexPosition;
attribute vec3 aVertexNormal;
attribute vec2 aVertexUV;
uniform mat4 uNormalMatrix;
uniform mat4 uModelViewMatrix;
uniform mat4 uProjectionMatrix;
uniform float time;
varying vec3 vNormal;
varying vec2 vUV;
varying vec3 vVertexPosition;
varying vec3 vVertexColor;
#define PI 3.1415926535897932384626433832795
void main() {
float u = aVertexUV.y * cos(aVertexUV.x * 2.0 * PI);
float v = aVertexUV.y * sin(aVertexUV.x * 2.0 * PI);
float r = 1.0 * snoise(vec3(u, v, time/10.0));
vec4 pos = vec4(vec3(aVertexPosition) + r * aVertexNormal, 1.0);
gl_Position = uProjectionMatrix * uModelViewMatrix * pos;
vNormal = vec3(uNormalMatrix * vec4(aVertexNormal, 1.0));
vUV = aVertexUV;
vVertexPosition = vec3(uModelViewMatrix * pos);
float offset = snoise(vec3(u + time/100.0, v * 10.0, time/10.0));
vVertexColor = hsv2rgb(vec3(0.0 + 0.1 * offset, 0.9 + 0.1 * offset, 1.0));
}
`;
// fragment shader source
const stripFsSource = `
#define PI 3.141592653597932384626433832795
precision lowp float;
varying vec3 vVertexPosition;
varying vec3 vVertexColor;
varying vec2 vUV;
float map(float value, float start1, float stop1, float start2, float stop2) {
return start2 + (value - start1) / (stop1 - start1) * (stop2 - start2);
}
void main() {
vec4 bg = vec4(vVertexColor, 0.3);
vec4 strip = vec4(vVertexColor, cos(2.0 * PI * vUV.x * 500.0));
float far = -8.0;
float near = -4.0;
float m = map(vVertexPosition.z, far, near, 0.0, 1.0);
if(vVertexPosition.z<far) {
gl_FragColor = bg;
} else if (vVertexPosition.z<near) {
gl_FragColor = mix(bg, strip, m);
} else {
gl_FragColor = strip;
}
// gl_FragColor = mix(bg, strip, m);
// gl_FragColor = mix(vec4(0.0, 0.0, 0.0, 0.0), bg, m);
// gl_FragColor = mix(vec4(0.0, 0.0, 0.0, 0.0), strip, m);
// gl_FragColor = bg;
// gl_FragColor = strip;
}
`;
const specularFsSource = `
precision lowp float;
uniform vec3 uLightPosition;
varying vec3 vNormal;
varying vec3 vVertexPosition;
varying vec2 vUV;
void main() {
vec3 N = normalize(vNormal);
vec3 L = normalize(uLightPosition - vVertexPosition);
vec3 reflectDir = reflect(-L, N);
vec3 viewDir = normalize(-vVertexPosition);
float specAngle = abs(dot(reflectDir, viewDir));
float specular = pow(specAngle, 10.0);
vec4 spec = vec4(specular, specular, specular, 0.5);
gl_FragColor = spec;
}
`;
// shader loader
function loadShader(gl, type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.log('An error occurred compiling the shaders: ' + gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return;
}
return shader;
}
// shader program loader
function initShaderProgram(gl, vsSource, fsSource) {
const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource);
const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource);
const shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
console.error('Unable to initialize the shader program: ' + gl.getProgramInfoLog(shaderProgram));
return;
}
return shaderProgram;
}
// buffer generator
function initBuffers(gl) {
let positions = [];
let normals = [];
let textureCoordinates = [];
for (let i = 0; i <= RING_SEGMENT; i++) {
let rad = 2 * Math.PI * (i / RING_SEGMENT);
let tilde = Math.PI * RING_KNOTS * (i / RING_SEGMENT);
for (let j = 0; j <= BAND_SEGMENT; j++) {
let wave = 2 * Math.PI * BAND_WAVE_PERIOD * (j / BAND_SEGMENT - 0.5);
let waveOffset = BAND_WAVE * Math.sin(wave);
let bandOffset = BAND_WIDTH * (j / BAND_SEGMENT - 0.5);
let ringOffset = RING_RADIUS;
let normal = vec3.fromValues(1, 0, BAND_WAVE * (2 * Math.PI * BAND_WAVE_PERIOD / BAND_SEGMENT) * Math.cos(wave));
{
let mat = mat4.create();
mat4.rotate(mat, mat, rad, [0, 0, 1]);
mat4.rotate(mat, mat, Math.PI/2 + tilde, [0, -1, 0]);
vec3.transformMat4(normal, normal, mat);
vec3.normalize(normal, normal);
}
let pos = vec3.create();
{
let mat = mat4.create();
mat4.rotate(mat, mat, rad, [0, 0, 1]);
mat4.translate(mat, mat, [ringOffset, 0, 0]);
mat4.rotate(mat, mat, tilde, [0, -1, 0]);
mat4.translate(mat, mat, [bandOffset, 0, waveOffset]);
vec3.transformMat4(pos, pos, mat);
}
positions.push(pos[0], pos[1], pos[2]);
normals.push(normal[0], normal[1], normal[2]);
textureCoordinates.push(i / RING_SEGMENT, j / BAND_SEGMENT);
}
}
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
const normalBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(normals), gl.STATIC_DRAW);
const textureCoordinateBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, textureCoordinateBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoordinates), gl.STATIC_DRAW);
const indices = [];
for (let i = 0; i < RING_SEGMENT; i++) {
for (let j = 0; j < BAND_SEGMENT; j++) {
let a = (i * (BAND_SEGMENT + 1) + j);
let b = ((i + 1) * (BAND_SEGMENT + 1) + j);
let c = (i * (BAND_SEGMENT + 1) + (j + 1));
let d = ((i + 1) * (BAND_SEGMENT + 1) + (j + 1));
indices.push(a, c, b);
indices.push(b, c, d);
}
}
const indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);
return {
position: positionBuffer,
normal: normalBuffer,
indices: indexBuffer,
uv: textureCoordinateBuffer,
};
}
let ringRotation = 0.0;
let n = 0;
let time = 0;
function drawScene(gl, programInfo, buffers, deltaTime) {
const fieldOfView = 90 * Math.PI / 180;
const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
const zNear = 0.1;
const zFar = 200;
const projectMatrix = mat4.create();
mat4.perspective(projectMatrix, fieldOfView, aspect, zNear, zFar);
const modelViewMatrix = mat4.create();
mat4.translate(modelViewMatrix, modelViewMatrix, [0, 0, -10]);
mat4.rotate(modelViewMatrix, modelViewMatrix, rotation.z, [0, 0, 1]);
mat4.rotate(modelViewMatrix, modelViewMatrix, rotation.y, [0, 1, 0]);
mat4.rotate(modelViewMatrix, modelViewMatrix, rotation.x, [1, 0, 0]);
mat4.rotate(modelViewMatrix, modelViewMatrix, ringRotation, [0, 0, 1]);
mat4.rotate(modelViewMatrix, modelViewMatrix, ringRotation * 0.7, [0, 1, 0]);
const normalMatrix = mat4.create();
mat4.invert(normalMatrix, modelViewMatrix);
mat4.transpose(normalMatrix, normalMatrix);
{
const numComponents = 3;
const type = gl.FLOAT;
const normalize = false;
const stride = 0;
const offset = 0;
gl.bindBuffer(gl.ARRAY_BUFFER, buffers.position);
gl.vertexAttribPointer(programInfo.attributeLocations.vertexPosition, numComponents, type, normalize, stride, offset);
gl.enableVertexAttribArray(programInfo.attributeLocations.vertexPosition);
gl.bindBuffer(gl.ARRAY_BUFFER, buffers.normal);
gl.vertexAttribPointer(programInfo.attributeLocations.vertexNormal, numComponents, type, normalize, stride, offset);
gl.enableVertexAttribArray(programInfo.attributeLocations.vertexNormal);
}
{
const numComponents = 2;
const type = gl.FLOAT;
const normalize = false;
const stride = 0;
const offset = 0;
gl.bindBuffer(gl.ARRAY_BUFFER, buffers.uv);
gl.vertexAttribPointer(programInfo.attributeLocations.vertexUV, numComponents, type, normalize, stride, offset);
gl.enableVertexAttribArray(programInfo.attributeLocations.vertexUV);
}
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffers.indices);
gl.useProgram(programInfo.program);
gl.uniformMatrix4fv(programInfo.uniformLocations.projectMatrix, false, projectMatrix);
gl.uniformMatrix4fv(programInfo.uniformLocations.modelMatrix, false, modelViewMatrix);
gl.uniformMatrix4fv(programInfo.uniformLocations.normalMatrix, false, normalMatrix);
gl.uniform3f(programInfo.uniformLocations.lightPosition, light.x, light.y, light.z);
gl.uniform1f(programInfo.uniformLocations.time, time);
{
// const indexCount = RING_SEGMENT * BAND_SEGMENT * 3 * 2;
const indexCount = Math.min(n*6, RING_SEGMENT * BAND_SEGMENT * 3 * 2);
const type = gl.UNSIGNED_SHORT;
const offset = 0;
gl.drawElements(gl.TRIANGLES, indexCount, type, offset);
}
ringRotation += deltaTime / 10;
n += BAND_SEGMENT;
time += deltaTime;
}
window.onload = function () {
let gui = new dat.GUI();
let l = gui.addFolder('light');
l.add(light, 'x', -40, 40);
l.add(light, 'y', -40, 40);
l.add(light, 'z', -40, 40);
let r = gui.addFolder('rotation');
r.add(rotation, 'x', -Math.PI, Math.PI);
r.add(rotation, 'y', -Math.PI, Math.PI);
r.add(rotation, 'z', -Math.PI, Math.PI);
let canvas = document.getElementById('stage');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
let gl = canvas.getContext('webgl');
if (!gl) {
console.error('Unable to initialize WebGL.');
return;
}
window.onresize = function() {
gl.canvas.width = window.innerWidth;
gl.canvas.height = window.innerHeight;
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
};
const stripShaderProgram = initShaderProgram(gl, vsSource, stripFsSource);
const stripProgramInfo = {
program: stripShaderProgram,
attributeLocations: {
vertexPosition: gl.getAttribLocation(stripShaderProgram, 'aVertexPosition'),
vertexColor: gl.getAttribLocation(stripShaderProgram, 'aVertexColor'),
vertexNormal: gl.getAttribLocation(stripShaderProgram, 'aVertexNormal'),
vertexUV: gl.getAttribLocation(stripShaderProgram, 'aVertexUV'),
},
uniformLocations: {
projectMatrix: gl.getUniformLocation(stripShaderProgram, 'uProjectionMatrix'),
modelMatrix: gl.getUniformLocation(stripShaderProgram, 'uModelViewMatrix'),
normalMatrix: gl.getUniformLocation(stripShaderProgram, 'uNormalMatrix'),
lightPosition: gl.getUniformLocation(stripShaderProgram, 'uLightPosition'),
time: gl.getUniformLocation(stripShaderProgram, 'time'),
},
};
const specularShaderProgram = initShaderProgram(gl, vsSource, specularFsSource);
const specularProgramInfo = {
program: specularShaderProgram,
attributeLocations: {
vertexPosition: gl.getAttribLocation(specularShaderProgram, 'aVertexPosition'),
vertexColor: gl.getAttribLocation(specularShaderProgram, 'aVertexColor'),
vertexNormal: gl.getAttribLocation(specularShaderProgram, 'aVertexNormal'),
vertexUV: gl.getAttribLocation(specularShaderProgram, 'aVertexUV'),
},
uniformLocations: {
projectMatrix: gl.getUniformLocation(specularShaderProgram, 'uProjectionMatrix'),
modelMatrix: gl.getUniformLocation(specularShaderProgram, 'uModelViewMatrix'),
normalMatrix: gl.getUniformLocation(specularShaderProgram, 'uNormalMatrix'),
lightPosition: gl.getUniformLocation(specularShaderProgram, 'uLightPosition'),
time: gl.getUniformLocation(specularShaderProgram, 'time'),
},
};
const buffers = initBuffers(gl);
let then = 0;
function render(now) {
now *= 0.001;
const deltaTime = now - then;
then = now;
gl.enable(gl.BLEND);
gl.disable(gl.DEPTH_TEST);
gl.clearColor(0, 0, 0, 1);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE);
gl.clear(gl.COLOR_BUFFER_BIT);
drawScene(gl, stripProgramInfo, buffers, deltaTime);
drawScene(gl, specularProgramInfo, buffers, deltaTime);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
};
<script src="https://cdn.rawgit.com/dataarts/dat.gui/v0.7.1/build/dat.gui.js"></script>
<script src="https://cdn.rawgit.com/toji/gl-matrix/v2.4.0/dist/gl-matrix.js"></script>
html, body {
margin: 0;
padding: 0;
}
#stage {
width: 100%;
height: 100%;
}
<link href="https://github.com/dataarts/dat.gui/blob/v0.7.1/build/dat.gui.css" rel="stylesheet" />
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment