想跳伞嘛?在 WebGL 的世界中尽情降落吧!

这是我在 Github 开源中,Fork 的一个 WebGL 创意 Demo!

通过 WebGL 构建一个跳伞降落场景镜头,360 度旋转镜头,从高中俯视这一片大地,喊出来,"逃离城市的苦闷,我要掉到远方去!!!"。

介绍

如果所示,一个勇敢的少年,背着还未打开降落伞,投身如广袤的天地中,在重力的作用下,风都如何袖带般,在他身边穿梭而过,簌簌地声响肉眼可见,那强大的冲击力,震荡着整个天地都在晃荡~~

思路

好了以下,从代码角度介绍,作者开发历程:

  1. 创建全景天空盒:
tsx 复制代码
import { useTexture } from "@react-three/drei";
import { Sphere } from "@react-three/drei";
import { BackSide } from "three";
export const World = () => {
  const skyTexture = useTexture("sky-texture.jpg");
  return (
    <Sphere args={[5, 60, 60]}>
      <meshBasicMaterial toneMapped={false} map={skyTexture} side={BackSide} />
    </Sphere>
  );
};

这里加载skyTexture纹理图片,设置到meshBasicMaterial.map属性,构建全景天空盒。

  1. 添加少年人物模型:
tsx 复制代码
import { useEffect } from "react";
import { useFrame } from "@react-three/fiber";
import { useAnimations, useGLTF, useTexture } from "@react-three/drei";
import { DoubleSide } from "three";
export const Model = () => {
  const [skyDiverTextureBaseColor, skyDiverTextureRoughness, skyDiverTextureMetallic, skyDiverTextureNormal, skyDiverTextureClothes] =
    useTexture(
      [
        "texture/skydiver_BaseColor.webp",
        "texture/skydiver_Roughness.webp",
        "texture/skydiver_Metallic.webp",
        "texture/skydiver_Normal.webp",
        "texture/skydiver_Clothes.webp",
      ],
      ([baseColor, roughness, metallic, normal, clothes]) => {
        baseColor.flipY = roughness.flipY = metallic.flipY = normal.flipY = clothes.flipY = false;
      }
    );
  const test = useGLTF("/skydiver.glb");
  const { nodes, animations, scene } = useGLTF("/skydiver.glb");
  const { ref, actions, names } = useAnimations(animations, scene);
  const { mixamorigHips, skydiver_2: skydiver } = nodes;
  const onBeforeCompile = shader => {
    Object.assign(shader.uniforms, { ...skydiver.material.uniforms });
    shader.vertexShader = `
        uniform float uTime;
        uniform sampler2D uClothes;
        ${shader.vertexShader}
        `;
    shader.vertexShader = shader.vertexShader.replace(
      `#include <begin_vertex>`,
      `
          vec3 clothesTexture = vec3(texture2D(uClothes, vUv));
          float circleTime = 2.0;
          float amplitude = 30.0;
          float circleTimeParam = mod(uTime, circleTime);
          vec3 transformed = vec3( position );
          transformed.y += min(clothesTexture.y * sin( circleTimeParam * amplitude * (PI  / circleTime)) * 0.025, 0.5);
        `
    );
  };
  useEffect(() => {
    actions["animation_0"].reset().play();
    skydiver.material.uniforms = {
      uTime: { value: 0 },
      uClothes: { value: skyDiverTextureClothes },
    };
  }, []);
  useFrame(({ clock }) => {
    if (skydiver.material.uniforms?.uTime) {
      skydiver.material.uniforms.uTime.value = clock.getElapsedTime();
    }
  });
  return (
    <group dispose={null}>
      <group ref={ref}>
        <primitive object={mixamorigHips} />
        <skinnedMesh geometry={skydiver.geometry} skeleton={skydiver.skeleton}>
          <meshStandardMaterial
            side={DoubleSide}
            map={skyDiverTextureBaseColor}
            roughnessMap={skyDiverTextureRoughness}
            metalnessMap={skyDiverTextureMetallic}
            normalMap={skyDiverTextureNormal}
            normalScale={[-0.2, 0.2]}
            envMapIntensity={0.8}
            toneMapped={false}
            onBeforeCompile={onBeforeCompile}
            uniforms={{ uTime: { value: 0 } }}
          />
        </skinnedMesh>
      </group>
    </group>
  );
};

步骤:

  • 使用useGLTF函数加载模型数据
  • 将模型数据分解化成单个图元 DOM
  • skinnedMesh骨骼 Mesh 添加材质(map 贴图、roughness 贴图、metalness 贴图、normal 贴图)
  • 通过meshStandardMaterial.onBeforeCompile函数修改材质 shader,并在useFrame每帧修改uTime
  1. 添加袖带般的可视化风:
