Appearance
粒子旋转拖尾
点击运行
<template>
<div>
<div @click="onTrigger" class="pointer">点击{{ !isRunning ? '运行' : '关闭' }}</div>
<canvas v-if="isRunning" id="rotatingTail" class="stage"></canvas>
</div>
</template>
<script lang="ts" setup>
import { ref, nextTick, onUnmounted } from 'vue'
let animationFrame
let sceneResources
const isRunning = ref(false)
const onTrigger = async () => {
if (!isRunning.value) {
isRunning.value = true
await nextTick()
sceneResources = initScene()
} else {
isRunning.value = false
destroy()
}
}
const initScene = () => {
const c = document.getElementById('rotatingTail') as any
const gl: any = c.getContext('webgl', {
preserveDrawingBuffer: true
})
let width = c.width
let height = c.height
const webgl: any = {}
webgl.vertexShaderSource = `
attribute vec2 a_position;
attribute vec2 a_color;
uniform float u_tick;
uniform vec2 u_resolution;
varying vec2 v_color;
void main() {
gl_Position = vec4(vec2(1, -1) * (a_position / u_resolution) * 2.0, 0, 1);
v_color = a_color;
if (a_color.x > 0.0)
v_color = vec2(a_color.x + u_tick / 100.0, a_color.y);
}
`
webgl.fragmentShaderSource = `
precision mediump float;
varying vec2 v_color;
vec3 h2rgb(float h) {
return clamp(abs(mod(h * 6.0 + vec3(0, 4, 2), 6.0) - 3.0) -1.0, 0.0, 1.0);
}
void main() {
vec4 color = vec4(0, 0, 0, v_color.y);
if (v_color.x > 0.0) {
color.rgb = h2rgb(v_color.x / 5.0);
}
gl_FragColor = color;
}
`
webgl.vertexShader = gl.createShader(gl.VERTEX_SHADER)
gl.shaderSource(webgl.vertexShader, webgl.vertexShaderSource)
gl.compileShader(webgl.vertexShader)
webgl.fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)
gl.shaderSource(webgl.fragmentShader, webgl.fragmentShaderSource)
gl.compileShader(webgl.fragmentShader)
webgl.shaderProgram = gl.createProgram()
gl.attachShader(webgl.shaderProgram, webgl.vertexShader)
gl.attachShader(webgl.shaderProgram, webgl.fragmentShader)
gl.linkProgram(webgl.shaderProgram)
gl.useProgram(webgl.shaderProgram)
webgl.attribLocs = {
// 从之前创建的 GLSL 着色程序中找到这个属性值所在的位置a_position
position: gl.getAttribLocation(webgl.shaderProgram, 'a_position'),
color: gl.getAttribLocation(webgl.shaderProgram, 'a_color')
}
webgl.buffers = {
position: gl.createBuffer(),
color: gl.createBuffer()
}
webgl.uniformLocs = {
tick: gl.getUniformLocation(webgl.shaderProgram, 'u_tick'),
resolution: gl.getUniformLocation(webgl.shaderProgram, 'u_resolution')
}
// 需要告诉 WebGL 怎么从之前准备的缓冲中获取数据给着色器中的属性。
// 1、首先需要启用对应属性
gl.enableVertexAttribArray(webgl.attribLocs.position)
gl.enableVertexAttribArray(webgl.attribLocs.color)
// 2、指定从缓冲中读取数据的方式
// gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer)
gl.bindBuffer(gl.ARRAY_BUFFER, webgl.buffers.position)
// 一个隐藏信息是 gl.vertexAttribPointer 是将属性绑定到当前的 ARRAY_BUFFER。
// 换句话说就是属性绑定到了 positionBuffer 上。
// 意味着现在利用绑定点将 ARRAY_BUFFER 绑定到其它数据上后,该属性依然从 positionBuffer 上读取数据。
// gl.vertexAttribPointer(positionAttributeLocation, size, type, normalize, stride, offset)
// var positionAttributeLocation = webgl.attribLocs.position
// var size = 2 每次迭代运行提取两个单位数据
// var type = gl.FLOAT 每个单位的数据类型是32位浮点型
// var normalize = false 不需要归一化数据
// var stride = 0 0 = 移动单位数量 * 每个单位占用内存(sizeof(type)),每次迭代运行运动多少内存到下一个数据开始点
// var offset = 0 从缓冲起始位置开始读取
gl.vertexAttribPointer(
webgl.attribLocs.position,
2,
gl.FLOAT,
false,
0,
0
)
gl.bindBuffer(gl.ARRAY_BUFFER, webgl.buffers.color)
gl.vertexAttribPointer(webgl.attribLocs.color, 2, gl.FLOAT, false, 0, 0)
gl.enable(gl.BLEND)
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
// 需要告诉 WebGL 怎样把提供的 gl_Position 裁剪空间坐标对应到画布像素坐标。
// 通常也把画布像素坐标叫做屏幕空间。
// 为了实现这个目的,只需要调用 gl.viewport 方法并传递画布的当前尺寸。
gl.viewport(0, 0, width, height)
// 设置全局变量 分辨率
gl.uniform2f(webgl.uniformLocs.resolution, width, height)
webgl.data = {
position: [],
color: []
}
webgl.draw = function (glType, glAmount) {
// 绑定位置信息缓冲
gl.bindBuffer(gl.ARRAY_BUFFER, webgl.buffers.position)
// 通过绑定点向缓冲中存放数据
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array(webgl.data.position),
gl.STATIC_DRAW
)
gl.bindBuffer(gl.ARRAY_BUFFER, webgl.buffers.color)
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array(webgl.data.color),
gl.STATIC_DRAW
)
// 绘制
// var primitiveType = gl.TRIANGLES或者其他的gl形状;
// var offset = 0;
// var count = 3;
// 因为 count = 3,所以顶点着色器将运行三次。
// 第一次运行将会从位置缓冲中读取前两个值赋给属性值 a_position.x 和 a_position.y。
// 第二次运行 a_position.xy 将会被赋予后两个值,最后一次运行将被赋予最后两个值。
// var count = 6;
// 显然需要告诉 WebGL 要运行六次顶点着色器来画两个三角形,所以将 count 改成 6。
// 此时的各个顶点为var positions = [10, 20, 80, 20, 10, 30, 10, 30, 80, 20, 80, 30]
// gl.drawArrays(primitiveType, offset, count)
gl.drawArrays(glType, 0, glAmount)
}
webgl.clear = function (z) {
const a = width / 2,
A = -a,
b = height / 2,
B = -b
// 将位置信息转换为像素坐标,通过绘制两个三角形来绘制一个矩形,每个三角形有三个点
webgl.data.position = [A, B, a, B, A, b, A, b, a, b, a, B]
webgl.data.color = [-1, z, -1, z, -1, z, -1, z, -1, z, -1, z]
webgl.draw(gl.TRIANGLES, 6)
webgl.data.color.length = webgl.data.position.length = 0
}
const particles: any = []
let tick = 0
const opts = {
baseW: 0.01,
addedW: 0.03
}
function Particle(radius, radian) {
this.radius = radius
this.radian = radian + 6.2831853071
this.x = this.radius * Math.cos(this.radian)
this.y = this.radius * Math.sin(this.radian)
this.w = opts.baseW + opts.addedW * Math.random()
this.wcos = Math.cos(this.w)
this.wsin = Math.sin(this.w)
}
Particle.prototype.step = function () {
if (Math.random() < 0.1) {
this.w += (Math.random() - 0.5) / 100000
this.wcos = Math.cos(this.w)
this.wsin = Math.sin(this.w)
}
const px = this.x,
py = this.y,
pr = this.radian
this.x = this.x * this.wcos - this.y * this.wsin
this.y = px * this.wsin + this.y * this.wcos
this.radian += this.w
webgl.data.position.push(px, py, this.x, this.y, 0, 0, this.x, this.y)
webgl.data.color.push(
pr,
1,
this.radian,
1,
this.radian,
0.01,
this.radian,
0.04
)
}
function anim() {
animationFrame = requestAnimationFrame(anim)
if (!animationFrame) return
webgl.clear(0.1)
if (particles.length < 1000) {
for (let i = 0; i < 3; ++i) {
particles.push(
new Particle(
Math.random() * Math.min(width, height),
Math.random() * 6.283185307179586476925286766559
)
)
}
}
particles.map(function (particle) {
particle.step()
})
++tick
gl.uniform1f(webgl.uniformLocs.tick, tick)
webgl.draw(gl.LINES, webgl.data.position.length / 2)
webgl.data.position.length = 0
webgl.data.color.length = 0
}
anim()
return {
gl
}
}
const destroy = () => {
cancelAnimationFrame(animationFrame)
animationFrame = null
}
onUnmounted(() => {
if (sceneResources) {
sceneResources.gl && sceneResources.gl.getExtension("WEBGL_lose_context").loseContext()
}
destroy()
})
</script>
<style scoped>
canvas {
background-color: #111;
}
</style>
粒子波浪
点击运行
<template>
<div>
<div @click="onTrigger" class="pointer">点击{{ !isRunning ? '运行' : '关闭' }}</div>
<canvas v-if="isRunning" id="pointsWave" class="stage"></canvas>
</div>
</template>
<script lang="ts" setup>
import { ref, nextTick, onUnmounted } from 'vue'
let animationFrame
let sceneResources
const isRunning = ref(false)
const onTrigger = async () => {
if (!isRunning.value) {
isRunning.value = true
await nextTick()
sceneResources = initScene()
} else {
isRunning.value = false
destroy()
}
}
const initScene = () => {
let canvas = document.getElementById('pointsWave') as any
let gl: any = canvas.getContext('webgl') || canvas.getContext('experimental-webgl')
let w = canvas.width
let h = canvas.height
let num = 100
initWebGl()
window.addEventListener('resize', function () {
w = canvas.width = window.innerWidth
h = canvas.height = window.innerHeight
})
// attribute声明vec4类型变量a_Position
// precision mediump float; 表示片元着色器中所有浮点型精度为中精度
// uniform vec4类型的color变量,并设置为片元着色器的输出
function initWebGl() {
const vs_source = `
attribute vec4 a_Position;
void main() {
gl_Position = a_Position;
gl_PointSize = 3.0;
}
`
// 绘制圆
// 距离大于0.5都舍去
const fs_source = `
#ifdef GL_ES
precision mediump float;
#endif
uniform vec4 color;
void main() {
float d = distance(gl_PointCoord, vec2(0.5, 0.5));
if (d < 0.5) {
gl_FragColor = vec4(0.2, 0.3, 0.5, 1.0);
} else {
discard;
}
}
`
// 创建顶点着色器
let sShader = gl.createShader(gl.VERTEX_SHADER)
// 创建片段着色器
let fShader = gl.createShader(gl.FRAGMENT_SHADER)
// 创建一个着色器程序
let glprogam = gl.createProgram()
// 绑定资源
gl.shaderSource(sShader, vs_source)
gl.shaderSource(fShader, fs_source)
// 编译着色器
gl.compileShader(sShader)
gl.compileShader(fShader)
gl.attachShader(glprogam, sShader) // 把顶点着色器添加到着色器程序
gl.attachShader(glprogam, fShader) // 把片元着色器添加到着色器程序
// 链接程序,在链接操作执行以后,可以任意修改 shader 的源代码,对 shader 重新编译
// 不会影响整个程序,除非重新链接程序
gl.linkProgram(glprogam)
// 加载并使用链接好的程序
gl.useProgram(glprogam)
gl.program = glprogam
render()
}
function render() {
animationFrame = requestAnimationFrame(render)
if (!animationFrame) return
num = num - 2
let pointdata = createPointData()
setPointType(pointdata.data, pointdata.nums)
}
function createPointData() {
let max = 10
let number = 100
let tier = 10
let arr = []
let degs = function (deg) {
return (Math.PI * deg) / 180
}
for (let i = 0; i < number; i++) {
for (let j = 0; j < tier; j++) {
let gap = i * 7 - j * 20 + num
let x = webX(-(w / 2) - 280 + i * ((w + 300) / number) + j * 20)
let y = webY(
-(h / 2) + Math.sin(degs(gap)) * (max + j * 10) + j * 20
)
let z = -1
let arrtwo: any = [x, y, z]
arr = arr.concat(arrtwo)
}
}
return {
data: new Float32Array(arr),
nums: number * tier
}
}
function setPointType(data, num) {
// 1.创建缓冲区对象
let buffer = gl.createBuffer()
if (!buffer) {
console.log('缓冲区创建失败')
return -1
}
// 2.绑定缓冲区对象
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
// 3.向缓冲区对象中写入数据
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)
// 4.获取存储位置变量
let aPosition = gl.getAttribLocation(gl.program, 'a_Position')
// 5.把缓冲区对象分配给a_Position变量
gl.vertexAttribPointer(aPosition, 3, gl.FLOAT, false, 0, 0)
// 连接缓冲区对象和a_Position 变量
gl.enableVertexAttribArray(aPosition)
//用設置的顔色清空畫布
gl.clearColor(0.1, 0.7, 0.8, 1.0)
gl.clear(gl.COLOR_BUFFER_BIT)
//繪製畫布内容
gl.drawArrays(gl.POINTS, 0, num)
}
//X軸
function webX(n) {
return n / (w / 2)
}
//Y軸
function webY(n) {
return n / (h / 2)
}
return {
gl
}
}
const destroy = () => {
cancelAnimationFrame(animationFrame)
animationFrame = null
}
onUnmounted(() => {
if (sceneResources) {
sceneResources.gl && sceneResources.gl.getExtension("WEBGL_lose_context").loseContext()
}
destroy()
})
</script>
图片合并
点击运行
<template>
<div>
<div @click="onTrigger" class="pointer">点击{{ !isRunning ? '运行' : '关闭' }}</div>
<canvas v-if="isRunning" id="mergeImage" class="stage"></canvas>
</div>
</template>
<script lang="ts" setup>
import { ref, nextTick, onUnmounted } from 'vue'
let animationFrame
let sceneResources
const vertexSource = `
precision mediump float;
attribute vec4 a_Position;
attribute vec2 a_TexCoord;
varying vec2 v_TexCoord;
void main() {
gl_Position = a_Position; // 顶点坐标
v_TexCoord = a_TexCoord; // 纹理坐标系下的坐标
}
`
const fragmentSource = `
precision mediump float;
uniform sampler2D u_Sampler0; // 纹理
uniform sampler2D u_Sampler1; // 纹理
varying vec2 v_TexCoord; // 纹理坐标系下的坐标
void main() {
vec4 color0 = texture2D(u_Sampler0, v_TexCoord);
vec4 color1 = texture2D(u_Sampler1, v_TexCoord);
gl_FragColor = color0 * color1;
}
`
const isRunning = ref(false)
const onTrigger = async () => {
if (!isRunning.value) {
isRunning.value = true
await nextTick()
sceneResources = initScene()
} else {
isRunning.value = false
destroy()
}
}
const initScene = () => {
const canvas: any = document.getElementById('mergeImage')
const gl = canvas.getContext('webgl')
// 创建顶点着色器
const vertexShader = gl.createShader(gl.VERTEX_SHADER)
// 给顶点着色器赋值
gl.shaderSource(vertexShader, vertexSource)
// 编译顶点着色器
gl.compileShader(vertexShader)
// 创建片元着色器
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)
// 给片元着色器赋值
gl.shaderSource(fragmentShader, fragmentSource)
// 编译片元着色器
gl.compileShader(fragmentShader)
// 检测着色器创建是否正确
if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
alert(gl.getShaderInfoLog(fragmentShader))
}
// 创建程序
const program = gl.createProgram()
// 给程序赋值
gl.attachShader(program, vertexShader)
gl.attachShader(program, fragmentShader)
// 连接程序
gl.linkProgram(program)
// 使用此着色器
gl.useProgram(program)
// 变量的处理
// 获取顶点着色器中的变量a_Position
const a_Position = gl.getAttribLocation(program, 'a_Position')
// 获取顶点着色器中的变量a_TexCoord
const a_TexCoord = gl.getAttribLocation(program, 'a_TexCoord')
// 获取片元着色器中的变量u_Sampler
const u_Sampler0 = gl.getUniformLocation(program, 'u_Sampler0')
// 获取片元着色器中的变量u_Sampler
const u_Sampler1 = gl.getUniformLocation(program, 'u_Sampler1')
// 顶点坐标与纹理坐标
// -0.5, 0.5, 0.0, 1.0:
// U坐标 -0.5 和 V坐标 0.5 表示这个顶点位于平面的左上角。纹理图像的左边缘(U=0)和上边缘(V=1)被映射到这里。
// 最后两个数字 0.0 和 1.0 可能是用来表示颜色或者其他属性的,但在标准的UV坐标中,它们不是UV坐标的一部分。
// -0.5, -0.5, 0.0, 0.0:
// U坐标 -0.5 和 V坐标 -0.5 表示这个顶点位于平面的左下角。纹理图像的左边缘(U=0)和下边缘(V=0)被映射到这里。
// 最后两个数字 0.0 和 0.0 同样表示额外的数据。
// 0.5, 0.5, 1.0, 1.0:
// U坐标 0.5 和 V坐标 0.5 表示这个顶点位于平面的右上角。纹理图像的右边缘(U=1)和上边缘(V=1)被映射到这里。
// 最后两个数字 1.0 和 1.0 表示额外的数据。
// 0.5, -0.5, 1.0, 0.0:
// U坐标 0.5 和 V坐标 -0.5 表示这个顶点位于平面的右下角。纹理图像的右边缘(U=1)和下边缘(V=0)被映射到这里。
// 最后两个数字 1.0 和 0.0 表示额外的数据。
const vertexTexCoords = new Float32Array([
-0.5, 0.5, 0.0, 1.0,
-0.5, -0.5, 0.0, 0.0,
0.5, 0.5, 1.0, 1.0,
0.5, -0.5, 1.0, 0.0,
])
const f32Seize = vertexTexCoords.BYTES_PER_ELEMENT
// 给定点设置坐标 几何图形与纹理的坐标
const vertexBuffer = gl.createBuffer()
// 绑定buffer
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer)
// 绑定数据
gl.bufferData(gl.ARRAY_BUFFER, vertexTexCoords, gl.STATIC_DRAW)
// 给a_Position赋值
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, f32Seize * 4, 0)
// 使用此变量
gl.enableVertexAttribArray(a_Position)
// 纹理坐标
const texCoordBuffer = gl.createBuffer()
// 绑定buffer
gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer)
// 绑定数据
gl.bufferData(gl.ARRAY_BUFFER, vertexTexCoords, gl.STATIC_DRAW)
// 给a_TexCoord赋值
gl.vertexAttribPointer(a_TexCoord, 2, gl.FLOAT, false, f32Seize * 4, f32Seize * 2)
// 使用此变量
gl.enableVertexAttribArray(a_TexCoord)
gl.clearColor(0, 0, 0, 1.0)
// 获取图片的素材
getImage('/images/mergeImage1.jpg', u_Sampler0, gl.TEXTURE0, 0)
getImage('/images/mergeImage2.jpg', u_Sampler1, gl.TEXTURE1, 1)
function getImage(imgYrl, u_Sampler, TEXTURE, num) {
// 文件里的文本会在这里被打印出来
const img = new Image()
img.src = imgYrl
img.crossOrigin = ''
img.onload = () => {
const texture = gl.createTexture()
showImage(texture, img, u_Sampler, TEXTURE, num)
}
}
function showImage(texture, img, u_Sampler, TEXTURE, texUnit) {
console.log(texture, img, u_Sampler, TEXTURE, texUnit)
// document.body.appendChild(img)
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1)
// 开始0号纹理通道
gl.activeTexture(TEXTURE)
// 想目标绑定纹理对象
gl.bindTexture(gl.TEXTURE_2D, texture)
// 配置纹理的参数
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_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.MIRRORED_REPEAT)
// 设置着色器参数
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, img)
// 设置纹理数据
gl.uniform1i(u_Sampler, texUnit)
gl.clear(gl.COLOR_BUFFER_BIT)
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4)
}
}
const destroy = () => {
cancelAnimationFrame(animationFrame)
animationFrame = null
}
onUnmounted(() => {
if (sceneResources) {
sceneResources.gl && sceneResources.gl.getExtension("WEBGL_lose_context").loseContext()
}
destroy()
})
</script>