html5 canva实现粒子波浪滚动特效

<!doctype html>

粒子波
<style>
    html,
    body {
        height: 100%;
    }
    
    body {
        margin: 0;
        background: #000;
    }
    
    canvas {
        display: block;
    }
    
    .waves {
        position: absolute;
        left: 0;
        top: 0;
        right: 0;
        bottom: 0;
    }
</style>
<script>
    class ShaderProgram {

        constructor(holder, options = {}) {

            options = Object.assign({
                antialias: false,
                depthTest: false,
                mousemove: false,
                autosize: true,
                side: 'front',
                vertex: `
    precision highp float;

    attribute vec4 a_position;
    attribute vec4 a_color;

    uniform float u_time;
    uniform vec2 u_resolution;
    uniform vec2 u_mousemove;
    uniform mat4 u_projection;

    varying vec4 v_color;

    void main() {

      gl_Position = u_projection * a_position;
      gl_PointSize = (10.0 / gl_Position.w) * 100.0;

      v_color = a_color;

    }`,
                fragment: `
    precision highp float;

    uniform sampler2D u_texture;
    uniform int u_hasTexture;

    varying vec4 v_color;

    void main() {

      if ( u_hasTexture == 1 ) {

        gl_FragColor = v_color * texture2D(u_texture, gl_PointCoord);

      } else {

        gl_FragColor = v_color;

      }

    }`,
                uniforms: {},
                buffers: {},
                camera: {},
                texture: null,
                onUpdate: (() => {}),
                onResize: (() => {}),
            }, options)

            const uniforms = Object.assign({
                time: {
                    type: 'float',
                    value: 0
                },
                hasTexture: {
                    type: 'int',
                    value: 0
                },
                resolution: {
                    type: 'vec2',
                    value: [0, 0]
                },
                mousemove: {
                    type: 'vec2',
                    value: [0, 0]
                },
                projection: {
                    type: 'mat4',
                    value: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]
                },
            }, options.uniforms)

            const buffers = Object.assign({
                position: {
                    size: 3,
                    data: []
                },
                color: {
                    size: 4,
                    data: []
                },
            }, options.buffers)

            const camera = Object.assign({
                fov: 60,
                near: 1,
                far: 10000,
                aspect: 1,
                z: 100,
                perspective: true,
            }, options.camera)

            const canvas = document.createElement('canvas')
            const gl = canvas.getContext('webgl', {
                antialias: options.antialias
            })

            if (!gl) return false

            this.count = 0
            this.gl = gl
            this.canvas = canvas
            this.camera = camera
            this.holder = holder
            this.onUpdate = options.onUpdate
            this.onResize = options.onResize
            this.data = {}

            holder.appendChild(canvas)

            this.createProgram(options.vertex, options.fragment)

            this.createBuffers(buffers)
            this.createUniforms(uniforms)

            this.updateBuffers()
            this.updateUniforms()

            this.createTexture(options.texture)

            gl.enable(gl.BLEND)
            gl.enable(gl.CULL_FACE)
            gl.blendFunc(gl.SRC_ALPHA, gl.ONE)
            gl[options.depthTest ? 'enable' : 'disable'](gl.DEPTH_TEST)

            if (options.autosize)
                window.addEventListener('resize', e => this.resize(e), false)
            if (options.mousemove)
                window.addEventListener('mousemove', e => this.mousemove(e), false)

            this.resize()

            this.update = this.update.bind(this)
            this.time = {
                start: performance.now(),
                old: performance.now()
            }
            this.update()

        }

        mousemove(e) {

            let x = e.pageX / this.width * 2 - 1
            let y = e.pageY / this.height * 2 - 1

            this.uniforms.mousemove = [x, y]

        }

        resize(e) {

            const holder = this.holder
            const canvas = this.canvas
            const gl = this.gl

            const width = this.width = holder.offsetWidth
            const height = this.height = holder.offsetHeight
            const aspect = this.aspect = width / height
            const dpi = this.dpi = devicePixelRatio

            canvas.width = width * dpi
            canvas.height = height * dpi
            canvas.style.width = width + 'px'
            canvas.style.height = height + 'px'

            gl.viewport(0, 0, width * dpi, height * dpi)
            gl.clearColor(0, 0, 0, 0)

            this.uniforms.resolution = [width, height]
            this.uniforms.projection = this.setProjection(aspect)

            this.onResize(width, height, dpi)

        }

        setProjection(aspect) {

            const camera = this.camera

            if (camera.perspective) {

                camera.aspect = aspect

                const fovRad = camera.fov * (Math.PI / 180)
                const f = Math.tan(Math.PI * 0.5 - 0.5 * fovRad)
                const rangeInv = 1.0 / (camera.near - camera.far)

                const matrix = [
                    f / camera.aspect, 0, 0, 0,
                    0, f, 0, 0,
                    0, 0, (camera.near + camera.far) * rangeInv, -1,
                    0, 0, camera.near * camera.far * rangeInv * 2, 0
                ]

                matrix[14] += camera.z
                matrix[15] += camera.z

                return matrix

            } else {

                return [
                    2 / this.width, 0, 0, 0,
                    0, -2 / this.height, 0, 0,
                    0, 0, 1, 0, -1, 1, 0, 1,
                ]

            }

        }

        createShader(type, source) {

            const gl = this.gl
            const shader = gl.createShader(type)

            gl.shaderSource(shader, source)
            gl.compileShader(shader)

            if (gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {

                return shader

            } else {

                console.log(gl.getShaderInfoLog(shader))
                gl.deleteShader(shader)

            }

        }

        createProgram(vertex, fragment) {

            const gl = this.gl

            const vertexShader = this.createShader(gl.VERTEX_SHADER, vertex)
            const fragmentShader = this.createShader(gl.FRAGMENT_SHADER, fragment)

            const program = gl.createProgram()

            gl.attachShader(program, vertexShader)
            gl.attachShader(program, fragmentShader)
            gl.linkProgram(program)

            if (gl.getProgramParameter(program, gl.LINK_STATUS)) {

                gl.useProgram(program)
                this.program = program

            } else {

                console.log(gl.getProgramInfoLog(program))
                gl.deleteProgram(program)

            }

        }

        createUniforms(data) {

            const gl = this.gl
            const uniforms = this.data.uniforms = data
            const values = this.uniforms = {}

            Object.keys(uniforms).forEach(name => {

                const uniform = uniforms[name]

                uniform.location = gl.getUniformLocation(this.program, 'u_' + name)

                Object.defineProperty(values, name, {
                    set: value => {

                        uniforms[name].value = value
                        this.setUniform(name, value)

                    },
                    get: () => uniforms[name].value
                })

            })

        }

        setUniform(name, value) {

            const gl = this.gl
            const uniform = this.data.uniforms[name]

            uniform.value = value

            switch (uniform.type) {
                case 'int':
                    {
                        gl.uniform1i(uniform.location, value)
                        break
                    }
                case 'float':
                    {
                        gl.uniform1f(uniform.location, value)
                        break
                    }
                case 'vec2':
                    {
                        gl.uniform2f(uniform.location, ...value)
                        break
                    }
                case 'vec3':
                    {
                        gl.uniform3f(uniform.location, ...value)
                        break
                    }
                case 'vec4':
                    {
                        gl.uniform4f(uniform.location, ...value)
                        break
                    }
                case 'mat2':
                    {
                        gl.uniformMatrix2fv(uniform.location, false, value)
                        break
                    }
                case 'mat3':
                    {
                        gl.uniformMatrix3fv(uniform.location, false, value)
                        break
                    }
                case 'mat4':
                    {
                        gl.uniformMatrix4fv(uniform.location, false, value)
                        break
                    }
            }

            // ivec2       : uniform2i,
            // ivec3       : uniform3i,
            // ivec4       : uniform4i,
            // sampler2D   : uniform1i,
            // samplerCube : uniform1i,
            // bool        : uniform1i,
            // bvec2       : uniform2i,
            // bvec3       : uniform3i,
            // bvec4       : uniform4i,

        }

        updateUniforms() {

            const gl = this.gl
            const uniforms = this.data.uniforms

            Object.keys(uniforms).forEach(name => {

                const uniform = uniforms[name]

                this.uniforms[name] = uniform.value

            })

        }

        createBuffers(data) {

            const gl = this.gl
            const buffers = this.data.buffers = data
            const values = this.buffers = {}

            Object.keys(buffers).forEach(name => {

                const buffer = buffers[name]

                buffer.buffer = this.createBuffer('a_' + name, buffer.size)

                Object.defineProperty(values, name, {
                    set: data => {

                        buffers[name].data = data
                        this.setBuffer(name, data)

                        if (name == 'position')
                            this.count = buffers.position.data.length / 3

                    },
                    get: () => buffers[name].data
                })

            })

        }

        createBuffer(name, size) {

            const gl = this.gl
            const program = this.program

            const index = gl.getAttribLocation(program, name)
            const buffer = gl.createBuffer()

            gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
            gl.enableVertexAttribArray(index)
            gl.vertexAttribPointer(index, size, gl.FLOAT, false, 0, 0)

            return buffer

        }

        setBuffer(name, data) {

            const gl = this.gl
            const buffers = this.data.buffers

            if (name == null && !gl.bindBuffer(gl.ARRAY_BUFFER, null)) return

            gl.bindBuffer(gl.ARRAY_BUFFER, buffers[name].buffer)
            gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW)

        }

        updateBuffers() {

            const gl = this.gl
            const buffers = this.buffers

            Object.keys(buffers).forEach(name =>
                buffers[name] = buffer.data
            )

            this.setBuffer(null)

        }

        createTexture(src) {

            const gl = this.gl
            const texture = gl.createTexture()

            gl.bindTexture(gl.TEXTURE_2D, texture)
            gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([0, 0, 0, 0]))

            this.texture = texture

            if (src) {

                this.uniforms.hasTexture = 1
                this.loadTexture(src)

            }

        }

        loadTexture(src) {

            const gl = this.gl
            const texture = this.texture

            const textureImage = new Image()

            textureImage.onload = () => {

                gl.bindTexture(gl.TEXTURE_2D, texture)

                gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, textureImage)

                gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
                gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)

                gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
                gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)

                // gl.generateMipmap( gl.TEXTURE_2D )

            }

            textureImage.src = src

        }

        update() {

            const gl = this.gl

            const now = performance.now()
            const elapsed = (now - this.time.start) / 5000
            const delta = now - this.time.old
            this.time.old = now

            this.uniforms.time = elapsed

            if (this.count > 0) {
                gl.clear(gl.COLORBUFFERBIT)
                gl.drawArrays(gl.POINTS, 0, this.count)
            }

            this.onUpdate(delta)

            requestAnimationFrame(this.update)

        }

    }

    const pointSize = 2.5

    const waves = new ShaderProgram(document.querySelector('.waves'), {
        texture: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAb1BMVEUAAAD///8v0wLRAAAAJHRSTlMAC/goGvDhmwcExrVjWzrm29TRqqSKenRXVklANSIUE8mRkGpv+HOfAAABCElEQVQ4y4VT13LDMAwLrUHteO+R9f/fWMfO6dLaPeKVEECRxOULWsEGpS9nULDwia2Y+ALqUNbAWeg775zv+sA4/FFRMxt8U2FZFCVWjR/YrH4/H9sarclSKdPMWKzb8VsEeHB3m0shkhVCyNzeXeAQ9Xl4opEieX2QCGnwGbj6GMyjw9t1K0fK9YZunPXeAGsfJtYjwzxaBnozGGorYz0ypK2HzQSYx1y8DgSRo2ewOiyh2QWOEk1Y9OrQV0a8TiBM1a8eMHWYnRMy7CZ4t1CmyRkhSUvP3gRXyHOCLBxNoC3IJv//ZrJ/kxxUHPUB+6jJZZHrpg6GOjnqaOmzp4NDR48OLxn/H27SRQ08S0ZJAAAAAElFTkSuQmCC',
        uniforms: {
            size: {
                type: 'float',
                value: pointSize
            },
            field: {
                type: 'vec3',
                value: [0, 0, 0]
            },
            speed: {
                type: 'float',
                value: 5
            },
        },
        vertex: `
#define M_PI 3.1415926535897932384626433832795

precision highp float;

attribute vec4 a_position;
attribute vec4 a_color;

uniform float u_time;
uniform float u_size;
uniform float u_speed;
uniform vec3 u_field;
uniform mat4 u_projection;

varying vec4 v_color;

void main() {

  vec3 pos = a_position.xyz;

  pos.y += (
    cos(pos.x / u_field.x * M_PI * 8.0 + u_time * u_speed) +
    sin(pos.z / u_field.z * M_PI * 8.0 + u_time * u_speed)
  ) * u_field.y;

  gl_Position = u_projection * vec4( pos.xyz, a_position.w );
  gl_PointSize = ( u_size / gl_Position.w ) * 100.0;

  v_color = a_color;

}`,
        fragment: `
precision highp float;

uniform sampler2D u_texture;

varying vec4 v_color;

void main() {

  gl_FragColor = v_color * texture2D(u_texture, gl_PointCoord);

}`,
        onResize(w, h, dpi) {

            const position = [],
                color = []

            const width = 400 * (w / h)
            const depth = 400
            const height = 3
            const distance = 5

            for (let x = 0; x < width; x += distance) {
                for (let z = 0; z < depth; z += distance) {

                    position.push(-width / 2 + x, -30, -depth / 2 + z)
                    color.push(0, 1 - (x / width) * 1, 0.5 + x / width * 0.5, z / depth)

                }
            }

            this.uniforms.field = [width, height, depth]

            this.buffers.position = position
            this.buffers.color = color

            this.uniforms.size = (h / 400) * pointSize * dpi

        },
    })
</script>
效果图如下: ![`在这里插入图片描述`](https://img-blog.csdnimg.cn/500f421474254bad9885b84796da4baa.png#pic_center)
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值