Skip to content

Commit 29927aa

Browse files
feat: kuwahara (#172)
* init effect * wip effect * wip effect * add credits and optimize performance shader * new version effect * wip code and test * wip code and example * wip * wip doc * add demo * lint * add warning code and new uniform * change doc * change doc * change doc
1 parent 03f4986 commit 29927aa

File tree

9 files changed

+426
-8
lines changed

9 files changed

+426
-8
lines changed

docs/.vitepress/config.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,21 +51,21 @@ 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: 'Sepia', link: '/guide/pmndrs/sepia' },
55+
{ text: 'Vignette', link: '/guide/pmndrs/vignette' },
56+
{ text: 'Hue & Saturation', link: '/guide/pmndrs/hue-saturation' },
57+
{ text: 'Kuwahara', link: '/guide/pmndrs/kuwahara' },
5458
{ text: 'Color Average', link: '/guide/pmndrs/color-average' },
5559
{ text: 'Depth of Field', link: '/guide/pmndrs/depth-of-field' },
5660
{ text: 'Dot Screen', link: '/guide/pmndrs/dot-screen' },
5761
{ text: 'Glitch', link: '/guide/pmndrs/glitch' },
58-
{ text: 'Hue & Saturation', link: '/guide/pmndrs/hue-saturation' },
59-
{ text: 'Lens Distortion', link: '/guide/pmndrs/lens-distortion' },
6062
{ text: 'Noise', link: '/guide/pmndrs/noise' },
61-
{ text: 'Outline', link: '/guide/pmndrs/outline' },
63+
{ text: 'Outline', link: '/guide/pmndrs/outline' },
6264
{ text: 'Pixelation', link: '/guide/pmndrs/pixelation' },
6365
{ text: 'Scanline', link: '/guide/pmndrs/scanline' },
64-
{ text: 'Sepia', link: '/guide/pmndrs/sepia' },
6566
{ text: 'Shock Wave', link: '/guide/pmndrs/shock-wave' },
6667
{ text: 'Tilt Shift', link: '/guide/pmndrs/tilt-shift' },
6768
{ text: 'Tone Mapping', link: '/guide/pmndrs/tone-mapping' },
68-
{ text: 'Vignette', link: '/guide/pmndrs/vignette' },
6969
],
7070
},
7171
{
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<script setup lang="ts">
2+
import { ContactShadows, Environment, OrbitControls, useGLTF } from '@tresjs/cientos'
3+
import { TresCanvas } from '@tresjs/core'
4+
import { TresLeches, useControls } from '@tresjs/leches'
5+
import { EffectComposerPmndrs, KuwaharaPmndrs } from '@tresjs/post-processing'
6+
import { BlendFunction } from 'postprocessing'
7+
import { NoToneMapping } from 'three'
8+
import { reactive, watch } from 'vue'
9+
10+
import '@tresjs/leches/styles'
11+
12+
const gl = {
13+
clearColor: '#3386E0',
14+
toneMapping: NoToneMapping,
15+
multisampling: 8,
16+
}
17+
18+
const { scene: scenePlantJar } = await useGLTF('https://raw.githubusercontent.com/Tresjs/assets/main/models/gltf/kuwahara-effect/plant-jar/plant-jar.glb', { draco: true })
19+
const { scene: sceneWatermelon } = await useGLTF('https://raw.githubusercontent.com/Tresjs/assets/main/models/gltf/kuwahara-effect/watermelon/watermelon_fruit.glb', { draco: true })
20+
21+
const effectProps = reactive({
22+
blendFunction: BlendFunction.NORMAL,
23+
})
24+
25+
const { enabled, radius, sectorCount } = useControls({
26+
enabled: true,
27+
radius: { value: 10, min: 1, max: 15, step: 1 },
28+
sectorCount: { value: 4, min: 1, max: 8, step: 1 },
29+
})
30+
31+
watch(enabled.value, () => {
32+
effectProps.blendFunction = enabled.value.value ? BlendFunction.NORMAL : BlendFunction.SKIP
33+
})
34+
</script>
35+
36+
<template>
37+
<TresLeches style="left: initial;right:10px; top:10px;" />
38+
39+
<TresCanvas
40+
v-bind="gl"
41+
>
42+
<TresPerspectiveCamera
43+
:position="[0, 6.5, 15]"
44+
/>
45+
46+
<OrbitControls />
47+
48+
<TresAmbientLight :intensity="1" />
49+
50+
<TresDirectionalLight />
51+
52+
<primitive :position-x="-3" :position-y="-3.5" :scale="5" :object="scenePlantJar" />
53+
<primitive :position-x="4" :scale="20" :object="sceneWatermelon" />
54+
55+
<ContactShadows
56+
:opacity=".25"
57+
:position-y="-3.85"
58+
:scale="20"
59+
:blur=".65"
60+
/>
61+
62+
<ContactShadows
63+
:opacity=".5"
64+
:position-y="-3.85"
65+
:scale="20"
66+
:blur=".65"
67+
/>
68+
69+
<Suspense>
70+
<Environment :blur="0.2" preset="snow" />
71+
</Suspense>
72+
73+
<Suspense>
74+
<EffectComposerPmndrs>
75+
<KuwaharaPmndrs :blendFunction="effectProps.blendFunction" :radius="radius.value" :sectorCount="sectorCount.value" />
76+
</EffectComposerPmndrs>
77+
</Suspense>
78+
</TresCanvas>
79+
</template>

docs/components.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,16 @@ declare module 'vue' {
2121
HalftoneThreeDemo: typeof import('./.vitepress/theme/components/three/HalftoneThreeDemo.vue')['default']
2222
HueSaturation: typeof import('./.vitepress/theme/components/pmdrs/HueSaturationDemo.vue')['default']
2323
HueSaturationDemo: typeof import('./.vitepress/theme/components/pmdrs/HueSaturationDemo.vue')['default']
24+
KuwaharaDemo: typeof import('./.vitepress/theme/components/pmdrs/KuwaharaDemo.vue')['default']
2425
LensDistortionDemo: typeof import('./.vitepress/theme/components/pmdrs/LensDistortionDemo.vue')['default']
2526
LoveVueThreeJS: typeof import('./.vitepress/theme/components/LoveVueThreeJS.vue')['default']
2627
NoiseDemo: typeof import('./.vitepress/theme/components/pmdrs/NoiseDemo.vue')['default']
2728
OutlineDemo: typeof import('./.vitepress/theme/components/pmdrs/OutlineDemo.vue')['default']
2829
PixelationDemo: typeof import('./.vitepress/theme/components/pmdrs/PixelationDemo.vue')['default']
2930
PixelationThreeDemo: typeof import('./.vitepress/theme/components/three/PixelationThreeDemo.vue')['default']
3031
ScanlineDemo: typeof import('./.vitepress/theme/components/pmdrs/ScanlineDemo.vue')['default']
31-
ShockWaveDemo: typeof import('./.vitepress/theme/components/pmdrs/ShockWaveDemo.vue')['default']
3232
SepiaDemo: typeof import('./.vitepress/theme/components/pmdrs/SepiaDemo.vue')['default']
33+
ShockWaveDemo: typeof import('./.vitepress/theme/components/pmdrs/ShockWaveDemo.vue')['default']
3334
SMAAThreeDemo: typeof import('./.vitepress/theme/components/three/SMAAThreeDemo.vue')['default']
3435
TiltShiftDemo: typeof import('./.vitepress/theme/components/pmdrs/TiltShiftDemo.vue')['default']
3536
ToneMappingDemo: typeof import('./.vitepress/theme/components/pmdrs/ToneMappingDemo.vue')['default']

docs/guide/pmndrs/kuwahara.md

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Kuwahara (Watercolor Painting)
2+
3+
<DocsDemo>
4+
<KuwaharaDemo />
5+
</DocsDemo>
6+
7+
The `Kuwahara` effect is part of the [`postprocessing`](https://pmndrs.github.io/postprocessing/public/docs/class/src/effects/KuwaharaEffect.js~KuwaharaEffect.html) package. It allows you to apply a Kuwahara filter to your scene, providing a painterly effect.
8+
9+
The Kuwahara effect smooths out an image while keeping the edges sharp. It splits the image into small parts, checks each part for differences, and uses the part with the least differences. This makes the image look like a painting, reducing noise but keeping important details.
10+
11+
## Usage
12+
13+
The `<KuwaharaPmndrs>` component is straightforward to use and provides customizable options to fine-tune the Kuwahara effect.
14+
15+
```vue{2,5-9,26-32}
16+
<script setup lang="ts">
17+
import { EffectComposerPmndrs, KuwaharaPmndrs } from '@tresjs/post-processing'
18+
import { BlendFunction } from 'postprocessing'
19+
20+
const effectProps = reactive({
21+
radius: 1,
22+
blendFunction: BlendFunction.NORMAL,
23+
sectorCount: 4,
24+
})
25+
</script>
26+
27+
<template>
28+
<TresCanvas>
29+
<TresPerspectiveCamera
30+
:position="[5, 5, 5]"
31+
:look-at="[0, 0, 0]"
32+
/>
33+
34+
<OrbitControls auto-rotate />
35+
36+
<TresMesh :position="[0, 1, 0]">
37+
<TresBoxGeometry :args="[2, 2, 2]" />
38+
<TresMeshPhysicalMaterial color="green" />
39+
</TresMesh>
40+
41+
<Suspense>
42+
<EffectComposerPmndrs>
43+
<KuwaharaPmndrs
44+
v-bind="effectProps"
45+
/>
46+
</EffectComposerPmndrs>
47+
</Suspense>
48+
</TresCanvas>
49+
</template>
50+
```
51+
52+
## Props
53+
54+
| Prop | Description | Default |
55+
| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ |
56+
| **radius** | The intensity of the Kuwahara effect. A value between `0` (no effect) and `1` (maximum effect). | `1` |
57+
| **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` |
58+
| **sectorCount** | The number of sectors used in the Kuwahara filter. Higher values can improve the quality of the effect but may reduce performance. <br> It is preferable that the value is an **`Integer`**. <br> The maximum value is **`8`**. | `4` |
59+
60+
::: warning
61+
It is normal to experience a drastic drop in FPS when you significantly increase the `radius` in the Kuwahara effect. This is because a higher `radius` increases the number of calculations performed for each pixel, which can be very costly in terms of performance. If you decide to have a higher radius due to aesthetic constraints or other reasons, the `sectorCount` value has been integrated to counteract the frame drop.
62+
63+
The `sectorCount` value in the shader determines the number of sectors used to calculate the variance and average color in the Kuwahara effect. It divides the space around each pixel into several sectors to perform these calculations. A higher number of sectors can improve the quality of the effect but also increases the computational cost. Therefore, the `sectorCount` value helps find a good compromise between rendering quality and performance.
64+
65+
Therefore, you should reduce the `sectorCount` value if you decide to increase the `radius` and you experience frame drops.
66+
:::
67+
68+
## Further Reading
69+
70+
Inspired by and based on the post [On Crafting Painterly Shaders](https://blog.maximeheckel.com/posts/on-crafting-painterly-shaders/).
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<script setup lang="ts">
2+
import { Environment, OrbitControls } from '@tresjs/cientos'
3+
import { TresCanvas } from '@tresjs/core'
4+
import { TresLeches, useControls } from '@tresjs/leches'
5+
import { EffectComposerPmndrs, KuwaharaPmndrs } 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: '#3386E0',
13+
toneMapping: NoToneMapping,
14+
multisampling: 8,
15+
}
16+
17+
const { radius, blendFunction, sectorCount } = useControls({
18+
radius: { value: 10, min: 1, max: 40, step: 1 },
19+
sectorCount: { value: 4, min: 1, max: 8, step: 1 },
20+
blendFunction: {
21+
options: Object.keys(BlendFunction).map((key: string) => ({
22+
text: key,
23+
value: BlendFunction[key as keyof typeof BlendFunction],
24+
})),
25+
value: BlendFunction.NORMAL,
26+
},
27+
})
28+
</script>
29+
30+
<template>
31+
<TresLeches />
32+
33+
<TresCanvas
34+
v-bind="gl"
35+
>
36+
<TresPerspectiveCamera
37+
:position="[0, 5, 12.5]"
38+
/>
39+
<OrbitControls auto-rotate />
40+
41+
<TresAmbientLight :intensity="1.25" />
42+
43+
<TresMesh :position-y="0">
44+
<TresBoxGeometry :args="[4, 4, 4]" />
45+
<TresMeshPhysicalMaterial color="white" :reflectivity="1" :roughness="0" :metalness="1.0" :clearcoat="1.0" />
46+
</TresMesh>
47+
48+
<Suspense>
49+
<Environment background :blur="0" preset="snow" />
50+
</Suspense>
51+
52+
<Suspense>
53+
<EffectComposerPmndrs>
54+
<KuwaharaPmndrs :blendFunction="Number(blendFunction.value)" :radius="radius.value" :sectorCount="sectorCount.value" />
55+
</EffectComposerPmndrs>
56+
</Suspense>
57+
</TresCanvas>
58+
</template>

playground/src/router.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export const postProcessingRoutes = [
3737
makeRoute('Glitch', '📺', false),
3838
makeRoute('Depth of Field', '📷', false),
3939
makeRoute('Hue & Saturation', '📷', false),
40+
makeRoute('Kuwahara', '🖼️', false),
4041
makeRoute('Tilt Shift', '🔍', false),
4142
makeRoute('Dot Screen', '🔘', false),
4243
makeRoute('Pixelation', '👾', false),

src/core/pmndrs/KuwaharaPmndrs.vue

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<script lang="ts" setup>
2+
import type { BlendFunction } from 'postprocessing'
3+
import { KuwaharaEffect } from './custom/kuwahara/index'
4+
import { makePropWatchers } from '../../util/prop'
5+
import { useEffectPmndrs } from './composables/useEffectPmndrs'
6+
7+
export interface KuwaharaPmndrsProps {
8+
/**
9+
* The blend function for the effect.
10+
* Determines how this effect blends with other effects.
11+
*/
12+
blendFunction?: BlendFunction
13+
14+
/**
15+
* The intensity of the barrel distortion.
16+
* A value between 0 (no distortion) and 1 (maximum distortion).
17+
*/
18+
radius?: number
19+
20+
/**
21+
* The number of sectors.
22+
* Determines the number of angular divisions used in the Kuwahara filter.
23+
* Higher values can improve the quality of the effect but may reduce performance.
24+
* The maximum value is defined by MAX_SECTOR_COUNT = 8 in the kuwahara/index.ts file.
25+
* It is preferable that the value is an integer.
26+
*/
27+
sectorCount?: number
28+
}
29+
30+
const props = defineProps<KuwaharaPmndrsProps>()
31+
32+
const { pass, effect } = useEffectPmndrs(
33+
() => new KuwaharaEffect(props),
34+
props,
35+
)
36+
37+
defineExpose({ pass, effect })
38+
39+
makePropWatchers(
40+
[
41+
[() => props.blendFunction, 'blendMode.blendFunction'],
42+
[() => props.radius, 'radius'],
43+
[() => props.sectorCount, 'sectorCount'],
44+
],
45+
effect,
46+
() => new KuwaharaEffect(),
47+
)
48+
</script>

0 commit comments

Comments
 (0)