Skip to content
This repository was archived by the owner on Feb 1, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export default defineConfig({
{ text: 'Hue & Saturation', link: '/guide/pmndrs/hue-saturation' },
{ text: 'Lens Distortion', link: '/guide/pmndrs/lens-distortion' },
{ text: 'Grid', link: '/guide/pmndrs/grid' },
{ text: 'Texture', link: '/guide/pmndrs/texture' },
{ text: 'FXAA', link: '/guide/pmndrs/fxaa' },
{ text: 'SMAA', link: '/guide/pmndrs/smaa' },
{ text: 'Kuwahara', link: '/guide/pmndrs/kuwahara' },
Expand Down
121 changes: 121 additions & 0 deletions docs/.vitepress/theme/components/pmdrs/TextureDemo.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<script setup lang="ts">
import { ContactShadows, Environment, OrbitControls } from '@tresjs/cientos'
import { TresCanvas, useTexture } from '@tresjs/core'
import { TresLeches, useControls } from '@tresjs/leches'
import { NoToneMapping, RepeatWrapping, SRGBColorSpace } from 'three'
import { BlendFunction, ColorChannel, ToneMappingMode } from 'postprocessing'
import { ref, watch } from 'vue'
import { EffectComposerPmndrs, TexturePmndrs, ToneMappingPmndrs } from '@tresjs/post-processing'
import type { Ref } from 'vue'
import type { EffectPass, TextureEffect } from 'postprocessing'

import '@tresjs/leches/styles'

const gl = {
clearColor: '#ffffff',
toneMapping: NoToneMapping,
toneMappingExposure: 2,
}

const textureEffectRef: Ref<{ pass: EffectPass, effect: TextureEffect } | null> = ref(null)

const texture = await useTexture(['https://raw.githubusercontent.com/Tresjs/assets/main/textures/dirt/color.jpg'])
texture.colorSpace = SRGBColorSpace
texture.wrapS = texture.wrapT = RepeatWrapping

const { blendFunction, rotation, opacity } = useControls({
blendFunction: {
options: Object.keys(BlendFunction).map(key => ({
text: key,
value: BlendFunction[key as keyof typeof BlendFunction],
})),
value: BlendFunction.MULTIPLY,
},
opacity: {
value: 1,
min: 0,
max: 1,
step: 0.01,
},
repeat: texture.repeat,
offset: texture.offset,
center: texture.center,
rotation: {
value: texture.rotation,
min: 0,
max: 2 * Math.PI,
step: 0.001,
},
mixColors: {
label: 'Mix colors (RGB)',
type: 'button',
onClick: () => {
textureEffectRef.value?.effect.setTextureSwizzleRGBA(
ColorChannel.GREEN,
ColorChannel.BLUE,
ColorChannel.RED,
ColorChannel.ALPHA,
)
},
size: 'md',
},
resetColors: {
label: 'Reset colors (RGB)',
type: 'button',
onClick: () => {
textureEffectRef.value?.effect.setTextureSwizzleRGBA(
ColorChannel.RED,
ColorChannel.GREEN,
ColorChannel.BLUE,
ColorChannel.ALPHA,
)
},
size: 'md',
},
})

watch(rotation, () => {
texture.rotation = rotation.value
})
</script>

<template>
<div class="aspect-16/9">
<TresCanvas
v-bind="gl"
>
<TresPerspectiveCamera
:position="[5, 7, 5]"
:look-at="[0, 0, 0]"
/>
<OrbitControls auto-rotate />

<TresMesh :position="[0, .5, 0]">
<TresBoxGeometry :args="[2, 2, 2]" />
<TresMeshBasicMaterial color="white" />
</TresMesh>

<ContactShadows
:opacity="1"
:position-y="-.5"
/>

<Suspense>
<Environment background :blur="0.1" preset="dawn" />
</Suspense>

<Suspense>
<EffectComposerPmndrs>
<TexturePmndrs
ref="textureEffectRef"
:blendFunction="Number(blendFunction)"
:texture="texture"
:opacity="opacity"
/>
<ToneMappingPmndrs :mode="ToneMappingMode.AGX" />
</EffectComposerPmndrs>
</Suspense>
</TresCanvas>
</div>
<TresLeches :float="false" />
</template>
1 change: 1 addition & 0 deletions docs/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ declare module 'vue' {
ShockWaveDemo: typeof import('./.vitepress/theme/components/pmdrs/ShockWaveDemo.vue')['default']
SMAADemo: typeof import('./.vitepress/theme/components/pmdrs/SMAADemo.vue')['default']
SMAAThreeDemo: typeof import('./.vitepress/theme/components/three/SMAAThreeDemo.vue')['default']
TextureDemo: typeof import('./.vitepress/theme/components/pmdrs/TextureDemo.vue')['default']
TiltShiftDemo: typeof import('./.vitepress/theme/components/pmdrs/TiltShiftDemo.vue')['default']
ToneMappingDemo: typeof import('./.vitepress/theme/components/pmdrs/ToneMappingDemo.vue')['default']
UnrealBloomThreeDemo: typeof import('./.vitepress/theme/components/three/UnrealBloomThreeDemo.vue')['default']
Expand Down
85 changes: 85 additions & 0 deletions docs/guide/pmndrs/texture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Texture

<DocsDemoGUI>
<TextureDemo />
</DocsDemoGUI>

<details>
<summary>Demo code</summary>

<<< @/.vitepress/theme/components/pmdrs/TextureDemo.vue{0}
</details>

The `TextureEffect` component is part of the [`postprocessing`](https://pmndrs.github.io/postprocessing/public/docs/class/src/effects/TextureEffect.js~TextureEffect.html) package.
It allows rendering a texture with customizable options to create various visual effects.

## Usage

The `<TexturePmndrs>` component is easy to use and provides customizable options to suit different visual styles.

:::info
This component is designed to work with a provided texture and **does not** include built-in functionality to modify the texture itself. <br><br>
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.
:::

```vue{2,16-20,41-45}
<script setup lang="ts">
import { EffectComposerPmndrs, TexturePmndrs } from '@tresjs/post-processing/pmndrs'
import { TresCanvas, useTexture } from '@tresjs/core'
import { NoToneMapping, RepeatWrapping, SRGBColorSpace } from 'three'
import { BlendFunction, ColorChannel } from 'postprocessing'

const gl = {
toneMapping: NoToneMapping,
}

const effectProps = {
blendFunction: BlendFunction.OVERLAY,
opacity: 0.65
}

const texture = await useTexture(['your-path-to-texture'])
texture.colorSpace = SRGBColorSpace
texture.wrapS = texture.wrapT = RepeatWrapping
texture.rotation = Math.PI / 2
texture.repeat.set( 2, 2 );

function setTextureSwizzleRGBA(red, green, blue, alpha) {
// This is an example of using a function belonging to the TextureEffect class.
// https://pmndrs.github.io/postprocessing/public/docs/file/src/effects/TextureEffect.js.html#lineNumber192
textureEffectRef.value?.effect.setTextureSwizzleRGBA(red, green, blue, alpha)
}

// Example how to mix texture's color channels.
setTextureSwizzleRGBA(ColorChannel.GREEN, ColorChannel.BLUE, ColorChannel.RED, ColorChannel.ALPHA)

// Example how to reset the texture's color channels (default).
setTextureSwizzleRGBA(ColorChannel.RED, ColorChannel.BLUE, ColorChannel.GREEN, ColorChannel.ALPHA)
</script>

<template>
<TresCanvas v-bind="gl">
<TresPerspectiveCamera :position="[5, 5, 5]" />

<!-- Your Scene -->

<Suspense>
<EffectComposerPmndrs>
<TexturePmndrs v-bind="effectProps" :texture="texture" />
</EffectComposerPmndrs>
</Suspense>
</TresCanvas>
</template>
```

## Props

| Prop | Description | Default |
| ------------- | ----------------------------------------------------------------------------------------------- | --------------------------- |
| 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` |
| texture | The texture used for the effect. See the [`Texture`](https://threejs.org/docs/#api/en/textures/Texture) documentation | `null` |
| opacity | The opacity of the texture. | `1.0` |

## Further Reading

For more details, see the [TextureEffect documentation](https://pmndrs.github.io/postprocessing/public/docs/class/src/effects/TextureEffect.js~TextureEffect.html).
102 changes: 102 additions & 0 deletions playground/src/pages/postprocessing/texture.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<script setup lang="ts">
import { ContactShadows, Environment, OrbitControls } from '@tresjs/cientos'
import { TresCanvas, useTexture } from '@tresjs/core'
import { TresLeches, useControls } from '@tresjs/leches'
import { NoToneMapping, RepeatWrapping, SRGBColorSpace } from 'three'
import { BlendFunction } from 'postprocessing'
import { ref, watch } from 'vue'
import { EffectComposerPmndrs, TexturePmndrs } from '@tresjs/post-processing'
import type { Ref } from 'vue'
import type { EffectPass, TextureEffect } from 'postprocessing'

import '@tresjs/leches/styles'

const gl = {
clearColor: '#ffffff',
toneMapping: NoToneMapping,
}

const textureEffectRef: Ref<{ pass: EffectPass, effect: TextureEffect } | null> = ref(null)

const texture = await useTexture(['https://raw.githubusercontent.com/Tresjs/assets/main/textures/dirt/color.jpg'])
texture.colorSpace = SRGBColorSpace
texture.wrapS = texture.wrapT = RepeatWrapping

const { blendFunction, rotation, opacity } = useControls({
blendFunction: {
options: Object.keys(BlendFunction).map(key => ({
text: key,
value: BlendFunction[key as keyof typeof BlendFunction],
})),
value: BlendFunction.PIN_LIGHT,
},
opacity: {
value: 1,
min: 0,
max: 1,
step: 0.01,
},
repeat: texture.repeat,
offset: texture.offset,
center: texture.center,
rotation: {
value: texture.rotation,
min: 0,
max: 2 * Math.PI,
step: 0.001,
},
})

watch(rotation, () => {
texture.rotation = rotation.value
})

// Example of using TextureEffect's setTextureSwizzleRGBA function
// () => textureEffectRef.value?.effect,
// () => {
// if (!textureEffectRef.value?.effect) { return}

// use setTextureSwizzleRGBA() from TextureEffect (https://pmndrs.github.io/postprocessing/public/docs/file/src/effects/TextureEffect.js.html#lineNumber192)
// textureEffectRef.value?.effect.setTextureSwizzleRGBA()
// },
// )
</script>

<template>
<TresLeches />

<TresCanvas
v-bind="gl"
>
<TresPerspectiveCamera
:position="[5, 5, 5]"
:look-at="[0, 0, 0]"
/>
<OrbitControls auto-rotate />

<TresMesh :position="[0, .5, 0]">
<TresBoxGeometry :args="[2, 2, 2]" />
<TresMeshPhysicalMaterial color="black" :roughness=".25" />
</TresMesh>

<ContactShadows
:opacity="1"
:position-y="-.5"
/>

<Suspense>
<Environment background :blur=".5" preset="snow" />
</Suspense>

<Suspense>
<EffectComposerPmndrs>
<TexturePmndrs
ref="textureEffectRef"
:blendFunction="Number(blendFunction)"
:texture="texture"
:opacity="opacity"
/>
</EffectComposerPmndrs>
</Suspense>
</TresCanvas>
</template>
1 change: 1 addition & 0 deletions playground/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export const postProcessingRoutes = [
makeRoute('Scanline', '📽️', false),
makeRoute('Color Depth', '🔳', false),
makeRoute('Grid', '#️⃣', false),
makeRoute('Texture', '🧩', false),
makeRoute('SMAA', '📐', false),
makeRoute('FXAA', '📐', false),
makeRoute('Shock Wave', '🌊', false),
Expand Down
57 changes: 57 additions & 0 deletions src/core/pmndrs/TexturePmndrs.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<script lang="ts" setup>
import type { BlendFunction } from 'postprocessing'
import { TextureEffect } from 'postprocessing'
import { makePropWatchers } from '../../util/prop'
import { useEffectPmndrs } from './composables/useEffectPmndrs'
import type { Texture } from 'three'
import { watch } from 'vue'

export interface TexturePmndrsProps {
/**
* The blend function.
*/
blendFunction?: BlendFunction
/**
* The texture.
*/
texture?: Texture
/**
* The opacity of the effect.
*/
opacity?: number
}

const props = defineProps<TexturePmndrsProps>()

const { pass, effect } = useEffectPmndrs(() => new TextureEffect(props), props)

defineExpose({
pass,
effect,
})

makePropWatchers(
[
[() => props.blendFunction, 'blendMode.blendFunction'],
],
effect,
() => new TextureEffect(),
)

watch(
[() => props.opacity],
() => {
if (props.opacity !== undefined) {
effect.value?.blendMode.setOpacity(props.opacity)
}
else {
const plainEffect = new TextureEffect()
effect.value?.blendMode.setOpacity(plainEffect.blendMode.getOpacity())
plainEffect.dispose()
}
},
{
immediate: true,
},
)
</script>
Loading