three+webgl实现逼真微缩海水和帆船模拟交互三维动画代码

代码语言:html

所属分类:三维

代码描述:three+webgl实现逼真微缩海水和帆船模拟交互三维动画代码,可调节参数,可旋转,可鼠标拖动水面产生波纹,非常真实。

代码标签: three webgl 逼真 微缩 海水 帆船 模拟 交互 三维 动画 代码

下面为部分代码预览,完整代码请点击下载或在bfwstudio webide中打开

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum=1.0,minimum=1.0,user-scalable=0" />

     <style>
     * {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  height: 100%;
  overflow: hidden;
  background: #05060a;
  font-family:
    ui-rounded,
    "Segoe UI",
    system-ui,
    -apple-system,
    sans-serif;
  color: #e9edf6;
  -webkit-tap-highlight-color: transparent;
}

#stage {
  position: fixed;
  inset: 0;
}

#stage canvas {
  position: fixed;
  inset: 0;
  z-index: 0;
  display: block;
  width: 100%;
  height: 100%;
  touch-action: none;
  cursor: grab;
}
#stage canvas:active {
  cursor: grabbing;
}

/* ---- HUD ---- */
#hud {
  position: fixed;
  left: 22px;
  top: 18px;
  z-index: 3;
  pointer-events: none;
  text-shadow: 0 2px 18px rgba(0, 0, 0, 0.7);
}
#hud h1 {
  margin: 0;
  font-size: 23px;
  font-weight: 700;
  letter-spacing: 0.04em;
  background: linear-gradient(180deg, #fff, #9fb8d8);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
#hud p {
  margin: 2px 0 0;
  font-size: 12px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: #7f8aa6;
}

#hint {
  position: fixed;
  right: 18px;
  top: 20px;
  z-index: 3;
  pointer-events: none;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 6px;
}
#hint span {
  font-size: 11px;
  letter-spacing: 0.06em;
  color: #aeb8d2;
  background: rgba(20, 26, 44, 0.42);
  border: 1px solid rgba(150, 180, 230, 0.14);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  padding: 5px 10px;
  border-radius: 999px;
}

/* ---- control panel ---- */
#panel {
  position: fixed;
  left: 50%;
  bottom: 20px;
  transform: translateX(-50%);
  z-index: 3;
  display: flex;
  gap: 18px;
  align-items: center;
  padding: 13px 18px;
  border-radius: 16px;
  background: rgba(14, 18, 32, 0.5);
  border: 1px solid rgba(150, 180, 230, 0.16);
  box-shadow:
    0 12px 40px rgba(0, 0, 0, 0.45),
    inset 0 1px 0 rgba(255, 255, 255, 0.05);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
}
.ctl {
  display: flex;
  flex-direction: column;
  gap: 7px;
  min-width: 150px;
}
.ctl span {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #9fabc9;
}
.ctl i {
  font-style: normal;
  font-size: 10px;
  color: #cfe0ff;
  opacity: 0.8;
  letter-spacing: 0.04em;
  text-transform: none;
}

input[type="range"] {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 4px;
  border-radius: 4px;
  background: linear-gradient(90deg, #5b7cff, #7fd9ff);
  outline: none;
  cursor: pointer;
}
input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: #eaf2ff;
  border: 2px solid #6f8dff;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.5);
  cursor: grab;
}
input[type="range"]::-moz-range-thumb {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: #eaf2ff;
  border: 2px solid #6f8dff;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.5);
  cursor: grab;
}

/* ---- loader ---- */
#loader {
  position: fixed;
  inset: 0;
  z-index: 5;
  display: flex;
  gap: 14px;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  background: radial-gradient(60% 60% at 50% 50%, #0a0d18, #05060b);
  color: #8a96b4;
  font-size: 13px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  transition: opacity 0.6s ease;
}
#loader.hide {
  opacity: 0;
  pointer-events: none;
}
.spin {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: 3px solid rgba(120, 150, 220, 0.18);
  border-top-color: #7fd9ff;
  animation: spin 1s linear infinite;
}
@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

/* ---- sky knob (rotary dial: day → eclipse → night) ---- */
.knob-ctl {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 7px;
}
.knob {
  position: relative;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  cursor: grab;
  touch-action: none;
  background: radial-gradient(circle at 50% 32%, #2b3655, #131a2b 70%);
  border: 1px solid rgba(150, 180, 230, 0.22);
  box-shadow:
    0 5px 16px rgba(0, 0, 0, 0.5),
    inset 0 1px 0 rgba(255, 255, 255, 0.1),
    inset 0 -3px 8px rgba(0, 0, 0, 0.4);
}
.knob:active {
  cursor: grabbing;
}
.knob-ind {
  position: absolute;
  left: 50%;
  top: 5px;
  width: 3px;
  height: 13px;
  margin-left: -1.5px;
  border-radius: 2px;
  background: linear-gradient(#ffe9a8, #ffbf4d);
  box-shadow: 0 0 6px rgba(255, 200, 110, 0.7);
  transform-origin: 50% 17px;
}
.knob-lbl {
  display: flex;
  gap: 6px;
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #9fabc9;
}
.knob-lbl i {
  font-style: normal;
  font-size: 10px;
  color: #cfe0ff;
  opacity: 0.8;
  letter-spacing: 0.04em;
  text-transform: none;
}

/* ---- sound toggle (button is injected by script.js) ---- */
#sndBtn {
  position: fixed;
  right: 18px;
  bottom: 20px;
  z-index: 4;
  width: 42px;
  height: 42px;
  border-radius: 12px;
  border: 1px solid rgba(150, 180, 230, 0.18);
  background: rgba(14, 18, 32, 0.5);
  color: #dfe8ff;
  font-size: 18px;
  line-height: 1;
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.45);
  cursor: pointer;
  transition:
    background 0.2s,
    transform 0.1s;
}
#sndBtn:hover {
  background: rgba(24, 30, 52, 0.6);
}
#sndBtn:active {
  transform: scale(0.94);
}
#sndBtn.on {
  background: rgba(46, 86, 150, 0.55);
  border-color: rgba(127, 217, 255, 0.45);
}

