การสร้าง SVG พื้นผิวแบบนอยส์
(daniel.do)- เมื่อภาพประกอบบนเว็บขยายจากเวกเตอร์แบบแบนไปสู่แนวทางที่ใช้ พื้นผิวแบบนอยส์ รวมถึงแสงและเงา การทำสิ่งนี้ด้วย 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 ความคิดเห็น
สมกับ Safari ที่ไม่เคยทำให้ผิดหวังจริง ๆ! เรียกว่าเป็น IE แห่งยุค 20s ก็ไม่เกินเลยนะ
ความคิดเห็นจาก 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 จะลู่เข้าหากันไหม
เรื่องนี้ https://razrfalcon.github.io/notes-on-svg-parsing/ น่าอ่าน
ในรายงานบั๊กของไลบรารี SVG มักเห็นคำว่า “SVG ของฉัน render ไม่ถูกต้อง” อยู่บ่อย ๆ แต่ประเด็นคือจริง ๆ แล้วไม่มีสิ่งที่เรียกว่า SVG ที่ render ได้ถูกต้องสมบูรณ์ เมื่อเริ่มใช้ ฟีเจอร์ขั้นสูง อย่างข้อความ ฟิลเตอร์ หรือแอนิเมชัน ก็พังต่างกันได้ง่ายในแต่ละ renderer
แค่เปลี่ยนพารามิเตอร์นิดเดียวก็ได้ดีไซน์ที่ต่างกันพอสมควรอย่าง scorpion, balance, spruce ซึ่งเท่มาก
คำที่ผู้เขียนกำลังหาน่าจะใกล้เคียงกับ dithering มากกว่า บทความ “Grainy Gradients” ของ CSS-Tricks ก็อธิบายเทคนิคเดียวกัน
https://css-tricks.com/grainy-gradients/
มีทางเลือกที่ทำคล้ายกันด้วย CSS ด้วย แต่เป็นวิธีที่ใช้กับทั้งภาพ
https://github.com/tomren1/dither-with-css
Dithering คือเทคนิคที่เมื่อ quantize ภาพที่มีความลึกสีสูงและไล่โทนเนียนให้เหลือจำนวนสีจำกัด จะสุ่มเลือกแต่ละพิกเซลให้เป็นค่าบนหรือล่างอย่างมีความน่าจะเป็น เพื่อลด banding ตรงบริเวณที่ตรงกับสีที่ต้องการพอดี อาจไม่ดูเหมือน noise ก็ได้
ในกรณีนี้มีเพียงพื้นผิว noise สม่ำเสมอเกิดขึ้น และไม่ได้ลด banding จริง ๆ ถ้าเป้าหมายเป็นด้านสุนทรียะ แบบนี้น่าจะดูดีกว่า dithering จริง
อย่างไรก็ตาม มันอาจมีผลช่วยปกปิดข้อบกพร่องแบบเดียวกับ dithering และเพิ่มความคมชัดที่รับรู้ได้ ทำให้ภาพดูมีคุณภาพขึ้น
ดูเหมือนผู้เขียนกำลังหาคำศัพท์ด้านดีไซน์ที่ใช้เรียกสไตล์ภาพ เช่น “flat”, “skeuomorphic”, “italian style”
เมื่อเห็นการผสมรูปทรงเรขาคณิตกับ grain แล้วนึกถึง Memphis style
กลับนึกถึงงานศิลปะโปสเตอร์ท่องเที่ยวแนววินเทจมากกว่า เช่น: https://www.graphicart-news.com/wp-content/uploads/2012/09/2...
[0] https://en.wikipedia.org/wiki/Stippling
ตอนทำสิ่งนี้ไม่เคยเห็นจริง ๆ และลองทำเองจนมาถึงข้อสรุปเดียวกัน
นึกถึงช่างภาพฟิล์มกับผู้สร้างภาพยนตร์ที่พยายามกำจัด film grain กันแทบตาย แต่ช่างภาพดิจิทัลกลับอยากใส่มันกลับเข้าไปอีก หญ้าฝั่งคนอื่นมักดูเขียวกว่าเสมอ
ท่ามกลางสิ่งที่ปลอดเชื้อแบบดิจิทัลเต็มไปหมด ตัวอย่างในหน้านี้ดูมีเสน่ห์ทีเดียว
ปัญหาไม่ใช่ตัว noise เอง แต่ประเด็นคือมีมากเกินไปหรือเปล่าเสมอ
มีเอกสารที่สรุปไหมว่าในแต่ละ 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
สำหรับคนที่หาเหตุผลรองรับค่าไลเซนส์ Adobe ได้ยาก และก็ยังไม่ค่อยถูกกับ InkScape ถ้าไม่นับจุดแปลก ๆ แบบนี้ Designer ก็เป็นทางเลือกที่สมบูรณ์แบบเลย
เว็บไซต์ “ApeFest”[1] ก็มี noise แบบไดนามิกอยู่ เลยไปดู CSS มา นั่นคือเว็บของงานเดียวกับที่เผาเรตินาผู้เข้าร่วมด้วย UV นั่นแหละ
จริง ๆ แล้วใช้ PNG overlay[2] แบบ tile แล้วใช้ CSS animation ขยับให้ดูเหมือน “สุ่ม” แก้ไข: จริง ๆ ดูเหมือนขยับด้วย JS เป็นวิธีค่อนข้าง low-tech แต่ก็ใช้ได้
[1] https://apefest.com/
[2] https://framerusercontent.com/images/rR6HYXBrMmX4cRpXfXUOvpv...
ผมเคยอยากใช้ฟิลเตอร์ SVG แบบนี้หลายครั้ง แต่ปัญหาคือมัน แสดงผลต่างกันมาก ตามแต่ละ engine และตามระดับการซูมเข้า/ออก
ดังนั้นจึงใช้ได้เฉพาะกับเอฟเฟกต์ละเอียด ๆ ที่ความไม่แม่นยำไม่ค่อยสำคัญ สุดท้ายก็น่าเสียดายที่ต้องกลับไปใช้แรสเตอร์อยู่เรื่อย ๆ
อีกอย่าง เพราะมันทำงานเป็นหน่วยพิกเซล ของอย่าง
feDisplacementMapจึงทำให้ anti-aliasing พัง และทำให้เสียโอกาสใช้งานไปประมาณครึ่งหนึ่งของสถานการณ์ที่อยากใช้Perlin noise และตัวต่อยอดอย่าง fractal noise นั้นคุ้มค่ามากเมื่อเทียบกับต้นทุน หากต้องการเพิ่มความสมจริงหรือ ความรู้สึกเป็นธรรมชาติ
ตัวอย่างเช่น เห็นได้ว่าถูกใช้ได้อย่างมีประสิทธิภาพมากในฉากหนีออกจาก Cave of Wonders ใน Aladdin (1992)
ไม่รู้ว่าจริง ๆ แล้วมันควรแสดงเป็นอย่างไร แต่บนมือถือของผมเห็นเป็นแค่ gradient ที่เรียบเนียน ไม่มี noise
ผมชอบเอฟเฟกต์นี้นะ แต่มีวิธีทำให้มันเป็นอิสระจากระดับการซูมเข้า/ออกไหม?
ที่เกี่ยวข้องกัน ดูเหมือนสิ่งนี้จะเพิ่มความสุ่มด้วย เลยสงสัยว่ามีวิธีให้ seed ได้ไหม เช่น ถ้ากำหนดผลลัพธ์การเรนเดอร์ที่ถูกต้องหนึ่งแบบสำหรับขนาดหนึ่งได้ก็คงดี
seedและอัลกอริทึมของฟังก์ชันสุ่มก็ถูกนิยามไว้ในสเปกเป็น โค้ด C 186 บรรทัด[0] ดังนั้นในทางทฤษฎี ผลลัพธ์ควรเหมือนกันในแต่ละ implementationแต่พอดูโค้ดแล้วมีการใช้การคำนวณแบบ floating-point ซึ่งเท่าที่รู้เป็น implementation-defined เลยดูมีโอกาสที่แต่ละแพลตฟอร์มจะทำงานต่างกันได้
[0]: https://drafts.fxtf.org/filter-effects/#elementdef-feturbule...