Tạo SVG có kết cấu nhiễu
(daniel.do)- Khi minh họa web mở rộng từ vector phẳng sang hướng sử dụng kết cấu nhiễu cùng ánh sáng và đổ bóng, cách triển khai chúng bằng SVG nhẹ và có thể tái sử dụng trở nên quan trọng
- Mục tiêu cốt lõi là thêm hiệu ứng bóng có texture vào các hình đơn giản, rồi chuyển thành component Svelte hoặc React để có thể thay đổi màu sắc và hình dạng khi dùng
- Cấu trúc cơ bản là định nghĩa hình một lần trong
<defs>và tái sử dụng bằng<use>, sau đó dùngmaskvàlinearGradientđể tạo mặt nạ gradient ít bị ràng buộc vào màu sắc hơn - Cảm giác hạt được tạo bằng bộ lọc SVG dựa trên
<feTurbulence>, rồi kết hợp<feComposite>,<feColorMatrix>,<feBlend>để hòa trộn với màu tô đã chọn - Kết quả đạt được kết cấu mong muốn, nhưng vẫn còn khác biệt khi render trên Safari, nên cần điều chỉnh thêm cho cách hiển thị theo từng trình duyệt và khi áp dụng vào các minh họa phức tạp hơn
Thêm kết cấu nhiễu vào SVG chỉ bằng code
- Trong thiết kế web gần đây, minh họa vector vẫn tiếp tục được dùng, đồng thời các phong cách ánh sáng và đổ bóng có cảm giác hạt thô nổi bật hơn so với các hình phẳng sạch sẽ
- Dù phong cách này có thể được xuất từ chương trình minh họa sang SVG, texture có thể bị đưa vào dưới dạng raster hoặc làm kích thước vector tăng lên
- Mục tiêu là tạo hiệu ứng bóng có texture nhiễu chỉ bằng đặc tả SVG thông thường, đồng thời giữ cấu trúc dễ component hóa
- Cấu hình sao cho cùng một cách vẫn hoạt động ngay cả khi thay đổi hình và màu sắc để tăng khả năng tái sử dụng
Hình cơ bản và gradient
- Các hình SVG cơ bản như hình tròn hoặc hình chữ nhật có thể được vẽ dễ dàng, nhưng trong component có thể tái sử dụng thì chỉ dùng gradient màu cố định là chưa đủ
- Để tạo gradient thay đổi theo màu nền, sử dụng mask (mặt nạ)
- SVG mask kiểm soát vùng được hiển thị hoặc bị che bằng đen và trắng
- Cách này có thể hơi khó hiểu, nhưng có thể bắt đầu bằng một ví dụ đơn giản
- Nếu có cấu trúc dùng cùng một hình tròn nhiều lần, định nghĩa
<circle id="shape">trong<defs>rồi tham chiếu bằng<use href="#shape"> - Cách này là một cấu trúc DRY, giúp giảm lặp lại và gom định nghĩa hình vào một nơi
Cách tạo texture nhiễu
- Để đặt texture lên trên gradient, dùng filter (bộ lọc) của SVG
- Việc tạo nhiễu do
<feTurbulence>đảm nhiệm<feTurbulence>là một primitive bộ lọc SVG tạo texture nhân tạo- Cần điều chỉnh nhiều giá trị để đạt được cảm giác mong muốn
- Bộ lọc cuối cùng hòa trộn texture với màu sắc qua nhiều bước
- Tạo nhiễu bằng
<feTurbulence type="fractalNoise" baseFrequency="19.5" numOctaves="10"> - Dùng
<feComposite>để composite kết quả turbulence vào trongSourceAlpha - Giảm biến đổi màu bằng
<feColorMatrix type="luminanceToAlpha"> - Composite với đồ họa gốc bằng
<feBlend mode="color-burn">
- Tạo nhiễu bằng
- Bộ lọc được áp dụng vào phần tử
<use>cuối cùng bằngfilter="url('#noise')"
Cấu trúc SVG cuối cùng
- Ví dụ cuối cùng định nghĩa hình, bộ lọc, mask và gradient cùng nhau trong
<defs> - Gradient được cấu hình bằng
linearGradient id="fade"- Tại điểm
0%dùng màu đen vớistop-opacity="0.6" - Tại các điểm
65%và75%dùng màu trắng để điều chỉnh độ trong suốt dần dần
- Tại điểm
- Hình cuối cùng được áp dụng đồng thời màu tô, mask và bộ lọc
fill="hsl(337, 92%, 69%)"mask="url(#gradient)"filter="url('#noise')"
- Bằng cách thêm màu và điều chỉnh gradient tinh tế hơn, có được kết quả gần với hiệu ứng thị giác mong muốn
- Có thể xem toàn bộ kết quả tại Full Demo, và mã triển khai được công khai trong repository
Kết quả và giới hạn
- Sau các thử nghiệm bổ sung, tác giả đã tạo được minh họa ưng ý, nhưng kết quả chưa ở mức hoàn hảo
- Đặc biệt trên Safari, nó được render khác so với các trình duyệt khác
- So với minh họa phức tạp được tham khảo thì vẫn còn đơn giản, nhưng nếu tiếp tục tinh chỉnh, có khả năng dùng được trong nhiều tình huống hơn
- Theo phần bổ sung ngày 7 tháng 12 năm 2023, sau khi gửi lên Hacker News, tác giả biết đến bài viết CSS-Tricks năm 2021 của Jimmy Chion, Grainy Gradients
- Kỹ thuật mà bài viết đó đạt được có vẻ tương tự với cách này, nên có thể tham khảo cùng như tài liệu bổ sung
2 bình luận
Đúng là Safari không làm người ta thất vọng! Quả thật xứng danh là IE của thập niên 2020.
Ý kiến Hacker News
Gần đây khi vọc SVG, có lúc muốn thêm drop shadow mà với canvas thì khó làm, nhưng trên Safari thì CSS drop shadow bên trong SVG lại không hoạt động, nên phải dùng bộ lọc drop shadow gốc của SVG, và kết quả hiển thị cũng khác các trình duyệt khác
Ví dụ cụ thể có tại https://www.einarmagnus.net/pages/logo, ở đó tác giả dùng JavaScript để phát hiện Safari rồi bật phiên bản SVG và tắt phiên bản CSS
Nền “âm dương đệ quy” có tại https://einarmagnus.net/blog/2020/01/22/finding-the-logo
Tôi cũng muốn thử nghiệm thêm với texture, nhưng việc Safari hiển thị quá khác biệt thật sự rất khó chịu. Không biết rồi có ngày nào cách diễn giải SVG sẽ hội tụ lại không
Chủ đề này có bài https://razrfalcon.github.io/notes-on-svg-parsing/ khá đáng đọc
Trong các bug report của thư viện SVG, người ta thường nói “SVG của tôi không được render đúng”, nhưng ý chính ở đây là thật ra không hề tồn tại cái gọi là SVG được render đúng. Một khi bắt đầu dùng các tính năng nâng cao như văn bản, bộ lọc, hoạt ảnh, thì rất dễ bị vỡ khác nhau tùy renderer
Chỉ cần đổi tham số một chút là đã ra những thiết kế khá khác biệt như scorpion, balance, spruce, rất ấn tượng
Thuật ngữ mà tác giả đang tìm có lẽ gần với dithering hơn. Bài “Grainy Gradients” của CSS-Tricks cũng mô tả cùng kỹ thuật này
https://css-tricks.com/grainy-gradients/
Cũng có một phương án thay thế xử lý tương tự bằng CSS, nhưng áp dụng cho toàn bộ hình ảnh
https://github.com/tomren1/dither-with-css
Dithering là kỹ thuật khi lượng tử hóa ảnh có độ sâu màu cao, mượt, xuống số lượng màu hạn chế để giảm banding, bằng cách chọn xác suất mỗi pixel nhận một trong hai giá trị trên hoặc dưới. Ở những điểm khớp chính xác với màu mong muốn thì thậm chí có thể không trông giống noise
Ở đây chỉ tạo ra một texture noise đồng đều chứ không thực sự làm giảm banding. Nếu mục đích là thẩm mỹ thì cách này có vẻ đẹp hơn dithering thật
Dù vậy, nó vẫn có thể mang lại hiệu ứng giống dithering là che bớt khuyết điểm và tăng cảm giác sắc nét, khiến chất lượng hình ảnh có vẻ tốt hơn
Có vẻ tác giả đang tìm một thuật ngữ thiết kế để gọi phong cách thị giác, kiểu như “flat”, “skeuomorphic”, “italian style”
Nhìn sự kết hợp giữa hình khối hình học và độ hạt, tôi liên tưởng đến Memphis style
Nó làm tôi nghĩ nhiều hơn đến tranh áp phích du lịch phong cách vintage. Ví dụ: https://www.graphicart-news.com/wp-content/uploads/2012/09/2...
[0] https://en.wikipedia.org/wiki/Stippling
Lúc làm cái này tôi thực sự chưa hề xem qua, chỉ là tự mày mò rồi đi đến cùng một kết luận
Điều này làm tôi nhớ đến việc các nhiếp ảnh gia phim và nhà làm phim từng cố gắng loại bỏ film grain, còn các nhiếp ảnh gia số thì lại muốn thêm nó vào. Đúng là cỏ bên kia luôn xanh hơn
Giữa vô số thứ mang cảm giác vô trùng kiểu kỹ thuật số, ví dụ trên trang này trông khá cuốn hút
Vấn đề chưa bao giờ là bản thân noise, mà luôn là liệu nó có quá nhiều hay không
Có tài liệu nào tổng hợp xem từng implementation làm hỏng những tính năng nào không? Tôi muốn biết cách giữ gìn sức khỏe tinh thần khi làm việc với SVG
Gần đây tôi đã mắc sai lầm là làm một poster hội nghị phức tạp bằng SVG, và giờ nó được render khác nhau tùy theo trình duyệt và chương trình
https://kxygk.github.io/imergination/
Đây là những gì tôi gặp phải khi làm: có thể nhúng hoặc liên kết SVG, nhưng nếu bên trong nó lại có SVG khác thì sẽ không hoạt động. Ví dụ, nếu cấu trúc là poster → sơ đồ → nhiều biểu đồ/bản đồ, thì SVG cập nhật động khi ảnh con thay đổi về thực chất là bất khả thi
Trình duyệt web đôi khi cũng tùy tiện không hiển thị ảnh được nhúng/liên kết vì lý do “bảo mật”. Nếu mở SVG ở link trên trong một tab riêng thì nó sẽ không hiển thị đúng: https://raw.githubusercontent.com/wiki/kxygk/imergination/ag...
Việc chuyển SVG phức tạp sang PDF cũng khó xử lý cho nhất quán, thậm chí còn có thể cạn bộ nhớ. Mỗi bộ chuyển đổi SVG-to-PDF lại cho ra kết quả khác nhau
Nhiều implementation như Inkscape đôi khi còn xử lý SVG được nhúng như đồ họa raster một cách tùy ý, và tôi rốt cuộc vẫn không xác định được khi nào chuyện đó xảy ra. Khi render sang PNG cũng có vấn đề là Inkscape không truyền thiết lập DPI xuống các ảnh con
Hay đấy. Có vẻ Inkscape hỗ trợ kiểu thêm nhiễu này thông qua nhiều hiệu ứng trong menu Filters. Tôi xuất sang Plain SVG rồi xem mã thì thấy nó dùng
feTurbulenceĐể so sánh, tôi cũng thử phần tô nhiễu của Affinity Designer 2, nhưng trong SVG xuất ra thì nhiễu lại bị nhúng thành ảnh JPEG base64
Với người khó biện minh cho giấy phép Adobe và cũng không thực sự hợp với InkScape như tôi, thì ngoài mấy điểm kỳ quặc này ra, Designer là một lựa chọn thay thế hoàn hảo
Trang web “ApeFest”[1] cũng có nhiễu động, nên tôi đã xem CSS của nó. Chính là trang cho sự kiện đã thiêu võng mạc người tham dự bằng tia UV
Thực tế họ dùng một lớp phủ PNG dạng tile[2], rồi rung nó như thể “ngẫu nhiên” bằng animation CSS. Sửa lại: có vẻ thực ra nó được di chuyển bằng JS. Khá low-tech nhưng hoạt động
[1] https://apefest.com/
[2] https://framerusercontent.com/images/rR6HYXBrMmX4cRpXfXUOvpv...
Tôi từng muốn dùng các bộ lọc SVG này nhiều lần, nhưng vấn đề là biểu hiện thay đổi rất lớn tùy engine và mức phóng to/thu nhỏ
Vì vậy chỉ có thể dùng cho những hiệu ứng tinh tế mà sai lệch không quá quan trọng, và cuối cùng, đáng tiếc là tôi vẫn đều đặn quay lại raster
Thêm nữa, vì bản chất hoạt động theo từng pixel nên những thứ như
feDisplacementMapphá hỏng anti-aliasing, làm hỏng khoảng một nửa các tình huống mà người ta muốn dùng nóPerlin noise và phần tiếp nối của nó là fractal noise có hiệu quả trên chi phí cực kỳ cao khi cần tăng cảm giác chân thực hay hữu cơ
Ví dụ, có thể thấy nó được dùng rất hiệu quả trong cảnh thoát khỏi Cave of Wonders của Aladdin (1992)
Tôi không biết lẽ ra cái này phải trông như thế nào, nhưng trên điện thoại của tôi nó chỉ hiện thành gradient mượt không có nhiễu
Tôi thích hiệu ứng này, nhưng có cách nào làm nó độc lập với mức phóng to/thu nhỏ không?
Liên quan đến đó, cái này dường như cũng thêm tính ngẫu nhiên, nên tôi cũng tò mò có cách nào đưa vào seed hay không. Ví dụ, sẽ tốt nếu có thể xác định một kết quả render đúng duy nhất ở một kích thước cụ thể
seed, và thuật toán hàm ngẫu nhiên cũng được đặc tả bằng 186 dòng mã C trong specification[0], nên về lý thuyết các implementation phải cho ra cùng kết quảTuy nhiên nhìn vào mã thì có dùng phép toán số thực dấu phẩy động, mà theo tôi biết thì điều này phụ thuộc implementation, nên có vẻ vẫn có khả năng hành vi khác nhau giữa các nền tảng
[0]: https://drafts.fxtf.org/filter-effects/#elementdef-feturbule...