April 11, 2025

AntiThree(2) - banana


Banana

🔥 可利用概念

clipboard.png

/*
Gumroad tutorial: https://0xca0a.gumroad.com/l/B4N4N4S
-------------------------------------------------------------------------------
Model auto-generated by: https://github.com/pmndrs/gltfjsx
Author: thesidekick (https://sketchfab.com/thesidekick)
License: CC-BY-4.0 (http://creativecommons.org/licenses/by/4.0/)
Source: https://sketchfab.com/3d-models/banana-dda3a1f707a94c52bed79578e120937c
Title: Banana
*/

import { createRoot } from 'react-dom/client'
import { Suspense, useState } from 'react'
import './styles.css'
import Overlay from './layout/Overlay'
import { FadeIn, LeftMiddle } from './layout/styles'

import Bananas from './Bananas'
// Comment the above and uncomment the following to import the WebGL BG lazily for faster loading times
// const Bananas = lazy(() => import('./Bananas'))

function App() {
  const [speed, set] = useState(1)
  return (
    <>
      <Suspense fallback={null}>
        <Bananas speed={speed} />
        <FadeIn />
      </Suspense>
      <Overlay />
      <LeftMiddle>
        <input type="range" min="0" max="10" value={speed} step="1" onChange={(e) => set(e.target.value)} />
      </LeftMiddle>
    </>
  )
}

createRoot(document.getElementById('root')).render(<App />)
import * as THREE from 'three'
import { useRef, useState } from 'react'
import { Canvas, useThree, useFrame } from '@react-three/fiber'
import { useGLTF, Detailed, Environment } from '@react-three/drei'
import { EffectComposer, DepthOfField, ToneMapping } from '@react-three/postprocessing'

function Banana({ index, z, speed }) {
  const ref = useRef()
  const { viewport, camera } = useThree()
  const { width, height } = viewport.getCurrentViewport(camera, [0, 0, -z])
  // useGLTF is an abstraction around R3F's useLoader(GLTFLoader, url)
  // It can automatically handle draco and meshopt-compressed assets without you having to
  // worry about binaries and such ...
  const { nodes, materials } = useGLTF('/banana-v1-transformed.glb')


  const [data] = useState({
    y: THREE.MathUtils.randFloatSpread(height * 2),
    x: THREE.MathUtils.randFloatSpread(2),
    spin: THREE.MathUtils.randFloat(8, 12),
    // Some random rotations, Math.PI represents 360 degrees in radian
    rX: Math.random() * Math.PI,
    rZ: Math.random() * Math.PI
  })

  useFrame((state, dt) => {
    if (dt < 0.1)
      ref.current.position.set(
        index === 0 ? 0 : data.x * width,
        (data.y += dt * speed),
        -z
      );
    // Rotate the object around
    ref.current.rotation.set(
      (data.rX += dt / data.spin),
      Math.sin(index * 1000 + state.clock.elapsedTime / 10) * Math.PI,
      (data.rZ += dt / data.spin)
    );
    // If they're too far up, set them back to the bottom
    if (data.y > height * (index === 0 ? 4 : 1))
      data.y = -(height * (index === 0 ? 4 : 1));
  });

  // Using drei's detailed is a nice trick to reduce the vertex count because
  // we don't need high resolution for objects in the distance. The model contains 3 decimated meshes ...
  return (
    <Detailed ref={ref} distances={[0, 65, 80]}>
      <mesh geometry={nodes.banana_high.geometry} material={materials.skin} material-emissive="#ff9f00" />
      <mesh geometry={nodes.banana_mid.geometry} material={materials.skin} material-emissive="#ff9f00" />
      <mesh geometry={nodes.banana_low.geometry} material={materials.skin} material-emissive="#ff9f00" />
    </Detailed>
  )
}

export default function Bananas({ speed = 1, count = 80, depth = 80, easing = (x) => Math.sqrt(1 - Math.pow(x - 1, 2)) }) {
  return (
    // No need for antialias (faster), dpr clamps the resolution to 1.5 (also faster than full resolution)
    // As of three > r154 if postprocessing is used the canvas can not have tonemapping (which is what "flat" is, no tonemapping)
    <Canvas flat gl={{ antialias: false }} dpr={[1, 1.5]} camera={{ position: [0, 0, 10], fov: 20, near: 0.01, far: depth + 15 }}>
      <color attach="background" args={['#ffbf40']} />
      {/* As of three > r153 lights work differently in threejs, to get similar results as before you have to add decay={0} */}
      <spotLight position={[10, 20, 10]} penumbra={1} decay={0} intensity={3} color="orange" />
      {/* Using cubic easing here to spread out objects a little more interestingly, i wanted a sole big object up front ... */}
      {Array.from({ length: count }, (_, i) => <Banana key={i} index={i} z={Math.round(easing(i / count) * depth)} speed={speed} /> /* prettier-ignore */)}
      <Environment preset="sunset" />
      {/* Multisampling (MSAA) is WebGL2 antialeasing, we don't need it (faster)
          The normal-pass is not required either, saves a bit of performance */}
      <EffectComposer disableNormalPass multisampling={0}>
        <DepthOfField target={[0, 0, 60]} focalLength={0.4} bokehScale={14} height={700} />
        {/* As of three > r154 tonemapping is not applied on rendertargets any longer, it requires a pass */}
        <ToneMapping />
      </EffectComposer>
    </Canvas>
  )
}

(此處不應該使用 useState)

const [data] = useState({
  y: THREE.MathUtils.randFloatSpread(height * 2),
  x: THREE.MathUtils.randFloatSpread(2),
  spin: THREE.MathUtils.randFloat(8, 12),
  // Some random rotations, Math.PI represents 360 degrees in radian
  rX: Math.random() * Math.PI,
  rZ: Math.random() * Math.PI
})
if (data.y > height * (index === 0 ? 4 : 1))
  data.y = -(height * (index === 0 ? 4 : 1));
<Detailed ref={ref} distances={[10, 20]}>
  {/* <mesh geometry={nodes.banana_high.geometry} material={materials.skin} material-emissive="#ff9f00" /> */}
  {/* <mesh geometry={nodes.banana_mid.geometry} material={materials.skin} material-emissive="#ff9f00" /> */}
  {/* <mesh geometry={nodes.banana_low.geometry} material={materials.skin} material-emissive="#ff9f00" /> */}
  <mesh>
    <boxGeometry />
    <meshStandardMaterial color="hotpink" />
  </mesh>
  <mesh>
    <boxGeometry />
    <meshStandardMaterial color="orange" />
  </mesh>
  <mesh>
    <boxGeometry />
    <meshStandardMaterial color="gray" />
  </mesh>
</Detailed>
<DepthOfField
  target={[0, 0, 60]}      // 對焦的目標位置,通常是某個物件的位置
  focalLength={0.4}        // 焦距,影響對焦範圍(越小越淺景深)
  bokehScale={14}          // 景深模糊的強度(越大模糊越明顯)
  height={700}             // 解決模糊效果的精細度,通常設為視窗高度
/>