- แนะนำวิธีจำลอง เอฟเฟ็กต์ Liquid Glass ที่ Apple เปิดตัวในงาน WWDC 2025 บนเว็บด้วย CSS, SVG และการคำนวณการหักเหตามหลักฟิสิกส์
- อธิบายหลักการของ การหักเหของแสง และกระบวนการจำลองการหักเหพร้อม การสร้างผิวกระจก โดยใช้ กฎของสเนลล์ (Snell's law)
- ใช้ SVG displacement map เพื่อสร้าง ฟิลด์เวกเตอร์การเลื่อนตำแหน่ง ที่เหมาะกับการเรนเดอร์ และสาธิตว่าสามารถนำไปใช้กับคอมโพเนนต์ UI จริงได้
- ใน Chrome สามารถใช้ SVG filter เป็น backdrop-filter ได้ และยกตัวอย่างการนำไปใช้กับ องค์ประกอบ UI หลายแบบ (แว่นขยาย, สวิตช์, เพลเยอร์ ฯลฯ)
- ระบุว่าสามารถทำเอฟเฟ็กต์แบบเรียลไทม์ได้ แต่ยังมีปัญหาเรื่องการรองรับข้ามเบราว์เซอร์และข้อจำกัดด้านประสิทธิภาพ พร้อมกล่าวถึงแผนโอเพนซอร์สในอนาคต
บทนำ
Apple เปิดตัว เอฟเฟ็กต์ Liquid Glass เป็นครั้งแรกในงาน WWDC เดือนมิถุนายน 2025 เอฟเฟ็กต์นี้ทำให้องค์ประกอบของอินเทอร์เฟซดูเหมือน กระจกหักเหแบบโค้ง จึงมอบประสบการณ์ภาพที่คล้ายพื้นผิวกระจกจริง บทความนี้พาไล่ทำเอฟเฟ็กต์ลักษณะเดียวกันบนเว็บด้วย CSS, SVG displacement map และการคำนวณการหักเหตามหลักฟิสิกส์ เป้าหมายไม่ใช่การทำให้เหมือนสมบูรณ์ทุกจุด แต่เป็น proof of concept ที่ขยายต่อได้ ซึ่งจำลองคุณลักษณะสำคัญอย่างการหักเหและไฮไลต์จากการสะท้อน โดยเริ่มจากหลักพื้นฐานว่าแสง หักเห อย่างไรเมื่อผ่านวัสดุต่างชนิดกัน แล้วค่อยสร้างเอฟเฟ็กต์ทีละขั้น เดโมแบบโต้ตอบที่ให้ไว้ท้ายบทความใช้งานได้ถูกต้องใน Chrome เท่านั้นในตอนนี้
ทำความเข้าใจปรากฏการณ์การหักเห
การหักเห คือปรากฏการณ์ที่ทิศทางของแสงเปลี่ยนไปเมื่อเคลื่อนจากวัสดุหนึ่งไปสู่อีกวัสดุหนึ่ง เกิดขึ้นเพราะแต่ละวัสดุมี ความเร็วของแสง แตกต่างกัน และความสัมพันธ์ระหว่างมุมตกกระทบกับมุมหักเหสามารถอธิบายได้ด้วย กฎของสเนลล์ (Snell's law):
- n1 * sin(θ1) = n2 * sin(θ2)
- n1: ดัชนีหักเหของตัวกลางแรก
- θ1: มุมตกกระทบ
- n2: ดัชนีหักเหของตัวกลางที่สอง
- θ2: มุมหักเห
สิ่งที่ดูได้จากไดอะแกรมแบบโต้ตอบ:
- หากดัชนีหักเหของตัวกลางทั้งสองเท่ากัน แสงจะวิ่งตรงโดยไม่หักเห
- หากตัวกลางที่สองมีดัชนีหักเหสูงกว่า แสงจะหักเหเข้าหาแนวปกติ
- หากตัวกลางที่สองมีดัชนีหักเหต่ำกว่า แสงจะหักเหออกห่าง และบางกรณีอาจเกิดการสะท้อนกลับหมดภายใน
- หากแสงตกกระทบตั้งฉากกับพื้นผิว มันจะวิ่งตรงโดยไม่ขึ้นกับดัชนีหักเห
ข้อจำกัดของโปรเจกต์นี้
เพื่อจำกัดความซับซ้อนและทำให้อัลกอริทึมง่ายขึ้น จึงตั้งเงื่อนไขดังต่อไปนี้:
- ดัชนีหักเหของตัวกลางภายนอกเป็น 1 (อากาศ)
- วัสดุกระจกภายในใช้ค่า 1.5 (กระจกทั่วไป)
- พิจารณาเหตุการณ์การหักเหเพียงครั้งเดียว (ละเลยการหักเหตอนออก)
- แสงตกกระทบตั้งฉากกับระนาบพื้นหลังเสมอ
- วัตถุทั้งหมดเป็นรูปทรง 2D และใช้เฉพาะรูปวงกลม (สามารถขยายไปยังสี่เหลี่ยมผืนผ้า ฯลฯ ได้ แต่ต้องคำนวณเพิ่ม)
- ไม่มีช่องว่างระหว่างวัตถุกับระนาบพื้นหลัง
- ภายใต้เงื่อนไขเหล่านี้ สามารถใช้กฎของสเนลล์คำนวณรังสีทั้งหมดได้อย่างง่าย
การสร้างพื้นผิวกระจก
ในการสร้างเอฟเฟ็กต์ Liquid Glass จำเป็นต้องนิยามหน้าตัดของกระจกเสมือน (เลนส์หรือแผงโค้ง) ด้วยฟังก์ชันทางคณิตศาสตร์
ฟังก์ชันพื้นผิว
พื้นผิวกระจกนิยามด้วย surface function ซึ่งแสดงความหนาตั้งแต่ขอบไปจนถึงด้านในที่แบนราบ
- ค่าป้อนเข้า 0: ขอบนอก, 1: ปลายขอบ bezel (จุดเริ่มของระนาบ)
- จากความหนาในแต่ละจุด ใช้อนุพันธ์เพื่อหา เวกเตอร์แนวปกติ ของพื้นผิว
const height = f(distanceFromSide);
const delta = 0.001;
const y1 = f(distanceFromSide - delta);
const y2 = f(distanceFromSide + delta);
const derivative = (y2 - y1) / (2 * delta);
const normal = { x: -derivative, y: 1 };
ประเภทหลักของฟังก์ชันพื้นผิว
- Convex Circle: y = sqrt((1 - (1 - x))^2)
- รูปส่วนโค้งวงกลมแบบเรียบง่าย จะแบนราบเร็วเมื่อเข้าใกล้ด้านใน ทำให้ขอบการหักเหเด่นชัด
- Convex Squircle: y = ((1 - (1 - x))^4)^(1/4)
- รูปทรงที่ Apple ใช้บ่อย เส้นแบ่งระหว่างส่วนโค้งกับพื้นราบนุ่มนวล จึงดูเป็นธรรมชาติแม้ขยายสเกล
- Concave: y = 1 - Convex(x)
- รูปเว้า (ตรงข้ามกับนูน) ทำให้แสงหักเหออกนอกขอบวัตถุ จึงต้องมีการสุ่มตัวอย่างนอกขอบ
- Lip: y = mix(Convex(x), Concave(x), Smootherstep(x))
- โครงสร้างผสมที่มีขอบยื่นและร่องตื้นบริเวณกึ่งกลาง
เพียงสี่ฟังก์ชันนี้ก็เพียงพอสำหรับเปรียบเทียบความต่างของการหักเหตามรูปทรงพื้นผิวได้อย่างมีประสิทธิภาพ
การจำลอง
มีการจำลอง เส้นทางการหักเหของรังสีแสง สำหรับแต่ละฟังก์ชันพื้นผิวเพื่อให้เห็นความแตกต่างของเอฟเฟ็กต์จริง
- แบบนูน (Convex) จะรวมเส้นทางแสงเข้าด้านใน ส่วนแบบเว้า (Concave) จะผลักออกด้านนอก
- Liquid Glass ของ Apple ส่วนใหญ่ชอบใช้ทรงนูน (มีข้อยกเว้นอย่าง Switch)
- ลูกศรพื้นหลังใช้การลงสีตามขนาดของการหักเห (การเลื่อนตำแหน่ง) เพื่อแสดงผล
- จุดที่อยู่ห่างจากขอบด้านซ้ายและขวาเท่ากันจะมีการเลื่อนตำแหน่งเท่ากัน จึงนำกลับมาใช้ซ้ำอย่างมีประสิทธิภาพได้
การสร้างฟิลด์เวกเตอร์การเลื่อนตำแหน่ง
สร้างฟิลด์เวกเตอร์ที่บอก ทิศทางและขนาดของการเลื่อนตำแหน่งของแสง ในแต่ละตำแหน่งบนพื้นผิวกระจก
- สำหรับรูปวงกลม จะเคลื่อนที่ตามแนวปกติโดยอิงจากขอบเสมอ
คำนวณขนาดการเลื่อนตำแหน่งล่วงหน้า
- เนื่องจากขนาดการเลื่อนตำแหน่งมีความสมมาตรตามระยะจากขอบ จึงคำนวณล่วงหน้าตามหน่วยรัศมีแล้วเก็บเป็นอาร์เรย์
- ใน 2D คำนวณเพียงครั้งเดียว (จำลองรังสี 127 เส้น) จากนั้นหมุนรอบแกน z เพื่อสร้างทั้งฟิลด์
การทำให้เวกเตอร์เป็น normalized
เพื่อใช้เวกเตอร์กับ displacement map จึงทำ normalization (สเกลสูงสุด 1.0)
- ใช้ค่าการเลื่อนตำแหน่งสูงสุดเป็นฐาน แล้วหารขนาดของเวกเตอร์อื่นทั้งหมดเพื่อ normalize
const maximumDisplacement = Math.max(...displacementMagnitudes);
displacementVector_normalized = {
angle: normalAtBorder,
magnitude: magnitude / maximumDisplacement,
};
เมื่อแปลงกลับเป็นหน่วยพิกเซลจริงใน SVG displacement map จะคูณค่าการเลื่อนตำแหน่งสูงสุดกลับผ่าน scale เพื่อคืนขนาดเดิม
SVG Displacement Map
เพื่อให้นำผลการคำนวณการหักเหทางคณิตศาสตร์ไป ใช้กับการเรนเดอร์ในเบราว์เซอร์ได้จริง จึงใช้ SVG displacement map
- แต่ละแชนเนล (RGBA, 8 บิต) ของ
<feDisplacementMap />สามารถรับผิดชอบการเลื่อนตำแหน่งตามแกน X, Y ได้ - แต่ละแชนเนลมีค่า 0~255 และ 128 หมายถึงค่ากลาง (ไม่มีการเลื่อนตำแหน่ง)
- displacement map จำเป็นต้องแปลงเป็นรูปภาพก่อนเสมอ
<svg colorInterpolationFilters="sRGB">
<filter id={id}>
<feImage
href={displacementMapDataUrl}
x={0}
y={0}
width={width}
height={height}
result="displacement_map"
/>
<feDisplacementMap
in="SourceGraphic"
in2="displacement_map"
scale={scale}
xChannelSelector="R"
yChannelSelector="G"
/>
</filter>
</svg>
Scale
แมปค่าของแชนเนล Red(X) และ Green(Y) ไปยังช่วง [−1, 1]
- จากนั้นใช้พร็อพเพอร์ตี scale คูณขนาดการเลื่อนตำแหน่งสูงสุดในหน่วยพิกเซลเพื่อเรนเดอร์จริง
- สามารถทำแอนิเมชันกับ scale เพื่อปรับความแรงของเอฟเฟ็กต์ได้
การแปลงเวกเตอร์ → ค่า Red/Green
- แปลงเวกเตอร์การเลื่อนตำแหน่ง (มุม, ขนาด) ไปเป็นพิกัดฉาก x, y แล้วแมปเป็น 0~255 โดยอิง 128 เป็นค่ากลาง
const x = Math.cos(angle) * magnitude;
const y = Math.sin(angle) * magnitude;
const result = {
r: 128 + x * 127,
g: 128 + y * 127,
b: 128,
a: 255,
};
ภาพที่ได้สามารถนำไปใช้เป็น displacement map ของ SVG filter ได้
Playground
ใน Playground แบบโต้ตอบ สามารถเปลี่ยนรูปทรงพื้นผิว ความหนา bezel ความหนากระจก และ effect scale แบบเรียลไทม์ เพื่อดูการเปลี่ยนแปลงของฟิลด์การหักเห displacement map และผลเรนเดอร์สุดท้ายได้
Specular Highlight
ขั้นสุดท้ายคือเพิ่ม specular highlight (ไฮไลต์สว่างตามขอบบนพื้นผิวกระจก)
- การทำของ Apple ใช้แนวทาง rim light (แสงตามขอบ) ซึ่งความสว่างจะแตกต่างกันตามแนวปกติของพื้นผิวและมุมของแหล่งกำเนิดแสง
การรวมการหักเหและ Specular Highlight
ใน SVG filter ขั้นสุดท้าย จะโหลดทั้ง displacement map และภาพ specular highlight ผ่าน <feImage /> แล้วรวมกันด้วย <feBlend /> เพื่อสร้างเอฟเฟ็กต์สุดท้าย
- สามารถปรับพารามิเตอร์ของฟิลเตอร์เพื่อสร้างภาพลักษณ์ที่หลากหลายได้
การใช้ SVG filter เป็น backdrop-filter
- หากต้องการใส่เอฟเฟ็กต์ Liquid Glass ให้กับคอมโพเนนต์ UI จริง จำเป็นต้องอาศัยการรองรับ
backdrop-filter: url(#...)ของ Chrome - เนื่องจากขนาดภาพของ
backdrop-filterจะไม่ถูกปรับให้อัตโนมัติ จึงต้องเตรียม displacement map ให้ตรงกับขนาดของ element
.glass-panel {
backdrop-filter: url(#liquidGlassFilterId);
}
การนำไปใช้กับคอมโพเนนต์ UI จริง
มีการสาธิตการนำ refraction และ displacement map ที่คำนวณได้ไปใช้กับคอมโพเนนต์ UI ที่ดูสมจริง
- มีเพียง Chrome ที่สามารถประมวลผล SVG filter เป็น
backdrop-filterได้ - ตัวอย่างเหล่านี้ไม่ใช่คอมโพเนนต์โปรดักชันจริง แต่มีไว้เพื่อสาธิตว่าการใส่เอฟเฟ็กต์ Liquid Glass กับ UI แบบต่าง ๆ จะออกมาเป็นอย่างไร
Magnifying Glass
- ใช้ทั้ง refraction และ zoom สองด้าน พร้อม displacement map สองชุด
- เพิ่มเอฟเฟ็กต์เชิงโต้ตอบด้วยเงาและการปรับสเกล
- สามารถลากเพื่อเปลี่ยนรูปทรงเลนส์และสังเกตเส้นทางการหักเหได้
- เพิ่ม specular highlight แบบนุ่มนวล
Searchbox
- ใส่เอฟเฟ็กต์ Liquid Glass ให้กับช่องอินพุตมาตรฐาน
Switch
- ใช้ lip bezel โดยภายนอกเป็นนูน ภายในเป็นเว้า
- เฉพาะสไลเดอร์ตรงกลางเท่านั้นที่ถูกขยาย/ย่อ ส่วนขอบจะใช้การหักเหของภาพด้านใน
Slider
- ใช้ convex bezel แสดงค่าปัจจุบันผ่านกระจกโดยตรง และใช้การหักเหของพื้นหลังทั้งสองด้าน
Music Player
-
เลียนแบบสไตล์แผง Liquid Glass ของ Apple Music
-
ใช้ convex bezel และ specular highlight แบบบางเบาเพื่อเพิ่มมิติ
-
ใช้ iTunes Search API เพื่อโหลดข้อมูลอย่างอัลบั้มอาร์ตและชื่อเพลง
-
(แสดงข้อมูลชื่อเพลงและอัลบั้มในรูปแบบรายการ)
บทสรุป
ต้นแบบนี้นำแนวคิด Liquid Glass ของ Apple มาย่อให้อยู่ในรูปของ เอฟเฟ็กต์การหักเหแบบเรียลไทม์ และไฮไลต์อย่างง่าย ใช้งานได้จริงเฉพาะบน เบราว์เซอร์ที่ใช้ Chromium (หรือ Electron) ส่วนเบราว์เซอร์อื่นสามารถใช้เลเยอร์เบลอแทนได้
นี่เป็นการทดลองเชิงสำรวจ และการสร้าง displacement map ใหม่ทุกครั้งเมื่อ shape/size เปลี่ยนยังไม่มีประสิทธิภาพอย่างมาก (มีเพียงพารามิเตอร์บางอย่าง เช่น scale ของฟิลเตอร์ ที่ทำแอนิเมชันได้)
ผู้เขียนกำลังพิจารณาการเปิดซอร์สในอนาคต และระบุว่าสนใจเรื่องการปรับให้เหมาะสมและการจัดระเบียบโค้ดต่อไป
ยังไม่มีความคิดเห็น