4 คะแนน โดย GN⁺ 2023-12-08 | 2 ความคิดเห็น | แชร์ทาง WhatsApp
  • เมื่อภาพประกอบบนเว็บขยายจากเวกเตอร์แบบแบนไปสู่แนวทางที่ใช้ พื้นผิวแบบนอยส์ รวมถึงแสงและเงา การทำสิ่งนี้ด้วย SVG ที่เบาและนำกลับมาใช้ซ้ำได้จึงยิ่งสำคัญ
  • เป้าหมายหลักคือการเพิ่ม เอฟเฟกต์เงาที่มีเท็กซ์เจอร์ ให้กับรูปทรงเรียบง่าย และทำให้สามารถย้ายไปเป็นคอมโพเนนต์ Svelte หรือ React เพื่อเปลี่ยนสีและรูปทรงแล้วนำกลับไปใช้ได้
  • โครงสร้างพื้นฐานคือกำหนดรูปทรงไว้ครั้งเดียวใน <defs> แล้วนำกลับมาใช้ด้วย <use> จากนั้นใช้ mask และ linearGradient เพื่อสร้าง gradient mask ที่ไม่ยึดติดกับสีมากเกินไป
  • ความเป็นเกรนของพื้นผิวสร้างด้วย SVG filter ที่อิง <feTurbulence> และผสมกับสีเติมที่เลือกผ่านการใช้ <feComposite>, <feColorMatrix>, <feBlend> ร่วมกัน
  • ผลลัพธ์ไปถึงพื้นผิวตามที่ตั้งใจไว้แล้ว แต่ยังมี ความแตกต่างในการเรนเดอร์ของ Safari เหลืออยู่ จึงยังต้องปรับเพิ่มสำหรับการแสดงผลข้ามเบราว์เซอร์และการใช้งานกับภาพประกอบที่ซับซ้อนกว่า

เพิ่มพื้นผิวแบบนอยส์ให้ SVG ด้วยโค้ดล้วน

  • ในงานออกแบบเว็บช่วงหลัง แม้ภาพประกอบเวกเตอร์จะยังถูกใช้อย่างต่อเนื่อง แต่สไตล์แสงและเงาที่มี เกรนหยาบ กลับสะดุดตามากกว่ารูปทรงแบนสะอาดตา
  • แม้จะส่งออกสไตล์ลักษณะนี้จากโปรแกรมทำภาพประกอบเป็น SVG ได้ แต่ก็มีโอกาสที่เท็กซ์เจอร์จะถูกใส่มาเป็น raster หรือทำให้ขนาดเวกเตอร์ใหญ่ขึ้น
  • เป้าหมายคือการสร้างเอฟเฟกต์เงาที่มีเท็กซ์เจอร์แบบมีนอยส์ด้วย สเปก SVG มาตรฐาน เท่านั้น และคงโครงสร้างให้แตกเป็นคอมโพเนนต์ได้ง่าย
  • จัดโครงสร้างให้วิธีเดียวกันนี้ยังทำงานได้แม้จะเปลี่ยนรูปทรงและสี เพื่อให้ใช้งานซ้ำได้ดี

รูปทรงพื้นฐานและกราเดียนต์

  • แม้วาดรูปทรงพื้นฐานของ SVG อย่างวงกลมหรือสี่เหลี่ยมได้ง่าย แต่ในคอมโพเนนต์ที่นำกลับมาใช้ซ้ำได้ กราเดียนต์สีแบบตายตัวอย่างเดียวก็ยังไม่พอ
  • เพื่อสร้างกราเดียนต์ที่เปลี่ยนไปตามสีพื้นฐาน จึงใช้ mask
    • SVG mask ควบคุมพื้นที่ที่จะแสดงหรือถูกซ่อนด้วยสีดำและสีขาว
    • แม้วิธีนี้อาจชวนสับสนอยู่บ้าง แต่ก็เริ่มจากตัวอย่างง่าย ๆ ได้
  • หากเป็นโครงสร้างที่ใช้รูปวงกลมเดิมหลายครั้ง ก็ให้กำหนด <circle id="shape"> ไว้ใน <defs> แล้วอ้างอิงด้วย <use href="#shape">
  • วิธีนี้ช่วยลดการเขียนซ้ำ และรวมการกำหนดรูปทรงไว้ในที่เดียวตามแนวคิด DRY

