4 điểm bởi GN⁺ 2023-12-08 | 2 bình luận | Chia sẻ qua WhatsApp
  • 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ùng masklinearGradient để 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 trong SourceAlpha
    • 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">
  • Bộ lọc được áp dụng vào phần tử <use> cuối cùng bằng filter="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ới stop-opacity="0.6"
    • Tại các điểm 65%75% dùng màu trắng để điều chỉnh độ trong suốt dần dần
  • 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

 
ndrgrd 2023-12-09

Đú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.

 
GN⁺ 2023-12-08
Ý 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

    • Có lẽ trước mắt thì sẽ không hội tụ đâu. Đáng tiếc là chưa có nơi nào triển khai toàn bộ đặc tả SVG, và đến SVG 2 thì tình hình còn xa vời hơn nữa
      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
    • Ai vào trang ‘logo’ chỉ xem dạng sóng mặc định rồi thoát thì nên thử bấm vài preset trong menu thả xuống ở góc dưới bên phải
      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

    • Thực ra cái này gần với noise hơn, chính xác hơn là một dạng Perlin noise kết hợp với chế độ hòa trộn “color burn”. Nó chỉ được đặt chồng lên gradient nên trông giống dithering, chứ không phải dithering
      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
    • Dithering là một kỹ thuật thực sự có hiệu ứng cuối cùng tương tự, nhưng bản thân nó không có nghĩa là thêm độ hạt, và ví dụ này cũng không được tạo ra bằng dithering
      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ếu không phải để giả lập dải màu rộng hơn bằng một bảng màu hạn chế, thì tôi không chắc có thể thật sự gọi đây là dithering hay không
      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...
    • Có liên quan đến dithering, nhưng cá nhân tôi thấy nó gần với pointillism hơn [0]
      [0] https://en.wikipedia.org/wiki/Stippling
    • Cũng hơi ngượng thật. Chắc tôi nên cập nhật bài viết để thêm link tới bài đó
      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

    • Tốt nhất đừng khái quát hóa quá mức. Từ khi video số và sau đó là ảnh số xuất hiện, luôn đồng thời có những người muốn giảm noise, loại bỏ nó, mô phỏng nó, hoặc cố ý tận dụng nó
      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
    • Foveon X3 chẳng phải đã phần nào giải quyết được điều đó sao? Theo kiểu giống phim nhưng vẫn là kỹ thuật số
  • 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

    • Hiện tôi không ngồi trước PC nên không thể kiểm tra trực tiếp, nhưng tôi tò mò không biết họ nhúng một texture nhiễu rồi tái sử dụng nhiều lần, hay mỗi lần dùng nhiễu lại tạo một texture mới
      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
    • Affinity Designer 2 khá ổn như một lựa chọn thay cho Adobe Creative Cloud, nhưng số lần phải chỉnh tay SVG thực sự rất nhiều
  • 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...

    • Hiệu ứng apefest tốn nhiều CPU, và tương ứng cũng tốn nhiều năng lượng
  • 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ư feDisplacementMap phá 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

    • Ở đây cũng vậy. Tôi đang dùng Android Chrome
    • Có vẻ Android Chrome không hỗ trợ một tính năng nào đó trong đoạn mã này
  • 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ể

    • Tôi là tác giả bài viết. Có thể. Thuật toán nền tảng và implementation của SVG nhận giá trị seed: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/s...
    • Như câu trả lời khác đã nói, có thuộc tính 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...