👨⚕️ 主页: gis分享者 👨⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨⚕️ 收录于专栏:threejs gis工程师
文章目录
- 一、🍀前言
-
- 1.1 ☘️THREE.ShaderMaterial
-
- 1.1.1 ☘️注意事项
- 1.1.2 ☘️构造函数
- 1.1.3 ☘️属性
- 1.1.4 ☘️方法
- 1.2 ☘️THREE.EffectComposer 后期处理
-
- 1.2.1 ☘️代码示例
- 1.2.2 ☘️构造函数
- 1.2.3 ☘️属性
- 1.2.4 ☘️方法
- 1.3 ☘️THREE.BloomPass
-
- 1.3.1 ☘️构造函数
- 1.3.2 ☘️属性
- 1.3.3 ☘️方法
- 1.4 ☘️THREE.OutputPass
-
- 1.4.1 ☘️构造函数
- 1.4.2 ☘️属性
- 1.4.3 ☘️方法
- 二、🍀实现熔岩特效
-
- 1. ☘️实现思路
- 2. ☘️代码样例
一、🍀前言
本文详细介绍如何基于threejs在三维场景中实现熔岩特效,亲测可用。希望能帮助到您。一起学习,加油!加油!
1.1 ☘️THREE.ShaderMaterial
THREE.ShaderMaterial使用自定义shader渲染的材质。 shader是一个用GLSL编写的小程序 ,在GPU上运行。
1.1.1 ☘️注意事项
- ShaderMaterial 只有使用 WebGLRenderer 才可以绘制正常, 因为 vertexShader 和 fragmentShader 属性中GLSL代码必须使用WebGL来编译并运行在GPU中。
- 从 THREE r72开始,不再支持在ShaderMaterial中直接分配属性。 必须使用 BufferGeometry实例,使用BufferAttribute实例来定义自定义属性。
- 从 THREE r77开始,WebGLRenderTarget 或 WebGLCubeRenderTarget 实例不再被用作uniforms。 必须使用它们的texture 属性。
- 内置attributes和uniforms与代码一起传递到shaders。 如果您不希望WebGLProgram向shader代码添加任何内容,则可以使用RawShaderMaterial而不是此类。
- 您可以使用指令#pragma unroll_loop_start,#pragma unroll_loop_end 以便通过shader预处理器在GLSL中展开for循环。 该指令必须放在循环的正上方。循环格式必须与定义的标准相对应。
- 循环必须标准化normalized。
- 循环变量必须是i。
- 对于给定的迭代,值 UNROLLED_LOOP_INDEX 将替换为 i 的显式值,并且可以在预处理器语句中使用。
#pragma unroll_loop_start
for ( int i = 0; i < 10; i ++ ) {
// …
}
#pragma unroll_loop_end
代码示例
const material = new THREE.ShaderMaterial( {
uniforms: {
time: { value: 1.0 },
resolution: { value: new THREE.Vector2() }
},
vertexShader: document.getElementById( 'vertexShader' ).textContent,
fragmentShader: document.getElementById( 'fragmentShader' ).textContent
} );
1.1.2 ☘️构造函数
ShaderMaterial( parameters : Object ) parameters – (可选)用于定义材质外观的对象,具有一个或多个属性。 材质的任何属性都可以从此处传入(包括从Material继承的任何属性)。
1.1.3 ☘️属性
共有属性请参见其基类Material。
.clipping : Boolean 定义此材质是否支持剪裁; 如果渲染器传递clippingPlanes uniform,则为true。默认值为false。
.defaultAttributeValues : Object 当渲染的几何体不包含这些属性但材质包含这些属性时,这些默认值将传递给shaders。这可以避免在缓冲区数据丢失时出错。
this.defaultAttributeValues = {
'color': [ 1, 1, 1 ],
'uv': [ 0, 0 ],
'uv2': [ 0, 0 ]
};
.defines : Object 使用 #define 指令在GLSL代码为顶点着色器和片段着色器定义自定义常量;每个键/值对产生一行定义语句:
defines: {
FOO: 15,
BAR: true
}
这将在GLSL代码中产生如下定义语句:
#define FOO 15
#define BAR true
.extensions : Object 一个有如下属性的对象:
this.extensions = {
derivatives: false, // set to use derivatives
fragDepth: false, // set to use fragment depth values
drawBuffers: false, // set to use draw buffers
shaderTextureLOD: false // set to use shader texture LOD
};
.fog : Boolean 定义材质颜色是否受全局雾设置的影响; 如果将fog uniforms传递给shader,则为true。默认值为false。
.fragmentShader : String 片元着色器的GLSL代码。这是shader程序的实际代码。在上面的例子中, vertexShader 和 fragmentShader 代码是从DOM(HTML文档)中获取的; 它也可以作为一个字符串直接传递或者通过AJAX加载。
.glslVersion : String 定义自定义着色器代码的 GLSL 版本。仅与 WebGL 2 相关,以便定义是否指定 GLSL 3.0。有效值为 THREE.GLSL1 或 THREE.GLSL3。默认为空。
.index0AttributeName : String 如果设置,则调用gl.bindAttribLocation 将通用顶点索引绑定到属性变量。默认值未定义。
.isShaderMaterial : Boolean 只读标志,用于检查给定对象是否属于 ShaderMaterial 类型。
.lights : Boolean 材质是否受到光照的影响。默认值为 false。如果传递与光照相关的uniform数据到这个材质,则为true。默认是false。
.linewidth : Float 控制线框宽度。默认值为1。
由于OpenGL Core Profile与大多数平台上WebGL渲染器的限制,无论如何设置该值,线宽始终为1。
.flatShading : Boolean 定义材质是否使用平面着色进行渲染。默认值为false。
.uniforms : Object 如下形式的对象:
{ "uniform1": { value: 1.0 }, "uniform2": { value: 2 } }
指定要传递给shader代码的uniforms;键为uniform的名称,值(value)是如下形式:
{ value: 1.0 }
这里 value 是uniform的值。名称必须匹配 uniform 的name,和GLSL代码中的定义一样。 注意,uniforms逐帧被刷新,所以更新uniform值将立即更新GLSL代码中的相应值。
.uniformsNeedUpdate : Boolean 可用于在 Object3D.onBeforeRender() 中更改制服时强制进行制服更新。默认为假。
.vertexColors : Boolean 定义是否使用顶点着色。默认为假。
.vertexShader : String 顶点着色器的GLSL代码。这是shader程序的实际代码。 在上面的例子中,vertexShader 和 fragmentShader 代码是从DOM(HTML文档)中获取的; 它也可以作为一个字符串直接传递或者通过AJAX加载。
.wireframe : Boolean 将几何体渲染为线框(通过GL_LINES而不是GL_TRIANGLES)。默认值为false(即渲染为平面多边形)。
.wireframeLinewidth : Float 控制线框宽度。默认值为1。
由于OpenGL Core Profile与大多数平台上WebGL渲染器的限制,无论如何设置该值,线宽始终为1。
1.1.4 ☘️方法
共有方法请参见其基类Material。
.clone () : ShaderMaterial this : ShaderMaterial 创建该材质的一个浅拷贝。需要注意的是,vertexShader和fragmentShader使用引用拷贝; attributes的定义也是如此; 这意味着,克隆的材质将共享相同的编译WebGLProgram; 但是,uniforms 是 值拷贝,这样对不同的材质我们可以有不同的uniforms变量。
1.2 ☘️THREE.EffectComposer 后期处理
THREE.EffectComposer 用于在three.js中实现后期处理效果。该类管理了产生最终视觉效果的后期处理过程链。 后期处理过程根据它们添加/插入的顺序来执行,最后一个过程会被自动渲染到屏幕上。
1.2.1 ☘️代码示例
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';
// 初始化 composer
const composer = new EffectComposer(renderer);
// 创建 RenderPass 并添加到 composer
const renderPass = new RenderPass(scene, camera);
composer.addPass(renderPass);
// 添加其他后期处理通道(如模糊)
// composer.addPass(blurPass);
// 在动画循环中渲染
function animate() {
composer.render();
requestAnimationFrame(animate);
}
1.2.2 ☘️构造函数
EffectComposer( renderer : WebGLRenderer, renderTarget : WebGLRenderTarget ) renderer – 用于渲染场景的渲染器。 renderTarget – (可选)一个预先配置的渲染目标,内部由 EffectComposer 使用。
1.2.3 ☘️属性
.passes : Array 一个用于表示后期处理过程链(包含顺序)的数组。
渲染通道:
BloomPass 该通道会使得明亮区域参入较暗的区域。模拟相机照到过多亮光的情形
DotScreenPass 将一层黑点贴到代表原始图片的屏幕上
FilmPass 通过扫描线和失真模拟电视屏幕
MaskPass 在当前图片上贴一层掩膜,后续通道只会影响被贴的区域
RenderPass 该通道在指定的场景和相机的基础上渲染出一个新的场景
SavePass 执行该通道时,它会将当前渲染步骤的结果复制一份,方便后面使用。这个通道实际应用中作用不大;
ShaderPass 使用该通道你可以传入一个自定义的着色器,用来生成高级的、自定义的后期处理通道
TexturePass 该通道可以将效果组合器的当前状态保存为一个纹理,然后可以在其他EffectCoposer对象中将该纹理作为输入参数
.readBuffer : WebGLRenderTarget 内部读缓冲区的引用。过程一般从该缓冲区读取先前的渲染结果。
.renderer : WebGLRenderer 内部渲染器的引用。
.renderToScreen : Boolean 最终过程是否被渲染到屏幕(默认帧缓冲区)。
.writeBuffer : WebGLRenderTarget 内部写缓冲区的引用。过程常将它们的渲染结果写入该缓冲区。
1.2.4 ☘️方法
.addPass ( pass : Pass ) : undefined pass – 将被添加到过程链的过程
将传入的过程添加到过程链。
.dispose () : undefined 释放此实例分配的 GPU 相关资源。每当您的应用程序不再使用此实例时调用此方法。
.insertPass ( pass : Pass, index : Integer ) : undefined pass – 将被插入到过程链的过程。
index – 定义过程链中过程应插入的位置。
将传入的过程插入到过程链中所给定的索引处。
.isLastEnabledPass ( passIndex : Integer ) : Boolean passIndex – 被用于检查的过程
如果给定索引的过程在过程链中是最后一个启用的过程,则返回true。 由EffectComposer所使用,来决定哪一个过程应当被渲染到屏幕上。
.removePass ( pass : Pass ) : undefined pass – 要从传递链中删除的传递。
从传递链中删除给定的传递。
.render ( deltaTime : Float ) : undefined deltaTime – 增量时间值。
执行所有启用的后期处理过程,来产生最终的帧,
.reset ( renderTarget : WebGLRenderTarget ) : undefined renderTarget – (可选)一个预先配置的渲染目标,内部由 EffectComposer 使用。
重置所有EffectComposer的内部状态。
.setPixelRatio ( pixelRatio : Float ) : undefined pixelRatio – 设备像素比
设置设备的像素比。该值通常被用于HiDPI设备,以阻止模糊的输出。 因此,该方法语义类似于WebGLRenderer.setPixelRatio()。
.setSize ( width : Integer, height : Integer ) : undefined width – EffectComposer的宽度。 height – EffectComposer的高度。
考虑设备像素比,重新设置内部渲染缓冲和过程的大小为(width, height)。 因此,该方法语义类似于WebGLRenderer.setSize()。
.swapBuffers () : undefined 交换内部的读/写缓冲。
1.3 ☘️THREE.BloomPass
THREE.BloomPass是 Three.js 后期处理模块中用于实现辉光效果(Bloom)的通道,可模拟强光溢出、柔化高亮区域的效果,常用于增强场景的光照表现。
1.3.1 ☘️构造函数
const bloomPass = new BloomPass(
strength, // 辉光强度
kernelSize, // 模糊核大小
sigma, // 模糊强度
resolution // 分辨率缩放(可选)
);
- strength number 默认1 辉光效果的强度(值越大越明亮)。
- kernelSize number 默认25 模糊核大小(必须为奇数,如 15, 25, 35)。值越大模糊范围越广。
- sigma number 默认4.0 高斯模糊的锐度(值越大越模糊)。
- resolution number 默认256 内部渲染缓冲区的分辨率(值越低性能越好,但效果越粗糙)。
1.3.2 ☘️属性
.enabled:boolean 是否启用此通道(默认 true)。设为 false 可临时禁用效果。
.strength:number 动态调整辉光强度(等同于构造函数参数)。
.renderToScreen:boolean 是否直接渲染到屏幕(默认 false)。若为最后通道,需设为 true。
1.3.3 ☘️方法
.setSize(width, height) 调整通道的渲染尺寸(通常由 EffectComposer 自动调用)。 width: 画布宽度(像素)。 height: 画布高度(像素)。
1.4 ☘️THREE.OutputPass
OutputPass 是 Three.js 后期处理(Post-Processing)中的一个通道(Pass),用于控制最终渲染输出的颜色空间、色调映射(Tone Mapping)和抗锯齿等效果。它通常作为 EffectComposer 的最后一个通道,负责将处理后的图像输出到屏幕。
1.4.1 ☘️构造函数
new OutputPass(resolution) resolution (Vector2): 可选参数,指定输出分辨率。默认值为 null,自动匹配画布尺寸。
1.4.2 ☘️属性
.output:WebGLRenderTarget 类型: WebGLRenderTarget 描述: 存储最终渲染结果的渲染目标对象。 .uniforms:Object 类型: Object 描述: 包含着色器统一变量(Uniforms)的对象,用于控制输出效果。常用属性: toneMappingExposure (Number): 色调映射曝光值,默认 1.0。 toneMappingType (Number): 色调映射算法类型,默认 NoToneMapping。 .needsSwap:boolean 类型: Boolean 描述: 是否需要交换帧缓冲区,通常设为 true 以确保输出到屏幕。
1.4.3 ☘️方法
.setSize(width, height) 调整通道的渲染尺寸(通常由 EffectComposer 自动调用)。 width: 画布宽度(像素)。 height: 画布高度(像素)。 .render(renderer, writeBuffer, readBuffer) 功能: 执行渲染操作(通常由 EffectComposer 自动调用)。
二、🍀实现熔岩特效
1. ☘️实现思路
通过threejs的ShaderMaterial自定义着色器实现熔岩特效。具体代码参考下面代码样例。
2. ☘️代码样例
<!DOCTYPE html>
<html lang="en">
<head>
<title>熔岩特效</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<link type="text/css" rel="stylesheet" href="main.css">
</head>
<body>
<div id="container"></div>
<div id="info"><a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> – shader material demo. featuring lava shader by <a href="http://irrlicht.sourceforge.net/phpBB2/viewtopic.php?t=21057" target="_blank" rel="noopener">TheGameMaker</a></div>
<script id="fragmentShader" type="x-shader/x-fragment">
uniform float time;
uniform float fogDensity;
uniform vec3 fogColor;
uniform sampler2D texture1;
uniform sampler2D texture2;
varying vec2 vUv;
void main( void ) {
vec2 position = – 1.0 + 2.0 * vUv;
vec4 noise = texture2D( texture1, vUv );
vec2 T1 = vUv + vec2( 1.5, – 1.5 ) * time * 0.02;
vec2 T2 = vUv + vec2( – 0.5, 2.0 ) * time * 0.01;
T1.x += noise.x * 2.0;
T1.y += noise.y * 2.0;
T2.x -= noise.y * 0.2;
T2.y += noise.z * 0.2;
float p = texture2D( texture1, T1 * 2.0 ).a;
vec4 color = texture2D( texture2, T2 * 2.0 );
vec4 temp = color * ( vec4( p, p, p, p ) * 2.0 ) + ( color * color – 0.1 );
if( temp.r > 1.0 ) { temp.bg += clamp( temp.r – 2.0, 0.0, 100.0 ); }
if( temp.g > 1.0 ) { temp.rb += temp.g – 1.0; }
if( temp.b > 1.0 ) { temp.rg += temp.b – 1.0; }
gl_FragColor = temp;
float depth = gl_FragCoord.z / gl_FragCoord.w;
const float LOG2 = 1.442695;
float fogFactor = exp2( – fogDensity * fogDensity * depth * depth * LOG2 );
fogFactor = 1.0 – clamp( fogFactor, 0.0, 1.0 );
gl_FragColor = mix( gl_FragColor, vec4( fogColor, gl_FragColor.w ), fogFactor );
}
</script>
<script id="vertexShader" type="x-shader/x-vertex">
uniform vec2 uvScale;
varying vec2 vUv;
void main()
{
vUv = uvScale * uv;
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
gl_Position = projectionMatrix * mvPosition;
}
</script>
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.183.0/build/three.module.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.183.0/examples/jsm/"
}
}
</script>
<script type="module">
import * as THREE from 'three';
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
import { BloomPass } from 'three/addons/postprocessing/BloomPass.js';
import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
let camera, renderer, composer, timer;
let uniforms, mesh;
init();
function init() {
const container = document.getElementById( 'container' );
camera = new THREE.PerspectiveCamera( 35, window.innerWidth / window.innerHeight, 1, 3000 );
camera.position.z = 4;
const scene = new THREE.Scene();
timer = new THREE.Timer();
timer.connect( document );
const textureLoader = new THREE.TextureLoader();
const cloudTexture = textureLoader.load( './images/cloud.png' );
const lavaTexture = textureLoader.load( './images/lavatile.jpg' );
lavaTexture.colorSpace = THREE.SRGBColorSpace;
cloudTexture.wrapS = cloudTexture.wrapT = THREE.RepeatWrapping;
lavaTexture.wrapS = lavaTexture.wrapT = THREE.RepeatWrapping;
uniforms = {
'fogDensity': { value: 0.45 },
'fogColor': { value: new THREE.Vector3( 0, 0, 0 ) },
'time': { value: 1.0 },
'uvScale': { value: new THREE.Vector2( 3.0, 1.0 ) },
'texture1': { value: cloudTexture },
'texture2': { value: lavaTexture }
};
const size = 0.65;
const material = new THREE.ShaderMaterial( {
uniforms: uniforms,
vertexShader: document.getElementById( 'vertexShader' ).textContent,
fragmentShader: document.getElementById( 'fragmentShader' ).textContent
} );
mesh = new THREE.Mesh( new THREE.TorusGeometry( size, 0.3, 30, 30 ), material );
mesh.rotation.x = 0.3;
scene.add( mesh );
//
renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setAnimationLoop( animate );
renderer.autoClear = false;
container.appendChild( renderer.domElement );
//
const renderModel = new RenderPass( scene, camera );
const effectBloom = new BloomPass( 1.25 );
const outputPass = new OutputPass();
composer = new EffectComposer( renderer );
composer.addPass( renderModel );
composer.addPass( effectBloom );
composer.addPass( outputPass );
//
window.addEventListener( 'resize', onWindowResize );
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
composer.setSize( window.innerWidth, window.innerHeight );
}
//
function animate() {
timer.update();
const delta = 5 * timer.getDelta();
uniforms[ 'time' ].value += 0.2 * delta;
mesh.rotation.y += 0.0125 * delta;
mesh.rotation.x += 0.05 * delta;
renderer.clear();
composer.render( 0.01 );
}
</script>
</body>
</html>
效果如下:
源码





