A Pen by Lingjia Liu on CodePen.
Created
August 24, 2018 01:09
-
-
Save mutoo/0c518d1261f3b0bce71948fb7fe5301c to your computer and use it in GitHub Desktop.
Mobius Silk Strip
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <canvas id="stage"></canvas> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | |
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <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> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| html, body { | |
| margin: 0; | |
| padding: 0; | |
| } | |
| #stage { | |
| width: 100%; | |
| height: 100%; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <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