Skip to content

Commit 0c77105

Browse files
committed
feat: ascii & n8ao.color
1 parent 71cdff0 commit 0c77105

File tree

5 files changed

+147
-20
lines changed

5 files changed

+147
-20
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@
4848
},
4949
"dependencies": {
5050
"maath": "^0.5.3",
51-
"n8ao": "^1.2.1",
52-
"postprocessing": "^6.30.2",
51+
"n8ao": "^1.3.1",
52+
"postprocessing": "^6.31.0",
5353
"screen-space-reflections": "2.5.0",
5454
"three-stdlib": "^2.21.10"
5555
},

src/effects/ASCII.tsx

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
// From: https://github.com/emilwidlund/ASCII
2+
// https://twitter.com/emilwidlund/status/1652386482420609024
3+
4+
import { forwardRef, useMemo } from 'react'
5+
import { CanvasTexture, Color, NearestFilter, RepeatWrapping, Texture, Uniform } from 'three'
6+
import { Effect } from 'postprocessing'
7+
8+
const fragment = `
9+
uniform sampler2D uCharacters;
10+
uniform float uCharactersCount;
11+
uniform float uCellSize;
12+
uniform bool uInvert;
13+
uniform vec3 uColor;
14+
15+
const vec2 SIZE = vec2(16.);
16+
17+
vec3 greyscale(vec3 color, float strength) {
18+
float g = dot(color, vec3(0.299, 0.587, 0.114));
19+
return mix(color, vec3(g), strength);
20+
}
21+
22+
vec3 greyscale(vec3 color) {
23+
return greyscale(color, 1.0);
24+
}
25+
26+
void mainImage(const in vec4 inputColor, const in vec2 uv, out vec4 outputColor) {
27+
vec2 cell = resolution / uCellSize;
28+
vec2 grid = 1.0 / cell;
29+
vec2 pixelizedUV = grid * (0.5 + floor(uv / grid));
30+
vec4 pixelized = texture2D(inputBuffer, pixelizedUV);
31+
float greyscaled = greyscale(pixelized.rgb).r;
32+
33+
if (uInvert) {
34+
greyscaled = 1.0 - greyscaled;
35+
}
36+
37+
float characterIndex = floor((uCharactersCount - 1.0) * greyscaled);
38+
vec2 characterPosition = vec2(mod(characterIndex, SIZE.x), floor(characterIndex / SIZE.y));
39+
vec2 offset = vec2(characterPosition.x, -characterPosition.y) / SIZE;
40+
vec2 charUV = mod(uv * (cell / SIZE), 1.0 / SIZE) - vec2(0., 1.0 / SIZE) + offset;
41+
vec4 asciiCharacter = texture2D(uCharacters, charUV);
42+
43+
asciiCharacter.rgb = uColor * asciiCharacter.r;
44+
asciiCharacter.a = pixelized.a;
45+
outputColor = asciiCharacter;
46+
}
47+
`
48+
49+
interface IASCIIEffectProps {
50+
characters?: string
51+
fontSize?: number
52+
cellSize?: number
53+
color?: string
54+
invert?: boolean
55+
}
56+
57+
class ASCIIEffect extends Effect {
58+
constructor({
59+
characters = ` .:,'-^=*+?!|0#X%WM@`,
60+
fontSize = 54,
61+
cellSize = 16,
62+
color = '#ffffff',
63+
invert = false,
64+
}: IASCIIEffectProps = {}) {
65+
const uniforms = new Map<string, Uniform>([
66+
['uCharacters', new Uniform(new Texture())],
67+
['uCellSize', new Uniform(cellSize)],
68+
['uCharactersCount', new Uniform(characters.length)],
69+
['uColor', new Uniform(new Color(color))],
70+
['uInvert', new Uniform(invert)],
71+
])
72+
73+
super('ASCIIEffect', fragment, { uniforms })
74+
75+
const charactersTextureUniform = this.uniforms.get('uCharacters')
76+
77+
if (charactersTextureUniform) {
78+
charactersTextureUniform.value = this.createCharactersTexture(characters, fontSize)
79+
}
80+
}
81+
82+
/** Draws the characters on a Canvas and returns a texture */
83+
public createCharactersTexture(characters: string, fontSize: number): THREE.Texture {
84+
const canvas = document.createElement('canvas')
85+
const SIZE = 1024
86+
const MAX_PER_ROW = 16
87+
const CELL = SIZE / MAX_PER_ROW
88+
89+
canvas.width = canvas.height = SIZE
90+
const texture = new CanvasTexture(canvas, undefined, RepeatWrapping, RepeatWrapping, NearestFilter, NearestFilter)
91+
const context = canvas.getContext('2d')
92+
93+
if (!context) {
94+
throw new Error('Context not available')
95+
}
96+
97+
context.clearRect(0, 0, SIZE, SIZE)
98+
context.font = `${fontSize}px arial`
99+
context.textAlign = 'center'
100+
context.textBaseline = 'middle'
101+
context.fillStyle = '#fff'
102+
103+
for (let i = 0; i < characters.length; i++) {
104+
const char = characters[i]
105+
const x = i % MAX_PER_ROW
106+
const y = Math.floor(i / MAX_PER_ROW)
107+
context.fillText(char, x * CELL + CELL / 2, y * CELL + CELL / 2)
108+
}
109+
110+
texture.needsUpdate = true
111+
return texture
112+
}
113+
}
114+
115+
export const ASCII = forwardRef<ASCIIEffect, IASCIIEffectProps>(
116+
({ characters = ` .:,'-^=*+?!|0#X%WM@`, fontSize = 54, cellSize = 16, color = '#ffffff', invert = false }, fref) => {
117+
const effect = useMemo(
118+
() => new ASCIIEffect({ characters, fontSize, cellSize, color, invert }),
119+
[characters, fontSize, cellSize, color, invert]
120+
)
121+
return <primitive ref={fref} object={effect} />
122+
}
123+
)

