分享webgl魔幻星球

前言

webgl 是在网页上绘制和渲染三维图形的技术,可以让用户与其进行交互。div+css、canvas 2d 专注于二维图形。对公司而言,webgl 可以解决他们在三维模型的显示和交互上的问题;对开发者而言,webgl 可以让我们是实现更多、更炫酷的效果,让我们即使工作,也可以乐在其中,并且还会有一份不错的薪资。

色相偏移动画

!(function animation() {
    color.offsetHSL(0.005, 0, 0);
    gl.clearColor(color.r, color.g, color.b, 1);
    gl.clear(gl.COLOR_BUFFER_BIT);
    requestAnimationFrame(animation);
})();

webgl画布和着色器

webgl画布的建立和获取,和canvas 2d是一样的。一旦我们使用canvas.getContext()方法获取了webgl 类型的上下文对象,那这张画布就不再是以前的canvas 2d 画布。

canvas 2d 画布和webgl 画布使用的坐标系都是二维直角坐标系,只不过它们坐标原点、y 轴的坐标方向,坐标基底都不一样了。

canvas 2d 坐标系的原点在左上角。

canvas 2d 坐标系的y 轴方向是朝下的。

canvas 2d 坐标系的坐标基底有两个分量,分别是一个像素的宽和一个像素的高,即1个单位的宽便是1个像素的宽,1个单位的高便是一个像素的高。

webgl坐标系的坐标原点在画布中心。

webgl坐标系的y 轴方向是朝上的。

webgl坐标基底中的两个分量分别是半个canvas的宽和canvas的高,即1个单位的宽便是半个个canvas的宽,1个单位的高便是半个canvas的高。

绘图逻辑

webgl 的绘图逻辑和canvas 2d 的绘图逻辑有一个本质的差别。

浏览器有三大线程: js 引擎线程、GUI 渲染线程、浏览器事件触发线程。

其中GUI 渲染线程就是用于渲图的,在这个渲染线程里,有负责不同渲染工作的工人。比如有负责渲染HTML+css的工人,有负责渲染二维图形的工人,有负责渲染三维图形的工人。

渲染二维图形的工人和渲染三维图形的工人不是一个国家的,他们说的语言不一样。

渲染二维图形的工人说的是js语言。

渲染三维图形的工人说的是GLSL ES 语言。

而我们在做web项目时,业务逻辑、交互操作都是用js 写的。

我们在用js 绘制canvas 2d 图形的时候,渲染二维图形的工人认识js 语言,所以它可以正常渲图。

但我们在用js 绘制webgl图形时,渲染三维图形的工人就不认识这个js 语言了,因为它只认识GLSL ES 语言。

手绘板 - 程序对象

就像手绘板,在webgl 里叫“程序对象”。手绘板 - 程序对象,承载GLSL ES语言,翻译GLSL ES语言和js语言,使两者可以相互通信。

着色器shader

webgl 的着色器语言是GLSL ES语言,webgl 绘图需要两种着色器:

顶点着色器(Vertex shader):描述顶点的特征,如位置、颜色等。

片元着色器(Fragment shader):进行逐片元处理,如光照。

//顶点着色器
<script id="vertexShader" type="x-shader/x-vertex">
    void main() {
        gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
        gl_PointSize = 100.0;
    }
</script>
//片元着色器
<script id="fragmentShader" type="x-shader/x-fragment">
    void main() {
        gl_FragColor = vec4(1.0, 1.0, 0.0, 1.0);
    }
</script>

在js中获取顶点着色器和片元着色器的文本

const vsSource = document.getElementById('vertexShader').innerText;
const fsSource = document.getElementById('fragmentShader').innerText;

初始化着色器

initShaders(gl, vsSource, fsSource);

指定将要用来清空绘图区的颜色

gl.clearColor(0,0,0,1);

使用之前指定的颜色,清空绘图区

gl.clear(gl.COLOR_BUFFER_BIT);

绘制顶点

gl.drawArrays(gl.POINTS, 0, 1);

完整代码

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)
})

 渲染效果截图

d87235398463404eac2b25e28face0f5.png

参见:

jQuery插件库-收集最全最新最好的jQuery插件

相关推荐

  1. C语言每日一题—矩阵

    2024-04-08 03:58:02       43 阅读
  2. 前端汪的逆袭:从Excel表格到网页

    2024-04-08 03:58:02       36 阅读
  3. <span style='color:red;'>WebGIS</span>

    WebGIS

    2024-04-08 03:58:02      31 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-04-08 03:58:02       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-04-08 03:58:02       100 阅读
  3. 在Django里面运行非项目文件

    2024-04-08 03:58:02       82 阅读
  4. Python语言-面向对象

    2024-04-08 03:58:02       91 阅读

热门阅读

  1. 出国留学哪个国家好又便宜

    2024-04-08 03:58:02       122 阅读
  2. Redis

    Redis

    2024-04-08 03:58:02      38 阅读
  3. MySQL CASE 语句

    2024-04-08 03:58:02       40 阅读
  4. 机器学习入门实践-鸢尾花分类

    2024-04-08 03:58:02       36 阅读
  5. 2022大厂嵌入式系统面试题必看

    2024-04-08 03:58:02       37 阅读
  6. sql之日期函数

    2024-04-08 03:58:02       33 阅读