分享webgl魔幻星球

本文介绍了WebGL技术如何在网页上实现实时三维图形渲染,包括WebGL与canvas2d的区别,以及顶点着色器和片元着色器的作用。着重讲解了GLSLES语言在WebGL中的重要性,通过实例展示了如何编写和使用着色器来创建动态效果。
摘要由CSDN通过智能技术生成

前言

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 Shader(GLSL ES)着色器(Shader)是用来实现图像渲染的,用来替代固定渲染管线的可编辑程序。webgl 的着色器语言是GLSL ES语言,GLSL 代表 openGL Shading Language,它是着色器程序的特定标准,webgl实际是opengl的裁剪版,webgl底层还是调用的opengl。所以webgl shader本质上还是GLSL语言。

OpenGL 全称为 Open Graphics Library(开放图形库)。 OpenGL ES 全称为 OpenGL for Embedded Systems(嵌入式系统开放图形库)。 GLSL 全称为 OpenGL Shading Language(OpenGL 着色语言)。 GLSL ES 全称为 OpenGL ES Shading Language(OpenGL ES 着色语言)同WebGL着色语言。

webgl 绘图需要两种着色器:

顶点着色器(Vertex Shader):描述顶点的特征,如位置、颜色等,主要负责顶点的几何关系等的运算。是对顶点进行一系列操作的着色器。顶点除了有最基本的位置属性,还可能包含很多其他属性,比如纹理,法线等等。通过顶点着色器,显卡就知道顶点应该绘制在具体什么位置。顶点着色器是非常重要的着色器,且必须要我们自己去定义。 顶点着色器作用于每个顶点,可以生成每个顶点的最终位置。针对每个顶点,它都会执行一次,一旦每个顶点的最终位置确定了,GPU就可以把这些可见顶点的集合组装成点、直线以及三角形,从而提高渲染场景和模型的速度。

片元着色器(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插件

webgl shader中级

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

:MNongSciFans

抛铜币以舒赞同,解兜囊以现支持

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值