src/effects/N8AO.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import { Ref, forwardRef, useLayoutEffect, useMemo } from 'react'
55
/* @ts-ignore */
66
import { N8AOPostPass } from 'n8ao'
7-
import { useThree } from '@react-three/fiber'
7+
import { useThree, ReactThreeFiber, applyProps } from '@react-three/fiber'
88

99
type N8AOProps = {
1010
aoRadius?: number
@@ -14,6 +14,7 @@ type N8AOProps = {
1414
aoSamples?: number
1515
denoiseSamples?: number
1616
denoiseRadius?: number
17+
color?: ReactThreeFiber.Color
1718
}
1819

1920
export const N8AO = forwardRef<N8AOPostPass, N8AOProps>(
@@ -26,21 +27,23 @@ export const N8AO = forwardRef<N8AOPostPass, N8AOProps>(
2627
denoiseRadius = 12,
2728
distanceFalloff = 1,
2829
intensity = 1,
30+
color,
2931
},
3032
ref: Ref<N8AOPostPass>
3133
) => {
3234
const { camera, scene } = useThree()
3335
const effect = useMemo(() => new N8AOPostPass(scene, camera), [])
3436
useLayoutEffect(() => {
35-
Object.assign(effect.configuration, {
37+
applyProps(effect.configuration, {
38+
color,
3639
aoRadius,
3740
distanceFalloff,
3841
intensity,
3942
aoSamples,
4043
denoiseSamples,
4144
denoiseRadius,
4245
})
43-
}, [aoRadius, distanceFalloff, intensity, aoSamples, denoiseSamples, denoiseRadius])
46+
}, [color, aoRadius, distanceFalloff, intensity, aoSamples, denoiseSamples, denoiseRadius])
4447
useLayoutEffect(() => {
4548
if (quality) effect.setQualityMode(quality.charAt(0).toUpperCase() + quality.slice(1))
4649
}, [quality])

src/index.tsx

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1+
export * from './Selection'
2+
export * from './EffectComposer'
3+
export * from './util'
4+
15
export * from './effects/Autofocus'
26
export * from './effects/Bloom'
37
export * from './effects/BrightnessContrast'
48
export * from './effects/ChromaticAberration'
59
export * from './effects/ColorAverage'
610
export * from './effects/ColorDepth'
711
export * from './effects/Depth'
8-
export { DepthOfField } from './effects/DepthOfField'
12+
export * from './effects/DepthOfField'
913
export * from './effects/DotScreen'
1014
export * from './effects/Glitch'
1115
export * from './effects/GodRays'
@@ -18,7 +22,6 @@ export * from './effects/ScanlineEffect'
1822
export * from './effects/SelectiveBloom'
1923
export * from './effects/Sepia'
2024
export * from './effects/SSAO'
21-
export * from './effects/N8AO'
2225
export * from './effects/SMAA'
2326
export * from './effects/Texture'
2427
export * from './effects/ToneMapping'
@@ -27,10 +30,8 @@ export * from './effects/ShockWave'
2730
export * from './effects/LUT'
2831
export * from './effects/TiltShift'
2932
export * from './effects/TiltShift2'
33+
export * from './effects/ASCII'
3034

31-
// This is not an effect pass
35+
// These are not effect passes
3236
export * from './effects/SSR'
33-
34-
export * from './Selection'
35-
export * from './EffectComposer'
36-
export * from './util'
37+
export * from './effects/N8AO'

yarn.lock

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7645,10 +7645,10 @@ mute-stream@~1.0.0:
76457645
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-1.0.0.tgz#e31bd9fe62f0aed23520aa4324ea6671531e013e"
76467646
integrity sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==
76477647

7648-
n8ao@^1.2.1:
7649-
version "1.2.1"
7650-
resolved "https://registry.yarnpkg.com/n8ao/-/n8ao-1.2.1.tgz#2c9688ffb8dc6b2364abda134d0498b33d903d97"
7651-
integrity sha512-Mc0JVk3ESA5keQO2hFqeiGJX0CJtEwRpGSR2cd4TZJs8by559ta+iY0A72rFzsfxRZmnMq8jVL7o1ucqNDp0bw==
7648+
n8ao@^1.3.1:
7649+
version "1.3.1"
7650+
resolved "https://registry.yarnpkg.com/n8ao/-/n8ao-1.3.1.tgz#03e4a06005a443fb187bf63ec241940e14a5c082"
7651+
integrity sha512-lwt88T/DjJbmIcZFT8y9mX4BpSR4n4+SnNaMB9+QZ0eupNlPfsD751bJn2DfDs3kkobzXrsyOP86EgUBJ2vODA==
76527652

76537653
nanoid@^3.3.1, nanoid@^3.3.6:
76547654
version "3.3.6"
@@ -8487,10 +8487,10 @@ postcss@^8.4.23:
84878487
picocolors "^1.0.0"
84888488
source-map-js "^1.0.2"
84898489

8490-
postprocessing@^6.30.2:
8491-
version "6.30.2"
8492-
resolved "https://registry.yarnpkg.com/postprocessing/-/postprocessing-6.30.2.tgz#8cfb5561bfce63c983fae5d2cd8efa5bbd04fe6d"
8493-
integrity sha512-Vt77s5DkHyUOV4bmk10J46DHJLglBfIo9ARPI0o62UIAx9omANfuPJTKiLVoYlC1ApkV9y3ldBDipF3IldB7YA==
8490+
postprocessing@^6.31.0:
8491+
version "6.31.0"
8492+
resolved "https://registry.yarnpkg.com/postprocessing/-/postprocessing-6.31.0.tgz#0d3d0157bfe963d2a66a73ee16cfa81a99138419"
8493+
integrity sha512-h1g2KDVrTS6QB4AHP55opp8FYzq66jJHh4JIFCptaj283RUX1y/tPkv8FBB2oK4WYrdPgqvElnKrXZwgiLWeHQ==
84948494

84958495
potpack@^1.0.1:
84968496
version "1.0.2"

0 commit comments

Comments
 (0)