วิธีสร้างเท็กซ์เจอร์แบบนอยส์

  • ใช้ SVG filter เพื่อวางพื้นผิวทับลงบนกราเดียนต์
  • การสร้างนอยส์เป็นหน้าที่ของ <feTurbulence>
    • <feTurbulence> คือ SVG filter primitive สำหรับสร้างเท็กซ์เจอร์สังเคราะห์
    • หากต้องการโทนที่ตรงใจ จำเป็นต้องปรับค่าหลายตัว
  • filter สุดท้ายจะผสมพื้นผิวเข้ากับสีผ่านหลายขั้นตอน
    • สร้างนอยส์ด้วย <feTurbulence type="fractalNoise" baseFrequency="19.5" numOctaves="10">
    • ใช้ <feComposite> เพื่อ composite ผลลัพธ์ของ turbulence เข้าไปใน SourceAlpha
    • ใช้ <feColorMatrix type="luminanceToAlpha"> เพื่อลดความเปลี่ยนแปลงของสี
    • ใช้ <feBlend mode="color-burn"> เพื่อผสานเข้ากับกราฟิกต้นฉบับ
  • นำ filter ไปใช้กับ <use> สุดท้ายด้วย filter="url('#noise')"

องค์ประกอบ SVG ขั้นสุดท้าย

  • ตัวอย่างสุดท้ายกำหนดรูปทรง, filter, mask และกราเดียนต์รวมกันไว้ใน <defs>
  • กราเดียนต์ประกอบด้วย linearGradient id="fade"
    • ที่จุด 0% ใช้สีดำร่วมกับ stop-opacity="0.6"
    • ที่จุด 65% และ 75% ใช้สีขาวเพื่อค่อย ๆ ปรับความโปร่งใส
  • กับรูปทรงสุดท้ายจะใช้สีเติม, mask และ filter พร้อมกัน
    • fill="hsl(337, 92%, 69%)"
    • mask="url(#gradient)"
    • filter="url('#noise')"
  • เมื่อเพิ่มสีและปรับกราเดียนต์ให้ละเอียดขึ้น ก็ได้ผลลัพธ์ที่ใกล้กับเอฟเฟกต์ภาพตามต้องการมากขึ้น
  • ผลลัพธ์ทั้งหมดดูได้ที่ Full Demo และโค้ด implementation เปิดเผยไว้ใน repository

ผลลัพธ์และข้อจำกัด

  • แม้จะได้ภาพประกอบที่น่าพอใจจากการทดลองเพิ่มเติม แต่ผลลัพธ์ก็ยังไม่ถึงขั้นสมบูรณ์แบบ
  • โดยเฉพาะใน Safari ที่เรนเดอร์ต่างจากเบราว์เซอร์อื่น
  • เมื่อเทียบกับภาพประกอบซับซ้อนที่ใช้อ้างอิง งานนี้ยังค่อนข้างเรียบง่าย แต่หากขัดเกลาเพิ่มเติมก็มีโอกาสนำไปใช้ได้ในหลายสถานการณ์มากขึ้น
  • ตามเนื้อหาที่เพิ่มเมื่อ 7 ธันวาคม 2023 หลังโพสต์ลง Hacker News ผู้เขียนได้รู้จักบทความ CSS-Tricks ปี 2021 ของ Jimmy Chion เรื่อง Grainy Gradients
  • บทความดังกล่าวดูเหมือนจะใช้เทคนิคที่ใกล้เคียงกัน จึงสามารถใช้เป็นข้อมูลประกอบเพิ่มเติมได้