tsx 复制代码
import { useRef } from "react";
import { useFrame, useThree } from "@react-three/fiber";
import { Instances, Instance } from "@react-three/drei";
import { AdditiveBlending, DoubleSide, MathUtils } from "three";
import { Vector3 } from "three";

function WindShape() {
  const ref = useRef();
  const state = useThree();
  const { height: viewPortHeight } = state.viewport.getCurrentViewport();
  const v3 = new Vector3();
  const randomPosition = {
    x: MathUtils.randFloatSpread(8),
    y: MathUtils.randFloatSpread(5),
    z: MathUtils.randFloatSpread(8),
  };
  const randomSpeed = MathUtils.randFloat(0.05, 0.5);
  useFrame(({ camera }) => {
    if (ref.current) {
      const { current: el } = ref;
      const { height: elHeight } = el.instance.current.geometry.parameters;
      const { y: elPosition } = el.position;
      const worldPosition = el.getWorldPosition(v3);
      const limitPos = viewPortHeight - (worldPosition.y + elHeight / 2);
      if (limitPos < 0) {
        el.position.y = -(viewPortHeight + elHeight / 2);
      }
      el.position.y += randomSpeed;
      el.rotation.y = camera.rotation.y;
    }
  });
  return <Instance ref={ref} color='white' position={[randomPosition.x, randomPosition.y, randomPosition.z]} />;
}

export const WindEffect = () => {
  const INSTANCE = {
    number: 130,
  };
  return (
    <group>
      <Instances>
        <planeGeometry args={[0.0135, 1.2]} />
        <meshBasicMaterial side={DoubleSide} blending={AdditiveBlending} opacity={0.15} transparent />
        {Array(INSTANCE.number)
          .fill()
          .map((_, key) => (
            <WindShape key={key} />
          ))}
      </Instances>
    </group>
  );
};

步骤:

  • 创建Instance实例WindShape,并通过并在useFrame每帧修改Instance位置
  • 指定一定的数量,遍历Instance实例WindShape量,并统一设置planeGeometry几何体和meshBasicMaterial材质信息
  1. 添加光照
tsx 复制代码
<ambientLight intensity={0.2} />
<directionalLight position={[-0.15, -2, 0]} intensity={2} />

世界必须要有光和希望,WebGL 世界也是要有光和希望! 给平凡的人前进,指引陪伴吧!

  1. 设置后期处理

其实,有了以上内容已经足够了,但是没有人能拒绝美颜,那么 EffectComposer 处理必不可少的!

tsx 复制代码
<EffectComposer>
  <Bloom luminanceThreshold={0.6} luminanceSmoothing={0.5} intensity={1.2} mipmapBlur />
  <Vignette offset={0.5} darkness={0.5} />
</EffectComposer>

在这里设置了两个后处理:

  • Bloom

Bloom 后处理是一种图形渲染效果,常用于增强图像或游戏中的光效。它模拟了真实世界中光线透过镜头时产生的光晕效果,给画面增加了柔和、明亮、发光的外观。

  • Vignette

Vignette 后处理是一种图形渲染效果,常用于增强图像或游戏中的焦点效果。它通过在图像边缘或特定区域添加暗影或光晕效果,使焦点区域更加突出,同时增强画面的艺术感和吸引力。

添加这些后期处理,让焦点(明亮的)区域更加突出、吸引人的同时,为画面增添一种独特的艺术感。

  1. 添加相机晃荡
tsx 复制代码
<CameraShake yawFrequency={4} pitchFrequency={4} rollFrequency={5} intensity={0.3} />

这里通过CameraShake让相机产生晃荡,如同随着坠落,在风中摇摆,强烈了真实感!

总结

看完整个项目的代码,让我受益匪浅,作者的编码能力和创意真值得我学习和努力,虽然 Three.js 很小众,但是创意没有边界,能力也不要局限,有什么值得学习和模仿的,去做就好了,看这样优秀的项目,也是一件令人愉悦的事情!

相关资料

  • Three.js
  • React-Three-Fiber
  • 作者的项目仓库\] ([github.com/sebastien-l...](https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Fsebastien-lempens%2Fwebgl-skydiving "https://github.com/sebastien-lempens/webgl-skydiving"))

相关推荐
恋猫de小郭1 小时前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅7 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60618 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了8 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅8 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅9 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅9 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment9 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅9 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊10 小时前
jwt介绍
前端