学习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>

效果如下:

源码

相关推荐
ai超级个体2 天前
金三银四,一个面试官连连夸赞的个人网页技术分享
前端·面试·three.js·threejs·网页设计·网页灵感·网页分享
WarPigs2 天前
Unity CG着色器实战
unity·着色器
不吃鱼的猫7483 天前
【从零开始学 OpenGL:现代图形渲染实战】第03篇-深入着色器与GLSL
图形渲染·着色器
gis分享者7 天前
学习threejs,实现带有GLSL着色器的动画
动画·threejs·着色器·glsl·shadermaterial·effectcomposer·unrealbloompass
WarPigs20 天前
着色器multi_compile笔记
unity·着色器
ct97821 天前
ThreeJs材质、模型加载、核心API
webgl·材质·threejs
a11177623 天前
飞机躲避炸弹 网页游戏
前端·开源·html·threejs
a11177623 天前
3D赛车躲避游戏(html threeJS开源)
前端·游戏·3d·开源·html·threejs
a11177624 天前
水体渲染系统(html开源)
前端·开源·threejs·水体渲染