<div id="wrapper"></div>

<div id="log">
  <p>[memory]</p>
  <p id="log-geometries"></p>
  <p id="log-textures"></p>
  <p>[render]</p>
  <p id="log-draw-calls"></p>
  <p id="log-triangles"></p>
  <p id="log-points"></p>
  <p id="log-lines"></p>
</div>

<script id="vertexShader" type="v-shader">
uniform mat4 projectionMatrix;
uniform mat4 modelViewMatrix;

attribute vec3 position;
attribute vec2 uv;

varying vec2 vUv;

void main(void) {
  vUv = uv;
  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
</script>

<script id="fragmentShader" type="f-shader">
precision mediump float;
  
#define PI 3.141592653589793

uniform float uTime;
uniform sampler2D uTexture;
uniform vec2 uOffsetAmp;
uniform vec3 uOffsetRate;
uniform vec3 uWaveRate;
uniform vec3 uWaveAmp;
uniform vec2 uScale;
uniform float uTimeRate;
uniform float uDeg;
  
varying vec2 vUv;
  
float circle(vec2 p) {
  return length(p);
}  
  
mat2 scaleMatrix(vec2 s) {
  return mat2(
    s.x, 0.,
    0., s.y
  );
}  

mat2 rotationMatrix(float deg) {
  return mat2(
    -cos(deg * PI / 180.), sin(deg * PI / 180.),
    sin(deg * PI / 180.), cos(deg * PI / 180.)
  );  
}
  
vec2 warp(vec2 p, vec2 uv, vec2 offsetAmp) {
  return vec2(
    uv.x + (abs(p.x) * offsetAmp.x * sign(p.x)),
    uv.y - (abs(p.y) * offsetAmp.y * sign(p.y))
  );
}
  
void main() {
  vec4 color = vec4(0.0);
  float time = uTime / 1000.0;
  vec2 p = vUv;

  vec2 offset = uOffsetAmp * uTimeRate;
  
  // vec2 redOffset = offset * uRedOffset;
  // vec2 greenOffset = offset * uGreenOffset;
  // vec2 blueOffset = offset * uBlueOffset;
  
  vec2 redOffset = offset * uOffsetRate.r + vec2(sin(p.y * uWaveRate.r) * uWaveAmp.r, 0.) * uTimeRate;
  vec2 greenOffset = offset * uOffsetRate.g + vec2(sin(p.y * uWaveRate.g) * uWaveAmp.g, 0.) * uTimeRate;
  vec2 blueOffset = offset * uOffsetRate.b + vec2(sin(p.y * uWaveRate.b) * uWaveAmp.z, 0.) * uTimeRate;
  
  p = p * 2.0 - 1.0;

  // p *= scaleMatrix(uScale); 
  // p *= rotationMatrix(uDeg);
  
  vec2 redUV = vUv - redOffset;
  vec2 greenUV = vUv - greenOffset;
  vec2 blueUV = vUv - blueOffset;
  
  vec4 redTextureColor = texture2D(uTexture, redUV);
  vec4 greenTextureColor = texture2D(uTexture, greenUV);
  vec4 blueTextureColor = texture2D(uTexture, blueUV);
  
  // color.r = 1. - redTextureColor.r;
  // color.g = 1. - greenTextureColor.g;
  // color.b = 1. - blueTextureColor.b;

  vec4 r = vec4(vec3(1. - redTextureColor.rgb), redTextureColor.a);
  vec4 g = vec4(vec3(1. - greenTextureColor.rgb), greenTextureColor.a);
  vec4 b = vec4(vec3(1. - blueTextureColor.rgb), blueTextureColor.a);
  
  color.a = 1.;

  color.r += mix(color.r, r.r, r.a);
  color.g += mix(color.g, g.g, g.a);
  color.b += mix(color.b, b.b, b.a);
 
  // color.a *= r.a + g.a + b.a;

  gl_FragColor = color;
  
  if(gl_FragColor.a < .05) {
    discard;
  }
}
</script>
#wrapper {
  canvas {
    position: fixed;
    top: 0;
    left: 0;
  }
}

#log {
  position: fixed;
  left: 0;
  bottom: 0;
  background-color: rgba(0, 0, 0, 0.7);
  font-weight: bold;
  color: red;
  font-size: 10px;
}
View Compiled

const createTexture = (str, fontSize, textureSize) => {
  const canvas = document.createElement('canvas');
  const ctx = canvas.getContext('2d');
  canvas.width = textureSize;
  canvas.height = textureSize;
  ctx.font = `bold ${fontSize}px Arial sans-serif`;
  ctx.textAlign = 'center';
  ctx.textBaseline = 'middle';
  ctx.fillStyle = '#fff';
  ctx.fillRect(0, 0, textureSize, textureSize);
  ctx.fillStyle = '#000';
  ctx.fillText(str, textureSize / 2, textureSize / 2);
  const texture = new THREE.CanvasTexture(canvas);
  return texture;
}

let textTexture = createTexture("B", 500, 512);

const renderer = new THREE.WebGLRenderer();
renderer.setClearColor(0xa7bfe8);

