1

I just wrote a program that implements the waving effect in canvas2D. Here is my implementation:
1. Save the image data of the origin canvas.
2. Calculate the new position of each point and give it the color of the old position.
3. Make the vibration amplitude of each point increase with distance.
4. Increasing the brightness of the monotone decreasing area, and decreasing the brightness of the monotone decreasing area.

var IMG_MAX_WIDTH = 600
var IMG_MAX_HEIGHT = 600
var imgWidth, imgHeight
var oImgData, imgData
var oPixels, pixels
var ctx, canvasWidth, canvasHeight

var image = new Image()
image.crossOrigin = 'anonymous'
image.src = 'https://i.imgur.com/ZKMnXce.png'

var amplitude = 15
var period = 2
var coX

image.onload = function () {

imgWidth = Math.floor(image.width)
imgHeight = Math.floor(image.height)

var canvas = document.getElementById('flagCanvas')
var scale = 1
if (imgWidth > IMG_MAX_WIDTH) {
  scale = IMG_MAX_WIDTH / imgWidth
}
if (imgHeight > IMG_MAX_HEIGHT) {
  scale = scale * IMG_MAX_HEIGHT / imgHeight
}

canvasWidth = imgWidth
canvasHeight = imgHeight + amplitude * 2
canvas.width = canvasWidth
canvas.height = canvasHeight
canvas.style.transform = 'translate3d(-50%,-50%,0) scale(' + scale + ')'

// offscreenCtx = offscreenCanvas.getContext('2d')
ctx = canvas.getContext('2d')
ctx.drawImage(image, 0, amplitude, imgWidth, imgHeight)
imgData = ctx.getImageData(0, 0, canvasWidth, canvasHeight)
pixels = imgData.data

oImgData = ctx.createImageData(canvasWidth, canvasHeight)
oPixels = pixels.slice()
oImgData.data = oPixels

coX = 2 * Math.PI / (imgWidth / period)

tick()
}

var stop = false
var timeNow = Date.now()
var timeLast = timeNow
var delta = 0
var interval
var fps = 70

var offset = 10

interval = 1000 / fps

var tick = function () {
if (stop) return false
timeNow = Date.now()
delta = timeNow - timeLast
if (delta > interval) {
  timeLast = timeNow

  ctx.clearRect(0, 0, canvasWidth, canvasHeight)
  var y0 = amplitude * (1 / imgWidth) * Math.sin(timeNow / 200)
  var yBuf = new Array(canvasWidth)
  var lastY = 0
  var r
  var g
  var b
  var a
  var oR
  var oG
  var oB
  var oA
  for (var i = 0; i < canvasHeight; i++) {
    for (var j = 0; j < canvasWidth; j++) {
      if (i === 0) {
        yBuf[j] = amplitude * (j / imgWidth) * Math.sin(j * coX - timeNow / 200) + y0
      }
      r = (i * canvasWidth + j) * 4
      g = r + 1
      b = r + 2
      a = r + 3
      oR = r + (~~(0.5 + yBuf[j])) * canvasWidth * 4
      oG = oR + 1
      oB = oR + 2
      oA = oR + 3
      offset = j === 0 ? 0 : (yBuf[j] - lastY) * 100
      pixels[r] = oPixels[oR] + offset
      pixels[g] = oPixels[oG] + offset
      pixels[b] = oPixels[oB] + offset
      pixels[a] = oPixels[oA]
      lastY = yBuf[j]
    }
  }
  ctx.putImageData(imgData, 0, 0)
}
requestAnimationFrame(tick)
}
* {
    margin: 0;
    padding: 0;
}

html, body {
    width: 100%;
    height: 100%;
}

body {
    position: relative;
    background: lightgrey;
}

#flagCanvas {
    position: absolute;
    top: 50%;
    left: 50%;
    transform-origin: center;
    transform: translate3d(-50%, -50%, 0);
}
<canvas id="flagCanvas"></canvas>

While after that done, I found that the performance is poor.

So I tried to rewrite it by webGL, hope that would help.

Now I can use webGL to create a simple cube,make some transformation,load a texture into a rect.

Can anyone provide an idea ? Best to show some core js or glsl code ..

thx.

+++++++++++++++update++++++++++++++++++

thx for help. I succeeded! https://codepen.io/oj8kay/pen/PBZjpe

  • So... where is the webgl code? – Jeroen van Langen Jul 06 '18 at 12:05
  • If you want your canvas version to be fast consider just using `drawImage`. It takes both a source and dest rectangle. There's no reason to use slow `getImageData` and `putImageData`. Showing you how to do it in WebGL is arguably too big a topic. [Consider reading some tutorials](https://webglfundamentals.org). – gman Jul 06 '18 at 15:22
  • @gman But I want to simulate reflective effect, so I need to change the color of each pixel which `drawImage` can't do.You can think that I can understand webgl and give me some idea or code,I will study it. – Smart Coder Jul 06 '18 at 16:24
  • You can change the color by drawing over each column with `fillRect` with an alpha < 1. As for studying WebGL there's 1000s of examples all over the net including in the link I already posted. If you can't be bothered to go read them you can't expect people to help you. SO isn't "write my code for me" site it's an "I read all the stuff and tried all these things and I'm having this one small issue" site. Teaching you webgl is not "one small issue" – gman Jul 06 '18 at 17:03
  • You could use some of the [techniques covered here](https://stackoverflow.com/questions/44372487/webgl-drawing-2d-image-with-depth-map-to-achieve-pseudo-3d-effect) to make a waving flag similar to yours above. – gman Jul 07 '18 at 14:02
  • @gman Thanks for your advice.I am studying these articles. – Smart Coder Jul 14 '18 at 19:23

1 Answers1

1

You can achieve a "wave" effect in WebGL by delegating all the computing to the GPU. To do this, you should divide a square (or rectangle) in a triangle strip like in the following schema:

1  3  5  7
| /| /| /|
|/ |/ |/ |
2  4  6  8

Put the vertex 1 at coords (0,0), the vertex 2 at (0,1), the vertex 7 at (1,0) and the vertex 8 at (1,1). You can easily deduce the coords for the rest of the vertices. These coords are the UV used in the texture to display your image. Moreover, you can use these coords to place the vertex on the screen. To get a "wave" effect, you can move up and down the vertices based on the time.

The vertex shader will be executed for every vertex and in parallel. Its code can be very simple. Here is an example:

// The time is the only variable value.
uniform float uniTime;
// Coords of the current vertex.
attribute vec2 attCoords;
// Propagate UV coords to the fragment shader
varying vec2 varUV;   

void main() {
  // Propagate UV coords to the fragment shader
  varUV = attCoords;
  // Separate X and Y coordinates.
  float x = attCoords.x;
  float y = attCoords.y;
  // Compute the vertical shift of the vertex according to the time.
  float h = cos( uniTime + x * 10.0 );
  // The following line is not mandatory, but it make the move look
  // more random.
  h += cos( x * 3.0 - uniTime * 0.1751 );
  // Define the size of the wave and make it steady on the left
  // to simulate a fixing point on a stick.
  y += h * x * 0.2;
  // In WebGL, the visble space is between -1 and +1 in each direction.
  gl_Position = vec4( 2.0 * x - 1.0, 0.5 - y, 0.0, 1.0 );
}

You can see a live example here: https://jsfiddle.net/63sj1rpk/53/

Tolokoban
  • 2,297
  • 14
  • 17