后期效果提升
-
烘焙贴图
- lightmap https://threejs.org/examples/?q=lightmap#webgl_materials_lightmap
- pcss https://threejs.org/examples/?q=pc#webgl_shadowmap_pcss
-
抗锯齿
- fxaa https://threejs.org/examples/?q=fxaa#webgl_postprocessing_fxaa
- smaa https://threejs.org/examples/?q=sma#webgl_postprocessing_smaa
- ssaa https://threejs.org/examples/?q=ssaa#webgl_postprocessing_ssaa
-
环境光遮蔽 屏幕光遮蔽
- ssr https://threejs.org/examples/?q=ssr#webgl_postprocessing_ssr
- sao https://threejs.org/examples/?q=sao#webgl_postprocessing_sao
- ssao https://threejs.org/examples/?q=sao#webgl_postprocessing_ssao
-
边缘检测 点选物体,把边缘轮廓阔起来
- sobel https://threejs.org/examples/?q=sobel#webgl_postprocessing_sobel
- outline https://threejs.org/examples/?q=outline#webgl_postprocessing_outline
-
发光 点亮效果
- unreal https://threejs.org/examples/?q=unreal#webgl_postprocessing_unreal_bloom_selective
-
镜面反射
- mirror https://threejs.org/examples/?q=mirr#webgl_mirror
- https://github.com/0beqz/screen-space-reflections
-
react 效果库
- https://github.com/pmndrs/drei
-
后期处理 效果库 postprocessing
- https://github.com/pmndrs/postprocessing
骨骼动画
第一个顶点坐标( 10.05, 30.10, 12.12 ).
第一个皮肤骨骼索引skinIndex值是( 10, 2, 0, 0 ). 第一个皮肤权重skin weight值是( 0.8, 0.2, 0, 0 ).
这两个皮肤骨骼索引skin index和皮肤权重skin weight数据表达的意思是
骨骼10 mesh.bones[10]对第一个顶点坐标影响权重80%.
骨骼2skeleton.bones[2]对第一个顶点的影响权重20%.
接下来的两个骨骼权重值的权重为0,因此对顶点坐标没有任何影响.
加载器
let event = {};
// 单张纹理图的加载
event.onLoad = function () {
console.log("图片加载完成");
};
event.onProgress = function (url, num, total) {
console.log("图片加载完成:", url);
console.log("图片加载进度:", num);
console.log("图片总数:", total);
let value = ((num / total) * 100).toFixed(2) + "%";
console.log("加载进度的百分比:", value);
div.innerHTML = value;
};
event.onError = function (e) {
console.log("图片加载出现错误");
console.log(e);
};
// 设置加载管理器
const loadingManager = new THREE.LoadingManager(
event.onLoad,
event.onProgress,
event.onError
);
// 导入纹理
const textureLoader = new THREE.TextureLoader(loadingManager);
const doorColorTexture = textureLoader.load(
"./textures/door/color.jpg"
// event.onLoad,
// event.onProgress,
// event.onError
);
const doorAplhaTexture = textureLoader.load("./textures/door/alpha.jpg");// 灰度纹理,用于控制整个表面的不透明度
const doorAoTexture = textureLoader.load("./textures/door/ambientOcclusion.jpg");//导入置换贴图
const doorHeightTexture = textureLoader.load("./textures/door/height.jpg");// 导入粗糙度贴图
const roughnessTexture = textureLoader.load("./textures/door/roughness.jpg");// 导入金属贴图
const metalnessTexture = textureLoader.load("./textures/door/metalness.jpg");// 导入法线贴图
const normalTexture = textureLoader.load("./textures/door/normal.jpg");
// 添加物体
const cubeGeometry = new THREE.BoxBufferGeometry(1, 1, 1, 100, 100, 100);
// 材质
const material = new THREE.MeshStandardMaterial({
color: "#ffff00",
map: doorColorTexture, // 颜色贴图。可以选择包括一个alpha通道,通常与.transparent 或.alphaTest。默认为null。 纹理贴图颜色由漫反射颜色.color调节。
alphaMap: doorAplhaTexture, // alpha贴图是一张灰度纹理,用于控制整个表面的不透明度。(黑色:完全透明;白色:完全不透明)。 默认值为null。
transparent: true,
aoMap: doorAoTexture, // 该纹理的红色通道用作环境遮挡贴图。默认值为null。aoMap需要第二组UV。
aoMapIntensity: 1, // 环境遮挡效果的强度。默认值为1。零是不遮挡效果。
displacementMap: doorHeightTexture, // 位移贴图会影响网格顶点的位置,与仅影响材质的光照和阴影的其他贴图不同,移位的顶点可以投射阴影,阻挡其他对象, 以及充当真实的几何体。位移纹理是指:网格的所有顶点被映射为图像中每个像素的值(白色是最高的),并且被重定位。
displacementScale: 0.1, // 位移贴图对网格的影响程度(黑色是无位移,白色是最大位移)。如果没有设置位移贴图,则不会应用此值。默认值为1。
roughness: 1, // 材质的粗糙程度。0.0表示平滑的镜面反射,1.0表示完全漫反射。默认值为1.0。如果还提供roughnessMap,则两个值相乘。
roughnessMap: roughnessTexture, // 该纹理的绿色通道用于改变材质的粗糙度
metalness: 1,// 材质与金属的相似度。非金属材质,如木材或石材,使用0.0,金属使用1.0,通常没有中间值。 默认值为0.0。0.0到1.0之间的值可用于生锈金属的外观。如果还提供了metalnessMap,则两个值相乘
metalnessMap: metalnessTexture,//该纹理的蓝色通道用于改变材质的金属度。
normalMap: normalTexture, //用于创建法线贴图的纹理。RGB值会影响每个像素片段的曲面法线,并更改颜色照亮的方式。法线贴图不会改变曲面的实际形状,只会改变光照
// opacity: 0.3,
// side: THREE.DoubleSide,
});
material.side = THREE.DoubleSide;
const cube = new THREE.Mesh(cubeGeometry, material);
scene.add(cube);
// 给cube添加第二组uv
cubeGeometry.setAttribute(
"uv2",
new THREE.BufferAttribute(cubeGeometry.attributes.uv.array, 2)
);
three.js优化
- 勾选几何数据 - 压缩 (压缩之后的文件用DRACOLoader解压缩,用GLTFLoader会报错)
- 服务器gzip压缩
- 来回拉近解决闪烁 两个物体太近 原因锯齿闪烁重影 antialias: true, // 设置抗锯齿
logarithmicDepthBuffer: true, // 深度检测renderer = new THREE.WebGLRenderer({ antialias: true, // 设置抗锯齿 logarithmicDepthBuffer: true, // 深度检测 physicallyCorrectLights:true //默认false 物理正确光源 });
- 更加真实可以 physicallyCorrectLights(物理正确光源
blender 模型优化与材质优化
- 减少定点数: 选中模型 进入编辑模式 a 全选 网格->清理-按间距合并-合并间距 到 0.2或者0.4
- 材质 的bsdf 全部改成为bsdf
- 复制相同的物体(mesh)用alt+d 不要用shift+d , 这样项目就不会变大
blender优化导出模型
- 图像用 几何数据 - 图形 - JPEG格式
- 勾选几何数据 - 压缩 (压缩之后的文件用DRACOLoader解压缩,用GLTFLoader会报错)
- 服务器gzip压缩
飞线效果 three.js
import gsap from 'gsap';
import * as THREE from 'three'
import scene from '../scene'
export default function createFlyLine(){
new FlyLine()
}
class FlyLine {
constructor(){
const curve = new THREE.CatmullRomCurve3( [
new THREE.Vector3( 0, 0, 0 ),
new THREE.Vector3( -5, 5, 5 ),
new THREE.Vector3( -10, 0, 10 ),
] );
this.geometry = new THREE.TubeBufferGeometry(
curve,
100,
0.5,
2, // 管道横截面的分段数目,1是线 2是平面 3是三角形
false
);
const textLoader = new THREE.TextureLoader()
this.texture = textLoader.load('./textures/z_11.png')
this.texture.wrapS = THREE.RepeatWrapping
this.texture.wrapT = THREE.MirroredRepeatWrapping
this.texture.repeat.set(1,2) // uv是两个面
this.material = new THREE.MeshBasicMaterial({
map:this.texture,
transparent:true
})
// Create the final object to add to the scene
const curveObject = new THREE.Mesh( this.geometry, this.material );
scene.add(curveObject)
gsap.to(this.texture.offset,{
x:-1,
duration:1,
repeat:-1,
ease:'none'
})
}
}
飞线效果 glsl
建筑线框特效
顶点着色器设置远离摄像机就比较小
127:28
在场景中调整摄像机位置时,有部分开不见
127:27
修改three.js源码,实现直线光带掠过城市效果,直线,线条
import * as THREE from "three";
import gsap from "gsap";
export default function modifyCityMaterial(mesh) {
mesh.material.onBeforeCompile = (shader) => {
// console.log(shader.vertexShader);
// console.log(shader.fragmentShader);
shader.fragmentShader = shader.fragmentShader.replace(
"#include <dithering_fragment>",
`
#include <dithering_fragment>
//#end#
`
);
addLightLine(shader);
};
}
export function addLightLine(shader) {
// 扩散的时间
shader.uniforms.uLightLineTime = { value: -1500 };
// 设置条带的宽度
shader.uniforms.uLightLineWidth = { value: 200 };
shader.fragmentShader = shader.fragmentShader.replace(
"#include <common>",
`
#include <common>
uniform float uLightLineTime;
uniform float uLightLineWidth;
`
);
shader.fragmentShader = shader.fragmentShader.replace(
"//#end#",
`
float LightLineMix = -(vPosition.x+vPosition.z-uLightLineTime)*(vPosition.x+vPosition.z-uLightLineTime)+uLightLineWidth;
if(LightLineMix>0.0){
gl_FragColor = mix(gl_FragColor,vec4(0.8,1.0,1.0,1),LightLineMix /uLightLineWidth);
}
//#end#
`
);
gsap.to(shader.uniforms.uLightLineTime, {
value: 1500,
duration: 5,
ease: "none",
repeat: -1,
});
}
修改three.js源码,实现光圈扩散效果,圆圈
export default function modifyCityMaterial(mesh) {
console.log(mesh);
mesh.material.onBeforeCompile = (shader) => {
addSpread(shader)
}
}
function addSpread(shader, center = new THREE.Vector2(100,200)){
// 时间 根据时间运动
shader.uniforms.uSpreadTime = { value:0 }
// 扩散宽度
shader.uniforms.uSpreadWidth = { value:100 }
// 设置扩散的中心点
shader.uniforms.uSpreadCenter = {
value : center
}
shader.fragmentShader = shader.fragmentShader.replace(
`#include <common>`,
`
#include <common>
uniform float uSpreadTime;
uniform float uSpreadWidth;
uniform vec2 uSpreadCenter;
`
)
shader.fragmentShader = shader.fragmentShader.replace(
'//#end#',
`
float radiusWidth = distance(vPosition.xz,uSpreadCenter);
// 扩散范围函数
float spreadIndex = -pow(radiusWidth-uSpreadTime,2.)+uSpreadWidth-10.;
// 0 到 uSpreadWidth
if(spreadIndex>0.){
gl_FragColor = mix(gl_FragColor,vec4(1,1,1,1),spreadIndex/uSpreadWidth);
}
//#end#`
)
gsap.to(shader.uniforms.uSpreadTime,{
value:200,
repeat:-1,
duration:2,
ease: "none",
// yoyo:true
})
}
修改three.js源码,用glsl,根据y轴高度渐变
import * as THREE from "three";
import gsap from "gsap";
export default function modifyCityMaterial(mesh) {
mesh.material.onBeforeCompile = (shader) => {
// console.log(shader.vertexShader);
// console.log(shader.fragmentShader);
shader.fragmentShader = shader.fragmentShader.replace(
"#include <dithering_fragment>",
`
#include <dithering_fragment>
//#end#
`
);
addToTopLine(shader);
};
}
export function addToTopLine(shader) {
// 扩散的时间
shader.uniforms.uToTopTime = { value: 0 };
// 设置条带的宽度
shader.uniforms.uToTopWidth = { value: 40 };
shader.fragmentShader = shader.fragmentShader.replace(
"#include <common>",
`
#include <common>
uniform float uToTopTime;
uniform float uToTopWidth;
`
);
shader.fragmentShader = shader.fragmentShader.replace(
"//#end#",
`
float ToTopMix = -(vPosition.y-uToTopTime)*(vPosition.y-uToTopTime)+uToTopWidth;
if(ToTopMix>0.0){
gl_FragColor = mix(gl_FragColor,vec4(0.8,0.8,1,1),ToTopMix /uToTopWidth);
}
//#end#
`
);
gsap.to(shader.uniforms.uToTopTime, {
value: 500,
duration: 3,
ease: "none",
repeat: -1,
});
}
全局背景1 hdr纹理 环境背景
import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader'
const rgbeLoader = new RGBELoader()
// HDR是指高动态范围图像(High-Dynamic Range,简称HDR) 可以用ps预览
rgbeLoader.loadAsync('./assets/2k.hdr').then((texture) => {
texture.mapping = THREE.EquirectangularReflectionMapping;
scene.background = texture
scene.environment = texture
})
const renderer = new THREE.WebGLRenderer({ alpha: true });
renderer.outputEncoding = THREE.sRGBEncoding
renderer.toneMapping = THREE.ACESFilmicToneMapping
renderer.toneMappingExposure = 0.1
全局背景2 环境背景
如何获取这六张图
// 在 /public/textures 下放入"px.jpg", "nx.jpg","py.jpg","ny.jpg","pz.jpg","nz.jpg"图片
const cubeTextureLoader = new THREE.CubeTextureLoader().setPath("./textures/");
const hdrUrls = ["px.jpg", "nx.jpg","py.jpg","ny.jpg","pz.jpg","nz.jpg",];
const texture = cubeTextureLoader.load(hdrUrls);
scene.background = texture
scene.environment = texture
glsl 元素移动
// main.js
// 传入时间
this.clock = new Three.Clock();
const elapsedTime = this.clock.getElapsedTime();
this.startMaterial.uniforms.uTime.value = elapsedTime;
// 传入要移动的距离
const astepArray = new Float32Array(3);
astepArray[0] = to.x - from.x;
astepArray[1] = to.y - from.y;
astepArray[2] = to.z - from.x;
this.startGeometry.setAttribute("aStep",new Three.BufferAttribute(astepArray, 3));
// vertex.gls
attribute vec3 aStep; // 要移动的全部距离
uniform float uTime; // 根据时间移动
uniform float uSize;
void main(){
vec4 modelPosition=modelMatrix*vec4(position,1.);
// 根据时间移动
modelPosition.xyz+=(aStep*uTime);
vec4 viewPosition=viewMatrix*modelPosition;
gl_Position=projectionMatrix*viewPosition;
// 设置顶点大小
gl_PointSize=uSize;
}
glsl 围着Y轴转圈圈
// 围着Y轴转圈圈 2.0是速度
transformed.x += sin(uTime) * 2.0;
transformed.z += cos(uTime) * 2.0;
加载音频
// 创建音频
this.linstener = new Three.AudioListener();
this.linstener1 = new Three.AudioListener();
this.sound = new Three.Audio(this.linstener);
this.sendSound = new Three.Audio(this.linstener1);
// 创建音频加载器
const audioLoader = new Three.AudioLoader();
audioLoader.load(
`./assets/audio/pow${Math.floor(Math.random() * 4) + 1}.ogg`,
(buffer) => {
this.sound.setBuffer(buffer);
this.sound.setLoop(false);
this.sound.setVolume(1);
}
);
audioLoader.load(`./assets/audio/send.mp3`, (buffer) => {
this.sendSound.setBuffer(buffer);
this.sendSound.setLoop(false);
this.sendSound.setVolume(1);
});
this.sound.play();
this.sendSound.play();
// 如果在requestAnimationFrame中执行,可以加一个开关
坐标
,X轴红色,Y轴绿色,Z轴蓝色 红绿蓝
修改three.js顶点位置
// 传入position的文件位置
\node_modules\three\src\renderers\shaders\ShaderChunk\begin_vertex.glsl.js
在main.js 中 , 创建一个材质
let basicMaterial = new THREE.MeshBasicMaterial({
color: "#00ff00",
side: THREE.DoubleSide,
});
basicMaterial.onBeforeCompile = (shader, renderer) => {
console.log('shader', shader.vertexShader);
console.log('shader', shader.fragmentShader);
// 修改顶点位置
shader.vertexShader = shader.vertexShader.replace(
`#include <begin_vertex>`,
`#include <begin_vertex>
transformed.x += 2.0;
transformed.y += 2.0;
`
)
}
打印出顶点着色器代码
#include <common>
#include <uv_pars_vertex>
#include <uv2_pars_vertex>
#include <envmap_pars_vertex>
#include <color_pars_vertex>
#include <fog_pars_vertex>
#include <morphtarget_pars_vertex>
#include <skinning_pars_vertex>
#include <logdepthbuf_pars_vertex>
#include <clipping_planes_pars_vertex>
void main() {
#include <uv_vertex>
#include <uv2_vertex>
#include <color_vertex>
#if defined ( USE_ENVMAP ) || defined ( USE_SKINNING )
#include <beginnormal_vertex>
#include <morphnormal_vertex>
#include <skinbase_vertex>
#include <skinnormal_vertex>
#include <defaultnormal_vertex>
#endif
// 目录路径为 \node_modules\three\src\renderers\shaders\ShaderChunk\begin_vertex.glsl.js
// 所以只要改这里 可以进去这里看源码
#include <begin_vertex>
#include <morphtarget_vertex>
#include <skinning_vertex>
#include <project_vertex>
#include <logdepthbuf_vertex>
#include <clipping_planes_vertex>
#include <worldpos_vertex>
#include <envmap_vertex>
#include <fog_vertex>
}
围着一个点转动, 修点顶点位置
// 声明时间对象
const basicUnifrom = {
uTime: {
value: 0
}
}
// 在材质的onBeforeCompile 函数修改
basicMaterial.onBeforeCompile = (shader, renderer) => {
// uniforms传入时间
shader.uniforms.uTime = basicUnifrom.uTime
// glsl接收
shader.vertexShader = shader.vertexShader.replace(
`#include <common>`,
`#include <common>
uniform float uTime;
`
)
// 下面的 + 2. 就是围着2.0转, *2.0是转动的速度,围着[2.,0.2.转
shader.vertexShader = shader.vertexShader.replace(
`#include <begin_vertex>`,
`#include <begin_vertex>
transformed.x += (sin(uTime) * 2.0 +2.);
transformed.z += (cos(uTime) * 2.0+2.);
`
)
}
// 刷新时间的值.在外面创建时间对象,函数里面传入值
const clock = new THREE.Clock();
function animate (t) {
const elapsetTime = clock.getElapsedTime()
basicUnifrom.uTime.value = elapsetTime
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
animate();