Web開発
Webにおけるディザリングとレトロシェーディングのアート
The Art of Dithering and Retro Shading for the Web (blog.maximeheckel.com)
要約
この記事は、Webサイトに3D要素とカスタムポストプロセッシング効果、特にディザリングとレトロシェーディングを適用する技術について解説しています。ディザリングは、限られた色数でより多くの色や滑らかさを表現するために生まれた初期のグラフィックス技術であり、現在では独特のレトロな雰囲気を加えるための芸術的な選択肢となっています。筆者は、React Three FiberとThree.jsプロジェクトでディザリングを実装する方法を、ホワイトノイズディザリングとバイヤーズマトリックスを用いた順序ディザリングの例を交えて紹介しています。
全文翻訳
Webにおけるディザリングとレトロシェーディングのアート
2024年8月6日
過去数ヶ月間、私はゼロからパーソナルウェブサイトを構築し、ついにシェーダーとWebGLのスキルを披露するための3Dワークを組み込む時間を取ることができました。この作業を通して、シーンを実際に良く見せるためにポストプロセッシングがいかに重要な役割を果たすかを真に理解するに至り、過去のReact Three Fiberやシェーダープロジェクトで、どれだけの労力と注意を払っても私のビジョンが実現しなかったという長年のフラストレーションを解消するいくつかの決意を固めました。カスタムポストプロセッシング効果を構築、組み合わせ、実験する時間を取ることで、追加の創造的なアウトレットを得ることができ、私が取り組んだ多くのタイプの中で、私が考案したいくつかの「レトロ」効果には常に特別な愛着がありました。ディザリング、カラー量子化、ピクセル化/CRT RGBセルといった微妙なディテールは、現代のWebランドスケープと、90年代/2000年代初頭の子供たちが時々懐かしむ遠い昔のテクノロジー時代との間に心地よいコントラストをもたらします。これらの効果のあらゆる側面を分析するために費やした時間を考えると、それらを支える概念と、その過程で学んだシェーダーテクニックについて、皆さんと深く掘り下げたいと思います。この記事では、ポストプロセッシング効果の力と、ウェブサイトにエレガントなレトロな雰囲気を適用することに勝るものはないと、皆さんに確信させたいと思っています👌✨。また、私が後で紹介するのと同じプロセスを使用している非常に才能のある人々によるディザリングとピクセルアートの例、そしてこのすべてを学びながら私が構築したいくつかの独自の作品も見ていきます。
ディザリングテクニック
ディザリングは、当時のマシンが出力できたよりも多くの色や滑らかさを視聴者の脳に見せるために、意図的に画像やレンダリングの上にノイズを導入するという、初期のグラフィックステクニックとして生まれました。当時のカラーパレットは非常に限られていたため、このようなテクニックに頼ることは、ゲームデザイナーがビジョンを実現するために不可欠でした。これにより、コンピューターが普及し始めたものの、高度なグラフィック機能がまだなかった特定の時代のゲームやメディアに、ユニークなルックアンドフィールがもたらされました。今日、ディザリングは回避策というよりも芸術的な選択となっています。多くのアーティストやゲームデザイナーは、このテクニックを創造的なアウトレットとして使用し、初期のゲーム時代を呼び起こすようなユニークなレトロな雰囲気を作品に与えたり、自己課した色の制限の範囲内で作業したりします。そのようなディザリングの使用の素晴らしい例には、以下のようなものがあります。
Basement StudioのBasement Chronicleゲーム:私の初期のゲーム体験を多く思い出させる、よく実行されたポイントアンドクリックゲーム。
@loackmeのアート:私は絶対的なファンです。
@aweusmeuhによるオリジナルのゲームボーイカメラを使用した実験的な写真:それは崇高なディザリング効果を備えています。
左から右へ、Basement Studio、@aweusmeuh、@loackme_による美しく実行されたディザリングアートの例。
後者は、このブログ投稿でディザリングをアプローチする方法です。React Three Fiber/Three.jsプロジェクトにユニークなスタイルを与えるためです!この最初のパートでは、ディザリングテクニックがどのように機能するかを探求し、それをシェーダーとして実装し、任意の3Dシーンに適用できるカスタムディザリングポストプロセッシング効果の最初のイテレーションを構築します。
React Three Fiberでのディザリングの最初の試み
このプロジェクトでは、カスタムポストプロセッシング効果を作成します。Moebiusスタイルのシェーダーで行ったように、ポストプロセッシングに依存することで、すでにレンダリングされたシーンにシェーダーを適用し、「画像フィルター」を写真に追加するようなスタイルを変更できます。カスタム効果を作成するには、次の手順を実行します。
Effectクラスを拡張するクラスを宣言します。
フラグメントシェーダーを定義し、スーパーキーワードを使用して親コンストラクタから呼び出します。
効果に必要なユニフォームのセットを定義します。
wrapEffect関数を@react-three/post-processingから、効果クラスを引数として呼び出します。これにより、EffectComposer内のJSXコンポーネントとして効果を使用できるようになります。
R3Fシーンで使用されるカスタムシェーダーポストプロセッシング効果のサンプル
1import { OrbitControls, OrthographicCamera, useFBO } from '@react-three/drei';
2import { Canvas } from '@react-three/fiber';
3import { wrapEffect, EffectComposer } from '@react-three/postprocessing';
4import { Effect } from 'postprocessing';
5import { Suspense, useRef, useState } from 'react';
6import { v4 as uuidv4 } from 'uuid';
7
8import fragmentShader from './fragmentShader.glsl';
9
10class RetroEffectImpl extends Effect {
11 constructor() {
12 super('RetroEffect', fragmentShader, {
13 uniforms: new Map([]),
14 });
15 }
16}
17
18const RetroEffect = wrapEffect(RetroEffectImpl);
19
20const Retro = () => {
21 const mesh = useRef();
22
23 return (
24 <>
25 <mesh receiveShadow castShadow>
26 <torusKnotGeometry args={[1, 0.25, 128, 100]} />
27 <meshStandardMaterial color="cyan" />
28 </mesh>
29 <EffectComposer>
30 <RetroEffect />
31 </EffectComposer>
32 </>
33 );
34};
効果を構築する最初のステップとして、単純な輝度ベースのホワイトノイズディザリングから始めます。この背後にある考え方は次のとおりです。
各ピクセルの輝度を確認します。
それを乱数(名前の「ホワイトノイズ」の由来)と比較します。
輝度が乱数より上か下かに基づいて、白または黒のピクセルを出力します。
カスタム効果のフラグメントシェーダーで実装されたホワイトノイズディザリング
1float random(vec2 c) {
2 return fract(sin(dot(c.xy, vec2(12.9898, 78.233))) * 43758.5453);
3}
4
5vec3 whiteNoiseDither(vec2 uv, float lum) {
6 vec3 color = vec3(0.0);
7
8 if (lum < random(uv)) {
9 color = vec3(0.0);
10 } else {
11 color = vec3(1.0);
12 }
13
14 return color;
15}
16
17void mainImage(const in vec4 inputColor, const in vec2 uv, out vec4 outputColor) {
18 vec4 color = texture2D(inputBuffer, uv);
19
20 float lum = dot(vec3(0.2126, 0.7152, 0.0722), color.rgb);
21 color.rgb = whiteNoiseDither(uv, lum);
22
23 outputColor = color;
24}
このコードが生成する効果は、以下のウィジェットで確認できます。これは同様のプロセスを再実装したものです。
[Enable Dithering]
これを実行すると、グレースケールバージョンのシーンが得られ、純粋に黒または白でないピクセルは、脳にさらに多くのグレーシェードを見させるようにディザリングされます。以下のデモは、単純なReact Three Fiberシーンの上に適用された効果を示しています。
順序ディザリングとバイヤーズマトリックス
私たちが構築した効果は機能しますが、ディザリングのしきい値にホワイトノイズを使用しているため、乱雑な結果につながります。この順序付けされたパターンにより、順序ディザリングとして一般に知られているテクニックを使用して、このすべてに秩序をもたらすことができます(🥁)。このテクニックは、バイヤーズマトリックスを介して定義されたしきい値マップに依存しており、これは、与えられたピクセルの色を黒または白に調整するかどうかを決定するために使用される値を含んでいます。
シーンの入力バッファに4x4バイヤーズマトリックスを適用し、各ピクセルのしきい値値と一致するしきい値値に基づいてディザリングパターンを取得するプロセスを示す図。
このタイプのディザリングがどのように機能するかを実証するために、このマトリックスが適用されたときにピクセルグリッドの出力がどのように変化するかを示す以下のウィジェットを構築しました。
[Enable Dithering]
[Bayer Matrix size]
2x2
4x4
8x8
上記で示した例を通して、基になるピクセルグリッドのグレースケールシェードと、しきい値値を取得するために使用されるバイヤーズマトリックスのサイズに基づいて、かなり明確なディザリングパターンが得られることがわかります。
単純なグレースケールグラデーションに適用された順序ディザリング。
これをGLSLで実装するには、各ピクセルの輝度を取得し、バイヤーズマトリックスから取得した同じピクセルに対応するしきい値値と比較する必要があります。それらの値の差が正の場合、ピクセルは白、それ以外の場合は黒です。
4x4バイヤーズマトリックスを使用した順序ディザリング
1const mat4x4 bayerMatrix4x4 = mat4x4(2 0.0, 8.0, 2.0, 10.0,3 12.0, 4.0, 14.0, 6.0,4 3.0, 11.0, 1.0, 9.0,5 15.0, 7.0, 13.0, 5.06) / 16.0;
7
8vec3 orderedDither(vec2 uv, float lum) {
9 vec3 color = vec3(0.0);
10
11 float threshold = 0.0;
12
13 int x = int(uv.x * res