Skip to content
This repository was archived by the owner on Feb 1, 2026. It is now read-only.

Commit 07a9ff5

Browse files
feat: texture (#202)
* feat: add texture effect (demo, source code) * clean demos --------- Co-authored-by: Tino Koch <>
1 parent c5dba7c commit 07a9ff5

8 files changed

Lines changed: 371 additions & 0 deletions

File tree

docs/.vitepress/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ export default defineConfig({
6161
{ text: 'Hue & Saturation', link: '/guide/pmndrs/hue-saturation' },
6262
{ text: 'Lens Distortion', link: '/guide/pmndrs/lens-distortion' },
6363
{ text: 'Grid', link: '/guide/pmndrs/grid' },
64+
{ text: 'Texture', link: '/guide/pmndrs/texture' },
6465
{ text: 'ASCII', link: '/guide/pmndrs/ascii' },
6566
{ text: 'FXAA', link: '/guide/pmndrs/fxaa' },
6667
{ text: 'SMAA', link: '/guide/pmndrs/smaa' },
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
<script setup lang="ts">
2+
import { ContactShadows, Environment, OrbitControls } from '@tresjs/cientos'
3+
import { TresCanvas, useTexture } from '@tresjs/core'
4+
import { TresLeches, useControls } from '@tresjs/leches'
5+
import { NoToneMapping, RepeatWrapping, SRGBColorSpace } from 'three'
6+
import { BlendFunction, ColorChannel, ToneMappingMode } from 'postprocessing'
7+
import { ref, watch } from 'vue'
8+
import { EffectComposerPmndrs, TexturePmndrs, ToneMappingPmndrs } from '@tresjs/post-processing'
9+
import type { Ref } from 'vue'
10+
import type { EffectPass, TextureEffect } from 'postprocessing'
11+
12+
import '@tresjs/leches/styles'
13+
14+
const gl = {
15+
clearColor: '#ffffff',
16+
toneMapping: NoToneMapping,
17+
toneMappingExposure: 2,
18+
}
19+
20+
const textureEffectRef: Ref<{ pass: EffectPass, effect: TextureEffect } | null> = ref(null)
21+
22+
const texture = await useTexture(['https://raw.githubusercontent.com/Tresjs/assets/main/textures/dirt/color.jpg'])
23+
texture.colorSpace = SRGBColorSpace
24+
texture.wrapS = texture.wrapT = RepeatWrapping
25+
26+
const { blendFunction, rotation, opacity } = useControls({
27+
blendFunction: {
28+
options: Object.keys(BlendFunction).map(key => ({
29+
text: key,
30+
value: BlendFunction[key as keyof typeof BlendFunction],
31+
})),
32+
value: BlendFunction.MULTIPLY,
33+
},
34+
opacity: {
35+
value: 1,
36+
min: 0,
37+
max: 1,
38+
step: 0.01,
39+
},
40+
repeat: texture.repeat,
41+
offset: texture.offset,
42+
center: texture.center,
43+
rotation: {
44+
value: texture.rotation,
45+
min: 0,
46+
max: 2 * Math.PI,
47+
step: 0.001,
48+
},
49+
mixColors: {
50+
label: 'Mix colors (RGB)',
51+
type: 'button',
52+
onClick: () => {
53+
textureEffectRef.value?.effect.setTextureSwizzleRGBA(
54+
ColorChannel.GREEN,
55+
ColorChannel.BLUE,
56+
ColorChannel.RED,
57+
ColorChannel.ALPHA,
58+
)
59+
},
60+
size: 'md',
61+
},
62+
resetColors: {
63+
label: 'Reset colors (RGB)',
64+
type: 'button',
65+
onClick: () => {
66+
textureEffectRef.value?.effect.setTextureSwizzleRGBA(
67+
ColorChannel.RED,
68+
ColorChannel.GREEN,
69+
ColorChannel.BLUE,
70+
ColorChannel.ALPHA,
71+
)
72+
},
73+
size: 'md',
74+
},
75+
})
76+
77+
watch(rotation, () => {
78+
texture.rotation = rotation.value
79+
})
80+
</script>
81+
82+
<template>
83+
<div class="aspect-16/9">
84+
<TresCanvas
85+
v-bind="gl"
86+
>
87+
<TresPerspectiveCamera
88+
:position="[5, 7, 5]"
89+
:look-at="[0, 0, 0]"
90+
/>
91+
<OrbitControls auto-rotate />
92+
93+
<TresMesh :position="[0, .5, 0]">
94+
<TresBoxGeometry :args="[2, 2, 2]" />
95+
<TresMeshBasicMaterial color="white" />
96+
</TresMesh>
97+
98+
<ContactShadows
99+
:opacity="1"
100+
:position-y="-.5"
101+
/>
102+
103+
<Suspense>
104+
<Environment background :blur="0.1" preset="dawn" />
105+
</Suspense>
106+
107+
<Suspense>
108+
<EffectComposerPmndrs>
109+
<TexturePmndrs
110+
ref="textureEffectRef"
111+
:blendFunction="Number(blendFunction)"
112+
:texture="texture"
113+
:opacity="opacity"
114+
/>
115+
<ToneMappingPmndrs :mode="ToneMappingMode.AGX" />
116+
</EffectComposerPmndrs>
117+
</Suspense>
118+
</TresCanvas>
119+
</div>
120+
<TresLeches :float="false" />
121+
</template>

docs/components.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ declare module 'vue' {
4343
ShockWaveDemo: typeof import('./.vitepress/theme/components/pmdrs/ShockWaveDemo.vue')['default']
4444
SMAADemo: typeof import('./.vitepress/theme/components/pmdrs/SMAADemo.vue')['default']
4545
SMAAThreeDemo: typeof import('./.vitepress/theme/components/three/SMAAThreeDemo.vue')['default']
46+
TextureDemo: typeof import('./.vitepress/theme/components/pmdrs/TextureDemo.vue')['default']
4647
TiltShiftDemo: typeof import('./.vitepress/theme/components/pmdrs/TiltShiftDemo.vue')['default']
4748
ToneMappingDemo: typeof import('./.vitepress/theme/components/pmdrs/ToneMappingDemo.vue')['default']
4849
UnrealBloomThreeDemo: typeof import('./.vitepress/theme/components/three/UnrealBloomThreeDemo.vue')['default']

docs/guide/pmndrs/texture.md

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# Texture
2+
3+
<DocsDemoGUI>
4+
<TextureDemo />
5+
</DocsDemoGUI>
6+
7+
<details>
8+
<summary>Demo code</summary>
9+
10+
<<< @/.vitepress/theme/components/pmdrs/TextureDemo.vue{0}
11+
</details>
12+
13+
The `TextureEffect` component is part of the [`postprocessing`](https://pmndrs.github.io/postprocessing/public/docs/class/src/effects/TextureEffect.js~TextureEffect.html) package.
14+
It allows rendering a texture with customizable options to create various visual effects.
15+
16+
## Usage
17+
18+
The `<TexturePmndrs>` component is easy to use and provides customizable options to suit different visual styles.
19+
20+
:::info
21+
This component is designed to work with a provided texture and **does not** include built-in functionality to modify the texture itself. <br><br>
22+
If you need to adjust properties such as **rotation**, **repeat**, or **other attributes**, you should modify them directly the texture *(See usage example below)* that you pass to the `<TexturePmndrs />` component.
23+
:::
24+
25+
```vue{2,16-20,41-45}
26+
<script setup lang="ts">
27+
import { EffectComposerPmndrs, TexturePmndrs } from '@tresjs/post-processing/pmndrs'
28+
import { TresCanvas, useTexture } from '@tresjs/core'
29+
import { NoToneMapping, RepeatWrapping, SRGBColorSpace } from 'three'
30+
import { BlendFunction, ColorChannel } from 'postprocessing'
31+
32+
const gl = {
33+
toneMapping: NoToneMapping,
34+
}
35+
36+
const effectProps = {
37+
blendFunction: BlendFunction.OVERLAY,
38+
opacity: 0.65
39+
}
40+
41+
const texture = await useTexture(['your-path-to-texture'])
42+
texture.colorSpace = SRGBColorSpace
43+
texture.wrapS = texture.wrapT = RepeatWrapping
44+
texture.rotation = Math.PI / 2
45+
texture.repeat.set( 2, 2 );
46+
47+
function setTextureSwizzleRGBA(red, green, blue, alpha) {
48+
// This is an example of using a function belonging to the TextureEffect class.
49+
// https://pmndrs.github.io/postprocessing/public/docs/file/src/effects/TextureEffect.js.html#lineNumber192
50+
textureEffectRef.value?.effect.setTextureSwizzleRGBA(red, green, blue, alpha)
51+
}
52+
53+
// Example how to mix texture's color channels.
54+
setTextureSwizzleRGBA(ColorChannel.GREEN, ColorChannel.BLUE, ColorChannel.RED, ColorChannel.ALPHA)
55+
56+
// Example how to reset the texture's color channels (default).
57+
setTextureSwizzleRGBA(ColorChannel.RED, ColorChannel.BLUE, ColorChannel.GREEN, ColorChannel.ALPHA)
58+
</script>
59+
60+
<template>
61+
<TresCanvas v-bind="gl">
62+
<TresPerspectiveCamera :position="[5, 5, 5]" />
63+
64+
<!-- Your Scene -->
65+
66+
<Suspense>
67+
<EffectComposerPmndrs>
68+
<TexturePmndrs v-bind="effectProps" :texture="texture" />
69+
</EffectComposerPmndrs>
70+
</Suspense>
71+
</TresCanvas>
72+
</template>
73+
```
74+
75+
## Props
76+
77+
| Prop | Description | Default |
78+
| ------------- | ----------------------------------------------------------------------------------------------- | --------------------------- |
79+
| 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` |
80+
| texture | The texture used for the effect. See the [`Texture`](https://threejs.org/docs/#api/en/textures/Texture) documentation | `null` |
81+
| opacity | The opacity of the texture. | `1.0` |
82+
83+
## Further Reading
84+
85+
For more details, see the [TextureEffect documentation](https://pmndrs.github.io/postprocessing/public/docs/class/src/effects/TextureEffect.js~TextureEffect.html).
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
<script setup lang="ts">
2+
import { ContactShadows, Environment, OrbitControls } from '@tresjs/cientos'
3+
import { TresCanvas, useTexture } from '@tresjs/core'
4+
import { TresLeches, useControls } from '@tresjs/leches'
5+
import { NoToneMapping, RepeatWrapping, SRGBColorSpace } from 'three'
6+
import { BlendFunction } from 'postprocessing'
7+
import { ref, watch } from 'vue'
8+
import { EffectComposerPmndrs, TexturePmndrs } from '@tresjs/post-processing'
9+
import type { Ref } from 'vue'
10+
import type { EffectPass, TextureEffect } from 'postprocessing'
11+
12+
import '@tresjs/leches/styles'
13+
14+
const gl = {
15+
clearColor: '#ffffff',
16+
toneMapping: NoToneMapping,
17+
}
18+
19+
const textureEffectRef: Ref<{ pass: EffectPass, effect: TextureEffect } | null> = ref(null)
20+
21+
const texture = await useTexture(['https://raw.githubusercontent.com/Tresjs/assets/main/textures/dirt/color.jpg'])
22+
texture.colorSpace = SRGBColorSpace
23+
texture.wrapS = texture.wrapT = RepeatWrapping
24+
25+
const { blendFunction, rotation, opacity } = useControls({
26+
blendFunction: {
27+
options: Object.keys(BlendFunction).map(key => ({
28+
text: key,
29+
value: BlendFunction[key as keyof typeof BlendFunction],
30+
})),
31+
value: BlendFunction.PIN_LIGHT,
32+
},
33+
opacity: {
34+
value: 1,
35+
min: 0,
36+
max: 1,
37+
step: 0.01,
38+
},
39+
repeat: texture.repeat,
40+
offset: texture.offset,
41+
center: texture.center,
42+
rotation: {
43+
value: texture.rotation,
44+
min: 0,
45+
max: 2 * Math.PI,
46+
step: 0.001,
47+
},
48+
})
49+
50+
watch(rotation, () => {
51+
texture.rotation = rotation.value
52+
})
53+
54+
// Example of using TextureEffect's setTextureSwizzleRGBA function
55+
// () => textureEffectRef.value?.effect,
56+
// () => {
57+
// if (!textureEffectRef.value?.effect) { return}
58+
59+
// use setTextureSwizzleRGBA() from TextureEffect (https://pmndrs.github.io/postprocessing/public/docs/file/src/effects/TextureEffect.js.html#lineNumber192)
60+
// textureEffectRef.value?.effect.setTextureSwizzleRGBA()
61+
// },
62+
// )
63+
</script>
64+
65+
<template>
66+
<TresLeches />
67+
68+
<TresCanvas
69+
v-bind="gl"
70+
>
71+
<TresPerspectiveCamera
72+
:position="[5, 5, 5]"
73+
:look-at="[0, 0, 0]"
74+
/>
75+
<OrbitControls auto-rotate />
76+
77+
<TresMesh :position="[0, .5, 0]">
78+
<TresBoxGeometry :args="[2, 2, 2]" />
79+
<TresMeshPhysicalMaterial color="black" :roughness=".25" />
80+
</TresMesh>
81+
82+
<ContactShadows
83+
:opacity="1"
84+
:position-y="-.5"
85+
/>
86+
87+
<Suspense>
88+
<Environment background :blur=".5" preset="snow" />
89+
</Suspense>
90+
91+
<Suspense>
92+
<EffectComposerPmndrs>
93+
<TexturePmndrs
94+
ref="textureEffectRef"
95+
:blendFunction="Number(blendFunction)"
96+
:texture="texture"
97+
:opacity="opacity"
98+
/>
99+
</EffectComposerPmndrs>
100+
</Suspense>
101+
</TresCanvas>
102+
</template>

playground/src/router.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ export const postProcessingRoutes = [
5252
makeRoute('Scanline', '📽️', false),
5353
makeRoute('Color Depth', '🔳', false),
5454
makeRoute('Grid', '#️⃣', false),
55+
makeRoute('Texture', '🧩', false),
5556
makeRoute('ASCII', '🔡', false),
5657
makeRoute('SMAA', '📐', false),
5758
makeRoute('FXAA', '📐', false),

src/core/pmndrs/TexturePmndrs.vue

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<script lang="ts" setup>
2+
import type { BlendFunction } from 'postprocessing'
3+
import { TextureEffect } from 'postprocessing'
4+
import { makePropWatchers } from '../../util/prop'
5+
import { useEffectPmndrs } from './composables/useEffectPmndrs'
6+
import type { Texture } from 'three'
7+
import { watch } from 'vue'
8+
9+
export interface TexturePmndrsProps {
10+
/**
11+
* The blend function.
12+
*/
13+
blendFunction?: BlendFunction
14+
/**
15+
* The texture.
16+
*/
17+
texture?: Texture
18+
/**
19+
* The opacity of the effect.
20+
*/
21+
opacity?: number
22+
}
23+
24+
const props = defineProps<TexturePmndrsProps>()
25+
26+
const { pass, effect } = useEffectPmndrs(() => new TextureEffect(props), props)
27+
28+
defineExpose({
29+
pass,
30+
effect,
31+
})
32+
33+
makePropWatchers(
34+
[
35+
[() => props.blendFunction, 'blendMode.blendFunction'],
36+
],
37+
effect,
38+
() => new TextureEffect(),
39+
)
40+
41+
watch(
42+
[() => props.opacity],
43+
() => {
44+
if (props.opacity !== undefined) {
45+
effect.value?.blendMode.setOpacity(props.opacity)
46+
}
47+
else {
48+
const plainEffect = new TextureEffect()
49+
effect.value?.blendMode.setOpacity(plainEffect.blendMode.getOpacity())
50+
plainEffect.dispose()
51+
}
52+
},
53+
{
54+
immediate: true,
55+
},
56+
)
57+
</script>

0 commit comments

Comments
 (0)