Appearance
粒子旋转拖尾
点击运行
<template>
<div>
<div @click="onTrigger" class="pointer">点击{{ !isRunning ? '运行' : '关闭' }}</div>
<canvas v-if="isRunning" id="rotatingTail" class="stage-webgl"></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.clientWidth
let height = c.clientHeight
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)
// 如果使用这行,就要设置成 let width = c.width let height = c.height
// 需要告诉 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-webgl"></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 = 3
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-webgl"></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) {
// 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>线宽以及图片嵌入
点击运行
点击展示图片
<template>
<div>
<div class="flex space-between">
<div @click="onTrigger" class="pointer">点击{{ !isRunning ? '运行' : '关闭' }}</div>
<div @click="onShowImg = !onShowImg" class="pointer">点击展示图片</div>
</div>
<img v-if="onShowImg" src="/public/markdown/webgl/lineWidthAndMap.png" alt="lineWidthAndMap" />
<canvas v-if="isRunning" id="lineWidthAndMap" class="stage-webgl"></canvas>
</div>
</template>
<script lang="ts" setup>
import { ref, nextTick, onUnmounted } from 'vue'
import earcut from 'earcut'
// gl.POINTS:绘制单个点。
// gl.LINES:绘制线段,每两个顶点构成一条线段。
// gl.LINE_LOOP:绘制线环,所有顶点依次连接成一个闭合的环。
// gl.LINE_STRIP:绘制线带,所有顶点依次连接成一条连续的线。
// gl.TRIANGLES:绘制三角形,每三个顶点构成一个三角形。
// gl.TRIANGLE_STRIP:绘制三角形带,每三个连续的顶点构成一个三角形。
// gl.TRIANGLE_FAN:绘制三角形扇,第一个顶点作为中心点,后续每两个顶点与中心点构成一个三角形。
const lineVertex = `
precision mediump float;
attribute vec2 a_Position;
varying vec3 vColor;
void main() {
vColor = vec3(0.0, 0.0, 1.0);
gl_Position = vec4(a_Position, 0.0, 1.0);
gl_PointSize = 1.0;
}
`
const lineFragment = `
precision mediump float;
varying vec3 vColor;
void main() {
gl_FragColor = vec4(vColor, 1.0);
}
`
const areaVertex = `
precision mediump float;
attribute vec2 a_Area;
varying vec3 vColor;
void main() {
vColor = vec3(1.0, 0.0, 0.0);
gl_Position = vec4(a_Area, 0.0, 1.0);
gl_PointSize = 1.0;
}
`
const areaFragment = `
precision mediump float;
varying vec3 vColor;
void main() {
gl_FragColor = vec4(vColor, 0.1);
}
`
const imgVertex = `
precision mediump float;
attribute vec2 a_Position;
attribute vec2 a_TexCoord;
varying vec2 v_TexCoord;
void main(){
gl_Position = vec4(a_Position, 0.0, 1.0);; // 顶点坐标
v_TexCoord = a_TexCoord; // 纹理坐标系下的坐标
}
`
const imgFragment = `
precision mediump float;
uniform sampler2D u_Sampler0; // 纹理
varying vec2 v_TexCoord; // 纹理坐标系下的坐标
void main(){
vec4 color0 = texture2D(u_Sampler0,v_TexCoord);
gl_FragColor = color0;
}
`
const lineWidthVertex = `
precision mediump float;
attribute vec2 a_center;
attribute vec2 a_prev_point;
attribute vec2 a_next_point;
attribute float a_side;
uniform float u_line_width;
varying vec3 vColor;
vec2 calculateNormal(vec2 dir) {
// 只能两行两列乘以两行一列,第一个的列数=第二个的行数
// 旋转矩阵 |cos, -sin||x| ---> 【x * cosθ - y * sinθ】 ---> x'
// 旋转矩阵 |sin, +cos||y| ---> 【x * sinθ + y * cosθ】 ---> y'
return normalize(vec2(-dir.y, dir.x));
}
void main() {
vColor = vec3(1.0, 0.0, 1.0);
float len = u_line_width * 0.5;
vec2 extrudedPosition;
// 特殊情况处理:起点和终点
if (distance(a_prev_point, a_center) < 0.000001) {
// 起点 - 使用下一段的法线
// 方法一:
// vec2 nextDir = normalize(a_center - a_next_point); // 注意:这两个※※※※这里是 a_center - a_next_point
// 方法二:
vec2 nextDir = normalize(a_next_point - a_center); // 注意:这两个※※※※这里是 a_next_point - a_center
vec2 normal = calculateNormal(nextDir);
extrudedPosition = a_center + normal * len * a_side;
} else if (distance(a_next_point, a_center) < 0.000001) {
// 终点 - 使用前一段的法线
// 方法一:
// vec2 prevDir = normalize(a_prev_point - a_center); // 注意:这两个※※※※这里是 a_prev_point - a_center
// 方法二:
vec2 prevDir = normalize(a_center - a_prev_point); // 注意:这两个※※※※这里是 a_center - a_prev_point
vec2 normal = calculateNormal(prevDir);
extrudedPosition = a_center + normal * len * a_side;
} else {
// 方法一:
// vec2 v1Norm = normalize(a_center - a_next_point);
// vec2 v2Norm = normalize(a_center - a_prev_point);
// vec2 v3Norm = calculateNormal(v1Norm);
// vec2 vNorm = normalize(v1Norm + v2Norm);
// float scale = len / dot(v3Norm, vNorm);
// extrudedPosition = a_center + vNorm * scale * a_side;
// 方法二:
vec2 v1Norm = normalize(a_next_point - a_center);
vec2 v2Norm = normalize(a_center - a_prev_point);
vec2 v3Norm = calculateNormal(v1Norm);
vec2 v4Norm = calculateNormal(v2Norm);
vec2 vNorm = normalize(v3Norm + v4Norm);
float scale = len / dot(v4Norm, vNorm);
extrudedPosition = a_center + vNorm * scale * a_side;
}
gl_Position = vec4(extrudedPosition, 0.0, 1.0);
gl_PointSize = 1.0;
}
`
const lineWidthFragment = `
precision mediump float;
varying vec3 vColor;
void main() {
gl_FragColor = vec4(vColor, 1.0);
}
`
class Webgl {
gl: any = null
ele: any = null
width = 0
height = 0
constructor(ele, width, height) {
this.ele = ele
this.width = width
this.height = height
this.gl = null
}
init() {
const canvas = document.querySelector(this.ele)
canvas.width = this.width // 设置绘制缓冲区宽度
canvas.height = this.height // 设置绘制缓冲区高度
this.gl = canvas.getContext('webgl2', {
antialias: true,
depth: true
})
}
initShader(vsSource, fsSource) {
// 创建程序对象
const program = this.gl.createProgram()
// 创建着色对象
const vertexShader = this._loadShader(this.gl.VERTEX_SHADER, vsSource)
const fragmentShader = this._loadShader(this.gl.FRAGMENT_SHADER, fsSource)
// 把顶点着色对象/片元着色对象,装进程序对象中
this.gl.attachShader(program, vertexShader)
this.gl.attachShader(program, fragmentShader)
// 连接webgl上下文对象和程序对象
this.gl.linkProgram(program)
// 启动程序对象
this.gl.useProgram(program)
// 将程序对象挂到上下文对象上
this.gl.program = program
}
/**
* 开启混合模式
* 这里的混合模式是指:当两个图形重叠时,如何处理它们的颜色
*/
runBlend() {
// 即重合部分的,则会叠加颜色
this.gl.enable(this.gl.BLEND)
this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE)
}
_loadShader(type, source) {
// 根据着色类型,创建着色器对象
const shader = this.gl.createShader(type)
// 将着色器源文件传入着色器对象中
this.gl.shaderSource(shader, source)
// 编译着色器对象
this.gl.compileShader(shader)
// 检查编译是否成功
if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) {
console.error('Shader compile error: ', this.gl.getShaderInfoLog(shader))
this.gl.deleteShader(shader)
return null
}
// 返回着色器对象
return shader
}
setAttribute(info) {
// 缓冲对象
const vertexBuffer = this.gl.createBuffer()
// 绑定缓冲对象
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, vertexBuffer)
// 写入数据
this.gl.bufferData(this.gl.ARRAY_BUFFER, info.data, this.gl.STATIC_DRAW)
// 获取 attribute 变量
const attribute = this.gl.getAttribLocation(this.gl.program, info.attrName)
// 修改 attribute 变量
this.gl.vertexAttribPointer(attribute, info.size, this.gl.FLOAT, false, 0, 0)
// 赋能-批处理
this.gl.enableVertexAttribArray(attribute)
}
setIndexBuffer(indices) {
const indexBuffer = this.gl.createBuffer()
this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, indexBuffer)
this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), this.gl.STATIC_DRAW)
}
// gl.uniform1f 用于传递 float 类型
// gl.uniform2f 用于传递 vec2 类型
// gl.uniform3f / gl.uniform4f:分别传递 vec3 和 vec4 类型的数据
// gl.uniform1fv(传递浮点数组)、gl.uniformMatrix4fv(传递 4x4 矩阵)
setUniform1f(name, value) {
const location = this.gl.getUniformLocation(this.gl.program, name)
if (location) {
this.gl.uniform1f(location, value)
}
}
setUniform2f(name, value1, value2) {
const location = this.gl.getUniformLocation(this.gl.program, name)
if (location) {
this.gl.uniform2f(location, value1, value2)
}
}
setUniform1i(name, value) {
const location = this.gl.getUniformLocation(this.gl.program, name)
if (location) {
this.gl.uniform1i(location, value)
}
}
setUniform2fv(name, value) {
const location = this.gl.getUniformLocation(this.gl.program, name)
if (location) {
if (Array.isArray(value[0])) { // 如果是数组的数组(如 vec2 数组)
const flat = value.flat() // 需要浏览器支持 .flat()
this.gl.uniform2fv(location, new Float32Array(flat))
} else {
this.gl.uniform2fv(location, value) // 假设是已扁平化的数组
}
}
}
setTexture(imgObj, uSamplerName, texturePassNum, textureIndex) {
const uSampler = this.gl.getUniformLocation(this.gl.program, uSamplerName)
const texture = this.gl.createTexture()
this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, 1)
//开始 texturePassNum 号纹理通道
this.gl.activeTexture(this.gl[texturePassNum])
//想目标绑定纹理对象
this.gl.bindTexture(this.gl.TEXTURE_2D, texture)
//配置纹理的参数
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR)
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE)
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.MIRRORED_REPEAT)
//设置着色器参数
this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGB, this.gl.RGB, this.gl.UNSIGNED_BYTE, imgObj)
//设置纹理数据
this.gl.uniform1i(uSampler, textureIndex)
}
clear() {
this.gl.clearColor(0, 0, 0, 1)
}
// gl.drawArrays 是一种直接使用顶点数组进行绘制的方法。它会按照顶点数组的顺序依次绘制顶点。
// gl.drawElements 是一种使用索引数组进行绘制的方法。它通过索引数组来指定顶点的顺序,从而可以重复使用顶点数据,减少顶点数据的冗余。
draw(type, count, useIndices = false) {
if (useIndices) {
this.gl.drawElements(this.gl[type], count, this.gl.UNSIGNED_SHORT, 0)
} else {
this.gl.drawArrays(this.gl[type], 0, count)
}
}
}
let animationFrame
let sceneResources
const isRunning = ref(false)
const onShowImg = ref(false)
const onTrigger = async () => {
if (!isRunning.value) {
isRunning.value = true
await nextTick()
sceneResources = initScene()
} else {
isRunning.value = false
destroy()
}
}
const initScene = () => {
const points = [
{ x: 0, y: 0 },
{ x: 0.5, y: 0.5 },
{ x: 0.5, y: 0 },
{ x: 0.25, y: -0.25 },
{ x: 0.5, y: -0.5 },
{ x: 0, y: -0.5 },
{ x: -0.5, y: -0.25 },
{ x: -0.5, y: 0.25 },
{ x: 0, y: 0 }, // 这两个补充线段的闭合
{ x: 0.5, y: 0.5 }, // 这两个补充线段的闭合
]
const lw = 0.04
const centers: any = []
const prevPoints: any = []
const nextPoints: any = []
const sides: any = []
const indices: any = []
for (let i = 0; i < points.length; i++) {
const now = points[i]
let prev, next
// 开始的点
if (i === 0) {
prev = now
} else {
prev = points[i - 1]
}
// 结束的点
if (i === points.length - 1) {
next = now
} else {
next = points[i + 1]
}
// 旋转90度 1
centers.push(now.x, now.y)
prevPoints.push(prev.x, prev.y)
nextPoints.push(next.x, next.y)
sides.push(1)
// 旋转90度 -1
centers.push(now.x, now.y)
prevPoints.push(prev.x, prev.y)
nextPoints.push(next.x, next.y)
sides.push(-1)
}
// 创建三角形索引
for (let i = 0; i < points.length - 1; i++) {
const base = i * 2
// 两个三角形组成一个四边形段
// 0 2 4
// ---------
// | /| /|
// | / | / |
// |/ |/ |
// ---------
// 1 3 5
indices.push(base, base + 1, base + 2)
indices.push(base + 1, base + 2, base + 3)
}
const tempLines: any = []
points.forEach((v: any) => {
tempLines.push(v.x)
tempLines.push(v.y)
})
const lines = new Float32Array(tempLines)
const ele: any = document.querySelector('#lineWidthAndMap')
const w = ele.clientWidth
const h = ele.clientHeight
const myGl = new Webgl('#lineWidthAndMap', w, h)
myGl.init()
myGl.clear()
const img = new Image()
img.src = '/images/star.jpg'
img.crossOrigin = ''
img.onload = () => {
// 区域填充色
const areaIndices = earcut(tempLines, [], 2) // 2表示每个顶点有2个坐标(x, y)。返回的是组成三角形的index。
const triangles = new Float32Array(areaIndices.length * 2)
for (let i = 0; i < areaIndices.length; i++) {
triangles[i * 2] = lines[areaIndices[i] * 2]
triangles[i * 2 + 1] = lines[areaIndices[i] * 2 + 1]
}
myGl.initShader(areaVertex, areaFragment)
myGl.setAttribute({ data: triangles, size: 2, attrName: 'a_Area' })
myGl.draw('TRIANGLES', triangles.length / 2)
// 创建纹理坐标
// 1、indices 和 lines 的含义
// ---- indices 是一个数组,表示三角形的顶点索引。这些索引是从 earcut 函数返回的,用于将多边形的顶点分割成三角形。
// ---- lines 是一个 Float32Array,包含多边形的所有顶点坐标。每个顶点由两个浮点数表示(x 和 y 坐标)。
// 2、texCoords 的作用
// ---- texCoords 是一个 Float32Array,用于存储每个顶点的纹理坐标。纹理坐标是二维的,范围通常是 [0, 1],表示纹理图像上的位置。
// ---- 纹理坐标用于告诉 GPU 如何将纹理图像映射到几何图形上。每个顶点都有一个对应的纹理坐标,GPU 会根据这些坐标将纹理图像映射到三角形上。
// 3、映射顶点坐标到纹理坐标
// ---- 顶点坐标范围是 [-1, 1](这是常见的归一化坐标范围),需要将这些坐标映射到 [0, 1] 范围,以便作为纹理坐标。
// ---- texCoords[i * 2] = (lines[indices[i] * 2] + 1) / 2:将 x 坐标从 [-1, 1] 映射到 [0, 1]。
// ---- texCoords[i * 2 + 1] = (lines[indices[i] * 2 + 1] + 1) / 2:将 y 坐标从 [-1, 1] 映射到 [0, 1]。
// ---- 原始范围:[−1,1]
// ---- 目标范围:[0,1]
// ---- 新坐标 = (旧坐标+1) / 2
const texCoords = new Float32Array(areaIndices.length * 2)
for (let i = 0; i < areaIndices.length; i++) {
texCoords[i * 2] = (lines[areaIndices[i] * 2] + 1) / 2 // 将顶点坐标映射到 [0, 1] 范围
texCoords[i * 2 + 1] = (lines[areaIndices[i] * 2 + 1] + 1) / 2
}
myGl.initShader(imgVertex, imgFragment)
myGl.setAttribute({ data: triangles, size: 2, attrName: 'a_Position' })
myGl.setAttribute({ data: texCoords, size: 2, attrName: 'a_TexCoord' })
myGl.setTexture(img, 'u_Sampler0', 'TEXTURE0', 0)
myGl.draw('TRIANGLES', triangles.length / 2)
// 画有宽度的线段
myGl.initShader(lineWidthVertex, lineWidthFragment)
myGl.setAttribute({ data: new Float32Array(centers), size: 2, attrName: 'a_center' })
myGl.setAttribute({ data: new Float32Array(prevPoints), size: 2, attrName: 'a_prev_point' })
myGl.setAttribute({ data: new Float32Array(nextPoints), size: 2, attrName: 'a_next_point' })
myGl.setAttribute({ data: new Float32Array(sides), size: 1, attrName: 'a_side' })
myGl.setIndexBuffer(indices)
myGl.setUniform1f('u_line_width', lw)
myGl.draw('TRIANGLES', indices.length, true)
// 画线段
myGl.initShader(lineVertex, lineFragment)
myGl.setAttribute({ data: lines, size: 2, attrName: 'a_Position' })
myGl.draw('LINE_STRIP', lines.length / 2)
}
}
const destroy = () => {
cancelAnimationFrame(animationFrame)
animationFrame = null
}
onUnmounted(() => {
if (sceneResources) {
sceneResources.gl && sceneResources.gl.getExtension("WEBGL_lose_context").loseContext()
}
destroy()
})
</script>线宽以及线条阴影
点击运行
<template>
<div>
<div class="flex space-between">
<div @click="onTrigger" class="pointer">点击{{ !isRunning ? '运行' : '关闭' }}</div>
</div>
<canvas v-if="isRunning" id="lineWidthAndShadow" class="stage-webgl"></canvas>
</div>
</template>
<script lang="ts" setup>
import { ref, nextTick, onUnmounted } from 'vue'
// gl.POINTS:绘制单个点。
// gl.LINES:绘制线段,每两个顶点构成一条线段。
// gl.LINE_LOOP:绘制线环,所有顶点依次连接成一个闭合的环。
// gl.LINE_STRIP:绘制线带,所有顶点依次连接成一条连续的线。
// gl.TRIANGLES:绘制三角形,每三个顶点构成一个三角形。
// gl.TRIANGLE_STRIP:绘制三角形带,每三个连续的顶点构成一个三角形。
// gl.TRIANGLE_FAN:绘制三角形扇,第一个顶点作为中心点,后续每两个顶点与中心点构成一个三角形。
// uv的垂直距离到当前线段的距离
const lineWidthVertex = `
precision highp float;
attribute vec2 a_center;
attribute vec2 a_prev_point;
attribute vec2 a_next_point;
attribute float a_side;
uniform float u_line_width;
varying vec3 vColor;
vec2 calculateNormal(vec2 dir) {
// 只能两行两列乘以两行一列,第一个的列数=第二个的行数
// 旋转矩阵 |cos, -sin||x| ---> 【x * cosθ - y * sinθ】 ---> x'
// 旋转矩阵 |sin, +cos||y| ---> 【x * sinθ + y * cosθ】 ---> y'
return normalize(vec2(-dir.y, dir.x));
}
void main() {
vColor = vec3(0.9, 0.0, 1.0);
float len = u_line_width * 0.5;
vec2 extrudedPosition;
// 特殊情况处理:起点和终点
if (distance(a_prev_point, a_center) < 0.00001) {
// 起点 - 使用下一段的法线
vec2 nextDir = normalize(a_center - a_next_point); // 注意:这两个※※※※这里是 a_center - a_next_point
vec2 normal = calculateNormal(nextDir);
extrudedPosition = a_center + normal * len * a_side;
} else if (distance(a_center, a_next_point) < 0.00001) {
// 终点 - 使用前一段的法线
vec2 prevDir = normalize(a_prev_point - a_center); // 注意:这两个※※※※这里是 a_prev_point - a_center
vec2 normal = calculateNormal(prevDir);
extrudedPosition = a_center + normal * len * a_side;
} else {
vec2 v1Norm = normalize(a_center - a_next_point);
vec2 v2Norm = normalize(a_center - a_prev_point);
vec2 v3Norm = calculateNormal(v1Norm);
vec2 vNorm = normalize(v1Norm + v2Norm);
float scale = len / dot(v3Norm, vNorm);
extrudedPosition = a_center + vNorm * scale * a_side;
}
gl_Position = vec4(extrudedPosition, 0.0, 1.0);
gl_PointSize = 1.0;
}
`
const lineWidthFragment = `
precision highp float;
uniform vec2 u_viewport;
uniform vec2 u_points[6];
uniform int u_points_len;
varying vec3 vColor;
void main() {
vec3 finalColor = vColor;
vec2 fragCoord = gl_FragCoord.xy;
vec2 uv = (fragCoord / u_viewport) * 2.0 - 1.0;
float distanceCenterToLine = 1000000.0; // 初始化为一个很大的值
// 定义线段的点
vec2 points[7];
points[0] = vec2(0.5, 0.5);
points[1] = vec2(0.5, 0.0);
points[2] = vec2(0.0, 0.0);
points[3] = vec2(0.0, -0.5);
points[4] = vec2(0.5, -0.5);
points[5] = vec2(0.5, -0.25);
points[6] = vec2(0.7, -0.35);
// 遍历所有线段
// 这个10不能设置得太高,在某些移动端 GPU 上无法正确执行
for (int i = 0; i < 10; i++) {
if (i >= u_points_len - 1) break;
vec2 p1 = points[i];
vec2 p2 = points[i + 1];
// vec2 p1 = u_points[i];
// vec2 p2 = u_points[i + 1];
// 计算线段的方向向量 lineDir,即终点 p2 减去起点 p1。
vec2 lineDir = p2 - p1;
// 计算线段的长度 lineLength,即方向向量 lineDir 的长度。
float lineLength = length(lineDir);
// float lineLength = distance(p2, p1);
// 将方向向量 lineDir 归一化,使其长度为 1。归一化后的方向向量可以用于后续的计算。
lineDir /= lineLength;
// 计算从线段起点 p1 到当前片段位置 uv 的向量 toFrag(碎片)。
vec2 toFrag = uv - p1;
// 计算向量 toFrag 在方向向量 lineDir 上的投影长度 projection。投影长度表示 uv 在线段方向上的位置。
float projection = dot(toFrag, lineDir);
// 计算当前片段 uv 在线段上的最近点 closestPoint。clamp(projection, 0.0, lineLength) 确保投影长度在 [0, 线段长度] 的范围内,避免超出线段范围。然后将投影长度乘以方向向量 lineDir,并加到起点 p1 上,得到最近点。
vec2 closestPoint = p1 + lineDir * clamp(projection, 0.0, lineLength);
// 计算当前片段 uv 到最近点 closestPoint 的距离 distance。
float distance = length(uv - closestPoint);
// 更新最小距离 distanceCenterToLine,取当前距离 distance 和之前计算的最小距离中的较小值。
distanceCenterToLine = min(distanceCenterToLine, distance);
}
if (distanceCenterToLine < 0.04) {
finalColor = vec3(0.0, 1.0, 0.0);
}
// 这个有瑕疵
// float distanceCenterToLine = 0.0; // 初始化
// float disA = distance(vPrev, vCenter);
// float disB = distance(vCenter, vNext);
// vec2 pointA = vec2(0.0);
// vec2 pointB = vec2(0.0);
// if (disA >= disB) {
// pointA = vCenter;
// pointB = vNext;
// } else {
// pointA = vPrev;
// pointB = vCenter;
// }
// vec2 lineDir = normalize(pointA - pointB);
// float distanceCenterToPoint = distance(pointA, uv);
// float distanceProject = distanceCenterToPoint * dot(lineDir, normalize(pointA - uv));
// distanceCenterToLine = pow(distanceCenterToPoint * distanceCenterToPoint - distanceProject * distanceProject, 0.5);
// if (distanceCenterToLine > 0.02) {
// finalColor = vec3(0.0, 1.0, 0.0);
// }
gl_FragColor = vec4(finalColor, 1.0);
}
`
const lineVertex = `
precision highp float;
attribute vec2 a_Position;
varying vec3 vColor;
void main() {
vColor = vec3(0.0, 0.0, 1.0);
gl_Position = vec4(a_Position, 0.0, 1.0);
gl_PointSize = 1.0;
}
`
const lineFragment = `
precision highp float;
varying vec3 vColor;
void main() {
gl_FragColor = vec4(vColor, 1.0);
}
`
class Webgl {
gl: any = null
ele: any = null
width = 0
height = 0
constructor(ele, width, height) {
this.ele = ele
this.width = width
this.height = height
this.gl = null
}
init() {
const canvas = document.querySelector(this.ele)
canvas.width = this.width // 设置绘制缓冲区宽度
canvas.height = this.height // 设置绘制缓冲区高度
this.gl = canvas.getContext('webgl2', {
antialias: true,
depth: true
})
}
initShader(vsSource, fsSource) {
// 创建程序对象
const program = this.gl.createProgram()
// 创建着色对象
const vertexShader = this._loadShader(this.gl.VERTEX_SHADER, vsSource)
const fragmentShader = this._loadShader(this.gl.FRAGMENT_SHADER, fsSource)
// 把顶点着色对象/片元着色对象,装进程序对象中
this.gl.attachShader(program, vertexShader)
this.gl.attachShader(program, fragmentShader)
// 连接webgl上下文对象和程序对象
this.gl.linkProgram(program)
// 启动程序对象
this.gl.useProgram(program)
// 将程序对象挂到上下文对象上
this.gl.program = program
}
/**
* 开启混合模式
* 这里的混合模式是指:当两个图形重叠时,如何处理它们的颜色
*/
runBlend() {
// 即重合部分的,则会叠加颜色
this.gl.enable(this.gl.BLEND)
this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE)
}
_loadShader(type, source) {
// 根据着色类型,创建着色器对象
const shader = this.gl.createShader(type)
// 将着色器源文件传入着色器对象中
this.gl.shaderSource(shader, source)
// 编译着色器对象
this.gl.compileShader(shader)
// 检查编译是否成功
if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) {
console.error('Shader compile error: ', this.gl.getShaderInfoLog(shader))
this.gl.deleteShader(shader)
return null
}
// 返回着色器对象
return shader
}
setAttribute(info) {
// 缓冲对象
const vertexBuffer = this.gl.createBuffer()
// 绑定缓冲对象
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, vertexBuffer)
// 写入数据
this.gl.bufferData(this.gl.ARRAY_BUFFER, info.data, this.gl.STATIC_DRAW)
// 获取 attribute 变量
const attribute = this.gl.getAttribLocation(this.gl.program, info.attrName)
// 修改 attribute 变量
this.gl.vertexAttribPointer(attribute, info.size, this.gl.FLOAT, false, 0, 0)
// 赋能-批处理
this.gl.enableVertexAttribArray(attribute)
}
setIndexBuffer(indices) {
const indexBuffer = this.gl.createBuffer()
this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, indexBuffer)
this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), this.gl.STATIC_DRAW)
}
// gl.uniform1f 用于传递 float 类型
// gl.uniform2f 用于传递 vec2 类型
// gl.uniform3f / gl.uniform4f:分别传递 vec3 和 vec4 类型的数据
// gl.uniform1fv(传递浮点数组)、gl.uniformMatrix4fv(传递 4x4 矩阵)
setUniform1f(name, value) {
const location = this.gl.getUniformLocation(this.gl.program, name)
if (location) {
this.gl.uniform1f(location, value)
}
}
setUniform2f(name, value1, value2) {
const location = this.gl.getUniformLocation(this.gl.program, name)
if (location) {
this.gl.uniform2f(location, value1, value2)
}
}
setUniform1i(name, value) {
const location = this.gl.getUniformLocation(this.gl.program, name)
if (location) {
this.gl.uniform1i(location, value)
}
}
setUniform2fv(name, value) {
const location = this.gl.getUniformLocation(this.gl.program, name)
if (location) {
if (Array.isArray(value[0])) { // 如果是数组的数组(如 vec2 数组)
const flat = value.flat() // 需要浏览器支持 .flat()
this.gl.uniform2fv(location, new Float32Array(flat))
} else {
this.gl.uniform2fv(location, value) // 假设是已扁平化的数组
}
}
}
setTexture(imgObj, uSamplerName, texturePassNum, textureIndex) {
const uSampler = this.gl.getUniformLocation(this.gl.program, uSamplerName)
const texture = this.gl.createTexture()
this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, 1)
//开始 texturePassNum 号纹理通道
this.gl.activeTexture(this.gl[texturePassNum])
//想目标绑定纹理对象
this.gl.bindTexture(this.gl.TEXTURE_2D, texture)
//配置纹理的参数
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR)
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE)
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.MIRRORED_REPEAT)
//设置着色器参数
this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGB, this.gl.RGB, this.gl.UNSIGNED_BYTE, imgObj)
//设置纹理数据
this.gl.uniform1i(uSampler, textureIndex)
}
clear() {
this.gl.clearColor(0, 0, 0, 1)
}
// gl.drawArrays 是一种直接使用顶点数组进行绘制的方法。它会按照顶点数组的顺序依次绘制顶点。
// gl.drawElements 是一种使用索引数组进行绘制的方法。它通过索引数组来指定顶点的顺序,从而可以重复使用顶点数据,减少顶点数据的冗余。
draw(type, count, useIndices = false) {
if (useIndices) {
this.gl.drawElements(this.gl[type], count, this.gl.UNSIGNED_SHORT, 0)
} else {
this.gl.drawArrays(this.gl[type], 0, count)
}
}
}
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 points = [
{ x: 0.5, y: 0.5 },
{ x: 0.5, y: 0 },
{ x: 0, y: 0 },
{ x: 0, y: -0.5 },
{ x: 0.5, y: -0.5 },
{ x: 0.5, y: -0.25 },
{ x: 0.7, y: -0.35 },
]
let lw = 0.16
let centers: any = []
let prevPoints: any = []
let nextPoints: any = []
let sides: any = []
let indices: any = []
for (let i = 0; i < points.length; i++) {
const now = points[i]
let prev, next
// 开始的点
if (i === 0) {
prev = now
} else {
prev = points[i - 1]
}
// 结束的点
if (i === points.length - 1) {
next = now
} else {
next = points[i + 1]
}
// 旋转90度 1
centers.push(now.x, now.y)
prevPoints.push(prev.x, prev.y)
nextPoints.push(next.x, next.y)
sides.push(1)
// 旋转90度 -1
centers.push(now.x, now.y)
prevPoints.push(prev.x, prev.y)
nextPoints.push(next.x, next.y)
sides.push(-1)
}
// 创建三角形索引
for (let i = 0; i < points.length - 1; i++) {
const base = i * 2
// 两个三角形组成一个四边形段
// 0 2 4
// ---------
// | /| /|
// | / | / |
// |/ |/ |
// ---------
// 1 3 5
indices.push(base, base + 1, base + 2)
indices.push(base + 1, base + 2, base + 3)
}
const ele: any = document.querySelector('#lineWidthAndShadow')
const w = ele.clientWidth
const h = ele.clientHeight
const myGl = new Webgl('#lineWidthAndShadow', w, h)
myGl.init()
myGl.clear()
myGl.initShader(lineWidthVertex, lineWidthFragment)
myGl.setAttribute({ data: new Float32Array(centers), size: 2, attrName: 'a_center' })
myGl.setAttribute({ data: new Float32Array(prevPoints), size: 2, attrName: 'a_prev_point' })
myGl.setAttribute({ data: new Float32Array(nextPoints), size: 2, attrName: 'a_next_point' })
myGl.setAttribute({ data: new Float32Array(sides), size: 1, attrName: 'a_side' })
myGl.setUniform1i('u_points_len', points.length)
myGl.setUniform1f('u_line_width', lw)
myGl.setUniform2fv('u_viewport', [w, h])
myGl.setUniform2fv('u_points', points.map(point => [point.x, point.y]))
myGl.setIndexBuffer(indices)
myGl.draw('TRIANGLES', indices.length, true)
const tempLines: any = []
points.forEach((v: any) => {
tempLines.push(v.x)
tempLines.push(v.y)
})
const lines = new Float32Array(tempLines)
myGl.initShader(lineVertex, lineFragment)
myGl.setAttribute({ data: lines, size: 2, attrName: 'a_Position' })
myGl.draw('LINE_STRIP', lines.length / 2)
}
const destroy = () => {
cancelAnimationFrame(animationFrame)
animationFrame = null
}
onUnmounted(() => {
if (sceneResources) {
sceneResources.gl && sceneResources.gl.getExtension("WEBGL_lose_context").loseContext()
}
destroy()
})
</script>