一、基础材质实现着色器编程
Three.js提供了着色器材质(ShaderMaterial和RawShaderMaterial,着色器材质可参考这篇文章),它允许我们编写自定义着色器代码,着色器代码就像是一份指导书,告诉计算机如何计算和着色每个像素,通过直接操作顶点和像素信息,来实现更高级更复杂的效果,着色器编程给我们提供了无限的创造的可能。如果我们只想做一些简单的修改,比如改变颜色或加一些特效。那么可以使用Three.js的基础材质,并且通过一个叫做onBeforeCompile
的方法来修改着色器代码。three.js的所有材质都是通过着色器编程实现的,而onBeforeCompile
这个方法可以在渲染物体之前拿到着色器代码,并且修改它们,从而实现我们想要的效果。
现在,让我们来看下这个函数的作用。
1、搭建一个three.js场景
(详细介绍可参考这篇)
2、创建一个物体
// 创建一个几何体const geometry = new THREE.PlaneGeometry(1, 1, 64, 64);// 基础材质:基础网格材质const basicMaterial = new THREE.MeshBasicMaterial({color: '#00ff00',side: THREE.DoubleSide,});// 创建网格:将几何体和材质结合起来const plane = new THREE.Mesh(geometry, basicMaterial);// 添加到场景scene.add(plane)// 创建一个几何体 const geometry = new THREE.PlaneGeometry(1, 1, 64, 64); // 基础材质:基础网格材质 const basicMaterial = new THREE.MeshBasicMaterial({ color: '#00ff00', side: THREE.DoubleSide, }); // 创建网格:将几何体和材质结合起来 const plane = new THREE.Mesh(geometry, basicMaterial); // 添加到场景 scene.add(plane)// 创建一个几何体 const geometry = new THREE.PlaneGeometry(1, 1, 64, 64); // 基础材质:基础网格材质 const basicMaterial = new THREE.MeshBasicMaterial({ color: '#00ff00', side: THREE.DoubleSide, }); // 创建网格:将几何体和材质结合起来 const plane = new THREE.Mesh(geometry, basicMaterial); // 添加到场景 scene.add(plane)
效果:
3、通过onBeforeCompile获取着色器代码
你可以把着色器代码想象成一本烹饪菜谱,告诉计算机如何烹饪和上色每个像素。而onBeforeCompile
就好比是给你一份菜谱的草稿,你可以在烹饪之前对菜谱进行调整,加入自己的食材和独特的烹饪技巧。
// 在编译shader程序之前立即执行的可选回调。此函数使用shader源码作为参数。用于修改内置材质。basicMaterial.onBeforeCompile = (shader, renderer) => {console.log(shader.vertexShader);console.log(shader.fragmentShader);`}// 在编译shader程序之前立即执行的可选回调。此函数使用shader源码作为参数。用于修改内置材质。 basicMaterial.onBeforeCompile = (shader, renderer) => { console.log(shader.vertexShader); console.log(shader.fragmentShader);` }// 在编译shader程序之前立即执行的可选回调。此函数使用shader源码作为参数。用于修改内置材质。 basicMaterial.onBeforeCompile = (shader, renderer) => { console.log(shader.vertexShader); console.log(shader.fragmentShader);` }
我们看下打印出来的顶点着色器和片元着色器代码:
着色器代码是以字符串形式表示的,因此我们可以使用字符串的替换方法replace
来修改着色器代码。
引入的#include <common>...
这些GLSL代码块可以在node_modules/three/src/renderers/shaders
目录下找到。对此感兴趣可以看下具体内容。
在顶点着色器#include <common>...
这个位置接收一个时间的uniform变量,在片元着色器中通过周期性的改变transformed
变量的x和z坐标来实现物体在x和z轴上的周期性移动。
transformed变量描述了物体在三维空间的位置、旋转和缩放。改变transform时物体会经过模型矩阵(本地坐标系变换到世界坐标系)改变它在三维空间的位置、旋转和缩放。
// 定义Unform变量const basicUniform = {uTime: {value: 0}}// 渲染函数中给uTime赋值//渲染函数const clock = new THREE.Clock()function render() {let time = clock.getElapsedTime();basicUniform.uTime.value = time;renderer.render(scene, camera);requestAnimationFrame(render);}basicMaterial.onBeforeCompile = (shader, renderer) => {//将uTime传入到着色器中shader.uniforms.uTime = basicUniform.uTime;// 找到要替换的位置进行替换:顶点着色器接收unifrom变量uTimeshader.vertexShader = shader.vertexShader.replace('#include <common>',`#include <common>uniform float uTime;`)// 这段代码将物体在x轴上按照`sin(uTime)`的值进行周期性移动,而在z轴上按照`cos(uTime)`的值进行周期性移动。shader.vertexShader = shader.vertexShader.replace('#include <begin_vertex>',`#include <begin_vertex>transformed.x += sin(uTime)* 2.0;transformed.z += cos(uTime)* 2.0;`)}// 定义Unform变量 const basicUniform = { uTime: { value: 0 } } // 渲染函数中给uTime赋值 //渲染函数 const clock = new THREE.Clock() function render() { let time = clock.getElapsedTime(); basicUniform.uTime.value = time; renderer.render(scene, camera); requestAnimationFrame(render); } basicMaterial.onBeforeCompile = (shader, renderer) => { //将uTime传入到着色器中 shader.uniforms.uTime = basicUniform.uTime; // 找到要替换的位置进行替换:顶点着色器接收unifrom变量uTime shader.vertexShader = shader.vertexShader.replace( '#include <common>', ` #include <common> uniform float uTime; ` ) // 这段代码将物体在x轴上按照`sin(uTime)`的值进行周期性移动,而在z轴上按照`cos(uTime)`的值进行周期性移动。 shader.vertexShader = shader.vertexShader.replace( '#include <begin_vertex>', ` #include <begin_vertex> transformed.x += sin(uTime)* 2.0; transformed.z += cos(uTime)* 2.0; ` ) }// 定义Unform变量 const basicUniform = { uTime: { value: 0 } } // 渲染函数中给uTime赋值 //渲染函数 const clock = new THREE.Clock() function render() { let time = clock.getElapsedTime(); basicUniform.uTime.value = time; renderer.render(scene, camera); requestAnimationFrame(render); } basicMaterial.onBeforeCompile = (shader, renderer) => { //将uTime传入到着色器中 shader.uniforms.uTime = basicUniform.uTime; // 找到要替换的位置进行替换:顶点着色器接收unifrom变量uTime shader.vertexShader = shader.vertexShader.replace( '#include <common>', ` #include <common> uniform float uTime; ` ) // 这段代码将物体在x轴上按照`sin(uTime)`的值进行周期性移动,而在z轴上按照`cos(uTime)`的值进行周期性移动。 shader.vertexShader = shader.vertexShader.replace( '#include <begin_vertex>', ` #include <begin_vertex> transformed.x += sin(uTime)* 2.0; transformed.z += cos(uTime)* 2.0; ` ) }
通过上面的修改,实现下图的动态效果。
二、修改gltf模型及其投影
1、引入gltf模型
GLTF是一种用于传输和加载3D模型的开放标准格式。它的作用是提供了一种通用的格式,以便在不同的3D软件或引擎之间共享和交换3D内容。
GLTF模型是一种以二进制格式存储的3D模型文件,它包含了模型的几何形状、材质、动画等相关信息。与传统的3D模型文件格式相比,GLTF文件通常更小,加载速度更快,同时还支持复杂的材质和动画效果。
three.js提供了内置的,GLTF加载器(GLTFLoader),可以方便的加载和展示GLTF模型。
引入GLTF加载器
import {GLTFLoader} from 'three/examples/jsm/loaders/GLTFLoader'import {GLTFLoader} from 'three/examples/jsm/loaders/GLTFLoader'import {GLTFLoader} from 'three/examples/jsm/loaders/GLTFLoader'
这里用到的模型是官网的一个例子
引入模型:
// 标准网格材质const material = new THREE.MeshStandardMaterial({map: colorTexture,normalMap: normalTexture,})// 深度网格材质,修改阴影const depthMaterial= new THREE.MeshDepthMaterial({depthPacking: THREE.RGBADepthPacking})// 实例化GLTF加载器const gltfLoader = new GLTFLoader();// 引入模型文件:import xxx from 'xxx' 直接这样引入会报错。放在dist目录下可以这样引入。// 不放在dist目录下需要这样引入。 运行时动态生成文件路径const gltfFile = new URL('../assets/img/LeePerrySmith.glb', import.meta.url).href;gltfLoader.load(gltfFile, (gltf) => {// 在回调函数中处理加载完成后的模型let mesh = gltf.scene.children[0];// 设置模型的材质mesh.material = material;// 设置模型的自定义深度材质 用于在渲染阶段更精确地控制模型的深度与阴影。mesh.customDepthMaterial = depthMaterial;// 开启模型的阴影投射mesh.castShadow = true;// 将模型添加到场景中scene.add(mesh);}, function (xhr) {// 加载过程中的回调函数,可用于获取加载进度信息console.log(xhr)}, (err) => {// 加载失败时的回调函数,用于处理错误console.log(err)})// 标准网格材质 const material = new THREE.MeshStandardMaterial({ map: colorTexture, normalMap: normalTexture, }) // 深度网格材质,修改阴影 const depthMaterial= new THREE.MeshDepthMaterial({ depthPacking: THREE.RGBADepthPacking }) // 实例化GLTF加载器 const gltfLoader = new GLTFLoader(); // 引入模型文件:import xxx from 'xxx' 直接这样引入会报错。放在dist目录下可以这样引入。 // 不放在dist目录下需要这样引入。 运行时动态生成文件路径 const gltfFile = new URL('../assets/img/LeePerrySmith.glb', import.meta.url).href; gltfLoader.load(gltfFile, (gltf) => { // 在回调函数中处理加载完成后的模型 let mesh = gltf.scene.children[0]; // 设置模型的材质 mesh.material = material; // 设置模型的自定义深度材质 用于在渲染阶段更精确地控制模型的深度与阴影。 mesh.customDepthMaterial = depthMaterial; // 开启模型的阴影投射 mesh.castShadow = true; // 将模型添加到场景中 scene.add(mesh); }, function (xhr) { // 加载过程中的回调函数,可用于获取加载进度信息 console.log(xhr) }, (err) => { // 加载失败时的回调函数,用于处理错误 console.log(err) })// 标准网格材质 const material = new THREE.MeshStandardMaterial({ map: colorTexture, normalMap: normalTexture, }) // 深度网格材质,修改阴影 const depthMaterial= new THREE.MeshDepthMaterial({ depthPacking: THREE.RGBADepthPacking }) // 实例化GLTF加载器 const gltfLoader = new GLTFLoader(); // 引入模型文件:import xxx from 'xxx' 直接这样引入会报错。放在dist目录下可以这样引入。 // 不放在dist目录下需要这样引入。 运行时动态生成文件路径 const gltfFile = new URL('../assets/img/LeePerrySmith.glb', import.meta.url).href; gltfLoader.load(gltfFile, (gltf) => { // 在回调函数中处理加载完成后的模型 let mesh = gltf.scene.children[0]; // 设置模型的材质 mesh.material = material; // 设置模型的自定义深度材质 用于在渲染阶段更精确地控制模型的深度与阴影。 mesh.customDepthMaterial = depthMaterial; // 开启模型的阴影投射 mesh.castShadow = true; // 将模型添加到场景中 scene.add(mesh); }, function (xhr) { // 加载过程中的回调函数,可用于获取加载进度信息 console.log(xhr) }, (err) => { // 加载失败时的回调函数,用于处理错误 console.log(err) })
*注:
customDepthMaterial
:three.js中的一个属性,用来决定一个物体在生成深度贴图(可以理解为每个像素距相机的位置信息)时长什么样子,它只记录物体的形状,不关注颜色或花纹。RGBADepthPacking
: 是一种纹理压缩技术。如果将深度信息单独存储在深度纹理中,需要额外的GPU内存和带宽,而RGBADepthPacking
方法是用来将深度信息转化为颜色信息,存储在每一个像素点的红、绿、蓝、透明度四个通道中,减少内内存和带宽的使用。
效果:模型正确的引入了。
接下来我们在这个模型背后添加一个平面,让模型在平面上留下投影。
// 平面const plane = new THREE.Mesh(new THREE.PlaneGeometry(20, 20),new THREE.MeshStandardMaterial())// 接收投影plane.receiveShadow = true;plane.position.set(0, 0, -6)scene.add(plane)// 平面 const plane = new THREE.Mesh( new THREE.PlaneGeometry(20, 20), new THREE.MeshStandardMaterial() ) // 接收投影 plane.receiveShadow = true; plane.position.set(0, 0, -6) scene.add(plane)// 平面 const plane = new THREE.Mesh( new THREE.PlaneGeometry(20, 20), new THREE.MeshStandardMaterial() ) // 接收投影 plane.receiveShadow = true; plane.position.set(0, 0, -6) scene.add(plane)
再看下效果:
2、改变模型效果
接下来,使用 onBeforeCompile 方法来获取 shader 代码,然后对这个模型进行修改。
给这个模型添加个扭曲效果。扭曲效果通过旋转顶点实现,旋转顶点实际是在改变顶点的位置和方向,就好像我们旋转一张纸,纸上的点的位置改变了,纸张也就变得扭曲了。
首先,在顶点着色器的顶部位置,添加一个旋转函数,这个函数是个2 * 2的矩阵,接收一个旋转角度作为参数,当我们调用这个函数时,它会根据传入的角度,得到一个对应的旋转矩阵。然后将顶点坐标(通常是一个4维向量,包含(x, y, z, w))与旋转矩阵相乘(矩阵乘法是线性代数的知识,关于矩阵和矩阵的运算感兴趣的可以自行去学习,咱的线性代数知识早就还给了老师,所以咱现在暂时还不会),得到一个新的顶点坐标。这个乘法操作实际上就是将旋转矩阵应用到顶点上,从而实现顶点的旋转。旋转矩阵和着色器编程可以直接参考这本书。
具体实现
首先,替换顶点着色器中的’#include <common>
,替换为旋转函数的定义,代码如下:
const uniformParams = {uTime: {value: 0,},}// 改变着色器材质material.onBeforeCompile = (shader) => {// 向着色器代码中添加一个名为uTime的uniform变量shader.uniforms.uTime = uniformParams.uTime.value;// 在顶点着色器增加旋转函数shader.vertexShader = shader.vertexShader.replace('#include <common>',` #include <common>uniform float uTime;mat2 rotate2D(float _angle){// 2 * 2的旋转矩阵return mat2 (cos(_angle),-sin(_angle),sin(_angle),cos(_angle));}`)}const clock = new THREE.Clock()//渲染函数function render() {let time = clock.getElapsedTime();uniformParams.uTime.value = time;renderer.render(scene, camera);requestAnimationFrame(render);}const uniformParams = { uTime: { value: 0, }, } // 改变着色器材质 material.onBeforeCompile = (shader) => { // 向着色器代码中添加一个名为uTime的uniform变量 shader.uniforms.uTime = uniformParams.uTime.value; // 在顶点着色器增加旋转函数 shader.vertexShader = shader.vertexShader.replace('#include <common>', ` #include <common> uniform float uTime; mat2 rotate2D(float _angle){ // 2 * 2的旋转矩阵 return mat2 (cos(_angle),-sin(_angle),sin(_angle),cos(_angle)); }` ) } const clock = new THREE.Clock() //渲染函数 function render() { let time = clock.getElapsedTime(); uniformParams.uTime.value = time; renderer.render(scene, camera); requestAnimationFrame(render); }const uniformParams = { uTime: { value: 0, }, } // 改变着色器材质 material.onBeforeCompile = (shader) => { // 向着色器代码中添加一个名为uTime的uniform变量 shader.uniforms.uTime = uniformParams.uTime.value; // 在顶点着色器增加旋转函数 shader.vertexShader = shader.vertexShader.replace('#include <common>', ` #include <common> uniform float uTime; mat2 rotate2D(float _angle){ // 2 * 2的旋转矩阵 return mat2 (cos(_angle),-sin(_angle),sin(_angle),cos(_angle)); }` ) } const clock = new THREE.Clock() //渲染函数 function render() { let time = clock.getElapsedTime(); uniformParams.uTime.value = time; renderer.render(scene, camera); requestAnimationFrame(render); }
打印下顶点着色器(shader.vertexShader),可以看到里面有一句这样的代码#include <beginnormal_vertex>
,我们可以打开这个glsl的源码看一看:它定义了一个顶点法线和切线的变量。这里只是定义了法向和切线的变量,并没有做具体的计算,这些变量在着色器的其他部分可能会被用来处理光照效果、纹理映射或其他图形处理操作。
法线(objectNormal)可以被想象成垂直于模型表面的箭头,用来表示每个顶点的朝向。切线(objectTangent)是与法线垂直的向量,用于处理一些纹理映射和光照计算。
// beginnormal_vertex.glsl.jsexport default /* glsl */`vec3 objectNormal = vec3( normal );// 条件编译#ifdef USE_TANGENTvec3 objectTangent = vec3( tangent.xyz );#endif`;// beginnormal_vertex.glsl.js export default /* glsl */` vec3 objectNormal = vec3( normal ); // 条件编译 #ifdef USE_TANGENT vec3 objectTangent = vec3( tangent.xyz ); #endif `;// beginnormal_vertex.glsl.js export default /* glsl */` vec3 objectNormal = vec3( normal ); // 条件编译 #ifdef USE_TANGENT vec3 objectTangent = vec3( tangent.xyz ); #endif `;
然后,修改法向量。修改法向量会影响光照效果。光照的表现受到光源的位置和方向以及模型表面法向量的影响。如果只修改模型的顶点而不调整光照,那么变换后的模型和光照效果将不匹配,无法达到预期的效果。
实现:替换顶点着色器中的#include <beginnormal_vertex>
部分,将其替换为修改法向光照的代码。这段代码通过在顶点着色器中计算一个角度,并根据该角度计算一个旋转矩阵,将物体的法向量进行旋转。
代码如下:
material.onBeforeCompile = (shader) => {// 修改法向光照shader.vertexShader = shader.vertexShader.replace('#include <beginnormal_vertex>', `#include <beginnormal_vertex>float angle = sin(position.y + uTime) * 0.5;mat2 rotateMatrix = rotate2D(angle);objectNormal.xz = rotateMatrix * objectNormal.xz;`)}material.onBeforeCompile = (shader) => { // 修改法向光照 shader.vertexShader = shader.vertexShader.replace('#include <beginnormal_vertex>', ` #include <beginnormal_vertex> float angle = sin(position.y + uTime) * 0.5; mat2 rotateMatrix = rotate2D(angle); objectNormal.xz = rotateMatrix * objectNormal.xz; `) }material.onBeforeCompile = (shader) => { // 修改法向光照 shader.vertexShader = shader.vertexShader.replace('#include <beginnormal_vertex>', ` #include <beginnormal_vertex> float angle = sin(position.y + uTime) * 0.5; mat2 rotateMatrix = rotate2D(angle); objectNormal.xz = rotateMatrix * objectNormal.xz; `) }
下图是修改光照前和后的对比图,可以明显看出区别。
最后,替换顶点着色器中的#include <begin_vertex>
部分,将其替换为对顶点进行旋转的代码。这段代码通过将顶点坐标与旋转矩阵相乘,实现顶点的旋转效果。
material.onBeforeCompile = (shader) => {// 设置顶点着色器的顶点进行旋转shader.vertexShader = shader.vertexShader.replace('#include <begin_vertex>', `#include <begin_vertex>transformed.xz = rotateMatrix * transformed.xz;`)}material.onBeforeCompile = (shader) => { // 设置顶点着色器的顶点进行旋转 shader.vertexShader = shader.vertexShader.replace('#include <begin_vertex>', ` #include <begin_vertex> transformed.xz = rotateMatrix * transformed.xz; ` ) }material.onBeforeCompile = (shader) => { // 设置顶点着色器的顶点进行旋转 shader.vertexShader = shader.vertexShader.replace('#include <begin_vertex>', ` #include <begin_vertex> transformed.xz = rotateMatrix * transformed.xz; ` ) }
效果:
可以看到人物模型扭曲了,但后面的投影还是扭曲前的投影,为了达到更真实的效果,接下来要给投影也加上同样的扭曲效果。
3、改变投影效果
代码和上面一样。
// 修改深度材质的着色器depthMaterial.onBeforeCompile = (shader) => {shader.vertexShader = shader.vertexShader.replace('#include <common>',` #include <common>uniform float uTime;mat2 rotate2D(float _angle){return mat2 (cos(_angle),-sin(_angle),sin(_angle),cos(_angle));}`)// 设置顶点着色器的顶点进行旋转shader.vertexShader = shader.vertexShader.replace('#include <begin_vertex>', `#include <begin_vertex>float angle = sin(position.y + uTime) * 0.5;mat2 rotateMatrix = rotate2D(angle);transformed.xz = rotateMatrix * transformed.xz;`)}// 修改深度材质的着色器 depthMaterial.onBeforeCompile = (shader) => { shader.vertexShader = shader.vertexShader.replace('#include <common>', ` #include <common> uniform float uTime; mat2 rotate2D(float _angle){ return mat2 (cos(_angle),-sin(_angle),sin(_angle),cos(_angle)); }` ) // 设置顶点着色器的顶点进行旋转 shader.vertexShader = shader.vertexShader.replace('#include <begin_vertex>', ` #include <begin_vertex> float angle = sin(position.y + uTime) * 0.5; mat2 rotateMatrix = rotate2D(angle); transformed.xz = rotateMatrix * transformed.xz; ` ) }// 修改深度材质的着色器 depthMaterial.onBeforeCompile = (shader) => { shader.vertexShader = shader.vertexShader.replace('#include <common>', ` #include <common> uniform float uTime; mat2 rotate2D(float _angle){ return mat2 (cos(_angle),-sin(_angle),sin(_angle),cos(_angle)); }` ) // 设置顶点着色器的顶点进行旋转 shader.vertexShader = shader.vertexShader.replace('#include <begin_vertex>', ` #include <begin_vertex> float angle = sin(position.y + uTime) * 0.5; mat2 rotateMatrix = rotate2D(angle); transformed.xz = rotateMatrix * transformed.xz; ` ) }
效果:
三、效果合成器
简单介绍下three.js自带的后期效果。
创建一个后期处理器
// 引入后期效果合成器import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer';// 添加合成效果const effectComposer = new EffectComposer(renderer)// 设置合成器大小effectComposer.setSize(window.innerWidth,window.innerHeight);// 添加一个渲染通道const renderPass = new RenderPass(scene,camera)// 将渲染通道添加到合成器effectComposer.addPass(renderPass)// 在渲染函数中渲染场景function render() {effectComposer.render()}// 引入后期效果合成器 import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer'; // 添加合成效果 const effectComposer = new EffectComposer(renderer) // 设置合成器大小 effectComposer.setSize(window.innerWidth,window.innerHeight); // 添加一个渲染通道 const renderPass = new RenderPass(scene,camera) // 将渲染通道添加到合成器 effectComposer.addPass(renderPass) // 在渲染函数中渲染场景 function render() { effectComposer.render() }// 引入后期效果合成器 import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer'; // 添加合成效果 const effectComposer = new EffectComposer(renderer) // 设置合成器大小 effectComposer.setSize(window.innerWidth,window.innerHeight); // 添加一个渲染通道 const renderPass = new RenderPass(scene,camera) // 将渲染通道添加到合成器 effectComposer.addPass(renderPass) // 在渲染函数中渲染场景 function render() { effectComposer.render() }
后期效果:
故障风:
import {GlitchPass} from 'three/examples/jsm/postprocessing/GlitchPass'const glitchPass = new GlitchPass();effectComposer.addPass(glitchPass);import {GlitchPass} from 'three/examples/jsm/postprocessing/GlitchPass' const glitchPass = new GlitchPass(); effectComposer.addPass(glitchPass);import {GlitchPass} from 'three/examples/jsm/postprocessing/GlitchPass' const glitchPass = new GlitchPass(); effectComposer.addPass(glitchPass);
效果可以看这个官网例子
发光效果
import {UnrealBloomPass} from 'three/examples/jsm/postprocessing/UnrealBloomPass'//发光效果const unrealBloomPass = new UnrealBloomPass();effectComposer.addPass(unrealBloomPass);//强度unrealBloomPass.strength = 1;//光晕的半径unrealBloomPass.radius = 0;//表示产生泛光的光照强unrealBloomPass.threshold = 1;import {UnrealBloomPass} from 'three/examples/jsm/postprocessing/UnrealBloomPass' //发光效果 const unrealBloomPass = new UnrealBloomPass(); effectComposer.addPass(unrealBloomPass); //强度 unrealBloomPass.strength = 1; //光晕的半径 unrealBloomPass.radius = 0; //表示产生泛光的光照强 unrealBloomPass.threshold = 1;import {UnrealBloomPass} from 'three/examples/jsm/postprocessing/UnrealBloomPass' //发光效果 const unrealBloomPass = new UnrealBloomPass(); effectComposer.addPass(unrealBloomPass); //强度 unrealBloomPass.strength = 1; //光晕的半径 unrealBloomPass.radius = 0; //表示产生泛光的光照强 unrealBloomPass.threshold = 1;
效果:官网例子
抗锯齿有好几种
// 抗锯齿import {SMAAPass} from 'three/examples/jsm/postprocessing/SMAAPass'const smaaPass = new SMAAPass();effectComposer.addPass(smaaPass);// 抗锯齿 import {SMAAPass} from 'three/examples/jsm/postprocessing/SMAAPass' const smaaPass = new SMAAPass(); effectComposer.addPass(smaaPass);// 抗锯齿 import {SMAAPass} from 'three/examples/jsm/postprocessing/SMAAPass' const smaaPass = new SMAAPass(); effectComposer.addPass(smaaPass);
import {SSAARenderPass} from 'three/examples/jsm/postprocessing/SSAARenderPass'// 创建并添加 SSAA 抗锯齿通道const ssaaRenderPass = new SSAARenderPass(scene, camera);ssaaRenderPass.unbiased = false; // 可选,设置是否开启无偏抗锯齿composer.addPass(ssaaRenderPass);import {SSAARenderPass} from 'three/examples/jsm/postprocessing/SSAARenderPass' // 创建并添加 SSAA 抗锯齿通道 const ssaaRenderPass = new SSAARenderPass(scene, camera); ssaaRenderPass.unbiased = false; // 可选,设置是否开启无偏抗锯齿 composer.addPass(ssaaRenderPass);import {SSAARenderPass} from 'three/examples/jsm/postprocessing/SSAARenderPass' // 创建并添加 SSAA 抗锯齿通道 const ssaaRenderPass = new SSAARenderPass(scene, camera); ssaaRenderPass.unbiased = false; // 可选,设置是否开启无偏抗锯齿 composer.addPass(ssaaRenderPass);
import {DotScreenPass} from 'three/examples/jsm/postprocessing/DotScreenPass';const dotScreenPass = new DotScreenPass();effectComposer.addPass(dotScreenPass);dotScreenPass.enabled = false;import {DotScreenPass} from 'three/examples/jsm/postprocessing/DotScreenPass'; const dotScreenPass = new DotScreenPass(); effectComposer.addPass(dotScreenPass); dotScreenPass.enabled = false;import {DotScreenPass} from 'three/examples/jsm/postprocessing/DotScreenPass'; const dotScreenPass = new DotScreenPass(); effectComposer.addPass(dotScreenPass); dotScreenPass.enabled = false;
效果:官网例子
更多效果可去查看官网
四、结语
希望大家的学习都是基于兴趣与好奇,而不是焦虑与不安。