当前位置: 首页 > news >正文

【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 结合 着色器 实现黑白照片动态效果

相关文章:

  • 【笔记】对抗训练-GAN
  • 论文精度:双分支图Transformer网络:视频驱动的3D人体网格重建新突破
  • 记一次某网络安全比赛三阶段webserver应急响应解题过程
  • Spring Cloud主要组件介绍
  • 解密Oracle数据库RAC技术原理:构建高可用、可扩展的集群数据库
  • sward安装及入门指南
  • 揭秘大数据 | 20、软件定义数据中心
  • ELK+Filebeat 深度部署指南与实战测试全解析
  • MySQL聚合查询
  • 利用 限制torch线程数与异步方法提升声纹识别效率
  • 旧版 VMware 虚拟机迁移至 KVM 平台-案例2
  • 动手强化学习之马尔可夫决策(机器人篇)
  • keil如何创建一个工程
  • STM32单片机入门学习——第36节: [11-1] SPI通信协议
  • C++中extern关键字
  • 【微信开发者工具】解决微信开发工具的调试器加载错误,从任务栏打开工具可能导致该问题,请不要从任务栏启动工具
  • Redis 常问知识
  • MCP的另一面
  • Spark-SQL
  • 贪心算法(18)(java)距离相等的条形码
  • A股三大股指涨跌互现:黄金股再度走强,两市成交10900亿元
  • 上海市政府常务会议部署多措并举促进消费,提高居民收入,减轻家庭负担
  • 美国多地举行抗议活动,特朗普经济政策支持率创新低
  • 东北三省,十年少了一个“哈尔滨”
  • 央视网评论员:婚约不是性许可——山西订婚强奸案背后的性教育盲区
  • 文理医工“四轮驱动”,复旦六大新工科创新学院核心团队均亮相