const wrapperElem = document.querySelector('#wrapper');
wrapperElem.appendChild(renderer.domElement);

const scene = new THREE.Scene();

const camera = new THREE.PerspectiveCamera(
  45, // fov: field of view (画角のこと)
  1, // aspect
  0.1, // min distance
  10000, // max distance
);

const controls = new THREE.OrbitControls(camera, renderer.domElement);

const vertexShader = document.querySelector("#vertexShader").textContent;

const fragmentShader = document.querySelector("#fragmentShader").textContent;

const uniforms = {
  uTime: {
    value: 0,
  },
  uTexture: {
    // value: textTexture,
    value: new THREE.TextureLoader().load('https://dl.dropbox.com/s/dt4l2ynuymf4osv/choju48_0035.png?dl=0'),
  },
  uOffsetAmp: {
    value: new THREE.Vector2(0.3, 0),
  },
  uOffsetRate: {
    value: new THREE.Vector3(1, 0.7, 0.3),
  },
  uWaveRate: {
    value: new THREE.Vector3(80, 50, 30),
  },
  uWaveAmp: {
    value: new THREE.Vector3(0.05, 0.05, 0.05),
  },
  uDeg: {
    value: 0,
  },
  uScale: {
    value: new THREE.Vector2(1, 1),
  },
  uTimeRate: {
    value: 1,
  },
};
textTexture.needsUpdate = true;

(() => {
  const gui = new dat.GUI();
  let f = null;
  f = gui.addFolder('uOffsetAmp');
  f.add(uniforms.uOffsetAmp.value, 'x', 0, 2, 0.01);
  f.add(uniforms.uOffsetAmp.value, 'y', 0, 2, 0.01);
  f = gui.addFolder('uOffsetRate');
  f.add(uniforms.uOffsetRate.value, 'x', 0, 1, 0.01);
  f.add(uniforms.uOffsetRate.value, 'y', 0, 1, 0.01);
  f.add(uniforms.uOffsetRate.value, 'z', 0, 1, 0.01);
  f = gui.addFolder('uWaveRate');
  f.add(uniforms.uWaveRate.value, 'x', 0, 200, 0.01);
  f.add(uniforms.uWaveRate.value, 'y', 0, 200, 0.01);
  f.add(uniforms.uWaveRate.value, 'z', 0, 200, 0.01);
  f = gui.addFolder('uWaveAmp');
  f.add(uniforms.uWaveAmp.value, 'x', 0, 1, 0.001);
  f.add(uniforms.uWaveAmp.value, 'y', 0, 1, 0.001);
  f.add(uniforms.uWaveAmp.value, 'z', 0, 1, 0.001);
  f = gui.addFolder('uScale');
  f.add(uniforms.uScale.value, 'x', 0.01, 5, 0.01);
  f.add(uniforms.uScale.value, 'y', 0.01, 5, 0.01);
  gui.addFolder('uDeg').add(uniforms.uDeg, 'value', -180, 180, 0.01);
  gui.addFolder('uTimeRate').add(uniforms.uTimeRate, 'value', 0, 1, 0.01);
})();

const mesh = new THREE.Mesh(
  new THREE.PlaneGeometry(3, 3),
  new THREE.RawShaderMaterial({
    fragmentShader,
    vertexShader,
    uniforms,
  }),
);
scene.add(mesh);

const stats = new Stats();
document.body.appendChild(stats.domElement);

const onWindowResize = () => {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
}
window.addEventListener('resize', onWindowResize);
onWindowResize();

camera.position.set(0, 0, 8);
camera.lookAt(new THREE.Vector3());

const logElems = {
  geometries: document.querySelector('#log-geometries'),
  textures: document.querySelector('#log-textures'),
  drawCalls: document.querySelector('#log-draw-calls'),
  triangles: document.querySelector('#log-triangles'),
  points: document.querySelector('#log-points'),
  lines: document.querySelector('#log-lines'),
  program: document.querySelector('#log-program'),
};

const tick = (time) => {
  stats.begin();
    
  mesh.material.uniforms.uTime.value = time;
  controls.update();
  renderer.render(scene, camera);

  // 一旦わかりやすく手続き的に
  logElems.geometries.textContent = `geometries: ${renderer.info.memory.geometries}`;
  logElems.textures.textContent = `textures: ${renderer.info.memory.textures}`;
  logElems.drawCalls.textContent = `drawCalls: ${renderer.info.render.calls}`;
  logElems.triangles.textContent = `triangles: ${renderer.info.render.triangles}`;
  logElems.points.textContent = `points: ${renderer.info.render.points}`;
  logElems.lines.textContent = `lines: ${renderer.info.render.lines}`;
  
  stats.end();
  requestAnimationFrame(tick);
}
requestAnimationFrame(tick);



View Compiled
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/three.js/95/three.min.js
  2. https://threejs.org/examples/js/controls/OrbitControls.js
  3. https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.2/dat.gui.min.js
  4. https://cdnjs.cloudflare.com/ajax/libs/stats.js/r16/Stats.min.js