前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >WebGL2 Shader实现的动态图形效果

WebGL2 Shader实现的动态图形效果

作者头像
全栈若城
发布2024-02-29 20:59:36
1250
发布2024-02-29 20:59:36
举报
文章被收录于专栏:若城技术专栏若城技术专栏

前言

本文将介绍如何使用WebGL2创建一个动态的图像效果,该效果基于一个经典的着色器。我们将使用JavaScript和GLSL编写代码,并通过使用顶点着色器和片段着色器将其传递给WebGL上下文。通过学习这个例子,你将了解一些基本的WebGL概念,如着色器编程、顶点缓冲区和Uniform变量。 在本文中,我们首先创建一个用于渲染的canvas元素并获取WebGL上下文。然后,我们设置一些基本的样式和初始化参数。接下来,我们编写顶点着色器和片段着色器的源代码,并将其编译为WebGL着色器对象。我们还创建了一个程序对象,并将顶点着色器和片段着色器附加到该程序对象上,并链接它们。 通过使用缓冲区对象,我们将顶点数据发送到顶点着色器中,并通过属性变量将其与顶点着色器关联起来。然后,我们设置一些Uniform变量,以便在渲染过程中传递给片段着色器。最后,我们使用requestAnimationFrame函数循环调用渲染函数,以达到动画效果。 我们还添加了鼠标和触摸事件监听器,以便在用户交互时更新鼠标坐标和触摸信息。这样,我们可以根据鼠标和触摸的位置和数量来改变片段着色器中的图像效果。

效果图

创建画布和WebGL上下文

    • 创建一个canvas元素,并赋值给变量canvas
    • 通过canvas.getContext("webgl2")获取WebGL上下文对象,并赋值给变量gl
代码语言:javascript
复制
const canvas = document.createElement("canvas")
const gl = canvas.getContext("webgl2")

设置页面标题和样式

    • 将页面标题设置为"??"
    • 清空页面的HTML内容
    • 将canvas元素添加到body中
    • 设置body的样式为"margin:0;touch-action:none;overflow:hidden;"
    • 设置canvas元素的样式,使其宽度为100%,高度自适应,并禁止用户选择内容
代码语言:javascript
复制
document.title = "??"
document.body.innerHTML = ""
document.body.appendChild(canvas)
document.body.style = "margin:0;touch-action:none;overflow:hidden;"
canvas.style.width = "100%"
canvas.style.height = "auto"
canvas.style.userSelect = "none"

定义设备像素比和窗口大小调整函数

    • 使用Math.max(1, .5*window.devicePixelRatio)计算设备像素比,并赋值给变量dpr
    • 定义名为resize的函数,用于在窗口大小变化时调整canvas的大小和视口
代码语言:javascript
复制
const dpr = Math.max(1, .5*window.devicePixelRatio)
function resize() {
  const {
    innerWidth: width,
    innerHeight: height
  } = window

  canvas.width = width * dpr
  canvas.height = height * dpr

  gl.viewport(0, 0, width * dpr, height * dpr)
}
window.onresize = resize

编写顶点着色器和片元着色器源代码

    • 定义顶点着色器源代码,使用ES 3.0版本编写
    • 定义片元着色器源代码,包含注释和一些uniform变量和函数
代码语言:javascript
复制
const vertexSource = `#version 300 es
    // 省略部分代码...
  `

const fragmentSource = `#version 300 es
    // 省略部分代码...
  `

编译着色器函数和创建程序

    • 定义名为compile的函数用于编译着色器源代码
    • 定义名为setup的函数,用于创建并链接程序对象,并将着色器附加到程序中
代码语言:javascript
复制
function compile(shader, source) {
  gl.shaderSource(shader, source)
  gl.compileShader(shader);

  if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
    console.error(gl.getShaderInfoLog(shader))
  }
}

let program

function setup() {
  const vs = gl.createShader(gl.VERTEX_SHADER)
  const fs = gl.createShader(gl.FRAGMENT_SHADER)

  compile(vs, vertexSource)
  compile(fs, fragmentSource)

  program = gl.createProgram()

  gl.attachShader(program, vs)
  gl.attachShader(program, fs)
  gl.linkProgram(program)

  if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
    console.error(gl.getProgramInfoLog(program))
  }
}

初始化顶点数据和缓冲区

    • 定义顶点坐标数组vertices,表示一个矩形的四个顶点坐标
    • 创建一个缓冲区对象,并将顶点数据存入缓冲区
代码语言:javascript
复制
let vertices, buffer

