学习threejs,实现山谷奔跑效果

👨‍⚕️ 主页: gis分享者

👨‍⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅!

👨‍⚕️ 收录于专栏:threejs gis工程师


文章目录

  • 一、🍀前言
    • [1.1 ☘️THREE.ShaderMaterial](#1.1 ☘️THREE.ShaderMaterial)
      • [1.1.1 ☘️注意事项](#1.1.1 ☘️注意事项)
      • [1.1.2 ☘️构造函数](#1.1.2 ☘️构造函数)
      • [1.1.3 ☘️属性](#1.1.3 ☘️属性)
      • [1.1.4 ☘️方法](#1.1.4 ☘️方法)
    • [1.2 ☘️THREE.EffectComposer 后期处理](#1.2 ☘️THREE.EffectComposer 后期处理)
      • [1.2.1 ☘️代码示例](#1.2.1 ☘️代码示例)
      • [1.2.2 ☘️构造函数](#1.2.2 ☘️构造函数)
      • [1.2.3 ☘️属性](#1.2.3 ☘️属性)
      • [1.2.4 ☘️方法](#1.2.4 ☘️方法)
    • [1.3 ☘️THREE.RenderPass](#1.3 ☘️THREE.RenderPass)
      • [1.3.1 ☘️构造函数](#1.3.1 ☘️构造函数)
      • [1.3.2 ☘️属性](#1.3.2 ☘️属性)
      • [1.3.3 ☘️方法](#1.3.3 ☘️方法)
    • [1.4 ☘️THREE.UnrealBloomPass](#1.4 ☘️THREE.UnrealBloomPass)
      • [1.4.1 ☘️构造函数](#1.4.1 ☘️构造函数)
      • [1.4.2 ☘️方法](#1.4.2 ☘️方法)
  • 二、🍀实现山谷奔跑效果
    • [1. ☘️实现思路](#1. ☘️实现思路)
    • [2. ☘️代码样例](#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 的显式值,并且可以在预处理器语句中使用。
javascript 复制代码
#pragma unroll_loop_start
for ( int i = 0; i < 10; i ++ ) {

	// ...

}
#pragma unroll_loop_end

代码示例

javascript 复制代码
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。这可以避免在缓冲区数据丢失时出错。

javascript 复制代码
this.defaultAttributeValues = {
	'color': [ 1, 1, 1 ],
	'uv': [ 0, 0 ],
	'uv2': [ 0, 0 ]
};

.defines : Object

使用 #define 指令在GLSL代码为顶点着色器和片段着色器定义自定义常量;每个键/值对产生一行定义语句:

javascript 复制代码
defines: {
	FOO: 15,
	BAR: true
}

这将在GLSL代码中产生如下定义语句:

javascript 复制代码
#define FOO 15
#define BAR true

.extensions : Object

一个有如下属性的对象:

javascript 复制代码
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

如下形式的对象:

javascript 复制代码
{ "uniform1": { value: 1.0 }, "uniform2": { value: 2 } }

指定要传递给shader代码的uniforms;键为uniform的名称,值(value)是如下形式:

javascript 复制代码
{ 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 ☘️代码示例

javascript 复制代码
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

一个用于表示后期处理过程链(包含顺序)的数组。

javascript 复制代码
渲染通道:
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.RenderPass

THREE.RenderPass用于将场景渲染到中间缓冲区,为后续的后期处理效果(如模糊、色调调整等)提供基础。

1.3.1 ☘️构造函数

RenderPass(scene, camera, overrideMaterial, clearColor, clearAlpha)

  • scene THREE.Scene 要渲染的 Three.js 场景对象。
  • camera THREE.Camera 场景对应的相机(如 PerspectiveCamera)。
  • overrideMaterial THREE.Material (可选) 覆盖场景中所有物体的材质(默认 null)。
  • clearColor THREE.Color (可选) 渲染前清除画布的颜色(默认不主动清除)。
  • clearAlpha number (可选) 清除画布的透明度(默认 0)。

1.3.2 ☘️属性

.enabled:boolean

是否启用此通道(默认 true)。设为 false 可跳过渲染。

.clear:boolean

渲染前是否清除画布(默认 true)。若需叠加多个 RenderPass,可设为 false。

.needsSwap:boolean

是否需要在渲染后交换缓冲区(通常保持默认 false)。

1.3.3 ☘️方法

.setSize(width, height)

调整通道的渲染尺寸(通常由 EffectComposer 自动调用)。

width: 画布宽度(像素)。

height: 画布高度(像素)。

1.4 ☘️THREE.UnrealBloomPass

UnrealBloomPass 是 Three.js 中实现高质量泛光效果的后期处理通道,通过模拟类似 Unreal Engine 的泛光效果,为场景中的明亮区域添加柔和的光晕,提升视觉表现力。

1.4.1 ☘️构造函数

new UnrealBloomPass(resolution, strength, radius, threshold)

  • resolution (Vector2): 泛光效果应用的场景分辨率,需与画布尺寸一致。
    示例:new THREE.Vector2(window.innerWidth, window.innerHeight)
  • strength (Number): 泛光强度,默认值 1.0。值越大,光晕越明显。
  • radius (Number): 模糊半径,默认值 0.4。值越大,光晕扩散范围越广。
  • threshold (Number): 泛光阈值,默认值 0.85。仅对亮度高于此值的区域生效。

1.4.2 ☘️方法

  • renderToScreen: 是否直接渲染到屏幕,默认为 false(需通过 EffectComposer 管理)。
  • clearColor: 设置背景清除颜色,默认为透明。

二、🍀实现山谷奔跑效果

1. ☘️实现思路

通过threejs的ShaderMaterial自定义着色器、UnrealBloomPass高质量泛光效果后期处理通道实现山谷奔跑效果。具体代码参考下面代码样例。

2. ☘️代码样例

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>山谷奔跑</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/110/three.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/simplex-noise/2.4.0/simplex-noise.min.js"></script>
    <script src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/168886/RenderPass.110.js"></script>
    <script src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/168886/EffectComposer.110.js"></script>
    <script src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/168886/UnrealBloomPass.110.js"></script>
</head>
<style>

    body {
        color: #ffffff;
        font-family: Monospace;
        font-size: 13px;
        text-align: center;
        font-weight: bold;
        background-color: #000;
        margin: 0px;
        overflow: hidden;
    }

    #info,
    #controls {
        position: absolute;
        width: 100%;
        padding: 5px;
        background: rgba(0, 0, 0, 0.4);
    }

    #info {
        top: 0;
    }

    #controls {
        bottom: 0;
    }

    a {
        color: #ffffff;
        transition: 150ms all;
    }
    a:hover, a:focus {
        color: #ffc107;
    }

    .stats-element {
        position: absolute;
        right: 0;
        top: 0;
    }

    .audio-embed {
        position: absolute;
        left: 0;
        bottom: 100%;
        border: 0;
    }
    #hide-audio:checked + .audio-embed {
        opacity: 0;
    }

</style>
<body>
<script id="vertexShader" type="x-shader/x-vertex">
  attribute vec3 center;
  uniform float uTime;
  varying float vDisp;
  varying vec3 vCenter;
  varying vec2 vSceneYZ;

  #define PULSE_TIME 1.16


  void main() {
    vCenter = center;
    vDisp = max(
      max(0., 1.-pow(3.*abs(uv.y-fract(-uTime*PULSE_TIME)+0.5),0.5)),
      1.-pow(3.*abs(uv.y-fract(-uTime*PULSE_TIME)-0.5),0.5)
    );
    // FIXME - magic numbers in displacement calculation
    vec4 scenePosition = modelViewMatrix*vec4(position+vec3(0.,1.,0.)*2.5*vDisp,1.);
    vSceneYZ = scenePosition.yz;
    gl_Position = projectionMatrix*scenePosition;
  }
</script>
<script id="fragmentShader" type="x-shader/x-fragment">
  uniform float uResolutionScale;
  varying float vDisp;
  varying vec3 vCenter;
  varying vec2 vSceneYZ;


  #define PI 3.14159265359
  #define WIREFRAME_WIDTH 2.5

  // adapted from https://github.com/mrdoob/three.js/blob/dev/examples/webgl_materials_wireframe.html for wireframe effect
  float edgeFactorTri() {
    vec3 a3 = smoothstep(vec3(0.), fwidth(vCenter.xyz)*WIREFRAME_WIDTH/uResolutionScale, vCenter.xyz);
    return min(min(a3.x, a3.y), a3.z);
  }


  void main( void ) {
    if (edgeFactorTri() > 0.98) discard;
    vec3 color = mix(
      mix(
        mix(
          vec3(1.,0.,0.6), // magenta base
          vec3(1., 0.9, .0), min(1.9,vDisp) // yellow pulse
        ),
        vec3(1.), max(0., (vSceneYZ.s-20.) / 120.) // lighter on top; FIXME - magic numbers with Y position
      ),
      vec3(0.), max(0., min(1., (-vSceneYZ.t - 80.) / 80.)) // fade to black; FIXME - magic numbers with Z position
    );
    gl_FragColor = gl_FrontFacing ?
      vec4(color, 1.0) :
      vec4(color, 0.5);
  }
</script>
<div id="container"></div>
<div id="info">Synth Canyon Run - with <a href="https://threejs.org" target="_blank">three.js</a>
</div>
<div id="controls">
    <label for="resolution">resolution: </label>
    <select id="resolution" value="2">
        <option value="0.5">0.5x</option>
        <option value="1" selected>1x</option>
        <option value="2">2x</option>
        <option value="4">4x</option>
        <option value="8">8x</option>
    </select>
    <label for="hide-audio">hide audio: </label>
    <input id="hide-audio" type="checkbox"></input>
    <iframe class="audio-embed" width="350" height="83" scrolling="no" frameborder="no" allow="autoplay" src="https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/450662742&color=%23a575d0&auto_play=false&hide_related=false&show_comments=true&show_user=true&show_reposts=false&show_teaser=true"></iframe>
</div>
</body>
<script>
  const SHOW_STATS = false;
  const CANYON_WIDTH = 400;
  const CANYON_LENGTH = 120;
  const CANYON_SEGMENTS_W = 27;
  const CANYON_SEGMENTS_L = 10;
  const CLIFF_BASE = 60;
  const CLIFF_VARY = 15;
  const FLOOR_VARY = 10;
  const CANYON_SPEED = 70;
  const CAMERA_DRIFT_DISTANCE = 15;
  const CAMERA_DRIFT_SPEED = 0.05;


  let lastUpdate;
  let camera, scene, renderer, composer;
  let cameraBaseX, cameraBaseY;
  let uResolutionScale;
  let uTime;
  let canyonA, canyonB;


  function init() {
    // stats
    if (SHOW_STATS) {
      const stats = new Stats();
      stats.domElement.classList.add('stats-element');
      document.body.appendChild(stats.domElement);
      requestAnimationFrame(function updateStats(){
        stats.update();
        requestAnimationFrame(updateStats);
      });
    }

    // basic setup
    const container = document.getElementById('container');
    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 300);
    const cameraDistance = 70;
    const cameraAngle = .05*Math.PI;
    camera.position.z = cameraDistance;
    cameraBaseX = 0;
    cameraBaseY = 0.3 * (CLIFF_BASE + CLIFF_VARY + FLOOR_VARY);
    camera.position.y = cameraBaseY;
    camera.rotation.x = -cameraAngle;
    scene = new THREE.Scene();
    renderer = new THREE.WebGLRenderer({ antialias: true });
    renderer.setClearColor(0x000000, 1.0);
    renderer.setPixelRatio(window.devicePixelRatio);


    // shader setup
    lastUpdate = new Date().getTime();
    const vertexShader = document.getElementById( 'vertexShader' ).textContent;
    const fragmentShader = document.getElementById( 'fragmentShader' ).textContent;
    uTime = { type: 'f', value: 1.0 };
    uResolutionScale = { type: 'f', value: 1.0 };

    // add objects
    const canyonGeometry = new THREE.PlaneGeometry(CANYON_WIDTH, CANYON_LENGTH, CANYON_SEGMENTS_W, CANYON_SEGMENTS_L);
    canyonGeometry.rotateX(-0.5 * Math.PI);
    const reverseGeometry = canyonGeometry.clone();
    const simplexA = new SimplexNoise(Math.floor(0xffff*Math.random()));
    const simplexB = new SimplexNoise(Math.floor(0xffff*Math.random()));
    for (let i = 0, l = canyonGeometry.vertices.length; i < l; i++) {
      const { x, z } = canyonGeometry.vertices[i];
      canyonGeometry.vertices[i].y =
        Math.min(1.0, Math.pow(x/50, 4)) * Math.round(CLIFF_BASE + simplexA.noise2D(x,z) * CLIFF_VARY) + Math.round(simplexB.noise2D(x,z) * FLOOR_VARY);
      reverseGeometry.vertices[i].y =
        Math.min(1.0, Math.pow(x/50, 4)) * Math.round(CLIFF_BASE + simplexA.noise2D(x,-z) * CLIFF_VARY) + Math.round(simplexB.noise2D(x,-z) * FLOOR_VARY);
    }
    const canyonMaterial = new THREE.ShaderMaterial({
      transparent: true,
      side: THREE.DoubleSide,
      uniforms: { uTime, uResolutionScale },
      vertexShader,
      fragmentShader
    });
    canyonMaterial.extensions.derivatives = true;
    canyonA = new THREE.Mesh(geomToBufferGeomWithCenters(canyonGeometry), canyonMaterial);
    scene.add(canyonA);
    canyonB = new THREE.Mesh(geomToBufferGeomWithCenters(reverseGeometry), canyonMaterial);
    canyonB.position.z -= CANYON_LENGTH;
    scene.add(canyonB);
    container.appendChild(renderer.domElement);

    // effect composition
    const renderScene = new THREE.RenderPass(scene, camera);
    const bloomPass = new THREE.UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 1.5, 0.4, 0.86);
    bloomPass.threshold = 0.3;
    bloomPass.strength = 2.5 * uResolutionScale.value;
    bloomPass.radius = 0.3 * uResolutionScale.value;
    composer = new THREE.EffectComposer(renderer);
    composer.addPass(renderScene);
    composer.addPass(bloomPass);

    // event listeners
    onWindowResize();
    window.addEventListener( 'resize', onWindowResize, false);
    document.getElementById('resolution').addEventListener('change', onResolutionChange, false);
  }


  // events
  function onWindowResize(evt) {
    camera.aspect =  window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(window.innerWidth, window.innerHeight);
    composer.setSize(window.innerWidth, window.innerHeight);
  }
  function onResolutionChange(evt) {
    uResolutionScale.value = parseFloat(evt.target.value);
    bloomPass.strength = 2.5 * uResolutionScale.value;
    bloomPass.radius = 0.3 * uResolutionScale.value;
    renderer.setPixelRatio( window.devicePixelRatio / uResolutionScale.value );
  }
  function animate() {
    const currentTime = new Date().getTime();
    const timeSinceLastUpdate = currentTime - lastUpdate;
    lastUpdate = currentTime;
    const deltaTime = timeSinceLastUpdate / 1000;
    uTime.value += deltaTime;
    // move canyons
    canyonA.position.z += deltaTime * CANYON_SPEED;
    canyonB.position.z += deltaTime * CANYON_SPEED;
    if (canyonA.position.z > CANYON_LENGTH) {
      canyonA.position.z -= 2*CANYON_LENGTH;
    }
    if (canyonB.position.z > CANYON_LENGTH) {
      canyonB.position.z -= 2*CANYON_LENGTH;
    }
    // drift camera (simple lissajous)
    camera.position.x = cameraBaseX + CAMERA_DRIFT_DISTANCE*Math.sin(7*CAMERA_DRIFT_SPEED*Math.PI*uTime.value);
    camera.position.y = cameraBaseY + CAMERA_DRIFT_DISTANCE*Math.sin(5*CAMERA_DRIFT_SPEED*Math.PI*uTime.value);
    // render
    // renderer.render( scene, camera );
    composer.render();
    requestAnimationFrame( animate );
  }


  // boot
  init();
  animate();


  // utils
  // adapted from https://github.com/mrdoob/three.js/blob/dev/examples/webgl_materials_wireframe.html for wireframe effect
  function geomToBufferGeomWithCenters(geom) {
    const buffGeom = new THREE.BufferGeometry().fromGeometry(geom);
    const vectors = [new THREE.Vector3(1,0,0), new THREE.Vector3(0,1,0), new THREE.Vector3(0,0,1)];
    const { position } = buffGeom.attributes;
    const centers = new Float32Array(position.count*3);
    for (let i=0, l=position.count; i<l; i++) {
      vectors[i%3].toArray(centers,i*3);
    }
    buffGeom.setAttribute('center', new THREE.BufferAttribute(centers,3));
    return buffGeom;
  }
</script>
</html>

效果如下:

源码

相关推荐
ct9781 天前
ThreeJs材质、模型加载、核心API
webgl·材质·threejs
a1117763 天前
飞机躲避炸弹 网页游戏
前端·开源·html·threejs
a1117763 天前
3D赛车躲避游戏(html threeJS开源)
前端·游戏·3d·开源·html·threejs
a1117764 天前
水体渲染系统(html开源)
前端·开源·threejs·水体渲染
HJHoMFoavQSO15 天前
基于Prescan、CarSim和Simulink的弯道超车避撞联合仿真
着色器
Chary201615 天前
opengl 着色器
opengl·着色器
三年模拟五年烧烤20 天前
easy-threesdk快速一键搭建threejs3d可视化场景
3d·threejs
loriloy20 天前
Three.js 光照教程 - 第四部分:Lighting详解
threejs
loriloy1 个月前
Three.js 材质教程 - 第三部分:Materials详解
threejs