= ({
{open && (
-
diff --git a/src/renderer/components/overlay/WebGLTracks.jsx b/src/renderer/components/overlay/WebGLTracks.jsx
index 9f1c2a5..2015d93 100644
--- a/src/renderer/components/overlay/WebGLTracks.jsx
+++ b/src/renderer/components/overlay/WebGLTracks.jsx
@@ -14,6 +14,7 @@ import {
SRGBColorSpace,
} from "three";
import { animationScheduler } from "../../utils/animationScheduler";
+import { fadePositionToUniform } from "../../../types/noteSettings";
const MAX_NOTES = 2048; // 씬에서 동시에 렌더링할 수 있는 최대 노트 수
@@ -235,16 +236,19 @@ const fragmentShader = `
float trackRelativeY = gradientRatio;
float fadePosFlag = uFadePosition;
- bool fadeDisabled = fadePosFlag > 2.5;
+ bool fadeDisabled = abs(fadePosFlag - 3.0) < 0.1;
+ bool fadeBoth = fadePosFlag > 3.5;
bool invertForFade = false;
- if (!fadeDisabled && fadePosFlag < 0.5) {
- invertForFade = (vReverse > 0.5);
- } else if (!fadeDisabled && abs(fadePosFlag - 1.0) < 0.1) {
- invertForFade = false;
- } else if (!fadeDisabled) {
- invertForFade = true;
+ if (!fadeDisabled && !fadeBoth) {
+ if (fadePosFlag < 0.5) {
+ invertForFade = (vReverse > 0.5);
+ } else if (abs(fadePosFlag - 1.0) < 0.1) {
+ invertForFade = false;
+ } else if (abs(fadePosFlag - 2.0) < 0.1) {
+ invertForFade = true;
+ }
}
- if (!fadeDisabled && invertForFade) {
+ if (!fadeDisabled && !fadeBoth && invertForFade) {
trackRelativeY = 1.0 - trackRelativeY;
}
@@ -267,8 +271,12 @@ const fragmentShader = `
alpha *= smoothAlpha;
}
- // 트랙 페이드 영역 적용 (상단 또는 하단)
- if (!fadeDisabled && trackRelativeY < fadeRatio) {
+ // 트랙 페이드 영역 적용
+ if (fadeBoth) {
+ float topFade = clamp(trackRelativeY / fadeRatio, 0.0, 1.0);
+ float bottomFade = clamp((1.0 - trackRelativeY) / fadeRatio, 0.0, 1.0);
+ alpha *= min(topFade, bottomFade);
+ } else if (!fadeDisabled && trackRelativeY < fadeRatio) {
alpha *= clamp(trackRelativeY / fadeRatio, 0.0, 1.0);
}
@@ -350,16 +358,8 @@ export const WebGLTracks = memo(
uDelayEnabled: {
value: noteSettings.delayedNoteEnabled ? 1.0 : 0.0,
},
- // fadePosition: 'auto' | 'top' | 'bottom' | 'none' -> 0 | 1 | 2 | 3
uFadePosition: {
- value:
- noteSettings.fadePosition === "top"
- ? 1.0
- : noteSettings.fadePosition === "bottom"
- ? 2.0
- : noteSettings.fadePosition === "none"
- ? 3.0
- : 0.0,
+ value: fadePositionToUniform(noteSettings.fadePosition),
},
},
vertexShader,
@@ -700,13 +700,7 @@ export const WebGLTracks = memo(
materialRef.current.uniforms.uDelayEnabled.value =
noteSettings.delayedNoteEnabled ? 1.0 : 0.0;
materialRef.current.uniforms.uFadePosition.value =
- noteSettings.fadePosition === "top"
- ? 1.0
- : noteSettings.fadePosition === "bottom"
- ? 2.0
- : noteSettings.fadePosition === "none"
- ? 3.0
- : 0.0;
+ fadePositionToUniform(noteSettings.fadePosition);
}
}, [
noteSettings.speed,
diff --git a/src/renderer/components/overlay/WebGLTracksOGL.jsx b/src/renderer/components/overlay/WebGLTracksOGL.jsx
index b7f5327..3ad48b3 100644
--- a/src/renderer/components/overlay/WebGLTracksOGL.jsx
+++ b/src/renderer/components/overlay/WebGLTracksOGL.jsx
@@ -1,6 +1,7 @@
import React, { memo, useEffect, useRef } from "react";
import { Renderer, Camera, Transform, Program, Geometry, Mesh } from "ogl";
import { animationScheduler } from "../../utils/animationScheduler";
+import { fadePositionToUniform } from "../../../types/noteSettings";
import { MAX_NOTES } from "@stores/noteBuffer";
import { isMac } from "@utils/platform";
@@ -167,16 +168,19 @@ const fragmentShader = `
float trackRelativeY = gradientRatio;
float fadePosFlag = uFadePosition;
- bool fadeDisabled = fadePosFlag > 2.5;
+ bool fadeDisabled = abs(fadePosFlag - 3.0) < 0.1;
+ bool fadeBoth = fadePosFlag > 3.5;
bool invertForFade = false;
- if (!fadeDisabled && fadePosFlag < 0.5) {
- invertForFade = (vReverse > 0.5);
- } else if (!fadeDisabled && abs(fadePosFlag - 1.0) < 0.1) {
- invertForFade = false;
- } else if (!fadeDisabled) {
- invertForFade = true;
+ if (!fadeDisabled && !fadeBoth) {
+ if (fadePosFlag < 0.5) {
+ invertForFade = (vReverse > 0.5);
+ } else if (abs(fadePosFlag - 1.0) < 0.1) {
+ invertForFade = false;
+ } else if (abs(fadePosFlag - 2.0) < 0.1) {
+ invertForFade = true;
+ }
}
- if (!fadeDisabled && invertForFade) {
+ if (!fadeDisabled && !fadeBoth && invertForFade) {
trackRelativeY = 1.0 - trackRelativeY;
}
@@ -202,7 +206,11 @@ const fragmentShader = `
}
float fadeMask = 1.0;
- if (!fadeDisabled && trackRelativeY < fadeRatio) {
+ if (fadeBoth) {
+ float topFade = clamp(trackRelativeY / fadeRatio, 0.0, 1.0);
+ float bottomFade = clamp((1.0 - trackRelativeY) / fadeRatio, 0.0, 1.0);
+ fadeMask = min(topFade, bottomFade);
+ } else if (!fadeDisabled && trackRelativeY < fadeRatio) {
fadeMask = clamp(trackRelativeY / fadeRatio, 0.0, 1.0);
}
bodyAlpha *= fadeMask;
@@ -435,14 +443,7 @@ export const WebGLTracksOGL = memo(
uTrackHeight: { value: noteSettings.trackHeight || 150 },
uReverse: { value: noteSettings.reverse ? 1.0 : 0.0 },
uFadePosition: {
- value:
- noteSettings.fadePosition === "top"
- ? 1.0
- : noteSettings.fadePosition === "bottom"
- ? 2.0
- : noteSettings.fadePosition === "none"
- ? 3.0
- : 0.0,
+ value: fadePositionToUniform(noteSettings.fadePosition),
},
},
});
@@ -657,13 +658,7 @@ export const WebGLTracksOGL = memo(
uniforms.uTrackHeight.value = noteSettings.trackHeight || 150;
uniforms.uReverse.value = noteSettings.reverse ? 1.0 : 0.0;
uniforms.uFadePosition.value =
- noteSettings.fadePosition === "top"
- ? 1.0
- : noteSettings.fadePosition === "bottom"
- ? 2.0
- : noteSettings.fadePosition === "none"
- ? 3.0
- : 0.0;
+ fadePositionToUniform(noteSettings.fadePosition);
}, [noteSettings]);
return (
diff --git a/src/renderer/locales/en.json b/src/renderer/locales/en.json
index a3b8e7f..454bf21 100644
--- a/src/renderer/locales/en.json
+++ b/src/renderer/locales/en.json
@@ -156,6 +156,7 @@
"top": "Top",
"bottom": "Bottom",
"none": "None",
+ "both": "Full",
"reverseEffect": "Reverse Effect",
"save": "Apply",
"cancel": "Cancel"
diff --git a/src/renderer/locales/ko.json b/src/renderer/locales/ko.json
index 18ccd56..6cbe2a9 100644
--- a/src/renderer/locales/ko.json
+++ b/src/renderer/locales/ko.json
@@ -156,6 +156,7 @@
"top": "상단",
"bottom": "하단",
"none": "없음",
+ "both": "전체",
"reverseEffect": "리버스 효과",
"save": "저장",
"cancel": "취소"
diff --git a/src/renderer/locales/ru.json b/src/renderer/locales/ru.json
index a334aba..785badd 100644
--- a/src/renderer/locales/ru.json
+++ b/src/renderer/locales/ru.json
@@ -156,6 +156,7 @@
"top": "Сверху",
"bottom": "Снизу",
"none": "Нет",
+ "both": "Полный",
"reverseEffect": "Обратный эффект",
"save": "Применить",
"cancel": "Отмена"
diff --git a/src/renderer/locales/zh-Hant.json b/src/renderer/locales/zh-Hant.json
index 56a802e..744fd98 100644
--- a/src/renderer/locales/zh-Hant.json
+++ b/src/renderer/locales/zh-Hant.json
@@ -156,6 +156,7 @@
"top": "頂部",
"bottom": "底部",
"none": "無",
+ "both": "全部",
"reverseEffect": "反向鍵雨",
"save": "應用",
"cancel": "取消"
diff --git a/src/renderer/locales/zh-cn.json b/src/renderer/locales/zh-cn.json
index 88f6625..4fdeb48 100644
--- a/src/renderer/locales/zh-cn.json
+++ b/src/renderer/locales/zh-cn.json
@@ -156,6 +156,7 @@
"top": "顶部",
"bottom": "底部",
"none": "无",
+ "both": "全部",
"reverseEffect": "反向键雨",
"save": "应用",
"cancel": "取消"
diff --git a/src/types/noteSettings.ts b/src/types/noteSettings.ts
index 5f57bd3..e5cc503 100644
--- a/src/types/noteSettings.ts
+++ b/src/types/noteSettings.ts
@@ -6,6 +6,7 @@ export const fadePositionSchema = z.union([
z.literal("top"),
z.literal("bottom"),
z.literal("none"),
+ z.literal("both"),
]);
export const noteSettingsSchema = z.object({
@@ -58,6 +59,19 @@ export const NOTE_SETTINGS_DEFAULTS: NoteSettings = Object.freeze({
keyDisplayDelayMs: NOTE_SETTINGS_CONSTRAINTS.keyDisplayDelayMs.default,
});
+/** fadePosition 문자열을 셰이더 uniform 값으로 변환 */
+const FADE_POSITION_UNIFORM: Record = {
+ auto: 0.0,
+ top: 1.0,
+ bottom: 2.0,
+ none: 3.0,
+ both: 4.0,
+};
+
+export function fadePositionToUniform(pos: string): number {
+ return FADE_POSITION_UNIFORM[pos] ?? 0.0;
+}
+
export function normalizeNoteSettings(raw: unknown): NoteSettings {
const parsed = noteSettingsSchema.safeParse({
...NOTE_SETTINGS_DEFAULTS,