Skip to content

Starry Sea bg

Thousands of little fish swimming upward in winding formations, with perspective depth and atmospheric fog.

Tech Keywords

NameDescription
Canvas ShaderWritten in GLSL and executed directly on the GPU, faster than the Canvas 2D API but harder to master
NoiseProduces more natural randomness than Math.random, commonly used for terrain, clouds, and textures
WebGL2 Instanced RenderingDraws large numbers of identical geometries at once via WebGL2 instanced drawing, greatly reducing draw calls
Simplex NoiseImproved Perlin Noise that computes faster without directional bias, commonly used in procedural generation
SDF (Signed Distance Field)Describes shape outlines with mathematical functions, commonly used in shaders to draw smooth graphics

Usage Examples

Basic Usage

View example source code
vue
<template>
  <div>
    <div class="grid grid-cols-2 gap-x-6 gap-y-2">
      <base-checkbox
        v-model="enable"
        :label="t('enable')"
        class="example-ctrl col-span-2"
      />

      <base-input
        v-model="options.fishSize"
        :label="`${t('fishSize')}: ${options.fishSize}`"
        type="range"
        :min="5"
        :max="60"
        :step="1"
      />

      <base-input
        v-model="options.spread"
        :label="`${t('spread')}: ${options.spread}`"
        type="range"
        :min="0.02"
        :max="0.5"
        :step="0.01"
      />

      <base-input
        v-model="options.riseSpeed"
        :label="`${t('riseSpeed')}: ${options.riseSpeed}`"
        type="range"
        :min="0"
        :max="0.002"
        :step="0.0001"
      />

      <base-input
        v-model="options.meanderStrength"
        :label="`${t('meanderStrength')}: ${options.meanderStrength}`"
        type="range"
        :min="0"
        :max="0.01"
        :step="0.0005"
      />

      <base-input
        v-model="options.followSpeed"
        :label="`${t('followSpeed')}: ${options.followSpeed}`"
        type="range"
        :min="0.005"
        :max="0.15"
        :step="0.005"
      />

      <base-input
        v-model="options.turnRate"
        :label="`${t('turnRate')}: ${options.turnRate}`"
        type="range"
        :min="0.01"
        :max="0.2"
        :step="0.01"
      />

      <base-input
        v-model="options.spreadCurve"
        :label="`${t('spreadCurve')}: ${options.spreadCurve}`"
        type="range"
        :min="0.5"
        :max="5"
        :step="0.5"
      />
    </div>

    <bg-starry-sea
      v-if="enable"
      class="pointer-events-none inset-0 z-[99999] !fixed"
      :fish-size="Number(options.fishSize)"
      :spread="Number(options.spread)"
      :rise-speed="Number(options.riseSpeed)"
      :meander-strength="Number(options.meanderStrength)"
      :follow-speed="Number(options.followSpeed)"
      :turn-rate="Number(options.turnRate)"
      :spread-curve="Number(options.spreadCurve)"
    />
  </div>
</template>

<script setup lang="ts">
import { useMediaQuery } from '@vueuse/core'
import { useData } from 'vitepress'
import { onUnmounted, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import BaseCheckbox from '../../base-checkbox.vue'
import BaseInput from '../../base-input.vue'
import BgStarrySea from '../bg-starry-sea.vue'

const { t } = useI18n()

const enable = ref(false)

const isMobile = useMediaQuery('(max-width: 768px)')

const options = ref({
  fishSize: isMobile.value ? 10 : 18,
  spread: 0.18,
  riseSpeed: 0.0005,
  meanderStrength: 0.003,
  followSpeed: 0.03,
  turnRate: 0.04,
  spreadCurve: 2,
})

const { isDark } = useData()

const oriValue = isDark.value

watch(enable, (value) => {
  isDark.value = value
})

onUnmounted(() => {
  isDark.value = oriValue
})
</script>

How it Works

  • Trail Path System: Multiple winding paths driven by Simplex Noise. Fish follow the path history in sequence, forming long dragon-like formations
  • WebGL2 Instanced Rendering: All fish share the same vertex data. Position, angle, color and depth are passed via instance attributes, completing the render in a single draw call -- even 10,000 fish at once without breaking a sweat! (ゝ∀・)b
  • SDF Fish Shape: An ellipse and rounded triangle SDF are combined in the fragment shader to form the fish silhouette, with a matching color glow effect
  • Perspective Depth: Paths carry Z-axis depth variation. Distant fish shrink, compress spacing, slow down, and fade into the background color for an atmospheric fog effect
  • Depth Sorting: Instance data is sorted by depth each frame, ensuring closer fish are drawn on top of distant ones

Source Code

API

Props

interface Props {
  /** 魚的數量。@default 10000 */
  fishCount?: number;

  /** 魚的大小(px)。@default 18 */
  fishSize?: number;

  /** 長龍路徑數量。@default 6 */
  trailCount?: number;

  /** 路徑歷史步數,越長龍身越長。@default 2000 */
  trailLength?: number;

  /** 魚群散佈寬度(正規化空間)。@default 0.18 */
  spread?: number;

  /** 上升速度。@default 0.0005 */
  riseSpeed?: number;

  /** 路徑蜿蜒幅度。@default 0.003 */
  meanderStrength?: number;

  /** 魚跟隨路徑的平滑速度,0~1,越大越緊跟。@default 0.03 */
  followSpeed?: number;

  /** 路徑轉彎頻率,越小轉彎越平緩。@default 0.04 */
  turnRate?: number;

  /** 散佈集中度,越大魚群越集中在路徑中心,偶有離群魚。@default 2 */
  spreadCurve?: number;
}

v0.69.0