Nightmare: ทำไมจอมืดและ effect ถูกบดบัง
ตรวจจาก client จริงวันที่ 2 พ.ย. 2026 · โค้ด
js/nightmare.js และ js/render3d.js เวอร์ชัน
63ac3f741469 และ
9d2e938e9ff1 (ค่าตรงกันทั้งสองเวอร์ชัน) · แมพ
W0 ช่วงที่ Nightmare เปิด
สรุป
ตอน Nightmare เปิด เกมไม่ได้เปลี่ยนแสงในฉาก 3D เลย แต่ใช้ CSS 3
ชั้นทับ canvas#game ทั้งแผ่น ทุกพิกเซลจึงโดนเหมือนกัน
รวมถึง effect สกิล ชั้นที่หนักที่สุดคือ #nm-tint ซึ่งใช้
mix-blend-mode: color แทนสีของทุกพิกเซลด้วยม่วง 42% ไฟ
น้ำแข็ง และสายฟ้าจึงเสียสีของธาตุ ผู้เล่นแยกธาตุจากสีไม่ออก
ข้อเสนอคือทำบรรยากาศ Nightmare ด้วยแสงในฉากผ่าน
applyMood ซึ่งมีอยู่แล้ว
วิธีนี้ทำให้โลกมืดและม่วงลงได้เหมือนเดิม แต่ effect
ที่ส่องสว่างในตัวไม่โดนย้อม และกลับเด่นขึ้นในความมืด ทดลองบน client
จริงแล้วได้ผลตามนี้
#nm-tint)
brightness(0.85))
saturate(0.8))
#nm-veil)
โค้ดปัจจุบัน
startNightmare(s) ใน nightmare.js ฝัง CSS
ลงหน้า และสร้าง div 2 แผ่นที่ z-index: 4 ทับ
canvas ทุกอย่างเปิดด้วย class nm-on บน
<html>
/* nightmare.js (CSS ที่ฝังใน startNightmare) */
html.nm-on canvas#game { filter: saturate(0.8) brightness(0.85) contrast(1.04); transition: filter 1.5s; }
#nm-tint { position: fixed; inset: 0; pointer-events: none; z-index: 4; opacity: 0; transition: opacity 1.5s;
background: #6b3fa0; mix-blend-mode: color; } /* (color: the violet's hue, the world's own light and shade) */
html.nm-on #nm-tint { opacity: 0.42; }
#nm-veil { position: fixed; inset: 0; pointer-events: none; z-index: 4; opacity: 0; transition: opacity 1.5s;
background: radial-gradient(ellipse at center, rgba(40, 0, 60, 0) 55%, rgba(40, 5, 65, 0.22) 80%, rgba(18, 0, 30, 0.5) 100%); }
html.nm-on #nm-veil { opacity: 1; animation: nm-breathe 5s ease-in-out infinite; }
// nightmare.js: เปิดโหมดเมื่อแมพปัจจุบันอยู่ในรายการ และสลับเพลง
const t = performance.now(),
o = a.on.length > 0 && t < a.until, // มี Nightmare และยังไม่หมดเวลา
i = o && a.on.includes(s.mapId); // แมพที่ยืนอยู่โดนด้วย
document.documentElement.classList.toggle("nm-on", i);
i && !u ? (G.setPlace("nightmare"), (y = !0)) : !i && u && (G.setPlace(s.mapId), (y = !1));
ผลของแต่ละชั้น
| ชั้น | ทำอะไร | ผลต่อ effect สกิล | ระดับ |
|---|---|---|---|
#nm-tint |
แผ่นสี #6b3fa0 แบบ
mix-blend-mode: color ทึบ 42% เปลี่ยน hue
และความสดของทุกพิกเซลเป็นม่วง แต่คงความสว่างเดิม
|
ไฟสีส้มกลายเป็นสีโคลนอมม่วง น้ำแข็งและสายฟ้าไปทางม่วงเหมือนกันหมด สีธาตุถูกลบเกือบครึ่ง | หนักที่สุด |
canvas#game filter |
saturate(0.8) brightness(0.85) contrast(1.04)
|
effect มืดลง 15% และซีดลง 20% แสงเรืองดูแบนลง | ปานกลาง |
#nm-veil |
vignette สีม่วงเข้ม 0% กลางจอ, 22% ที่ 80%, 50% ที่มุม หายใจเข้าออกทุก 5 วิ | effect ที่อยู่ขอบจอมืดลงเพิ่ม แต่กลางจอไม่โดน | เบา |
ค่าแสงของแมพ W0 ตอนตรวจปกติดี (sunI 2.4,
hemiI 1.05, exposure 1.0605) ความมืดทั้งหมดตอน Nightmare
จึงมาจาก 3 ชั้นนี้ แมพหิมะ (N) และ dungeon
(D) ตั้งให้มืดอยู่แล้วโดยตั้งใจ
ทำไม effect ถึงโดนไปด้วย
ทั้ง 3 ชั้นทำงานหลังจาก WebGL วาดภาพเสร็จแล้ว ตอนนั้น browser เห็นแค่พิกเซล 2D แผ่นเดียว ไม่รู้ว่าพิกเซลไหนเป็นพื้น มอนสเตอร์ หรือ effect จึงย้อมทุกอย่างเท่ากัน
ในฉาก 3D ต่างกัน effect ส่วนใหญ่ของเกมใช้
toneMapped: false และ blending แบบ additive
จึงไม่ขึ้นกับแสงของฉาก ถ้าลดแสงที่ sun และ
hemi พื้นและโมเดลจะมืดลง แต่ effect
ยังสว่างเท่าเดิมและตัดกับพื้นมืดได้ดีขึ้น
ฉากกลางคืนในเกมส่วนใหญ่ทำแบบนี้
ข้อเสนอ: ทำ Nightmare ด้วยแสงในฉาก
แบบปัจจุบัน CSS ทับจอ
- ทั้งจอเป็นม่วงหม่น รวมถึง effect
- สีธาตุของสกิลหายเกือบครึ่ง
- ปรับง่าย ไม่ต้องแตะ renderer
แบบเสนอ แสงในฉาก
- โลกมืดและม่วงด้วยแสง หมอก และท้องฟ้า
- effect คงสีจริงและเด่นขึ้นบนพื้นมืด
- ไม่มี filter ทับจอ ซึ่งเป็นงาน compositing เพิ่มทุกเฟรม
ทดลองบน client จริงที่แมพ W0 แล้ว ฉากยังมืดและม่วงแบบ
Nightmare แต่ไฟของ staff_4 ยังเป็นสีส้ม วงสายฟ้ายังเป็นม่วงสด
หอกน้ำแข็งยังเป็นสีฟ้า และประกายน้ำเงินเด่นบนพื้น
เทียบกับของเดิมที่ทุกอย่างกลายเป็นม่วงหม่นเหมือนกัน
ภาพเทียบไม่ได้แนบ เพราะมีชื่อผู้เล่นคนอื่นและแชทในจอ ดูของจริงได้ด้วยสคริปต์ในหัวข้อ ลองเองบน client
โค้ดที่ต้องแก้
1. render3d.js: เพิ่มโหมด Nightmare ใน renderer
ให้ applyMood จำ mood ของแมพไว้ แล้วแปลงเป็นแบบ Nightmare
เมื่อเปิดโหมด ต้องทำในจุดนี้ เพราะตอนเปลี่ยนแมพ
loadMap เรียก
this.applyMood(Di[t.id] || q[s]) ใหม่
ถ้าแก้ค่าจากข้างนอกจะถูกเขียนทับ
// Nightmare grading done with scene light, so self-lit effects keep their colour.
const NM_TINT = { sky: "#24123d", fog: "#3b2263", sun: "#b89cff", hemiSky: "#6b3fa0", hemiGround: "#1a0f26" };
const mixHex = (a, b, k) => `#${new p.Color(a).lerp(new p.Color(b), k).getHexString()}`;
const nightmareMood = (m) => ({
...m,
sky: mixHex(m.sky, NM_TINT.sky, 0.85),
fog: mixHex(m.fog, NM_TINT.fog, 0.88),
sun: mixHex(m.sun, NM_TINT.sun, 0.55),
sunI: m.sunI * 0.5,
hemiSky: mixHex(m.hemiSky, NM_TINT.hemiSky, 0.75),
hemiGround: mixHex(m.hemiGround, NM_TINT.hemiGround, 0.6),
hemiI: m.hemiI * 0.55,
fogNear: m.fogNear * 0.8,
fogFar: m.fogFar * 0.85,
});
// in class Renderer3D
applyMood(t) {
this.baseMood = t;
t = this.nightmare ? nightmareMood(t) : t;
// ...existing body unchanged...
}
setNightmare(on) {
if (this.nightmare === on) return;
this.nightmare = on;
if (this.baseMood) this.applyMood(this.baseMood);
}
2. nightmare.js: เรียก renderer และเอา CSS ที่ทับสีออก
startNightmare(s) ได้ game object มา และ
s.renderer เป็น Renderer3D อยู่แล้ว
(ยืนยันบน client จริง) จึงเรียกได้ตรง ๆ ส่วน class
nm-on เก็บไว้ใช้กับ banner และ vignette
// in b(), right after toggling nm-on
document.documentElement.classList.toggle("nm-on", i);
s.renderer?.setNightmare?.(i);
/* CSS: remove the filter and tint rules, keep a lighter vignette */
- html.nm-on canvas#game { filter: saturate(0.8) brightness(0.85) contrast(1.04); transition: filter 1.5s; }
- html.nm-on #nm-tint { opacity: 0.42; }
- html.nm-on #nm-veil { opacity: 1; animation: nm-breathe 5s ease-in-out infinite; }
+ html.nm-on #nm-veil { opacity: 0.45; }
ถ้าต้องการแก้น้อยที่สุดก่อน: ลด #nm-tint เหลือ ~0.2 และ
filter เป็น brightness(0.92) จะช่วยได้บางส่วน แต่ effect
ยังโดนย้อมอยู่ดี
ค่าที่ใช้ทดลอง (แมพ W0)
| ค่า | ปกติ | Nightmare (เสนอ) |
|---|---|---|
sky |
#a9c8e4 | #4d556e |
fog |
#ecd0a2 | #68556d |
sun |
#ffd9a0 | #dcbbdb |
sunI |
2.4 | 1.2 |
hemiSky |
#ffe9c8 | #a287ab |
hemiGround |
#7a5530 | #52382a |
hemiI |
1.05 | 0.58 |
fogNear / fogFar |
32 / 82 | 25.6 / 69.7 |
สีคำนวณด้วย Color.lerp ของ three.js จึงผสมใน linear space
ตัวเลขทั้งหมดเป็นจุดเริ่มต้นที่ปรับต่อได้ตามความชอบ
ลองเองบน client
ไฟล์
nightmare-lighting.js
ใช้ทดลองแบบเสนอบนเครื่องตัวเอง ทำงานเฉพาะ client นั้น ไม่ส่งอะไรไป
server
- เข้าแมพที่ Nightmare เปิดอยู่
- เปิด DevTools Console ของเกม แล้ววางเนื้อหาไฟล์และกด Enter
-
เทียบได้ด้วย
window.__nmDemo.off()และwindow.__nmDemo.on()
สคริปต์ทับค่าเฉพาะแมพปัจจุบัน ถ้าเปลี่ยนแมพหรือ reload
จะกลับเป็นแบบเดิม ซึ่งเป็นเหตุที่ต้องแก้ใน
applyMood ตามหัวข้อก่อนหน้า
ข้อควรระวัง
-
การเปลี่ยนแบบค่อยเป็นค่อยไป: CSS เดิมใช้
transition 1.5sแต่applyMoodเปลี่ยนทันที ถ้าต้องการความนุ่มนวล ให้ lerpsunI,hemiIและสีไปเป้าหมายใน loop หลักราว 1.5 วิ -
Allocation:
applyMoodสร้างColorและFogใหม่ทุกครั้ง ถ้าทำ lerp ทุกเฟรม ให้ set ค่าลง object เดิมแทน เพราะเธรดหลักเป็นคอขวดตามรายงาน FPS -
Bloom:
applyMoodตั้งbloomBaseตาม region ด้วย ถ้าอยากให้ effect เรืองขึ้นตอน Nightmare เพิ่มได้เล็กน้อย เช่นจาก 0.42 เป็น 0.5 แต่ไม่จำเป็น -
แมพที่มืดอยู่แล้ว:
cryptและ regionDมีsunI0.5 อยู่แล้ว ถ้าคูณ 0.5 อีกจะมืดเกิน ควรกำหนดขั้นต่ำ หรือข้าม regionD - Vignette: ยังเป็น CSS ทับจออยู่ แต่เบากว่าเดิมและกลางจอไม่โดน จึงไม่บัง effect ที่ผู้เล่นมองอยู่
ข้อจำกัด
-
ทดลองบนแมพ
W0แมพเดียว ยังไม่ได้ดูแมพหิมะ ทะเลทราย และcrypt - ตัดสินจากการดูภาพ ไม่ได้วัดค่าความต่างของสีเป็นตัวเลข
- ไม่ได้วัด FPS เทียบ เพราะทั้งสองแบบต่างกันแค่ค่าแสงและการมี filter ทับจอ คาดว่าแบบเสนอเบากว่าเล็กน้อย แต่ยังไม่ได้ยืนยัน
-
โค้ดใน โค้ดที่ต้องแก้ เขียนจากโค้ดที่ minify
แล้ว ชื่อตัวแปร
p,Di,qในซอร์สจริงอาจไม่ตรง