【Three.js基础学习】35.Particles Cursor Animation Shader
前言
关于着色器应用和画布,实现黑白色照片动态效果
一、代码
script.js
import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
import particlesVertexShader from './shaders/particles/vertex.glsl'
import particlesFragmentShader from './shaders/particles/fragment.glsl'
import { DoubleSide } from 'three'
/*
回顾:
颗粒变成圆盘
它们的尺寸取决于图片的亮度
修改粒子的颜色以匹配图片
实现动画,让粒子短暂的升高,随后恢复
用画布画,让画布移动粒子
二维画布(2D canvas)
我们打算
创建一个填充黑色的2D画布
在光标所在的每个框架上绘制一个白色光晕
在每个框架上稍微淡化整个画布
将画布用作粒子的位移纹理
*/
/**
* Base
*/
// Canvas
const canvas = document.querySelector('canvas.webgl')
// Scene
const scene = new THREE.Scene()
// Loaders
const textureLoader = new THREE.TextureLoader()
/**
* Sizes
*/
const sizes = {
width: window.innerWidth,
height: window.innerHeight,
pixelRatio: Math.min(window.devicePixelRatio, 2)
}
window.addEventListener('resize', () =>
{
// Update sizes
sizes.width = window.innerWidth
sizes.height = window.innerHeight
sizes.pixelRatio = Math.min(window.devicePixelRatio, 2)
// Materials
particlesMaterial.uniforms.uResolution.value.set(sizes.width * sizes.pixelRatio, sizes.height * sizes.pixelRatio)
// Update camera
camera.aspect = sizes.width / sizes.height
camera.updateProjectionMatrix()
// Update renderer
renderer.setSize(sizes.width, sizes.height)
renderer.setPixelRatio(sizes.pixelRatio)
})
/**
* Camera
*/
// Base camera
const camera = new THREE.PerspectiveCamera(35, sizes.width / sizes.height, 0.1, 100)
camera.position.set(0, 0, 18)
scene.add(camera)
// Controls
const controls = new OrbitControls(camera, canvas)
controls.enableDamping = true
/**
* Renderer
*/
const renderer = new THREE.WebGLRenderer({
canvas: canvas,
antialias: true
})
renderer.setClearColor('#181818')
renderer.setSize(sizes.width, sizes.height)
renderer.setPixelRatio(sizes.pixelRatio)
/*
Displacement 位移
*/
const displacement = {}
// 2D canvas
displacement.canvas = document.createElement('canvas');
displacement.canvas.width = 128;
displacement.canvas.height = 128;
displacement.canvas.style.position = 'fixed'
displacement.canvas.style.width = '256px'
displacement.canvas.style.height = '256px'
displacement.canvas.style.top = 0
displacement.canvas.style.left = 0
displacement.canvas.style.zIndex = 10
document.body.append(displacement.canvas)
// Context
displacement.context = displacement.canvas.getContext('2d') // 上下文
// displacement.context.fillStyle = 'red' // 这里注意 要在添加坐标的上面写才能改变颜色
displacement.context.fillRect(0,0,displacement.canvas.width,displacement.canvas.height) // 添加
// Glow image
displacement.glowImage = new Image()
displacement.glowImage.src = './glow.png'
// Interactive plane
displacement.interativePlane = new THREE.Mesh(
new THREE.PlaneGeometry(10,10),
new THREE.MeshBasicMaterial({color:'red',side:DoubleSide})
)
displacement.interativePlane.visible = false
scene.add(displacement.interativePlane)
// Raycaster
displacement.raycaster = new THREE.Raycaster() // 射线投射器
// Coordinates 坐标
displacement.screenCursor = new THREE.Vector2(9999,9999) // 屏幕光标坐标
displacement.canvasCursor = new THREE.Vector2(9999,9999) // 画布光标坐标,这样就可以更新画布在tick
displacement.canvasCursorPrevious = new THREE.Vector2(9999,9999) // 这个值 能像画布一样 用来实现鼠标停止 则最后一组淡出
window.addEventListener('pointermove',(event)=>{ // 监听鼠标移动
displacement.screenCursor.x = (event.clientX / sizes.width) * 2 - 1; //(-1到1)
displacement.screenCursor.y = - (event.clientY / sizes.height) * 2 + 1;
// console.log(displacement.screenCursor.x, displacement.screenCursor.y ,'zhi')
})
/*
texture 画布纹理类
*/
displacement.texture = new THREE.CanvasTexture(displacement.canvas)
/**
* Particles
*/
const particlesGeometry = new THREE.PlaneGeometry(10, 10, 128, 128)
particlesGeometry.setIndex(null)
particlesGeometry.deleteAttribute('normal')
const intensitiesArray = new Float32Array(particlesGeometry.attributes.position.count) // 根据粒子强度
const anglesArray = new Float32Array(particlesGeometry.attributes.position.count) // 根据粒子角度
for(let i = 0; i < particlesGeometry.attributes.position.count; i++){
intensitiesArray[i] = Math.random() // 增加随机性
anglesArray[i] = Math.random() * Math.PI * 2 // 增加随机性
}
particlesGeometry.setAttribute('aIntensity', new THREE.BufferAttribute(intensitiesArray,1))
particlesGeometry.setAttribute('aAngle', new THREE.BufferAttribute(anglesArray,1))
const particlesMaterial = new THREE.ShaderMaterial({
vertexShader: particlesVertexShader,
fragmentShader: particlesFragmentShader,
uniforms:
{
uResolution: new THREE.Uniform(new THREE.Vector2(sizes.width * sizes.pixelRatio, sizes.height * sizes.pixelRatio)),
uPicureTexture:new THREE.Uniform(textureLoader.load('./picture-2.png')), // 引入图片
uDisplacementTexture: new THREE.Uniform(displacement.texture)
},
// blending:THREE.AdditiveBlending
})
const particles = new THREE.Points(particlesGeometry, particlesMaterial) /// Points 粒子朝向自己
scene.add(particles)
/**
* Animate
*/
const tick = () =>
{
// Update controls
controls.update()
/*
Raycaster
*/
displacement.raycaster.setFromCamera(displacement.screenCursor,camera) // 设置光线发射器 从鼠标发出
const intersecets = displacement.raycaster.intersectObject(displacement.interativePlane)
if(intersecets.length){
// console.log(intersecets[0])
const uv = intersecets[0].uv
displacement.canvasCursor.x = uv.x * displacement.canvas.width
displacement.canvasCursor.y = (1 - uv.y) * displacement.canvas.height // 从0-1,但是画布画起来是反的 因此1-0
}
/*
Displacement
*/
displacement.context.globalCompositeOperation = 'source-atop' // 在这一帧 恢复默认值
displacement.context.globalAlpha = 0.02 // 设置alpha
displacement.context.fillRect(0,0,displacement.canvas.width,displacement.canvas.height)
// Spedd alpha
const cursorDistance = displacement.canvasCursorPrevious.distanceTo(displacement.canvasCursor) // 计算到上一个画布坐标的距离
displacement.canvasCursorPrevious.copy(displacement.canvasCursor)
const alpha = Math.min(cursorDistance * 0.1,1); // 确保不会超过1
// 两个问题 1. 光辉的大小 glowSize 2. 光辉边缘 displacement.canvasCursor.x - glowSize * 0.5
// Draw glow
const glowSize = displacement.canvas.width * 0.25
displacement.context.globalCompositeOperation = 'lighten' // 全球复合操作 让其变轻
displacement.context.globalAlpha = alpha // 在下一帧还原
displacement.context.drawImage(
displacement.glowImage,
displacement.canvasCursor.x - glowSize * 0.5,
displacement.canvasCursor.y - glowSize * 0.5,
32,
32
)
// texture
displacement.texture.needsUpdate = true
// Render
renderer.render(scene, camera)
// Call tick again on the next frame
window.requestAnimationFrame(tick)
}
tick()
style.css
*
{
margin: 0;
padding: 0;
}
html,
body
{
overflow: hidden;
}
.webgl
{
position: fixed;
top: 0;
left: 0;
outline: none;
}
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Particles cursor animation</title>
<link rel="stylesheet" href="./style.css">
</head>
<body>
<canvas class="webgl"></canvas>
<script type="module" src="./script.js"></script>
</body>
</html>
fragment.glsl
varying vec3 vColor;
void main()
{
vec2 uv = gl_PointCoord;
float distanceToCenter = distance(uv,vec2(0.5));
if(distanceToCenter > 0.5)
discard; // 表示丢弃物 大于0.5丢弃 不要
gl_FragColor = vec4(vColor, 1.0);
#include <tonemapping_fragment>
#include <colorspace_fragment>
}
vertex.glsl
uniform vec2 uResolution;
uniform sampler2D uPicureTexture;
uniform sampler2D uDisplacementTexture;
attribute float aIntensity;
attribute float aAngle;
varying vec3 vColor;
void main()
{
// Displacement
vec3 newPosition = position;
float displacementIntensity = texture(uDisplacementTexture, uv).r; // 位移强度
displacementIntensity = smoothstep(0.1,0.3,displacementIntensity);
vec3 displacement = vec3(
cos(aAngle) * 0.2,
sin(aAngle) * 0.2,
1.0
);
displacement = normalize(displacement);
displacement *= displacementIntensity;
displacement *= 3.0;
displacement *= aIntensity;
newPosition += displacement;
// Final position
vec4 modelPosition = modelMatrix * vec4(newPosition, 1.0);
vec4 viewPosition = viewMatrix * modelPosition;
vec4 projectedPosition = projectionMatrix * viewPosition;
gl_Position = projectedPosition;
// Picture
float pictureIntensity = texture(uPicureTexture, uv).r ;
// Point size
gl_PointSize = 0.15 * pictureIntensity * uResolution.y; // 乘 强度
gl_PointSize *= (1.0 / - viewPosition.z);
// Varyings
vColor = vec3(pow(pictureIntensity, 2.0));
}
二、效果
canvas 结合 着色器 实现黑白照片动态效果