【Three.js基础学习】29.Hologram Shader

发布于:2024-12-18 ⋅ 阅读:(93) ⋅ 点赞:(0)

前言

three.js 通过着色器如何实现全息影像,以及一些动态的效果。

一些难点的思维,代码目录

下面图是摄像机视角观看影响上的时候,如何实现光影的渐变,透视以及叠加等。

一、代码

1.index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Holographic</title>
    <link rel="stylesheet" href="./style.css">
</head>
<body>
    <canvas class="webgl"></canvas>
    <script type="module" src="./script.js"></script>
</body>
</html>

2.style.css

*
{
    margin: 0;
    padding: 0;
}

html,
body
{
    overflow: hidden;
}

.webgl
{
    position: fixed;
    top: 0;
    left: 0;
    outline: none;
}

3.script.js

import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
import GUI from 'lil-gui'
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'
import holographicVertexShader from './shaders/holographic/vertex.glsl'
import holographicFragmentShader from './shaders/holographic/fragment.glsl'


/**
 * Base
 */
// Debug
const gui = new GUI()

// Canvas
const canvas = document.querySelector('canvas.webgl')

// Scene
const scene = new THREE.Scene()

// Loaders
const gltfLoader = new GLTFLoader()

/**
 * Sizes
 */
const sizes = {
    width: window.innerWidth,
    height: window.innerHeight
}

window.addEventListener('resize', () =>
{
    // Update sizes
    sizes.width = window.innerWidth
    sizes.height = window.innerHeight

    // Update camera
    camera.aspect = sizes.width / sizes.height
    camera.updateProjectionMatrix()

    // Update renderer
    renderer.setSize(sizes.width, sizes.height)
    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
})

/**
 * Camera
 */
// Base camera
const camera = new THREE.PerspectiveCamera(25, sizes.width / sizes.height, 0.1, 100)
camera.position.set(7, 7, 7)
scene.add(camera)

// Controls
const controls = new OrbitControls(camera, canvas)
controls.enableDamping = true

/**
 * Renderer
 */
const rendererParameters = {}
rendererParameters.clearColor = '#1d1f2a'

const renderer = new THREE.WebGLRenderer({
    canvas: canvas,
    antialias: true
})
renderer.setClearColor(rendererParameters.clearColor)
renderer.setSize(sizes.width, sizes.height)
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))

gui
    .addColor(rendererParameters, 'clearColor')
    .onChange(() =>
    {
        renderer.setClearColor(rendererParameters.clearColor)
    })

/**
 * Material
 */
const materialParameters = {}
materialParameters.color = '#70c1ff'

gui.addColor(materialParameters,'color')
    .onChange(()=>{
        material.uniforms.uColor.value.set(materialParameters.color)
    })


const material = new THREE.ShaderMaterial({
    vertexShader:holographicVertexShader,
    fragmentShader:holographicFragmentShader,
    uniforms:{
        uTime:new THREE.Uniform(0),
        uColor:new THREE.Uniform(new THREE.Color(materialParameters.color))
    },
    transparent:true, // 设置透明材质
    side:THREE.DoubleSide,
    depthWrite:false, // 停止深度写入
    blending:THREE.AdditiveBlending, // 混合叠加 叠加起来的颜色更亮
    
})

/**
 * Objects
 */
// Torus knot
const torusKnot = new THREE.Mesh(
    new THREE.TorusKnotGeometry(0.6, 0.25, 128, 32),
    material
)
torusKnot.position.x = 3
scene.add(torusKnot)

// Sphere
const sphere = new THREE.Mesh(
    new THREE.SphereGeometry(),
    material
)
sphere.position.x = - 3
scene.add(sphere)

// Suzanne
let suzanne = null
gltfLoader.load(
    './suzanne.glb',
    (gltf) =>
    {
        suzanne = gltf.scene
        suzanne.traverse((child) =>
        {
            if(child.isMesh)
                child.material = material
        })
        scene.add(suzanne)
    }
)

/**
 * Animate
 */
const clock = new THREE.Clock()

const tick = () =>
{
    const elapsedTime = clock.getElapsedTime()

    // Update 
    material.uniforms.uTime.value = elapsedTime;

    // Rotate objects
    if(suzanne)
    {
        suzanne.rotation.x = - elapsedTime * 0.1
        suzanne.rotation.y = elapsedTime * 0.2
    }

    sphere.rotation.x = - elapsedTime * 0.1
    sphere.rotation.y = elapsedTime * 0.2

    torusKnot.rotation.x = - elapsedTime * 0.1
    torusKnot.rotation.y = elapsedTime * 0.2

    // Update controls
    controls.update()

    // Render
    renderer.render(scene, camera)

    // Call tick again on the next frame
    window.requestAnimationFrame(tick)
}

