• แนะนำวิธีจำลอง เอฟเฟ็กต์ 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 ของฟิลเตอร์ ที่ทำแอนิเมชันได้)
ผู้เขียนกำลังพิจารณาการเปิดซอร์สในอนาคต และระบุว่าสนใจเรื่องการปรับให้เหมาะสมและการจัดระเบียบโค้ดต่อไป

ยังไม่มีความคิดเห็น

ยังไม่มีความคิดเห็น