Three.js材质纹理扩散过渡
Three.js材质纹理扩散过渡
import * as THREE from "three";
import { ThreeHelper } from "@/src/ThreeHelper";
import { LoadGLTF, MethodBaseSceneSet } from "@/src/ThreeHelper/decorators";
import { MainScreen } from "@/src/components/Three/Canvas";
import { Injectable } from "@/src/ThreeHelper/decorators/DI";
import type { GUI } from "dat.gui";
import type { GLTF } from "three/examples/jsm/loaders/GLTFLoader";
import EventMesh from "@/src/ThreeHelper/decorators/EventMesh";
import { noise } from "@/src/ThreeHelper/addons/perlinNoise";
import { gsap } from "gsap";
@Injectable
export class Main extends MainScreen {
static instance: Main;
clock = new THREE.Clock();
iTime = { value: 0 };
iProgress = { value: 0.15 };
startPoint = { value: new THREE.Vector3(-0.02, 0.2, 0.7) };
// 初始贴图
matcap = {
value: this.helper.loadTexture("/public/textures/5E5855_C6C4CD_C89B67_8F8E98-512px.png"),
};
matcap2 = {
value: this.helper.loadTexture("/public/textures/B6B8B1_994A24_315C81_927963-512px.png"),
};
textures = [
this.helper.loadTexture("/public/textures/1A2461_3D70DB_2C3C8F_2C6CAC-512px.png"),
this.helper.loadTexture("/public/textures/1B1B1B_999999_575757_747474-512px.png"),
this.helper.loadTexture("/public/textures/3E2335_D36A1B_8E4A2E_2842A5-512px.png"),
this.helper.loadTexture("/public/textures/3F3A2F_91D0A5_7D876A_94977B-512px.png"),
this.helper.loadTexture("/public/textures/48270F_C4723B_9B5728_7B431B-512px.png"),
this.helper.loadTexture("/public/textures/4C462E_6D876C_9AAC8F_9AABA6-512px.png"),
this.helper.loadTexture("/public/textures/4F5246_8C8D84_7B7C74_131611-512px.png"),
this.helper.loadTexture("/public/textures/5E5855_C6C4CD_C89B67_8F8E98-512px.png"),
this.helper.loadTexture("/public/textures/B6B8B1_994A24_315C81_927963-512px.png"),
this.helper.loadTexture("/public/textures/C7C7D7_4C4E5A_818393_6C6C74-512px.png"),
];
play?: (swap: boolean) => void;
playing = false;
constructor(private helper: ThreeHelper) {
super(helper);
helper.main = this;
Main.instance = this;
this.init();
}
@MethodBaseSceneSet({
addAxis: false,
cameraPosition: new THREE.Vector3(0, 0, 2.7),
cameraTarget: new THREE.Vector3(0, 0, 0),
useRoomLight: true,
near: 0.1,
far: 800,
})
init() {
const { helper } = this;
this.loadModel();
}
@EventMesh.OnMouseDown(Main)
click(result: typeof EventMesh.RayInfo) {
// console.log(this.helper.gui.__controllers[1].name())
// this.helper.gui?.__controllers[1].fire()
if (!this.playing && result && result.point) {
this.startPoint.value.copy(result.point);
this.play && this.play(true);
}
}
@LoadGLTF("/public/models/猴头.glb")
// @LoadGLTF("/public/models/猴头细分x4.glb")
loadModel(gltf?: GLTF) {
if (gltf) {
// this.helper.add(gltf.scene);
// EventMesh.setIntersectObjects(gltf.scene.children);
gltf.scene.traverse((obj) => {
if (obj.type == "Mesh") {
const meshTemp = obj as THREE.Mesh;
const material = new THREE.ShaderMaterial({
uniforms: {
matcap: this.matcap,
matcap2: this.matcap2,
startPoint: this.startPoint,
iProgress: this.iProgress,
iTime: this.iTime,
},
side: THREE.DoubleSide,
defines: { PI: Math.PI },
vertexShader: /* glsl */ `
varying vec2 vUv;
varying vec3 vNormal;
varying vec3 vPosition;
varying vec3 vViewPosition;
attribute float aRandom;
attribute vec3 aCenter;
uniform float iProgress;
uniform vec3 startPoint;
#include <common>
mat4 rotation3d(vec3 axis, float angle) {
axis = normalize(axis);
float s = sin(angle);
float c = cos(angle);
float oc = 1.0 - c;
return mat4(
oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, 0.0,
oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0.0,
oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0.0,
0.0, 0.0, 0.0, 1.0
);
}
void main() {
vUv = uv;
vNormal = normalMatrix * normalize( normal );
vPosition = position;
vec3 transform = position - aCenter;
float distancePoint = distance( startPoint, position );
float percent = iProgress * 4.;
float diff = percent - distancePoint;
distancePoint = diff * 1.;
distancePoint = clamp( distancePoint, 0., 1.);
// float intensity = smoothstep( 0., iProgress * 4.,distancePoint);
float intensity = distancePoint;
// mat4 rotation = rotation3d(vec3(normal), PI * 2. * intensity);
mat4 rotation = rotation3d(vec3(normal), PI * 2. * intensity);
transform += ((( normal * intensity ) * position * (1.0 - intensity))) / 2.;
transform = (rotation * vec4(transform,1.)).xyz;
// transform = (rotation * vec4(transform,1.)).xyz;
transform += aCenter;
vec4 modelPosition = modelMatrix * vec4(transform, 1.0);
vec4 modelViewPosition = viewMatrix * modelPosition;
gl_Position = projectionMatrix * modelViewPosition;
vViewPosition = - modelViewPosition.xyz;
}
`,
fragmentShader: /* glsl */ `
varying vec3 vNormal;
varying vec2 vUv;
varying vec3 vPosition;
varying vec3 vViewPosition;
uniform float iTime;
uniform float iProgress;
uniform vec3 startPoint;
uniform sampler2D matcap;
uniform sampler2D matcap2;
${noise}
void main() {
vec3 viewDir = normalize( vViewPosition );
vec3 x = normalize( vec3( viewDir.z, 0.0, - viewDir.x ) );
vec3 y = cross( viewDir, x );
vec2 uv = vec2( dot( x, vNormal ), dot( y, vNormal ) ) * 0.495 + 0.5; // 0.495 to remove artifacts caused by undersized matcap disks
float noiseVal = noise(vPosition * 20.);
float distancePoint = distance( startPoint, vPosition );
//TODO 受物体的体积影响
float intensity = smoothstep( 0., iProgress * 4.,distancePoint);
vec3 matcapColor = texture2D(matcap,uv).rgb;
vec3 matcapColor2 = texture2D(matcap2,uv).rgb;
float range = (1.0 - intensity) * pow( intensity, (iProgress + 2.) * 8.) * 100.;
range = (range * noiseVal * 3.) + pow(intensity,4.);
// matcapColor = vec3(range);
range = clamp(range,0.,1.);
// range = smoothstep(0.,1.,range);
matcapColor = mix(matcapColor2,matcapColor,range);
// matcapColor *= vec3((noiseVal) * intensity);
gl_FragColor = vec4(matcapColor, 1.0);
}
`,
});
/**
* tip: 几何体去掉indexed 点的数量会增加数倍 不再计算共用顶点
*/
// const geometry = meshTemp.geometry;
const geometry = meshTemp.geometry.toNonIndexed();
const mesh = new THREE.Mesh(geometry, material);
this.setAttribute(geometry);
this.helper.add(mesh);
EventMesh.setIntersectObjects([mesh]);
}
});
}
}
setAttribute(geometry: THREE.BufferGeometry) {
const position = geometry.attributes.position.array;
const length = geometry.attributes.position.count;
const randoms = new Float32Array(length);
const centers = new Float32Array(length * 3);
for (let index = 0; index < length; index += 3) {
const random = Math.random() * 1;
randoms[index] = random;
randoms[index + 1] = random;
randoms[index + 2] = random;
const i3 = index * 3;
const x = position[i3];
const y = position[i3 + 1];
const z = position[i3 + 2];
const x1 = position[i3 + 3];
const y1 = position[i3 + 4];
const z1 = position[i3 + 5];
const x2 = position[i3 + 6];
const y2 = position[i3 + 7];
const z2 = position[i3 + 8];
const center = new THREE.Vector3(x + x1 + x2, y + y1 + y2, z + z1 + z2).divideScalar(3);
centers.set([center.x, center.y, center.z], index * 3);
centers.set([center.x, center.y, center.z], (index + 1) * 3);
centers.set([center.x, center.y, center.z], (index + 2) * 3);
}
geometry.setAttribute("aRandom", new THREE.BufferAttribute(randoms, 1));
geometry.setAttribute("aCenter", new THREE.BufferAttribute(centers, 3));
}
@ThreeHelper.InjectAnimation(Main)
animation() {
const delta = this.clock.getDelta();
this.iTime.value += delta / 7;
}
@ThreeHelper.AddGUI(Main)
createEnvTexture(gui: GUI) {
const progressBar = gui.add(this.iProgress, "value", 0, 1).step(0.001);
const play = (swap = true) => {
this.iProgress.value = 0;
this.playing = true;
gsap.to(this.iProgress, {
value: 1,
duration: 1.5,
onUpdate: () => {
progressBar.updateDisplay();
},
onComplete: () => {
this.playing = false;
if (swap) {
const temp = this.matcap.value;
this.matcap.value = this.matcap2.value;
this.matcap2.value = temp;
this.iProgress.value = 0;
}
},
});
};
this.play = play;
gui.addFunction(() => play(true)).name("play");
this.textures.forEach((texture, index) => {
gui.addFunction(() => {
if (!this.playing) {
this.matcap.value = this.matcap2.value;
this.matcap2.value = texture;
play(false);
}
}).name("texture:" + (index + 1));
});
}
clickImage(url: string) {
if (!this.playing) {
const t = this.textures.find((t) => t.image.src == url);
if (t) {
this.matcap.value = this.matcap2.value;
this.matcap2.value = t;
this.play && this.play(false);
}
}
}
}