@media (max-width: 560px) {
  #hint {
    display: none;
  }
  .ctl {
    min-width: 120px;
  }
  #hud h1 {
    font-size: 19px;
  }
  #sndBtn {
    bottom: 78px;
  }
}

      </style>
</head>
<body>
    <div id="stage">
  <header id="hud">
    <h1>Petri&nbsp;Dish</h1>
    <p>a little sea you can rock</p>
  </header>

  <div id="hint">
    <span>drag&nbsp;water&nbsp;to&nbsp;push</span>
    <span>drag&nbsp;edge&nbsp;to&nbsp;orbit</span>
    <span>spin&nbsp;the&nbsp;sky</span>
  </div>

  <div id="panel">
    <label class="ctl">
      <span>
        Wind
        <i id="vWind">breeze</i>
      </span>
      <input id="windStr" type="range" min="0" max="1000" value="460" />
    </label>
    <label class="ctl">
      <span>
        Direction
        <i id="vDir">SE</i>
      </span>
      <input id="windDir" type="range" min="0" max="1000" value="95" />
    </label>
    <div class="knob-ctl">
      <div id="skyKnob" class="knob" title="rotate: day → eclipse → night">
        <i class="knob-ind"></i>
      </div>
      <span class="knob-lbl">
        Sky
        <i id="vSky">day</i>
      </span>
    </div>
  </div>

  <div id="loader">
    <div class="spin"></div>
    <span>filling the dish…</span>
  </div>
</div>
<script>
    (async () => {
  "use strict";

  let THREE;
  try {
    THREE =
      await import("https://unpkg.com/three@0.160.0/build/three.module.js");
  } catch (e) {
    THREE =
      await import("https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js");
  }

  const DISH_OUT = 4.95,
    DISH_IN = 4.62,
    RIM_Y = 1.95,
    BASE_Y = 0.22;
  const WATER_R = 4.55;
  const WATER_Y = 1.0;
  const SIM = 256;
  const UV_R = 0.46;
  const WALL_R = 0.476;
  const CENTER = new THREE.Vector3(0, 0.85, 0);

  const clamp = (x, a, b) => (x < a ? a : x > b ? b : x);
  const lerp = (a, b, t) => a + (b - a) * t;
  const norm2 = (x, z) => {
    const l = Math.hypot(x, z) || 1;
    return [x / l, z / l];
  };

  const COMMON = `
  uniform vec3  uKeyDir, uKeyColor, uAmb;
  vec3 aces(vec3 x){ return clamp((x*(2.51*x+0.03))/(x*(2.43*x+0.59)+0.14),0.0,1.0); }
  float hash21(vec2 p){ return fract(sin(dot(p, vec2(127.1,311.7)))*43758.5453); }
  float vnoise(vec2 p){
    vec2 i=floor(p), f=fract(p); f=f*f*(3.0-2.0*f);
    float a=hash21(i), b=hash21(i+vec2(1,0)), c=hash21(i+vec2(0,1)), d=hash21(i+vec2(1,1));
    return mix(mix(a,b,f.x), mix(c,d,f.x), f.y);
  }
  float fbm(vec2 p){ float s=0.0,a=0.5; for(int i=0;i<4;i++){ s+=a*vnoise(p); p*=2.02; a*=0.5; } return s; }
  vec3 envColor(vec3 dir){
    float up = dir.y;
    vec3 c = mix(vec3(0.012,0.018,0.03), vec3(0.07,0.10,0.15), smoothstep(-0.1,0.95,up));
    c += vec3(0.16,0.19,0.26) * smoothstep(0.62,1.0,up) * 0.35;        
    float k = max(dot(normalize(dir), normalize(uKeyDir)), 0.0);
    c += uKeyColor * (pow(k,500.0)*2.6 + pow(k,80.0)*0.35);            
    return c;
  }
`;

  const stage = document.getElementById("stage");
  const renderer = new THREE.WebGLRenderer({
    antialias: true,
    alpha: false,
    powerPreference: "high-performance"
  });
  const isCoarse = matchMedia && matchMedia("(pointer:coarse)").matches;
  renderer.setPixelRatio(
    Math.min(window.devicePixelRatio || 1, isCoarse ? 1.0 : 1.6)
  );
  renderer.setClearColor(0x05060a, 1);
  renderer.outputColorSpace = THR.........完整代码请登录后点击上方下载按钮下载查看

网友评论0