2 ความคิดเห็น

 
ndrgrd 2023-12-09

สมกับ Safari ที่ไม่เคยทำให้ผิดหวังจริง ๆ! เรียกว่าเป็น IE แห่งยุค 20s ก็ไม่เกินเลยนะ

 
GN⁺ 2023-12-08
ความคิดเห็นจาก Hacker News
  • ช่วงหลังได้ลองจับ SVG แล้วพยายามใส่ เงาตกกระทบ ที่เคยทำด้วย canvas ได้ยาก แต่ใน Safari นั้น CSS drop shadow ภายใน SVG ไม่ทำงาน จึงต้องใช้ฟิลเตอร์ drop shadow แบบเนทีฟของ SVG และผลลัพธ์ก็ออกมาดูต่างจากเบราว์เซอร์อื่น
    ตัวอย่างแบบละเอียดอยู่ที่ https://www.einarmagnus.net/pages/logo และตั้งให้ JavaScript ตรวจจับ Safari เพื่อเปิดเวอร์ชัน SVG และปิดเวอร์ชัน CSS ไว้
    พื้นหลัง “หยินหยางแบบเรียกซ้ำ” อยู่ที่ https://einarmagnus.net/blog/2020/01/22/finding-the-logo
    อยากทดลองกับพื้นผิวเพิ่มอีก แต่หงุดหงิดที่ใน Safari มันดูต่างกันมาก สงสัยว่าสักวันวิธีตีความ SVG จะลู่เข้าหากันไหม

    • ตอนนี้คงยังไม่ลู่เข้าหากัน น่าเสียดายที่ไม่มีที่ไหน implement สเปก SVG ทั้งหมด และพอถึง SVG 2 ก็ยิ่งห่างออกไปอีกมาก
      เรื่องนี้ https://razrfalcon.github.io/notes-on-svg-parsing/ น่าอ่าน
      ในรายงานบั๊กของไลบรารี SVG มักเห็นคำว่า “SVG ของฉัน render ไม่ถูกต้อง” อยู่บ่อย ๆ แต่ประเด็นคือจริง ๆ แล้วไม่มีสิ่งที่เรียกว่า SVG ที่ render ได้ถูกต้องสมบูรณ์ เมื่อเริ่มใช้ ฟีเจอร์ขั้นสูง อย่างข้อความ ฟิลเตอร์ หรือแอนิเมชัน ก็พังต่างกันได้ง่ายในแต่ละ renderer
    • คนที่เข้าไปหน้า ‘logo’ แล้วเห็นแค่คลื่นพื้นฐานก็ควรลองกด preset สักสองสามอันจากดรอปดาวน์มุมขวาล่าง
      แค่เปลี่ยนพารามิเตอร์นิดเดียวก็ได้ดีไซน์ที่ต่างกันพอสมควรอย่าง scorpion, balance, spruce ซึ่งเท่มาก
  • คำที่ผู้เขียนกำลังหาน่าจะใกล้เคียงกับ dithering มากกว่า บทความ “Grainy Gradients” ของ CSS-Tricks ก็อธิบายเทคนิคเดียวกัน
    https://css-tricks.com/grainy-gradients/
    มีทางเลือกที่ทำคล้ายกันด้วย CSS ด้วย แต่เป็นวิธีที่ใช้กับทั้งภาพ
    https://github.com/tomren1/dither-with-css

    • จริง ๆ อันนี้ใกล้เคียงกับ noise เฉย ๆ มากกว่า และถ้าพูดให้แม่นคือเป็นการผสม noise ตระกูล Perlin เข้ากับโหมดผสม “color burn” แค่วางทับบน gradient เลยดูเหมือน dithering แต่ไม่ใช่ dithering
      Dithering คือเทคนิคที่เมื่อ quantize ภาพที่มีความลึกสีสูงและไล่โทนเนียนให้เหลือจำนวนสีจำกัด จะสุ่มเลือกแต่ละพิกเซลให้เป็นค่าบนหรือล่างอย่างมีความน่าจะเป็น เพื่อลด banding ตรงบริเวณที่ตรงกับสีที่ต้องการพอดี อาจไม่ดูเหมือน noise ก็ได้
      ในกรณีนี้มีเพียงพื้นผิว noise สม่ำเสมอเกิดขึ้น และไม่ได้ลด banding จริง ๆ ถ้าเป้าหมายเป็นด้านสุนทรียะ แบบนี้น่าจะดูดีกว่า dithering จริง
      อย่างไรก็ตาม มันอาจมีผลช่วยปกปิดข้อบกพร่องแบบเดียวกับ dithering และเพิ่มความคมชัดที่รับรู้ได้ ทำให้ภาพดูมีคุณภาพขึ้น
    • Dithering เป็นเทคนิคจริงที่ให้เอฟเฟกต์สุดท้ายคล้ายกัน แต่ตัวมันเองไม่ได้หมายถึง การเพิ่ม grain และตัวอย่างนี้ก็ไม่ได้ทำด้วย dithering
      ดูเหมือนผู้เขียนกำลังหาคำศัพท์ด้านดีไซน์ที่ใช้เรียกสไตล์ภาพ เช่น “flat”, “skeuomorphic”, “italian style”
      เมื่อเห็นการผสมรูปทรงเรขาคณิตกับ grain แล้วนึกถึง Memphis style
    • ถ้าไม่ได้ใช้พาเลตต์ที่จำกัดเพื่อเลียนแบบช่วงสีที่กว้างขึ้น ก็ไม่แน่ใจว่าควรเรียกว่า dithering จริง ๆ ได้ไหม
      กลับนึกถึงงานศิลปะโปสเตอร์ท่องเที่ยวแนววินเทจมากกว่า เช่น: https://www.graphicart-news.com/wp-content/uploads/2012/09/2...
    • เกี่ยวข้องกับ dithering อยู่ แต่โดยส่วนตัวดูใกล้เคียงกับ stippling มากกว่า [0]
      [0] https://en.wikipedia.org/wiki/Stippling
    • แอบเขินนิดหน่อย คงต้องอัปเดตบทความให้ลิงก์ไปยังบทความนั้น
      ตอนทำสิ่งนี้ไม่เคยเห็นจริง ๆ และลองทำเองจนมาถึงข้อสรุปเดียวกัน
  • นึกถึงช่างภาพฟิล์มกับผู้สร้างภาพยนตร์ที่พยายามกำจัด film grain กันแทบตาย แต่ช่างภาพดิจิทัลกลับอยากใส่มันกลับเข้าไปอีก หญ้าฝั่งคนอื่นมักดูเขียวกว่าเสมอ
    ท่ามกลางสิ่งที่ปลอดเชื้อแบบดิจิทัลเต็มไปหมด ตัวอย่างในหน้านี้ดูมีเสน่ห์ทีเดียว

    • ไม่ควรเหมารวมเกินไป ตั้งแต่วิดีโอดิจิทัลและต่อมาคือภาพถ่ายดิจิทัลปรากฏขึ้น ก็มีทั้งคนที่พยายามลด noise, คนที่พยายามกำจัดมัน, คนที่พยายามเลียนแบบมัน และคนที่จงใจใช้มัน อยู่ด้วยกันมาตลอด
      ปัญหาไม่ใช่ตัว noise เอง แต่ประเด็นคือมีมากเกินไปหรือเปล่าเสมอ
    • Foveon X3 แก้ได้ในระดับหนึ่งไม่ใช่หรือ? เป็นวิธีที่เหมือนฟิล์มแต่ยังเป็นดิจิทัล
  • มีเอกสารที่สรุปไหมว่าในแต่ละ implementation ฟีเจอร์อะไรพังบ้าง? อยากรู้ว่า จะรักษาสุขภาพจิตด้วย SVG ได้อย่างไร
    ช่วงหลังผมพลาดไปทำโปสเตอร์งานประชุมวิชาการที่ซับซ้อนด้วย SVG แล้วตอนนี้แต่ละเบราว์เซอร์กับแต่ละโปรแกรมก็เรนเดอร์ไม่เหมือนกัน
    https://kxygk.github.io/imergination/
    สิ่งที่เจอตอนทำคือแบบนี้: คุณสามารถฝังหรือลิงก์ SVG ได้ แต่ถ้าข้างในนั้นมี SVG อื่นซ้อนอยู่อีก มันจะไม่ทำงาน เช่น ถ้าโครงสร้างเป็น โปสเตอร์ → ไดอะแกรม → พล็อต/แผนที่หลายรูป SVG ที่อัปเดตแบบไดนามิกเมื่อรูปย่อยเปลี่ยนจึงแทบเป็นไปไม่ได้
    เว็บเบราว์เซอร์บางทีก็ไม่ยอมแสดงรูปที่ถูกฝัง/ลิงก์ตามใจชอบ ด้วยเหตุผลเรื่อง “ความปลอดภัย” ถ้าเปิด SVG ในลิงก์ข้างบนเป็นแท็บแยก จะไม่แสดงผลถูกต้อง: https://raw.githubusercontent.com/wiki/kxygk/imergination/ag...
    การแปลง SVG ซับซ้อนเป็น PDF ให้ได้ผลตรงกันก็ทำได้ยาก และบางครั้งหน่วยความจำก็หมด ผลลัพธ์ของตัวแปลง SVG-to-PDF แต่ละตัวไม่เหมือนกัน
    implementation หลายตัวอย่าง Inkscape บางทีก็จัดการ SVG ที่ฝังอยู่เหมือนเป็น กราฟิกแรสเตอร์ ตามอำเภอใจ และสุดท้ายก็ระบุไม่ได้ว่าเรื่องนี้เกิดขึ้นเมื่อไร นอกจากนี้ยังมีปัญหาที่เวลาเรนเดอร์เป็น PNG Inkscape ไม่ส่งต่อการตั้งค่า DPI ไปยังรูปย่อยด้วย

  • เจ๋งดี ดูเหมือน Inkscape จะรองรับ การเพิ่ม noise แบบนี้ผ่านเอฟเฟกต์หลายอย่างในเมนู Filters พอลอง export เป็น Plain SVG แล้วดูโค้ด ก็ยืนยันได้ว่าใช้ feTurbulence
    ผมลองทดสอบ noise fill ของ Affinity Designer 2 เพื่อเปรียบเทียบด้วย พบว่า SVG ที่ export ออกมามี noise ฝังเป็น รูป JPEG แบบ base64

    • ตอนนี้ไม่ได้อยู่หน้า PC เลยตรวจเองไม่ได้ แต่สงสัยว่ามันฝัง texture noise หนึ่งอันแล้วใช้ซ้ำหลายครั้ง หรือสร้าง texture ใหม่ทุกครั้งที่ใช้ noise
      สำหรับคนที่หาเหตุผลรองรับค่าไลเซนส์ Adobe ได้ยาก และก็ยังไม่ค่อยถูกกับ InkScape ถ้าไม่นับจุดแปลก ๆ แบบนี้ Designer ก็เป็นทางเลือกที่สมบูรณ์แบบเลย
    • Affinity Designer 2 เป็นทางเลือกที่ค่อนข้างดีสำหรับคนที่ไม่จ่าย Adobe Creative Cloud แต่มีหลายครั้งมากที่ต้องแก้ SVG ด้วยมือ
  • เว็บไซต์ “ApeFest”[1] ก็มี noise แบบไดนามิกอยู่ เลยไปดู CSS มา นั่นคือเว็บของงานเดียวกับที่เผาเรตินาผู้เข้าร่วมด้วย UV นั่นแหละ
    จริง ๆ แล้วใช้ PNG overlay[2] แบบ tile แล้วใช้ CSS animation ขยับให้ดูเหมือน “สุ่ม” แก้ไข: จริง ๆ ดูเหมือนขยับด้วย JS เป็นวิธีค่อนข้าง low-tech แต่ก็ใช้ได้
    [1] https://apefest.com/
    [2] https://framerusercontent.com/images/rR6HYXBrMmX4cRpXfXUOvpv...

    • เอฟเฟกต์ของ apefest ใช้ CPU เยอะ และก็ใช้ พลังงาน เยอะตามไปด้วย
  • ผมเคยอยากใช้ฟิลเตอร์ SVG แบบนี้หลายครั้ง แต่ปัญหาคือมัน แสดงผลต่างกันมาก ตามแต่ละ engine และตามระดับการซูมเข้า/ออก
    ดังนั้นจึงใช้ได้เฉพาะกับเอฟเฟกต์ละเอียด ๆ ที่ความไม่แม่นยำไม่ค่อยสำคัญ สุดท้ายก็น่าเสียดายที่ต้องกลับไปใช้แรสเตอร์อยู่เรื่อย ๆ
    อีกอย่าง เพราะมันทำงานเป็นหน่วยพิกเซล ของอย่าง feDisplacementMap จึงทำให้ anti-aliasing พัง และทำให้เสียโอกาสใช้งานไปประมาณครึ่งหนึ่งของสถานการณ์ที่อยากใช้

  • Perlin noise และตัวต่อยอดอย่าง fractal noise นั้นคุ้มค่ามากเมื่อเทียบกับต้นทุน หากต้องการเพิ่มความสมจริงหรือ ความรู้สึกเป็นธรรมชาติ
    ตัวอย่างเช่น เห็นได้ว่าถูกใช้ได้อย่างมีประสิทธิภาพมากในฉากหนีออกจาก Cave of Wonders ใน Aladdin (1992)

  • ไม่รู้ว่าจริง ๆ แล้วมันควรแสดงเป็นอย่างไร แต่บนมือถือของผมเห็นเป็นแค่ gradient ที่เรียบเนียน ไม่มี noise

    • ที่นี่ก็เหมือนกัน ใช้ Android Chrome อยู่
    • ดูเหมือน Android Chrome จะไม่รองรับฟีเจอร์บางอย่างในโค้ด
  • ผมชอบเอฟเฟกต์นี้นะ แต่มีวิธีทำให้มันเป็นอิสระจากระดับการซูมเข้า/ออกไหม?
    ที่เกี่ยวข้องกัน ดูเหมือนสิ่งนี้จะเพิ่มความสุ่มด้วย เลยสงสัยว่ามีวิธีให้ seed ได้ไหม เช่น ถ้ากำหนดผลลัพธ์การเรนเดอร์ที่ถูกต้องหนึ่งแบบสำหรับขนาดหนึ่งได้ก็คงดี

    • ผมเป็นคนเขียนบทความ ทำได้ครับ อัลกอริทึมพื้นฐานและ implementation ของ SVG รับค่า seed: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/s...
    • อย่างที่คำตอบอื่นบอก มีแอตทริบิวต์ seed และอัลกอริทึมของฟังก์ชันสุ่มก็ถูกนิยามไว้ในสเปกเป็น โค้ด C 186 บรรทัด[0] ดังนั้นในทางทฤษฎี ผลลัพธ์ควรเหมือนกันในแต่ละ implementation
      แต่พอดูโค้ดแล้วมีการใช้การคำนวณแบบ floating-point ซึ่งเท่าที่รู้เป็น implementation-defined เลยดูมีโอกาสที่แต่ละแพลตฟอร์มจะทำงานต่างกันได้
      [0]: https://drafts.fxtf.org/filter-effects/#elementdef-feturbule...