function init() {
  vertices = [
    -1., -1., 1.,
    -1., -1., 1.,
    -1., 1., 1.,
    -1., 1., 1.,
  ]

  buffer = gl.createBuffer()

  gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW)

  const position = gl.getAttribLocation(program, "position")

  gl.enableVertexAttribArray(position)
  gl.vertexAttribPointer(position, 2, gl.FLOAT, false, 0, 0)

  program.resolution = gl.getUniformLocation(program, "resolution")
  program.time = gl.getUniformLocation(program, "time")
  program.touch = gl.getUniformLocation(program, "touch")
  program.pointerCount = gl.getUniformLocation(program, "pointerCount")
}

定义鼠标事件处理对象和函数

    • 定义一个名为mouse的对象,包含鼠标的x、y坐标和触摸点的集合
    • 定义mouse对象的update方法,用于更新鼠标坐标和触摸点集合
    • 定义mouse对象的remove方法,用于移除触摸点
    • 监听窗口的鼠标事件,根据事件类型调用mouse对象的相应方法更新鼠标信息
    • 清空画布的颜色缓冲区
    • 使用程序对象进行渲染操作
    • 更新uniform变量的值
    • 绘制顶点数组
    • 调用setup函数创建程序对象并编译着色器
    • 调用init函数初始化顶点数据和缓冲区
    • 调用resize函数调整canvas的大小和视口
    • 调用loop函数开始渲染循环
    • 当鼠标按下时,调用mouse.update方法更新鼠标信息
    • 当鼠标抬起时,调用mouse.remove方法移除触摸点
    • 当鼠标移动时,如果鼠标的触摸点存在,则调用mouse.update方法更新鼠标信息
代码语言:javascript
复制
const mouse = {
  x: 0,
  y: 0,
  touches: new Set(),
  update: function(x, y, pointerId) {
    this.x = x * dpr;
    this.y = (innerHeight - y) * dpr;
    this.touches.add(pointerId)
  },
  remove: function(pointerId) { this.touches.delete(pointerId) }
}

function loop(now) {
  gl.clearColor(0, 0, 0, 1)
  gl.clear(gl.COLOR_BUFFER_BIT)
  gl.useProgram(program)
  gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
  gl.uniform2f(program.resolution, canvas.width, canvas.height)
  gl.uniform1f(program.time, now * 1e-3)
  gl.uniform2f(program.touch, mouse.x, mouse.y)
  gl.uniform1i(program.pointerCount, mouse.touches.size)
  gl.drawArrays(gl.TRIANGLES, 0, vertices.length * .5)
  requestAnimationFrame(loop)
}

setup()
init()
resize()
loop(0)

window.addEventListener("pointerdown", e => mouse.update(e.clientX, e.clientY, e.pointerId))
window.addEventListener("pointerup", e => mouse.remove(e.pointerId))
window.addEventListener("pointermove", e => {
  if (mouse.touches.has(e.pointerId))
    mouse.update(e.clientX, e.clientY, e.pointerId)
})

完整代码

代码语言:javascript
复制
对下面的代码进行分点分标题讲解说明, 并且代码与标题要对应 ;
const canvas = document.createElement("canvas")
const gl = canvas.getContext("webgl2")

document.title = "??"
document.body.innerHTML = ""
document.body.appendChild(canvas)
document.body.style = "margin:0;touch-action:none;overflow:hidden;"
canvas.style.width = "100%"
canvas.style.height = "auto"
canvas.style.userSelect = "none"

const dpr = Math.max(1, .5*window.devicePixelRatio)

function resize() {
  const {
    innerWidth: width,
    innerHeight: height
  } = window

  canvas.width = width * dpr
  canvas.height = height * dpr

  gl.viewport(0, 0, width * dpr, height * dpr)
}
window.onresize = resize

const vertexSource = `#version 300 es
    #ifdef GL_FRAGMENT_PRECISION_HIGH
    precision highp float;
    #else
    precision mediump float;
    #endif

    in vec4 position;

    void main(void) {
        gl_Position = position;
    }
    `

