Appearance
线条
fps: 0
点击运行
<template>
<div>
<div class="flex space-between">
<div>fps: {{ fps }}</div>
<div @click="onTrigger" class="pointer">点击{{ !isRunning ? '运行' : '关闭' }}</div>
</div>
<canvas v-if="isRunning" id="shaderLine" class="stage"></canvas>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref, nextTick, onUnmounted } from 'vue'
import {
Engine,
Scene,
ArcRotateCamera,
Vector3,
Color4,
Color3,
HemisphericLight,
MeshBuilder,
Effect,
ShaderMaterial
} from 'babylonjs'
import {
AdvancedDynamicTexture,
StackPanel,
Control,
TextBlock,
} from 'babylonjs-gui'
let sceneResources
const fps = ref(0)
const isRunning = ref(false)
const onTrigger = async () => {
if (!isRunning.value) {
isRunning.value = true
await nextTick()
sceneResources = await initScene()
} else {
isRunning.value = false
destroy()
}
}
// const vertexShader = `
// attribute vec3 position;
// attribute vec2 uv;
// uniform mat4 worldViewProjection;
// varying vec3 vPosition;
// varying vec2 vUv;
// void main() {
// vUv = uv;
// gl_Position = worldViewProjection * vec4(position, 1.0);
// }
// `
// const fragmentShader = `
// uniform float iTime;
// uniform vec2 iResolution;
// varying vec2 vUv;
// vec3 hsb2rgb(in vec3 c) {
// vec3 rgb = clamp(abs(mod(c.x*6.0+vec3(0.0,4.0,2.0), 6.0)-3.0)-1.0, 0.0,1.0 );
// rgb = rgb*rgb*(3.0-2.0*rgb);
// return c.z * mix( vec3(1.0), rgb, c.y);
// }
// void main(void) {
// vec2 p = (2.0*vUv.xy-iResolution.xy)/iResolution.y;
// float r = length(p) * 0.9;
// vec3 color = hsb2rgb(vec3(0.24, 0.7, 0.4));
// float a = pow(r, 2.0);
// float b = sin(r * 0.8 - 1.6);
// float c = sin(r - 0.010);
// float s = sin(a - iTime * 3.0 + b) * c;
// color *= abs(1.0 / (s * 10.8)) - 0.01;
// gl_FragColor = vec4(color, 1.);
// }
// `
const sinCosPosition = (i, dense) => {
let r = 20
let deg = (i / 180) * Math.PI * dense
let x = Math.cos(deg) * r
let y = Math.sin(deg) * r
return {
x,
y
}
}
const initScene = async () => {
const ele = document.getElementById("shaderLine") as any
ele.addEventListener('wheel', function(event) {
// 根据需要处理滚动
// 例如,可以修改相机的半径或角度
event.preventDefault() // 阻止默认滚动行为
})
const engine: any = new Engine(ele, true, {
preserveDrawingBuffer: true,
stencil: true,
disableWebGL2Support: false
})
const scene = new Scene(engine)
scene.useRightHandedSystem = true
const camera = new ArcRotateCamera('camera', -Math.PI / 1.5, Math.PI / 2.2, 15, new Vector3(0, 0, 0), scene)
camera.upperBetaLimit = Math.PI / 2.2
camera.wheelPrecision = 30
camera.panningSensibility = 200
camera.attachControl(ele, true)
camera.setPosition(new Vector3(200, 200, 200))
const createLight = () => {
const light = new HemisphericLight('light',new Vector3(1, 1, 0), scene)
return light
}
const createAxis = () => {
const axisX = MeshBuilder.CreateLines(
'axisX', {
colors: [new Color4(1, 0, 0, 1), new Color4(1, 0, 0, 1)],
points: [new Vector3(0, 0, 0), new Vector3(80, 0, 0)]
},
scene
)
const axisY = MeshBuilder.CreateLines(
'axisY', {
colors: [new Color4(0, 1, 0, 1), new Color4(0, 1, 0, 1) ],
points: [new Vector3(0, 0, 0), new Vector3(0, 80, 0) ]
},
scene
)
const axisZ = MeshBuilder.CreateLines(
'axisZ', {
colors: [new Color4(0, 0, 1, 1), new Color4(0, 0, 1, 1)],
points: [new Vector3(0, 0, 0), new Vector3(0, 0, 80)]
},
scene
)
return [axisX, axisY, axisZ]
}
const createGui = async () => {
const adt = AdvancedDynamicTexture.CreateFullscreenUI('UI')
const xBox = MeshBuilder.CreateBox('x', { size: 1 }, scene)
xBox.position = new Vector3(80, 0, 0)
const xPanel = new StackPanel()
xPanel.width = '20px'
xPanel.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_RIGHT
xPanel.verticalAlignment = Control.VERTICAL_ALIGNMENT_BOTTOM
const x = new TextBlock()
x.text = 'X'
x.height = '30px'
x.color = 'red'
adt.addControl(xPanel)
xPanel.addControl(x)
xPanel.linkWithMesh(xBox)
const yBox = MeshBuilder.CreateBox('x', { size: 1 }, scene)
yBox.position = new Vector3(0, 80, 0)
const yPanel = new StackPanel()
yPanel.width = '20px'
yPanel.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_RIGHT
yPanel.verticalAlignment = Control.VERTICAL_ALIGNMENT_BOTTOM
const y = new TextBlock()
y.text = 'Y'
y.height = '30px'
y.color = 'green'
adt.addControl(yPanel)
yPanel.addControl(y)
yPanel.linkWithMesh(yBox)
const zBox = MeshBuilder.CreateBox('x', { size: 1 }, scene)
zBox.position = new Vector3(0, 0, 80)
const zPanel = new StackPanel()
zPanel.width = '20px'
zPanel.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_RIGHT
zPanel.verticalAlignment = Control.VERTICAL_ALIGNMENT_BOTTOM
const z = new TextBlock()
z.text = 'Z'
z.height = '30px'
z.color = 'blue'
adt.addControl(zPanel)
zPanel.addControl(z)
zPanel.linkWithMesh(zBox)
}
const createLine = () => {
let uLen = 80.0
let uTotal = 360.0
let uVelocity = 1.0
let uSize = 5.0
let uTime = 0.0
let uColor = new Color3(1, 0, 0)
Effect.ShadersStore['lineShaderVertexShader'] = `
// 设置着色器精度
precision highp float;
attribute vec3 position;
uniform mat4 worldViewProjection;
attribute float current;
uniform float uSize;
uniform float uTime;
uniform float uLen;
uniform float uTotal;
uniform float uVelocity;
uniform vec3 uColor;
varying float vOpacity;
void main(void) {
float size = uSize;
// speed*time得到当前路程,然后除以总路程,就是当前第几个粒子
// mod(a, b) --> a / b的余数,20/100=0.2 --> 2
float curDis = mod(uTime * uVelocity, uTotal * 2.0);
// 判断当前像素点是否在uLen范围内
if (current < curDis && curDis - current < uLen) {
// 设置渐变大小,头大尾小
float sizePct = (uLen - (curDis - current)) / uLen;
// clamp函数将一个值限制在另外两个值之间
// clamp(a, min, max)
size *= sizePct;
vOpacity = clamp(1.0 * sizePct, 0.2, 1.0);
} else if (current < curDis) {
vOpacity = 0.2;
} else {
vOpacity = 0.2;
}
// gl_PointSize = size * 1.0;
gl_Position = worldViewProjection * vec4(position, 1.0);
}`
Effect.ShadersStore['lineShaderFragmentShader'] = `
precision highp float;
varying float vOpacity;
void main(void) {
if (vOpacity <= 0.2) {
gl_FragColor = vec4(0.0, 1.0, 0.0, vOpacity);
// discard;
} else {
gl_FragColor = vec4(1.0, 0.0, 0.0, vOpacity);
}
}`
const lineShader = new ShaderMaterial(
'lineShader',
scene, {
vertex: 'lineShader',
fragment: 'lineShader'
}, {
attributes: ['position', 'current'],
uniforms: [
'worldViewProjection',
'uLen',
'uTotal',
'uSize',
'uVelocity',
'uTime',
'uColor'
]
}
)
lineShader.setFloat('uLen', uLen)
lineShader.setFloat('uTotal', uTotal)
lineShader.setFloat('uSize', uSize)
lineShader.setFloat('uVelocity', uVelocity)
lineShader.setColor3('uColor', uColor)
let idx: any = []
let vPos: any = []
let index = 0
let add = 0
while (index < uTotal * 2) {
const {
x,
y
} = sinCosPosition(add, 2 * Math.PI)
vPos.push(new Vector3(add, x, y))
idx.push(index)
index++
add += 0.5
}
let me = MeshBuilder.CreateLines(
'tex', {
points: vPos,
updatable: true
},
scene
)
me.position = new Vector3(0, 20, 0)
me.setVerticesData('current', idx, true, 1)
me.material = lineShader
scene.registerBeforeRender(function() {
lineShader.setFloat('uTime', uTime)
uTime += 5
})
}
const runAnimate = () => {
engine.runRenderLoop(function() {
if (scene && scene.activeCamera) {
scene.render()
fps.value = engine.getFps().toFixed(2)
}
})
}
createLight()
createAxis()
createGui()
createLine()
runAnimate()
return {
scene,
engine,
}
}
const destroy = () => {
if (sceneResources) {
sceneResources.engine.stopRenderLoop()
sceneResources.engine.dispose()
sceneResources.scene.dispose()
sceneResources = null
}
}
onMounted(async() => {
await nextTick()
})
onUnmounted(() => {
destroy()
})
</script>
水面波纹
学习 CustomMaterial和Texture结合、 Fragment_Definitions、 Fragment_Before_Fog、 AddUniform、 Material.getEffect().setFloat('time', timeDiff)
fps: 0
点击运行
<template>
<div>
学习
<span style="color: red">CustomMaterial和Texture结合</span>、
<span style="color: green">Fragment_Definitions</span>、
<span style="color: blue">Fragment_Before_Fog</span>、
<span style="color: pink">AddUniform</span>、
<span style="color: orange">Material.getEffect().setFloat('time', timeDiff)</span>
<div class="flex space-between">
<div>fps: {{ fps }}</div>
<div @click="onTrigger" class="pointer">点击{{ !isRunning ? '运行' : '关闭' }}</div>
</div>
<canvas v-if="isRunning" id="shaderWaterRipples" class="stage"></canvas>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref, nextTick, onUnmounted } from 'vue'
import {
Engine,
Scene,
ArcRotateCamera,
Vector3,
HemisphericLight,
MeshBuilder,
Texture
} from 'babylonjs'
import {
CustomMaterial
} from 'babylonjs-materials'
let sceneResources
const fps = ref(0)
const isRunning = ref(false)
const onTrigger = async () => {
if (!isRunning.value) {
isRunning.value = true
await nextTick()
sceneResources = await initScene()
} else {
isRunning.value = false
destroy()
}
}
const initScene = async () => {
const ele = document.getElementById("shaderWaterRipples") as any
ele.addEventListener('wheel', function(event) {
// 根据需要处理滚动
// 例如,可以修改相机的半径或角度
event.preventDefault() // 阻止默认滚动行为
})
const engine: any = new Engine(ele, true, {
preserveDrawingBuffer: true,
stencil: true,
disableWebGL2Support: false
})
const scene = new Scene(engine)
scene.useRightHandedSystem = true
const camera = new ArcRotateCamera('camera', -Math.PI / 1.5, Math.PI / 2.2, 15, new Vector3(0, 0, 0), scene)
camera.upperBetaLimit = Math.PI / 2.2
camera.wheelPrecision = 30
camera.panningSensibility = 200
camera.attachControl(ele, true)
camera.setPosition(new Vector3(20, 20, 20))
const createLight = () => {
const light = new HemisphericLight('light',new Vector3(0, 1, 0), scene)
return light
}
const materialShader = (mat) => {
mat.Fragment_Definitions(`
// 用于在图形渲染中创建一种称为“焦散”(caustic)效果的视觉效果
// 焦散效果通常用于模拟光线通过透明介质(如水或玻璃)时的折射和反射
// 产生复杂的光斑和条纹
#define TAU 6.28318530718
#define MAX_ITER 5
#define SPEED 0.3
#define SCALE 30.0
vec4 caustic(vec2 uv) {
// uv * TAU 将纹理坐标 (u, v) 扩展到 (0, 2 * PI) 的范围。这意味着纹理坐标被拉伸,每个分量都乘以 TAU
// mod(uv * TAU, TAU) 将拉伸后的坐标映射回 (0, TAU) 的范围内。这是通过取模运算实现的,确保结果的每个分量都在 [0, TAU) 范围内
// 并减去 150,进行平移
vec2 p = mod(uv * TAU, TAU) - 150.0;
// 根据全局时间变量 time 和定义的速度 SPEED 计算动画偏移量 t
float t = time * SPEED + 23.0;
// 初始化一个二维向量 i,初始值为 p
vec2 i = vec2(p);
// 初始化一个变量 c,用于累加计算
float c = 1.0;
// 定义一个强度变量 intens,用于控制分形的细节
float intens = 0.005;
// 开始一个循环,从 0 到 MAX_ITER-1
for (int n = 0; n < MAX_ITER; n++) {
// 每次迭代调整 t 的值,使分形的迭代逐渐减弱
float nt = t * (1.0 - (3.5 / float(n + 1)));
// 根据 t 更新 i 的值,创建分形的迭代
i = p + vec2(cos(nt - i.x) + sin(nt + i.y), sin(nt - i.y) + cos(t + i.x));
// 根据 i 的值更新 c,用于计算最终的颜色
c += 1.0 / length(vec2(p.x / (sin(i.x + nt) / intens), p.y / (cos(i.y + nt) / intens)));
}
// 将 c 的值标准化,使其在 0 到 1 之间
c /= float(MAX_ITER);
// 对 c 应用非线性变换,增加对比度
c = 1.17 - pow(c, 1.4);
// 根据 c 的值计算颜色,使用 8 次幂函数增加对比度
vec3 color = vec3(pow(abs(c), 8.0));
// 将颜色限制在 [0, 1] 范围内
color = clamp(color + vec3(0.0, 0.0, 0.0), 0.0, 1.0);
// 定义对比度变量,对比度为 0 时无影响
float contrast = 0.0;
// 将颜色与白色混合,对比度为 0 时无影响
color = mix(color, vec3(1.0, 1.0, 1.0), contrast);
vec4 fColor = vec4(color, 0.0);
return fColor;
}
`)
// 用于在图形渲染的像素级别上混合颜色
mat.Fragment_Before_Fog(`
// 首先计算一个名为 coord 的二维向量
// 使用 fract 函数和 vPositionW 变量。vPositionW 是一个包含顶点世界空间坐标的向量
// fract 函数返回参数的小数部分,即 x - floor(x),这里用于获取坐标的小数部分,创建一种“抖动”效果
// 然后,这些小数部分被 SCALE 缩放因子除以,进一步调整坐标的范围
vec2 coord = vec2(fract(vPositionW.x / SCALE), fract(vPositionW.z / SCALE));
// 调用了之前定义的 caustic 函数,传入 vDiffuseUV 作为参数
// vDiffuseUV 是一个包含纹理坐标的向量
// caustic 函数返回一个 vec4 类型的颜色值
// 然后,使用 clamp 函数将结果的颜色值限制在 0.0 到 0.5 之间
// clamp 函数确保颜色值不会超出这个范围
vec4 causticColor = clamp(caustic(vDiffuseUV), 0.0, 0.5);
color = vec4(clamp(mix(color, causticColor, 0.5), 0.0, 1.0).rgb, 1.0);
`)
mat.AddUniform('time', 'float')
const startTime: any = new Date()
mat.onBindObservable.add(function() {
const endTime: any = new Date()
const timeDiff = (endTime - startTime) / 1000.0 // in s
mat.getEffect().setFloat('time', timeDiff)
})
}
const createGround = () => {
const ground = MeshBuilder.CreateGround(
'ground',
{
width: 12,
height: 12
},
scene
)
const grass = new CustomMaterial('grass', scene)
grass.diffuseTexture = new Texture('/images/grass.png', scene)
ground.material = grass
materialShader(grass)
}
const createSphere = () => {
const sphere = MeshBuilder.CreateSphere('sphere', { diameter: 8 }, scene)
const grass = new CustomMaterial('grass', scene)
grass.diffuseTexture = new Texture('/images/grass.png', scene)
sphere.material = grass
sphere.position.y = 5
materialShader(grass)
}
const runAnimate = () => {
engine.runRenderLoop(function() {
if (scene && scene.activeCamera) {
scene.render()
fps.value = engine.getFps().toFixed(2)
}
})
}
createLight()
createGround()
createSphere()
runAnimate()
return {
scene,
engine,
}
}
const destroy = () => {
if (sceneResources) {
sceneResources.engine.stopRenderLoop()
sceneResources.engine.dispose()
sceneResources.scene.dispose()
sceneResources = null
}
}
onMounted(async() => {
await nextTick()
})
onUnmounted(() => {
destroy()
})
</script>
sphere 的溶解
fps: 0
点击运行
<template>
<div>
<div class="flex space-between">
<div>fps: {{ fps }}</div>
<div @click="onTrigger" class="pointer">点击{{ !isRunning ? '运行' : '关闭' }}</div>
</div>
<div v-if="isRunning">
<span class="pointer" @click="changeSituation(1)">效果1</span>
<span class="pointer m-l-20" @click="changeSituation(2)">效果2</span>
<span class="pointer m-l-20" @click="changeSituation(3)">效果3</span>
</div>
<canvas v-if="isRunning" id="shaderSphereDissolve" class="stage"></canvas>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref, nextTick, onUnmounted } from 'vue'
import {
Engine,
Scene,
ArcRotateCamera,
Vector3,
Color4,
HemisphericLight,
MeshBuilder,
Effect,
ShaderMaterial,
Texture
} from 'babylonjs'
import {
AdvancedDynamicTexture,
StackPanel,
Control,
TextBlock,
} from 'babylonjs-gui'
let sceneResources
const fps = ref(0)
const isRunning = ref(false)
const onTrigger = async () => {
if (!isRunning.value) {
isRunning.value = true
await nextTick()
sceneResources = await initScene()
} else {
isRunning.value = false
destroy()
}
}
const situation1Text = () => {
return `
float noiseValue = noise(p + uTime * 1.0); // 随时间变化的噪声
float dissolve = smoothstep(baseColor.g, baseColor.b, noiseValue); // 循环
gl_FragColor = mix(vec4(0.0), baseColor, dissolve);
`
}
const situation2Text = () => {
return `
float noiseValue = noise(p + uTime * 1.0); // 随时间变化的噪声
// 大于某个值则消失
// baseColor.b * noiseValue应该是其他合适的值,现结果的溶解边缘不够圆润
if (uTime > baseColor.g * noiseValue) {
discard;
} else {
gl_FragColor = baseColor;
}
`
}
const situation3Text = () => {
return `
float noiseValue = noise(p + uRandom); // 随时间变化的噪声
// 大于某个值则消失
// baseColor.b * noiseValue应该是其他合适的值,现结果的溶解边缘不够圆润
if (uTime > noiseValue) {
discard;
} else {
gl_FragColor = baseColor;
}
`
}
const situationObj = {
'1': situation1Text(),
'2': situation2Text(),
'3': situation3Text()
}
const curSituation = ref(1)
const changeSituation = async (cur) => {
if (isRunning.value) {
destroy()
curSituation.value = cur
await nextTick()
setTimeout(async() => {
sceneResources = await initScene()
}, 500)
}
}
const initScene = async () => {
let time = 0
const ele = document.getElementById("shaderSphereDissolve") as any
ele.addEventListener('wheel', function(event) {
// 根据需要处理滚动
// 例如,可以修改相机的半径或角度
event.preventDefault() // 阻止默认滚动行为
})
const engine: any = new Engine(ele, true, {
preserveDrawingBuffer: true,
stencil: true,
disableWebGL2Support: false
})
const scene = new Scene(engine)
scene.useRightHandedSystem = true
const camera = new ArcRotateCamera('camera', -Math.PI / 1.5, Math.PI / 2.2, 15, new Vector3(0, 0, 0), scene)
camera.upperBetaLimit = Math.PI / 2.2
camera.wheelPrecision = 30
camera.panningSensibility = 200
camera.attachControl(ele, true)
camera.setPosition(new Vector3(20, 20, 20))
const createLight = () => {
const light = new HemisphericLight('light',new Vector3(1, 1, 0), scene)
return light
}
const createAxis = () => {
const axisX = MeshBuilder.CreateLines(
'axisX', {
colors: [new Color4(1, 0, 0, 1), new Color4(1, 0, 0, 1)],
points: [new Vector3(0, 0, 0), new Vector3(80, 0, 0)]
},
scene
)
const axisY = MeshBuilder.CreateLines(
'axisY', {
colors: [new Color4(0, 1, 0, 1), new Color4(0, 1, 0, 1) ],
points: [new Vector3(0, 0, 0), new Vector3(0, 80, 0) ]
},
scene
)
const axisZ = MeshBuilder.CreateLines(
'axisZ', {
colors: [new Color4(0, 0, 1, 1), new Color4(0, 0, 1, 1)],
points: [new Vector3(0, 0, 0), new Vector3(0, 0, 80)]
},
scene
)
return [axisX, axisY, axisZ]
}
const createGui = async () => {
const adt = AdvancedDynamicTexture.CreateFullscreenUI('UI')
const xBox = MeshBuilder.CreateBox('x', { size: 1 }, scene)
xBox.position = new Vector3(80, 0, 0)
const xPanel = new StackPanel()
xPanel.width = '20px'
xPanel.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_RIGHT
xPanel.verticalAlignment = Control.VERTICAL_ALIGNMENT_BOTTOM
const x = new TextBlock()
x.text = 'X'
x.height = '30px'
x.color = 'red'
adt.addControl(xPanel)
xPanel.addControl(x)
xPanel.linkWithMesh(xBox)
const yBox = MeshBuilder.CreateBox('x', { size: 1 }, scene)
yBox.position = new Vector3(0, 80, 0)
const yPanel = new StackPanel()
yPanel.width = '20px'
yPanel.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_RIGHT
yPanel.verticalAlignment = Control.VERTICAL_ALIGNMENT_BOTTOM
const y = new TextBlock()
y.text = 'Y'
y.height = '30px'
y.color = 'green'
adt.addControl(yPanel)
yPanel.addControl(y)
yPanel.linkWithMesh(yBox)
const zBox = MeshBuilder.CreateBox('x', { size: 1 }, scene)
zBox.position = new Vector3(0, 0, 80)
const zPanel = new StackPanel()
zPanel.width = '20px'
zPanel.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_RIGHT
zPanel.verticalAlignment = Control.VERTICAL_ALIGNMENT_BOTTOM
const z = new TextBlock()
z.text = 'Z'
z.height = '30px'
z.color = 'blue'
adt.addControl(zPanel)
zPanel.addControl(z)
zPanel.linkWithMesh(zBox)
}
const createShader = () => {
Effect.ShadersStore['customVertexShader'] = `
precision highp float;
attribute vec3 position;
uniform mat4 worldViewProjection;
attribute vec2 uv; // 纹理坐标,通常在0到1之间
varying vec2 vUv;
void main(void) {
vUv = uv;
gl_Position = worldViewProjection * vec4(position, 1.0);
}
`
Effect.ShadersStore['customFragmentShader'] = `
precision highp float;
uniform sampler2D uSampler;
uniform float uTime;
uniform float uRandom;
varying vec2 vUv;
vec2 grad(vec2 z) {
int n = int(z.x) + int(z.y) * 11111;
n = (n << 13) ^ n;
n = (n * (n * n * 15731 + 789221) + 1376312589) >> 16;
return vec2(cos(float(n)) ,sin(float(n)));
}
// 柏林噪音
float noise(vec2 p) {
vec2 i = vec2(floor(p));
vec2 f = fract(p);
vec2 u = f * f * (3.0 - 2.0 * f);
float mix1 = mix(dot(grad(i + vec2(0, 0)), f - vec2(0.0, 0.0)), dot(grad(i + vec2(1, 0)), f - vec2(1.0, 0.0)), u.x);
float mix2 = mix(dot(grad(i + vec2(0, 1)), f - vec2(0.0, 1.0)), dot(grad(i + vec2(1, 1)), f - vec2(1.0, 1.0)), u.x);
return mix(mix1, mix2, u.y) * 0.5 + 0.5;
}
void main(void) {
// texSampler:一个二维纹理采样器,它是纹理对象的引用
// texCoord:纹理坐标,通常在0到1之间(如果纹理没有使用非归一化坐标的话)
// OpenGL ES 2.0 或 OpenGL 3.x 之前的版本
// vec4 color = texture2D(texSampler, texCoord);
// gl_FragColor = texture2D(textureSampler, vUv);
// OpenGL 3.x 及更高版本
// vec4 color = texture(texSampler, texCoord);
// 基础颜色
vec4 baseColor = texture(uSampler, vUv);
vec2 p = vUv * 10.0; // 缩放噪声
${situationObj[curSituation.value]}
}
`
const sphereMat = new ShaderMaterial(
'custom',
scene, {
vertex: 'custom',
fragment: 'custom'
}, {
attributes: ['position', 'uv'],
uniforms: ['worldViewProjection', 'uSampler', 'uTime'],
samplers: ['uSampler'],
}
)
const texture = new Texture('/images/ground.jpg', scene)
sphereMat.setTexture('uSampler', texture)
sphereMat.setFloat('uTime', time)
sphereMat.setFloat('uRandom', Number((Math.random() * 123).toFixed(8)))
const sphere = MeshBuilder.CreateSphere('sphere', {
diameter: 10
}, scene)
sphere.material = sphereMat
sphere.material.alpha = 0 // 透明度未0
return sphereMat
}
const runAnimate = () => {
engine.runRenderLoop(function() {
if (scene && scene.activeCamera) {
scene.render()
fps.value = engine.getFps().toFixed(2)
}
})
}
createLight()
createAxis()
createGui()
const sphereMat = createShader()
runAnimate()
scene.onBeforeRenderObservable.add(() => {
time += curSituation.value === 1 ? 0.02 : 0.005
sphereMat.setFloat('uTime', time)
})
return {
scene,
engine,
}
}
const destroy = () => {
if (sceneResources) {
sceneResources.engine.stopRenderLoop()
sceneResources.engine.dispose()
sceneResources.scene.dispose()
sceneResources = null
}
}
onMounted(async() => {
await nextTick()
})
onUnmounted(() => {
destroy()
})
</script>
粒子的颜色变换
fps: 0
点击运行
<template>
<div>
<div class="flex space-between">
<div>fps: {{ fps }}</div>
<div @click="onTrigger" class="pointer">点击{{ !isRunning ? '运行' : '关闭' }}</div>
</div>
<canvas v-if="isRunning" id="shaderParticle" class="stage"></canvas>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref, nextTick, onUnmounted } from 'vue'
import {
Engine,
Scene,
ArcRotateCamera,
Vector3,
Color4,
Texture,
HemisphericLight,
MeshBuilder,
Effect,
ParticleSystem
} from 'babylonjs'
import {
AdvancedDynamicTexture,
StackPanel,
Control,
TextBlock,
} from 'babylonjs-gui'
let sceneResources
const fps = ref(0)
const isRunning = ref(false)
const onTrigger = async () => {
if (!isRunning.value) {
isRunning.value = true
await nextTick()
sceneResources = await initScene()
} else {
isRunning.value = false
destroy()
}
}
const initScene = async () => {
const ele = document.getElementById("shaderParticle") as any
ele.addEventListener('wheel', function(event) {
// 根据需要处理滚动
// 例如,可以修改相机的半径或角度
event.preventDefault() // 阻止默认滚动行为
})
const engine: any = new Engine(ele, true, {
preserveDrawingBuffer: true,
stencil: true,
disableWebGL2Support: false
})
const scene = new Scene(engine)
scene.useRightHandedSystem = true
const camera = new ArcRotateCamera('camera', -Math.PI / 1.5, Math.PI / 2.2, 15, new Vector3(0, 0, 0), scene)
camera.upperBetaLimit = Math.PI / 2.2
camera.wheelPrecision = 30
camera.panningSensibility = 200
camera.attachControl(ele, true)
camera.setPosition(new Vector3(10, 10, 10))
const createLight = () => {
const light = new HemisphericLight('light',new Vector3(1, 1, 0), scene)
return light
}
const createAxis = () => {
const axisX = MeshBuilder.CreateLines(
'axisX', {
colors: [new Color4(1, 0, 0, 1), new Color4(1, 0, 0, 1)],
points: [new Vector3(0, 0, 0), new Vector3(80, 0, 0)]
},
scene
)
const axisY = MeshBuilder.CreateLines(
'axisY', {
colors: [new Color4(0, 1, 0, 1), new Color4(0, 1, 0, 1) ],
points: [new Vector3(0, 0, 0), new Vector3(0, 80, 0) ]
},
scene
)
const axisZ = MeshBuilder.CreateLines(
'axisZ', {
colors: [new Color4(0, 0, 1, 1), new Color4(0, 0, 1, 1)],
points: [new Vector3(0, 0, 0), new Vector3(0, 0, 80)]
},
scene
)
return [axisX, axisY, axisZ]
}
const createGui = async () => {
const adt = AdvancedDynamicTexture.CreateFullscreenUI('UI')
const xBox = MeshBuilder.CreateBox('x', { size: 1 }, scene)
xBox.position = new Vector3(80, 0, 0)
const xPanel = new StackPanel()
xPanel.width = '20px'
xPanel.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_RIGHT
xPanel.verticalAlignment = Control.VERTICAL_ALIGNMENT_BOTTOM
const x = new TextBlock()
x.text = 'X'
x.height = '30px'
x.color = 'red'
adt.addControl(xPanel)
xPanel.addControl(x)
xPanel.linkWithMesh(xBox)
const yBox = MeshBuilder.CreateBox('x', { size: 1 }, scene)
yBox.position = new Vector3(0, 80, 0)
const yPanel = new StackPanel()
yPanel.width = '20px'
yPanel.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_RIGHT
yPanel.verticalAlignment = Control.VERTICAL_ALIGNMENT_BOTTOM
const y = new TextBlock()
y.text = 'Y'
y.height = '30px'
y.color = 'green'
adt.addControl(yPanel)
yPanel.addControl(y)
yPanel.linkWithMesh(yBox)
const zBox = MeshBuilder.CreateBox('x', { size: 1 }, scene)
zBox.position = new Vector3(0, 0, 80)
const zPanel = new StackPanel()
zPanel.width = '20px'
zPanel.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_RIGHT
zPanel.verticalAlignment = Control.VERTICAL_ALIGNMENT_BOTTOM
const z = new TextBlock()
z.text = 'Z'
z.height = '30px'
z.color = 'blue'
adt.addControl(zPanel)
zPanel.addControl(z)
zPanel.linkWithMesh(zBox)
}
const createParticle = () => {
const emitter = MeshBuilder.CreateBox('box', { size: 10 }, scene)
emitter.isVisible = false
const effect = engine.createEffectForParticles('customParticle', ['time'])
Effect.ShadersStore['customParticleFragmentShader'] = `
precision highp float;
varying vec2 vUV; // babylon.js提供
varying vec4 vColor; // babylon.js提供
uniform sampler2D diffuseSampler; // babylon.js提供
uniform float time;
void main() {
vec2 position = vUV;
vec2 center = vec2(0.5, 0.5);
float color = 0.0;
color = sin(distance(position, center) * 10.0 + time * vColor.g);
vec4 baseColor = texture2D(diffuseSampler, vUV);
gl_FragColor = baseColor * vColor * vec4(vec3(color, color, color), 1.0);
}
`
const particleSystem = new ParticleSystem("particles", 4000, scene, effect)
particleSystem.particleTexture = new Texture("/images/flare.png", scene)
particleSystem.minSize = 0.1
particleSystem.maxSize = 1.0
particleSystem.minLifeTime = 0.5
particleSystem.maxLifeTime = 5.0
particleSystem.minEmitPower = 0.5
particleSystem.maxEmitPower = 3.0
particleSystem.emitter = emitter
particleSystem.emitRate = 100;
particleSystem.blendMode = ParticleSystem.BLENDMODE_ONEONE
particleSystem.direction1 = new Vector3(-1, 1, -1)
particleSystem.direction2 = new Vector3(1, 1, 1)
particleSystem.color1 = new Color4(1, 1, 0, 1)
particleSystem.color2 = new Color4(0, 1, 0, 1)
particleSystem.gravity = new Vector3(0, -4.8, 0)
particleSystem.start()
let time = 0
let order = 0.1
effect.onBind = function () {
effect.setFloat('time', time)
time += order
if (time > 100 || time < 0) {
order *= -1
}
}
}
const runAnimate = () => {
engine.runRenderLoop(function() {
if (scene && scene.activeCamera) {
scene.render()
fps.value = engine.getFps().toFixed(2)
}
})
}
createLight()
createAxis()
createGui()
createParticle()
runAnimate()
return {
scene,
engine,
}
}
const destroy = () => {
if (sceneResources) {
sceneResources.engine.stopRenderLoop()
sceneResources.engine.dispose()
sceneResources.scene.dispose()
sceneResources = null
}
}
onMounted(async() => {
await nextTick()
})
onUnmounted(() => {
destroy()
})
</script>