April 11, 2025

AntiThree(3) - Lusion


Lusion

🔥 可利用概念

clipboard.png

特性<Lightformer /><ToneMapping/>
目的提供全局性柔和的環境光照,增強反射和光澤效果調整亮度範圍,將 HDR 轉換為可顯示的範圍
用途用於模擬攝影棚照明,強化反射、鏡面效果用於避免過曝或過暗,壓縮亮度範圍
控制方式控制光源的形狀、強度、顏色、位置控制圖像的亮度範圍,改變整體的亮度與對比度
應用場景高反射材質、HDRI 環境貼圖、產品展示瀏覽或渲染高動態範圍影像(HDR),避免過曝或過暗
效果類型改變光源的形狀、強度、顏色和環境照明效果將 HDR 圖像壓縮到顯示設備的亮度範圍,調整整體圖像效果
<Environment resolution={512}>
  <Lightformer form="circle" intensity={20} position={[0, 5, 10]} scale={5} />
</Environment>

可以取代 easing.dampC() 的顏色更換, 但是步驟較多

// 用來儲存目標顏色
const [targetColor, setTargetColor] = useState(new THREE.Color(color))

// 每次顏色改變時更新目標顏色
useEffect(() => {
  setTargetColor(new THREE.Color(color))
}, [color])

useFrame((state, delta) => {
  // 平滑顏色過渡
  if (ref.current) {
    ref.current.material.color.lerp(targetColor, 0.2) // lerp 步伐設定為 0.1
  }
})

// ======== 原本的顏色設定 =========
useFrame((state, delta) => {
  // 顏色流暢更換
  easing.dampC(ref.current.material.color, color, 0.2, delta)
})
// https://twitter.com/lusionltd/status/1701534187545636964
// https://lusion.co

import * as THREE from 'three'
import { useRef, useReducer, useMemo, useEffect } from 'react'
import { Canvas, useFrame, useThree } from '@react-three/fiber'
import { useGLTF, MeshTransmissionMaterial, Environment, Lightformer } from '@react-three/drei'
import { CuboidCollider, BallCollider, Physics, RigidBody } from '@react-three/rapier'
import { EffectComposer, N8AO } from '@react-three/postprocessing'
import { easing } from 'maath'

const accents = ['#4060ff', '#20ffa0', '#ff4060', '#ffcc00']

// 產生三種 顏色的物件, 各自有 material
const shuffle = (accent = 0) => [
  { color: '#444', roughness: 0.1 },
  { color: '#444', roughness: 0.75 },
  { color: '#444', roughness: 0.75 },
  { color: 'white', roughness: 0.1 },
  { color: 'white', roughness: 0.75 },
  { color: 'white', roughness: 0.1 },
  { color: accents[accent], roughness: 0.1, accent: true },
  { color: accents[accent], roughness: 0.75, accent: true },
  { color: accents[accent], roughness: 0.1, accent: true }
]

export const App = () => (
  <div className="container">
    <div className="nav">
      <h1 className="label" />
      <div />
      <span className="caption" />
      <div />
      <a href="https://lusion.co/">
        <div className="button">VISIT LUSION</div>
      </a>
      <div className="button gray">///</div>
    </div>
    <Scene style={{ borderRadius: 20 }} />
  </div>
)

function Scene(props) {
  const [accent, click] = useReducer((state) => ++state % accents.length, 0)
  const connectors = useMemo(() => shuffle(accent), [accent])

  return (
    <Canvas onClick={click} shadows dpr={[1, 1.5]} gl={{ antialias: false }} camera={{ position: [0, 0, 15], fov: 17.5, near: 1, far: 20 }} {...props}>
      <color attach="background" args={['#141622']} />
      <ambientLight intensity={0.4} />
      <spotLight position={[10, 10, 10]} angle={0.15} penumbra={1} intensity={1} castShadow />
      {/* 設定重力 */}
      <Physics /*debug*/ gravity={[0, 0, 0]} debug>
        {/* 滑鼠佔據一個物理可碰撞圓球 */}
        <Pointer />
        {connectors.map((props, i) => (
          <Connector key={i} {...props}>
            <planeGeometry />
          </Connector>
        ))}
        <Connector position={[0, 0, 0]}>
          <Model>
            <MeshTransmissionMaterial clearcoat={1} thickness={0.1} anisotropicBlur={0.1} chromaticAberration={0.1} samples={8} resolution={512} />
          </Model>
        </Connector>
      </Physics>
      <EffectComposer disableNormalPass multisampling={8}>
        <N8AO distanceFalloff={1} aoRadius={1} intensity={4} />
      </EffectComposer>
      <Environment resolution={256}>
        <group rotation={[-Math.PI / 3, 0, 1]}>
          <Lightformer form="circle" intensity={4} rotation-x={Math.PI / 2} position={[0, 5, -9]} scale={2} />
          <Lightformer form="circle" intensity={2} rotation-y={Math.PI / 2} position={[-5, 1, -1]} scale={2} />
          <Lightformer form="circle" intensity={2} rotation-y={Math.PI / 2} position={[-5, -1, -1]} scale={2} />
          <Lightformer form="circle" intensity={2} rotation-y={-Math.PI / 2} position={[10, 1, 0]} scale={8} />
        </group>
      </Environment>
    </Canvas>
  )
}

function Connector({ position, children, vec = new THREE.Vector3(), scale, r = THREE.MathUtils.randFloatSpread, accent, ...props }) {
  const api = useRef()
  const pos = useMemo(() => position || [r(10), r(10), r(10)], [])

  useFrame((state, delta) => {
    delta = Math.min(0.1, delta)
    api.current?.applyImpulse(vec.copy(api.current.translation()).negate().multiplyScalar(0.2))
  })
  return (
    <RigidBody linearDamping={4} angularDamping={1} friction={0.1} position={pos} ref={api} colliders={false}>
    
      <CuboidCollider args={[0.38, 1.27, 0.38]} />
      <CuboidCollider args={[1.27, 0.38, 0.38]} />
      <CuboidCollider args={[0.38, 0.38, 1.27]} />
      {children ? children : <Model {...props} />}
      {accent && <pointLight intensity={4} distance={2.5} color={props.color} />}
    </RigidBody>
  )
}

function Pointer({ vec = new THREE.Vector3() }) {
  const ref = useRef()
  useFrame(({ mouse, viewport }) => {
    ref.current?.setNextKinematicTranslation(vec.set((mouse.x * viewport.width) / 2, (mouse.y * viewport.height) / 2, 0))
  })
  return (
    <RigidBody position={[0, 0, 0]} type="kinematicPosition" colliders={false} ref={ref}>
      <BallCollider args={[1]} />
    </RigidBody>
  )
}

function Model({ children, color = 'white', roughness = 0, ...props }) {
  const ref = useRef()
  const { nodes, materials } = useGLTF('/c-transformed.glb')
  useFrame((state, delta) => {
    // 顏色流暢更換
    easing.dampC(ref.current.material.color, color, 0.2, delta)
  })
  return (
    // <primitive object={nodes.connector} scale={1} metalness={0.2} roughness={roughness} receiveShadow />
    <mesh ref={ref} castShadow receiveShadow scale={10} geometry={nodes.connector.geometry}>
      <meshStandardMaterial metalness={0.2} roughness={roughness} map={materials.base.map} />
      {children}
    </mesh>
  )
}