☰ Categories

Digital Sea

a VFX Artist focused on bioluminescent fluid simulations and particle-based environmental effects.

CategoryDevelopment › Coding
TagsDraftingAnalyzingDeveloperCode
Prompt
I want you to act as a VFX Artist focused on bioluminescent fluid simulations and particle-based environmental effects.

Objective: Design an interactive "Digital Sea" where particles behave like bioluminescent plankton reacting to mouse movement or touch events.

Key Mechanics:

Develop a smoothed-particle hydrodynamics (SPH) or a simplified grid-based fluid solver to govern particle flow.

Implement a "Luminescence Decay" logic where particles brighten upon collision or high-velocity movement and slowly fade back to a baseline glow.

Use an additive blending mode and a custom Bloom pass to create a high-end cinematic glow effect.

Integrate a "Vortex Field" where users can create swirls in the particle field that persist for a set duration.

Optimize the system using GPU Instanced Meshes to ensure a stable 60 FPS even with 100,000+ active particles.

Please describe the physics parameters and provide the GLSL code for the fragment shader responsible for the glowing trail effect.

What this prompt does

This prompt assigns a VFX Artist role to design an interactive Digital Sea. It includes SPH or grid fluid logic, luminescence decay, vortex fields, and optimization for 100,000+ particles.

Model comparison

ChatGPT is the most complete and implementable. Gemini is visually rich but has numeric and shader issues, while [C] is missing.

ChatGPTTop overall
44/ 50

+ Covers physics, vortices, bloom, and shaders at implementation level.

- trailLength is unused, and frame-dependent decay is insufficiently explained.

Gemini
39/ 50

+ Connects the GPU pipeline to visual direction with concrete values.

- Decay timing and viscosity units are inaccurate, and the trail is symmetric.

CriterionChatGPTGeminiLeader
Instruction following108ChatGPT +25%
Accuracy86ChatGPT +33%
Specificity99Tie
Structure99Tie
Right length87ChatGPT +14%

Scored 1–10 by gpt-5.6-sol with model names hidden (2026-09-25). This is an AI review, not a measurement.

Read full answers

We gave three models the same input and copied their answers unedited. Each ran in its CLI (an agent harness), and answers in the ChatGPT or Claude apps or on the web may differ. Outputs are in Korean.

gpt-5.6-luna (codex CLI · 에이전트 하네스) · Run on 2026-09-24

Digital Sea 설계

100,000개 이상의 입자를 안정적으로 처리하려면 입자마다 SPH 이웃 탐색을 수행하기보다, 저해상도 Eulerian 속도 필드 + GPU 인스턴싱 입자 조합을 권장합니다.

구조는 다음과 같습니다.

  1. 마우스/터치 입력을 속도와 힘으로 변환
  2. 저해상도 격자에 힘과 와류를 기록
  3. 속도 필드를 확산·감쇠
  4. 각 입자가 자신의 위치에서 속도 필드를 샘플링
  5. 속도와 충돌량을 기반으로 발광값 갱신
  6. 인스턴스 렌더링으로 입자 및 잔상 렌더링
  7. HDR 렌더 타깃에 additive blending
  8. 다운샘플링 Bloom pass 적용

권장 물리 파라미터

const fluidParams = {
  particleCount: 100000,

  // 시뮬레이션 공간
  gridWidth: 256,
  gridHeight: 256,
  worldWidth: 2.0,
  worldHeight: 2.0,

  // 유체
  viscosity: 0.012,
  velocityDamping: 0.985,
  pressureStrength: 0.18,
  curlStrength: 0.65,
  advectionStrength: 1.0,

  // 입력 반응
  mouseForce: 2.8,
  mouseRadius: 0.12,
  inputSmoothing: 0.82,

  // 입자
  particleDrag: 0.985,
  maxVelocity: 1.8,
  particleSize: 0.006,

  // 발광
  baseLuminescence: 0.08,
  collisionLuminescence: 1.4,
  velocityLuminescence: 0.55,
  luminescenceDecay: 0.92,
  luminescenceResponse: 0.35,

  // 와류
  vortexStrength: 3.5,
  vortexRadius: 0.22,
  vortexLifetime: 2.8,
  vortexDecay: 0.94,

  // Bloom
  bloomThreshold: 0.7,
  bloomStrength: 1.25,
  bloomRadius: 4.0
};

입자의 발광값은 다음과 같이 갱신할 수 있습니다.

luminescence =
    max(
        baseLuminescence,
        luminescence * luminescenceDecay +
        collisionAmount * collisionLuminescence +
        velocityLength * velocityLuminescence
    );

충돌은 화면 경계, 장애물 필드, 또는 주변 입자의 속도 차이를 기반으로 단순화할 수 있습니다.

float collisionAmount = clamp(
    length(velocity - sampledFluidVelocity) * 0.5,
    0.0,
    1.0
);

Vortex Field

사용자가 드래그하면 입력 위치에 다음 힘을 추가합니다.

vec2 offset = particlePosition - vortexPosition;
float distanceToVortex = length(offset);

float influence = 1.0 - smoothstep(0.0, vortexRadius, distanceToVortex);
vec2 tangent = vec2(-offset.y, offset.x);

velocity += normalize(tangent) *
            vortexStrength *
            influence *
            deltaTime;

와류는 위치, 반경, 강도, 남은 시간을 가진 이벤트로 저장합니다.