tick()

4.fragment.glsl 顶点着色器


uniform float uTime; 
uniform vec3 uColor;

varying vec3 vPosition;
varying vec3 vNormal;

/* 
    mod(X, Y)函数返回X对Y取模的结果。如果X和Y都是数组,mod函数会对数组的每个元素分别进行取模运算
    对上一步的结果取模 1.0,即计算该值除以 1.0 的余数。
    由于取模 1.0 的效果是将值限制在 0 到 1 之间(不包括1)
    ,这个操作实际上是在做一个周期性变换,
    使得 vUv.x 在 0 到 10 之间变化时,结果会在 0 到 1 之间循环

    pow(x,y)
    x的y次方。如果x小于0,结果是未定义的。同样,如果x=0并且y<=0,结果也是未定义的

    normalize 标准化向量,返回一个方向和x相同但长度为1的向量
    dot 向量x,y之间的点积

    Fresnel 根据相机角度,进行判断,相同方向1,直角0 ,相反 1
    同样法线应该渲染,vertex.glsl
*/

void main(){
    // Normal 法线标准值重新定义为1
    vec3 normal = normalize(vNormal); // 两个角度大的法线之间,的法线长度不是标准,重新初始化1
    if(!gl_FrontFacing)
        normal *= - 1.0;

    // Stripes 条纹 
    float stripes = mod((vPosition.y - uTime * 0.02) * 20.0, 1.0);
    stripes = pow(stripes, 3.0);

    // Fresnel 菲涅尔效果 全息
    vec3 viewDirection = normalize(vPosition - cameraPosition) ; // 模型位置 - 相机位置,得到视图位置
    float fresnel = dot(viewDirection,normal) + 1.0; // 这里加上一,原本的0变成1,-1变成0
    fresnel = pow(fresnel,2.0);

    // Falloff 平滑重映射  想要实现边界处 1-0转变 逐渐消失的效果
    float falloff = smoothstep(0.8,0.0,fresnel);
    

    // Holographic 全息
    float holographic = stripes * fresnel;
    holographic += fresnel * 1.25;
    holographic *= falloff;


    // Final Color
    gl_FragColor = vec4(uColor,holographic);
    #include <tonemapping_fragment>
    #include <colorspace_fragment>
}

5.vertex.glsl 片段着色器

uniform float uTime; 

varying vec3 vPosition;
varying vec3 vNormal;

/* 
    smoothstep(edge0, edge1, x) 
    如果x <= edge0,返回0.0 ;如果x >= edge1 返回1.0;
    如果edge0 < x < edge1,则执行0~1之间的平滑埃尔米特差值。
    如果edge0 >= edge1,结果是未定义的。
*/

#include ../includes/random2D.glsl

void main(){

    // Position
    vec4 modelPosition = modelMatrix * vec4(position,1.0);

    // Glitch  random2D函数会给我0-1的数据,减去0.5 相机的中心位置就不会偏移
    float glitchTime = uTime - modelPosition.y; // 设置故障时间
    float glitchStrength = sin(glitchTime) +  sin(glitchTime * 3.45) +  sin(glitchTime * 8.76); // 改变频率
    glitchStrength /= 3.0;
    glitchStrength = smoothstep(0.3,1.0,glitchStrength);
    glitchStrength *= 0.25;
    modelPosition.x += (random2D(modelPosition.xz + uTime) - 0.5) * glitchStrength;
    modelPosition.z += (random2D(modelPosition.zx + uTime) - 0.5) * glitchStrength;


    gl_Position = projectionMatrix * viewMatrix * modelPosition;

    // Model normal
    /* 
        当第四个值为1.0时,向量为“同质”,所有3个变换(翻译、旋转、比例)都将被应用。
        当第四个值为0.0时,向量不是“同质的”,因此不会应用转换
        在正常情况下是理想的,因为正常不是一个职位,而是一个
    */
    vec4 modelNormal = modelMatrix * vec4(normal,0.0); // 为什么设置0.0 就是不希望正常,保持法线向上

    vPosition = modelPosition.xyz;
    vNormal = modelNormal.xyz;
}

6.random2D.glsl 随机数方法

float random2D(vec2 value)
{
    return fract(sin(dot(value.xy, vec2(12.9898,78.233))) * 43758.5453123);
}

二、效果

Hologram Shader


总结

全息影像显示,可以加到自己个人的项目中。感觉还不错!


网站公告

今日签到

点亮在社区的每一天
去签到