const fragmentSource = `#version 300 es
    /*********
     * made by Matthias Hurrle (@atzedent) 
     * 
     * Adaptation of "Quasar" by @kishimisu 
     * Source: https://www.shadertoy.com/view/msGyzc
     */
    #ifdef GL_FRAGMENT_PRECISION_HIGH
    precision highp float;
    #else
    precision mediump float;
    #endif

    out vec4 fragColor;

    uniform vec2 resolution;
    uniform float time;
    uniform vec2 touch;
    uniform int pointerCount;

    #define mouse (touch/resolution)
    #define P pointerCount
    #define T (10.+time*.5)
    #define S smoothstep

    #define hue(a) (.6+.6*cos(6.3*(a)+vec3(0,23,21)))

    mat2 rot(float a) {
        float c = cos(a), s = sin(a);

        return mat2(c, -s, s, c);
    }

    float orbit(vec2 p, float s) {
        return floor(atan(p.x, p.y)*s+.5)/s;
    }

    void cam(inout vec3 p) {
        if (P > 0) {
            p.yz *= rot(mouse.y*acos(-1.)+acos(.0));
            p.xz *= rot(-mouse.x*acos(-1.)*2.);
        }
    }

    void main(void) {
        vec2 uv = (
            gl_FragCoord.xy-.5*resolution
        )/min(resolution.x, resolution.y);

        vec3 col = vec3(0), p = vec3(0),
        rd = normalize(vec3(uv, 1));

        cam(p);
        cam(rd);

        const float steps = 30.;
        float dd = .0;

        for (float i=.0; i<steps; i++) {
            p.z  -= 4.;
            p.xz *= rot(T*.2);
            p.yz *= rot(sin(T*.2)*.5); 
            p.zx *= rot(orbit(p.zx, 12.));

            float a = p.x;

            p.yx *= rot(orbit(p.yx, 2.));

            float b = p.x-T;

            p.x = fract(b-.5)-.5;

            float d = length(p)-(a-S(b+.05,b,T)*30.)*(cos(T)*5e-2+1e-1)*1e-1;
            dd += d;

            col += (hue(dd)*.04)/(1.+abs(d)*40.);

            p = rd * dd;
        }
        fragColor = vec4(col, 1);
    }
    `

function compile(shader, source) {
  gl.shaderSource(shader, source)
  gl.compileShader(shader);

  if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
    console.error(gl.getShaderInfoLog(shader))
  }
}

let program

function setup() {
  const vs = gl.createShader(gl.VERTEX_SHADER)
  const fs = gl.createShader(gl.FRAGMENT_SHADER)

  compile(vs, vertexSource)
  compile(fs, fragmentSource)

  program = gl.createProgram()

  gl.attachShader(program, vs)
  gl.attachShader(program, fs)
  gl.linkProgram(program)

  if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
    console.error(gl.getProgramInfoLog(program))
  }
}

let vertices, buffer

function init() {
  vertices = [
    -1., -1., 1.,
    -1., -1., 1.,
    -1., 1., 1.,
    -1., 1., 1.,
  ]

  buffer = gl.createBuffer()

  gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW)

  const position = gl.getAttribLocation(program, "position")

  gl.enableVertexAttribArray(position)
  gl.vertexAttribPointer(position, 2, gl.FLOAT, false, 0, 0)

  program.resolution = gl.getUniformLocation(program, "resolution")
  program.time = gl.getUniformLocation(program, "time")
  program.touch = gl.getUniformLocation(program, "touch")
  program.pointerCount = gl.getUniformLocation(program, "pointerCount")
}

const mouse = {
  x: 0,
  y: 0,
  touches: new Set(),
  update: function(x, y, pointerId) {
    this.x = x * dpr;
    this.y = (innerHeight - y) * dpr;
    this.touches.add(pointerId)
  },
  remove: function(pointerId) { this.touches.delete(pointerId) }
}

function loop(now) {
  gl.clearColor(0, 0, 0, 1)
  gl.clear(gl.COLOR_BUFFER_BIT)
  gl.useProgram(program)
  gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
  gl.uniform2f(program.resolution, canvas.width, canvas.height)
  gl.uniform1f(program.time, now * 1e-3)
  gl.uniform2f(program.touch, mouse.x, mouse.y)
  gl.uniform1i(program.pointerCount, mouse.touches.size)
  gl.drawArrays(gl.TRIANGLES, 0, vertices.length * .5)
  requestAnimationFrame(loop)
}

setup()
init()
resize()
loop(0)

window.addEventListener("pointerdown", e => mouse.update(e.clientX, e.clientY, e.pointerId))
window.addEventListener("pointerup", e => mouse.remove(e.pointerId))
window.addEventListener("pointermove", e => {
  if (mouse.touches.has(e.pointerId))
    mouse.update(e.clientX, e.clientY, e.pointerId)
})

总结

css 动画效果是最适合小白学习的案例, 希望本文可以给各位带来帮助哦!! 关注若城, 带你遨游代码的海洋!!

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023-10-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 效果图
  • 创建画布和WebGL上下文
  • 设置页面标题和样式
  • 定义设备像素比和窗口大小调整函数
  • 编写顶点着色器和片元着色器源代码
  • 编译着色器函数和创建程序
  • 初始化顶点数据和缓冲区
  • 定义鼠标事件处理对象和函数
  • 完整代码
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档