前言
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
总结
全息影像显示,可以加到自己个人的项目中。感觉还不错!