Skip to content

Commit bfb64ef

Browse files
feat: linotcut (#170)
* add demo and doc * lint --------- Co-authored-by: Alvaro Saburido <alvaro.saburido@gmail.com>
1 parent 727ef99 commit bfb64ef

File tree

9 files changed

+385
-0
lines changed

9 files changed

+385
-0
lines changed

docs/.vitepress/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ export default defineConfig({
5151
{ text: 'Barrel blur', link: '/guide/pmndrs/barrel-blur' },
5252
{ text: 'Bloom', link: '/guide/pmndrs/bloom' },
5353
{ text: 'Chromatic Aberration', link: '/guide/pmndrs/chromatic-aberration' },
54+
{ text: 'Linocut', link: '/guide/pmndrs/linocut' },
5455
{ text: 'Sepia', link: '/guide/pmndrs/sepia' },
5556
{ text: 'Vignette', link: '/guide/pmndrs/vignette' },
5657
{ text: 'Hue & Saturation', link: '/guide/pmndrs/hue-saturation' },
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<script setup lang="ts">
2+
import { ContactShadows, Environment, OrbitControls } from '@tresjs/cientos'
3+
import { TresCanvas } from '@tresjs/core'
4+
import { TresLeches, useControls } from '@tresjs/leches'
5+
import { EffectComposerPmndrs, LinocutPmndrs } from '@tresjs/post-processing'
6+
import { BlendFunction } from 'postprocessing'
7+
import { NoToneMapping } from 'three'
8+
9+
import '@tresjs/leches/styles'
10+
11+
const gl = {
12+
clearColor: '#00ff00',
13+
toneMapping: NoToneMapping,
14+
multisampling: 8,
15+
envMapIntensity: 10,
16+
}
17+
18+
const { blendFunction, scale, noiseScale, centerX, centerY, rotation } = useControls({
19+
scale: { value: 0.85, step: 0.01, min: 0, max: 2 },
20+
noiseScale: { value: 0.0, step: 0.01, min: 0, max: 1 },
21+
centerX: { value: 0.5, step: 0.01, min: 0, max: 1 },
22+
centerY: { value: 0.5, step: 0.01, min: 0, max: 1 },
23+
rotation: { value: 0.0, step: 0.01, min: -Math.PI, max: Math.PI },
24+
blendFunction: {
25+
options: Object.keys(BlendFunction).map(key => ({
26+
text: key,
27+
value: BlendFunction[key as keyof typeof BlendFunction],
28+
})),
29+
value: BlendFunction.NORMAL,
30+
},
31+
})
32+
</script>
33+
34+
<template>
35+
<TresLeches style="left: initial;right:10px; top:10px;" />
36+
37+
<TresCanvas
38+
v-bind="gl"
39+
>
40+
<TresPerspectiveCamera
41+
:position="[0, 6.5, 6.5]"
42+
/>
43+
<OrbitControls auto-rotate />
44+
45+
<TresMesh>
46+
<TresBoxGeometry :args="[2, 2, 2]" />
47+
<TresMeshStandardMaterial color="yellow" />
48+
</TresMesh>
49+
50+
<Suspense>
51+
<Environment :blur=".25" preset="snow" />
52+
</Suspense>
53+
54+
<ContactShadows
55+
:opacity=".65"
56+
:position-y="-1"
57+
:scale="35"
58+
:blur="1"
59+
/>
60+
61+
<Suspense>
62+
<EffectComposerPmndrs>
63+
<LinocutPmndrs
64+
:scale="scale.value"
65+
:noiseScale="noiseScale.value"
66+
:center="[centerX.value, centerY.value]"
67+
:rotation="rotation.value"
68+
:blendFunction="Number(blendFunction.value)"
69+
/>
70+
</EffectComposerPmndrs>
71+
</Suspense>
72+
</TresCanvas>
73+
</template>

docs/components.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ declare module 'vue' {
2323
HueSaturationDemo: typeof import('./.vitepress/theme/components/pmdrs/HueSaturationDemo.vue')['default']
2424
KuwaharaDemo: typeof import('./.vitepress/theme/components/pmdrs/KuwaharaDemo.vue')['default']
2525
LensDistortionDemo: typeof import('./.vitepress/theme/components/pmdrs/LensDistortionDemo.vue')['default']
26+
LinocutDemo: typeof import('./.vitepress/theme/components/pmdrs/LinocutDemo.vue')['default']
2627
LoveVueThreeJS: typeof import('./.vitepress/theme/components/LoveVueThreeJS.vue')['default']
2728
NoiseDemo: typeof import('./.vitepress/theme/components/pmdrs/NoiseDemo.vue')['default']
2829
OutlineDemo: typeof import('./.vitepress/theme/components/pmdrs/OutlineDemo.vue')['default']

docs/guide/pmndrs/linocut.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# Linocut
2+
3+
<DocsDemo>
4+
<LinocutDemo />
5+
</DocsDemo>
6+
7+
The `Linocut` effect is a custom shader effect inspired by traditional linocut and woodcut printmaking. It transforms the scene into a high-contrast black-and-white composition, featuring bold lines and intricate patterns that replicate the handcrafted aesthetic of relief printing techniques.
8+
9+
## Usage
10+
11+
The `<LinocutPmndrs>` component is straightforward to use and provides customizable options to fine-tune the linocut effect.
12+
13+
```vue{4,12-18,38-42}
14+
<script setup lang="ts">
15+
import { TresCanvas } from '@tresjs/core'
16+
import { Environment, OrbitControls } from '@tresjs/cientos'
17+
import { EffectComposerPmndrs, LinocutPmndrs } from '@tresjs/post-processing'
18+
19+
const gl = {
20+
clearColor: '#4f4f4f',
21+
toneMapping: NoToneMapping,
22+
multisampling: 8,
23+
}
24+
25+
const effectProps = reactive({
26+
scale: 0.85,
27+
noiseScale: 0.0,
28+
center: [0.5, 0.5],
29+
rotation: 0.0,
30+
blendFunction: BlendFunction.NORMAL,
31+
})
32+
</script>
33+
34+
<template>
35+
<TresCanvas v-bind="gl">
36+
<TresPerspectiveCamera
37+
:position="[0, 6.5, 6.5]"
38+
:look-at="[0, 0, 0]"
39+
/>
40+
<OrbitControls auto-rotate />
41+
42+
<Suspense>
43+
<Environment preset="shangai" />
44+
</Suspense>
45+
46+
<TresMesh>
47+
<TresBoxGeometry :args="[2, 2, 2]" />
48+
<TresMeshStandardMaterial color="yellow" />
49+
</TresMesh>
50+
51+
<Suspense>
52+
<EffectComposerPmndrs>
53+
<LinocutPmndrs v-bind="effectProps" />
54+
</EffectComposerPmndrs>
55+
</Suspense>
56+
</TresCanvas>
57+
</template>
58+
```
59+
60+
## Props
61+
62+
| Prop | Description | Default |
63+
| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ |
64+
| **scale** | Line width control. A value between 0 and 1. | `0.85` |
65+
| **noiseScale** | Noise intensity. A value between 0 and 1. | `0.0` |
66+
| **center** | Center of rotation (normalized coordinates). A `Vector2` value or an array of two numbers where both values are between 0 and 1. | `[0.5, 0.5]` |
67+
| **rotation** | Rotation angle (in radians). A value between -π and π. | `0.0` |
68+
| **blendFunction** | Defines how the effect blends with the original scene. See the [`BlendFunction`](https://pmndrs.github.io/postprocessing/public/docs/variable/index.html#static-variable-BlendFunction) options. | `BlendFunction.NORMAL` |
69+
70+
## Further Reading
71+
72+
For an example of the linocut effect in WebGL, see the [Linocut Effect on Shadertoy](https://www.shadertoy.com/view/4XVcDV).
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<script setup lang="ts">
2+
import { ContactShadows, Environment, OrbitControls } from '@tresjs/cientos'
3+
import { TresCanvas } from '@tresjs/core'
4+
import { TresLeches, useControls } from '@tresjs/leches'
5+
import { NoToneMapping } from 'three'
6+
import { BlendFunction } from 'postprocessing'
7+
import { EffectComposerPmndrs, LinocutPmndrs } from '@tresjs/post-processing'
8+
9+
import '@tresjs/leches/styles'
10+
11+
const gl = {
12+
clearColor: '#00ff00',
13+
toneMapping: NoToneMapping,
14+
multisampling: 8,
15+
envMapIntensity: 10,
16+
}
17+
18+
const { blendFunction, scale, noiseScale, centerX, centerY, rotation } = useControls({
19+
scale: { value: 0.85, step: 0.01, min: 0, max: 1 },
20+
noiseScale: { value: 0.0, step: 0.01, min: 0, max: 1 },
21+
centerX: { value: 0.5, step: 0.01, min: 0, max: 1 },
22+
centerY: { value: 0.5, step: 0.01, min: 0, max: 1 },
23+
rotation: { value: 0.0, step: 0.01, min: -Math.PI, max: Math.PI },
24+
blendFunction: {
25+
options: Object.keys(BlendFunction).map(key => ({
26+
text: key,
27+
value: BlendFunction[key as keyof typeof BlendFunction],
28+
})),
29+
value: BlendFunction.NORMAL,
30+
},
31+
})
32+
</script>
33+
34+
<template>
35+
<TresLeches />
36+
37+
<TresCanvas
38+
v-bind="gl"
39+
>
40+
<TresPerspectiveCamera
41+
:position="[0, 6.5, 6.5]"
42+
/>
43+
<OrbitControls auto-rotate />
44+
45+
<TresAmbientLight :intensity="1" />
46+
47+
<TresMesh>
48+
<TresBoxGeometry :args="[2, 2, 2]" />
49+
<TresMeshStandardMaterial color="yellow" :roughness=".5" :metalness="1" />
50+
</TresMesh>
51+
52+
<Suspense>
53+
<Environment :blur=".25" preset="snow" />
54+
</Suspense>
55+
56+
<ContactShadows
57+
:opacity=".65"
58+
:position-y="-1"
59+
:scale="35"
60+
:blur="1"
61+
/>
62+
63+
<Suspense>
64+
<EffectComposerPmndrs>
65+
<LinocutPmndrs
66+
:scale="scale.value"
67+
:noiseScale="noiseScale.value"
68+
:center="[centerX.value, centerY.value]"
69+
:rotation="rotation.value"
70+
:blendFunction="Number(blendFunction.value)"
71+
/>
72+
</EffectComposerPmndrs>
73+
</Suspense>
74+
</TresCanvas>
75+
</template>

playground/src/router.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export const postProcessingRoutes = [
4444
makeRoute('Bloom', '🌼', false),
4545
makeRoute('Noise', '📟', false),
4646
makeRoute('Chromatic Aberration', '🌈', false),
47+
makeRoute('Linocut', '🪵', false),
4748
makeRoute('Color Average', '🎞️', false),
4849
makeRoute('Lens Distortion', '🔍', false),
4950
makeRoute('Sepia', '🌅', false),

src/core/pmndrs/LinocutPmndrs.vue

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<script lang="ts" setup>
2+
import type { BlendFunction } from 'postprocessing'
3+
import { LinocutEffect } from './custom/linocut/index'
4+
import { makePropWatchers } from '../../util/prop'
5+
import { useEffectPmndrs } from './composables/useEffectPmndrs'
6+
import { Vector2 } from 'three'
7+
8+
export interface LinocutPmndrsProps {
9+
blendFunction?: BlendFunction
10+
scale?: number
11+
noiseScale?: number
12+
center?: Vector2 | [number, number]
13+
rotation?: number
14+
}
15+
16+
const props = defineProps<LinocutPmndrsProps>()
17+
18+
const { pass, effect } = useEffectPmndrs(
19+
() => new LinocutEffect({
20+
...props,
21+
center: Array.isArray(props.center) ? new Vector2().fromArray(props.center) : props.center,
22+
}),
23+
props,
24+
)
25+
26+
defineExpose({ pass, effect })
27+
28+
makePropWatchers(
29+
[
30+
[() => props.blendFunction, 'blendMode.blendFunction'],
31+
[() => props.scale, 'scale'],
32+
[() => props.noiseScale, 'noiseScale'],
33+
[() => props.center, 'center'],
34+
[() => props.rotation, 'rotation'],
35+
],
36+
effect,
37+
() => new LinocutEffect(),
38+
)
39+
</script>

0 commit comments

Comments
 (0)