{
  position: vec2,
  radius: 0.22,
  strength: 3.5,
  remaining: 2.8
}

매 프레임 다음과 같이 약화시킵니다.

vortex.strength *= 0.94;
vortex.remaining -= deltaTime;

Glow Trail Fragment Shader

아래 셰이더는 인스턴스화된 쿼드 하나를 입자 하나로 사용하는 방식입니다. vUv는 쿼드 좌표이며, vVelocity, vLuminescence, vLife는 인스턴스 버텍스 셰이더에서 전달됩니다.

#version 300 es
precision highp float;

in vec2 vUv;
in vec2 vVelocity;
in float vLuminescence;
in float vLife;

uniform vec3 uColor;
uniform float uTime;
uniform float uBloomThreshold;

layout(location = 0) out vec4 outColor;

float gaussian(float x, float sigma) {
    return exp(-(x * x) / (2.0 * sigma * sigma));
}

void main() {
    // 중심을 기준으로 한 쿼드 좌표
    vec2 p = vUv * 2.0 - 1.0;

    float speed = length(vVelocity);
    vec2 direction = speed > 0.001
        ? normalize(vVelocity)
        : vec2(1.0, 0.0);

    vec2 normal = vec2(-direction.y, direction.x);

    // 속도가 빠를수록 잔상이 길어짐
    float trailLength = mix(0.35, 1.8, smoothstep(0.0, 1.5, speed));

    float along = dot(p, direction);
    float across = dot(p, normal);

    // 진행 방향 뒤쪽으로 늘어진 꼬리
    float trailStart = -0.95;
    float trailEnd = 0.75;

    float trailMask = smoothstep(trailStart - 0.15, trailStart, along) *
                      (1.0 - smoothstep(trailEnd, trailEnd + 0.15, along));

    // 중앙은 밝고 외곽은 부드럽게 감쇠
    float width = mix(0.34, 0.12, smoothstep(0.0, 1.5, speed));
    float radialGlow = gaussian(across, width);

    // 입자 머리 부분의 강한 핵
    float head = exp(-length(p * vec2(1.0, 1.8)) * 5.0);

    // 뒤로 갈수록 밝기가 약해지는 잔상
    float tailFade = 1.0 - smoothstep(-0.95, 0.55, along);

    float glow = trailMask *
                 radialGlow *
                 mix(0.45, 1.0, tailFade);

    glow += head * 1.4;

    // 생명주기와 발광량 반영
    float intensity = glow *
                      max(vLuminescence, 0.05) *
                      mix(0.65, 1.0, vLife);

    // 약간의 유기적 깜빡임
    float flicker = 0.94 +
                    0.06 * sin(uTime * 8.0 + vLife * 23.0);

    intensity *= flicker;

    // HDR 값으로 출력해야 Bloom이 충분히 발생함
    vec3 emissive = uColor * intensity * 3.5;

    // 아주 약한 투명도 가장자리
    float alpha = clamp(glow * vLuminescence * 1.5, 0.0, 1.0);

    if (alpha < 0.01) {
        discard;
    }

    outColor = vec4(emissive, alpha);
}

렌더 상태는 다음과 같이 설정합니다.

material.blending = THREE.AdditiveBlending;
material.transparent = true;
material.depthWrite = false;
material.toneMapped = false;

Custom Bloom Pass

Bloom은 한 번에 강하게 블러하기보다 다음 단계가 안정적입니다.

HDR Scene
  ↓
Brightness Threshold
  ↓
Half Resolution Blur
  ↓
Quarter Resolution Blur
  ↓
Additive Composite
  ↓
Tone Mapping

Threshold fragment shader의 핵심은 다음과 같습니다.

vec3 color = texture(uScene, vUv).rgb;
float brightness = max(max(color.r, color.g), color.b);

float mask = smoothstep(
    uThreshold,
    uThreshold + 0.2,
    brightness
);

outColor = vec4(color * mask, 1.0);

최종 합성:

vec3 scene = texture(uScene, vUv).rgb;
vec3 bloom = texture(uBloom, vUv).rgb;

vec3 finalColor = scene + bloom * uBloomStrength;
outColor = vec4(finalColor, 1.0);

60 FPS 최적화 핵심

  • 입자 상태는 RGBA32F 또는 적절한 정밀도의 텍스처에 저장
  • 업데이트는 GPU ping-pong framebuffer로 처리
  • 렌더링은 InstancedMesh 또는 raw instanced buffer 사용
  • CPU에서 100,000개 입자를 순회하지 않음
  • 유체 속도 필드는 128²~256² 해상도로 유지
  • SPH를 사용할 경우 spatial hash/grid를 GPU에서 구성
  • Bloom은 원본 해상도에서 처리하지 않고 1/2, 1/4 해상도에서 처리
  • depthWrite = false로 투명 입자 깊이 쓰기 비용 제거
  • 잔상은 여러 파티클을 생성하지 않고 속도 방향으로 인스턴스 쿼드를 늘려 구현

이 구성은 실제 SPH보다 근사도가 낮지만, 100,000~500,000개 입자 규모에서 인터랙티브한 생물발광 바다와 안정적인 성능을 얻기에 적합합니다.

More in this category

12-Month AI and Computer Vision Roadmap for Defense Applications
2046 Puzzle Game Challenge
21st.dev component prompt
3D FACTORY
3D FPS Game