Skip to content

Commit 826d832

Browse files
damienmontastieralvarosabuTinoooo
authored
feat: fish-eye (#182)
* migrate to new tresleches, change devDependencies package.json file * add todo next commit * lint * init fish-eye * v1 fish-eye * modify doc * try floa false leches * refactor: improve BarrelBlurDemo component layout and styling * leches floatg * fisheye: modify doc and add float params for leches * review: add computed for array-vector lens --------- Co-authored-by: alvarosabu <alvaro.saburido@gmail.com> Co-authored-by: Tino Koch <17991193+Tinoooo@users.noreply.github.com> Co-authored-by: Tino Koch <>
1 parent 0df3394 commit 826d832

File tree

9 files changed

+458
-0
lines changed

9 files changed

+458
-0
lines changed

docs/.vitepress/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ export default defineConfig({
4949
text: 'Pmndrs',
5050
items: [
5151
{ text: 'Barrel blur', link: '/guide/pmndrs/barrel-blur' },
52+
{ text: 'Fish Eye', link: '/guide/pmndrs/fish-eye' },
5253
{ text: 'Bloom', link: '/guide/pmndrs/bloom' },
5354
{ text: 'Chromatic Aberration', link: '/guide/pmndrs/chromatic-aberration' },
5455
{ text: 'Linocut', link: '/guide/pmndrs/linocut' },
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
<!-- eslint-disable ts/no-use-before-define -->
2+
3+
<script setup lang="ts">
4+
import { gsap } from 'gsap'
5+
import { Environment, OrbitControls, Precipitation, RoundedBox } from '@tresjs/cientos'
6+
import { TresCanvas } from '@tresjs/core'
7+
import { TresLeches, useControls } from '@tresjs/leches'
8+
import { EffectComposerPmndrs, FishEyePmndrs } from '@tresjs/post-processing'
9+
import { BackSide, NoToneMapping } from 'three'
10+
import { ref, watch } from 'vue'
11+
import { BlendFunction } from 'postprocessing'
12+
13+
import '@tresjs/leches/styles'
14+
15+
const gl = {
16+
clearColor: '#ffffff',
17+
toneMapping: NoToneMapping,
18+
multisampling: 8,
19+
}
20+
21+
const lensParams = [
22+
{ lensSX: 1.0, lensSY: 1.0, lensFX: 0.0, lensFY: 1.0 },
23+
{ lensSX: 0.5, lensSY: 1.0, lensFX: -0.25, lensFY: 0.75 },
24+
{ lensSX: 1.0, lensSY: 1.0, lensFX: -1.0, lensFY: 1.0 },
25+
{ lensSX: 0.8, lensSY: 0.8, lensFX: -0.5, lensFY: 0.5 },
26+
{ lensSX: 1, lensSY: 1, lensFX: 0, lensFY: 2 },
27+
{ lensSX: -0.7, lensSY: 0.7, lensFX: 0.5, lensFY: -0.5 },
28+
{ lensSX: -1.2, lensSY: 1.2, lensFX: 1.0, lensFY: -1.0 },
29+
{ lensSX: 1, lensSY: 1, lensFX: 0, lensFY: 0 },
30+
{ lensSX: 0.9, lensSY: 0.9, lensFX: 1.5, lensFY: -1.5 },
31+
{ lensSX: 1.3, lensSY: 1.3, lensFX: 1.5, lensFY: -1.0 },
32+
]
33+
34+
const tweenParams = {
35+
duration: 2,
36+
ease: 'elastic.out(0.85,0.3)',
37+
}
38+
39+
const localBlendFunction = ref(BlendFunction.NORMAL)
40+
41+
const currentIndex = ref(0)
42+
43+
const onSwitch = () => {
44+
currentIndex.value = (currentIndex.value + 1) % lensParams.length
45+
46+
gsap.to(lensSX, { value: lensParams[currentIndex.value].lensSX, ...tweenParams })
47+
gsap.to(lensSY, { value: lensParams[currentIndex.value].lensSY, ...tweenParams })
48+
gsap.to(lensFX, { value: lensParams[currentIndex.value].lensFX, ...tweenParams })
49+
gsap.to(lensFY, { value: lensParams[currentIndex.value].lensFY, ...tweenParams })
50+
}
51+
52+
const { enabled, lensSX, lensSY, lensFX, lensFY, scale } = useControls({
53+
acceptBtn: {
54+
label: 'Switch Lens',
55+
type: 'button',
56+
onClick: onSwitch,
57+
size: 'md',
58+
},
59+
lensSX: { value: lensParams[0].lensSX, step: 0.01, min: -2, max: 2 },
60+
lensSY: { value: lensParams[0].lensFY, step: 0.01, min: -2, max: 2 },
61+
lensFX: { value: lensParams[0].lensFX, step: 0.01, min: -2, max: 2 },
62+
lensFY: { value: lensParams[0].lensFY, step: 0.01, min: -2, max: 2 },
63+
scale: { value: 1.0, step: 0.01, min: 0.1, max: 2 },
64+
enabled: true,
65+
})
66+
67+
watch(enabled, () => {
68+
localBlendFunction.value = enabled.value ? BlendFunction.NORMAL : BlendFunction.SKIP
69+
})
70+
</script>
71+
72+
<template>
73+
<div class="aspect-16/9">
74+
<TresCanvas
75+
v-bind="gl"
76+
>
77+
<TresPerspectiveCamera
78+
:position="[5, 5, 5]"
79+
/>
80+
<OrbitControls :target="[0, .5, 0]" auto-rotate :maxPolarAngle="Math.PI / 2" />
81+
82+
<Suspense>
83+
<Environment preset="snow" />
84+
</Suspense>
85+
86+
<TresAmbientLight :intensity=".5" />
87+
88+
<TresMesh :rotation-x="-Math.PI / 2">
89+
<TresPlaneGeometry :args="[15, 15]" />
90+
<TresMeshPhysicalMaterial
91+
:metalness=".5"
92+
:roughness=".85"
93+
/>
94+
</TresMesh>
95+
96+
<TresMesh>
97+
<TresSphereGeometry :args="[7.5, 32, 32]" />
98+
<TresMeshPhysicalMaterial
99+
:metalness=".5"
100+
:roughness=".25"
101+
:side="BackSide"
102+
/>
103+
</TresMesh>
104+
105+
<RoundedBox
106+
v-for="(positionX, index) in [-1.75, 1.75]"
107+
:key="index"
108+
:position="positionX"
109+
:position-y="1.05"
110+
:args="[2, 2, 2, 2, 0.25]"
111+
>
112+
<TresMeshPhysicalMaterial
113+
:metalness="0.5"
114+
:roughness=".3"
115+
/>
116+
</RoundedBox>
117+
118+
<Precipitation
119+
:randomness="3"
120+
:speed="1"
121+
:count="2500"
122+
/>
123+
124+
<Suspense>
125+
<EffectComposerPmndrs>
126+
<FishEyePmndrs
127+
:blendFunction="localBlendFunction"
128+
:lensS="[lensSX, lensSY]"
129+
:lensF="[lensFX, lensFY]"
130+
:scale="scale"
131+
/>
132+
</EffectComposerPmndrs>
133+
</Suspense>
134+
</TresCanvas>
135+
</div>
136+
<TresLeches :float="false" />
137+
</template>

docs/components.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ declare module 'vue' {
1919
DocsDemoGUI: typeof import('./.vitepress/theme/components/DocsDemoGUI.vue')['default']
2020
DotScreenDemo: typeof import('./.vitepress/theme/components/pmdrs/DotScreenDemo.vue')['default']
2121
Ducky: typeof import('./.vitepress/theme/components/Ducky.vue')['default']
22+
FishEyeDemo: typeof import('./.vitepress/theme/components/pmdrs/FishEyeDemo.vue')['default']
2223
GlitchDemo: typeof import('./.vitepress/theme/components/pmdrs/GlitchDemo.vue')['default']
2324
GlitchThreeDemo: typeof import('./.vitepress/theme/components/three/GlitchThreeDemo.vue')['default']
2425
HalftoneThreeDemo: typeof import('./.vitepress/theme/components/three/HalftoneThreeDemo.vue')['default']

docs/guide/pmndrs/fish-eye.md

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Fish Eye
2+
3+
<DocsDemoGUI>
4+
<FishEyeDemo />
5+
</DocsDemoGUI>
6+
7+
<details>
8+
<summary>Demo code</summary>
9+
10+
<<< @/.vitepress/theme/components/pmdrs/FishEyeDemo.vue{0}
11+
</details>
12+
13+
The `FishEye` is a custom effect simulates the wide-angle distortion of a fish-eye lens. Common in photography and videography, it creates a hemispherical view with a unique, immersive visual experience. The distortion bends the image outward from the center, creating a bubble-like appearance.
14+
15+
## Usage
16+
17+
The `<FishEyePmndrs>` component is straightforward to use and provides customizable options to fine-tune the fish-eye effect.
18+
19+
```vue{3,12-17,29-33}
20+
<script setup lang="ts">
21+
import { TresCanvas } from '@tresjs/core'
22+
import { EffectComposerPmndrs, FishEyePmndrs } from '@tresjs/post-processing'
23+
import { BlendFunction } from 'postprocessing'
24+
25+
const gl = {
26+
clearColor: '#4f4f4f',
27+
toneMapping: NoToneMapping,
28+
multisampling: 8,
29+
}
30+
31+
const effectProps = {
32+
blendFunction: BlendFunction.NORMAL,
33+
lensS: [1.0, 1.0],
34+
lensF: [0.0, 1.0],
35+
scale: 1.0,
36+
}
37+
</script>
38+
39+
<template>
40+
<TresCanvas v-bind="gl">
41+
<TresPerspectiveCamera :position="[5, 5, 5]" />
42+
43+
<TresMesh>
44+
<TresSphereGeometry :args="[5, 32, 32]" />
45+
<TresMeshPhysicalMaterial :metalness="0.5" :roughness="0.25" />
46+
</TresMesh>
47+
48+
<Suspense>
49+
<EffectComposerPmndrs>
50+
<FishEyePmndrs v-bind="effectProps" />
51+
</EffectComposerPmndrs>
52+
</Suspense>
53+
</TresCanvas>
54+
</template>
55+
```
56+
57+
## Props
58+
59+
| Prop | Description | Default |
60+
| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ |
61+
| **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` |
62+
| **lensS** | The lens scale. <br> A `Vector2` value or an array of two numbers. (ex: `[0.5, .75]`) | `Vector2(1.0, 1.0)` |
63+
| **lensF** | The lens factor. <br> A `Vector2` value or an array of two numbers. (ex: `[0.0, 0.5]`) | `Vector2(0.0, 1.0)` |
64+
| **scale** | The scale of the effect. A `number`. | `1.0` |
65+
66+
## Further Reading
67+
68+
For an example of the fish-eye effect in WebGL, see the [Fish Eye Effect on Shadertoy](https://www.shadertoy.com/view/MXyBRy).
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
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, FishEyePmndrs } from '@tresjs/post-processing'
8+
9+
import '@tresjs/leches/styles'
10+
11+
const gl = {
12+
clearColor: '#ffffff',
13+
toneMapping: NoToneMapping,
14+
multisampling: 8,
15+
envMapIntensity: 10,
16+
}
17+
18+
// Note: The variables [lensSX, lensSY], [lensFX, lensFY] are not used as vectors in useControls because the 'step' key is not yet available.
19+
20+
const { blendFunction, lensSX, lensSY, lensFX, lensFY, scale } = useControls({
21+
blendFunction: {
22+
options: Object.keys(BlendFunction).map(key => ({
23+
text: key,
24+
value: BlendFunction[key as keyof typeof BlendFunction],
25+
})),
26+
value: BlendFunction.NORMAL,
27+
},
28+
lensSX: { value: 1.0, step: 0.01, min: 0, max: 2 },
29+
lensSY: { value: 1.0, step: 0.01, min: 0, max: 2 },
30+
lensFX: { value: 0.0, step: 0.01, min: -1, max: 1 },
31+
lensFY: { value: 1.0, step: 0.01, min: -1, max: 1 },
32+
scale: { value: 1.0, step: 0.01, min: 0.1, max: 2 },
33+
})
34+
</script>
35+
36+
<template>
37+
<TresLeches />
38+
39+
<TresCanvas
40+
v-bind="gl"
41+
>
42+
<TresPerspectiveCamera
43+
:position="[0, 6.5, 7.5]"
44+
:look-at="[0, 0, 0]"
45+
/>
46+
<OrbitControls auto-rotate />
47+
48+
<TresMesh :position="[5, 0, 0]">
49+
<TresBoxGeometry :args="[1.65, 1.65, 1.65]" />
50+
<TresMeshNormalMaterial />
51+
</TresMesh>
52+
53+
<TresMesh :position="[0, 0, 0]">
54+
<TresBoxGeometry :args="[1.65, 1.65, 1.65]" />
55+
<TresMeshNormalMaterial />
56+
</TresMesh>
57+
58+
<TresMesh :position="[-5, 0, 0]">
59+
<TresBoxGeometry :args="[1.65, 1.65, 1.65]" />
60+
<TresMeshNormalMaterial />
61+
</TresMesh>
62+
63+
<TresMesh :position="[0, 0, -5]">
64+
<TresBoxGeometry :args="[1.65, 1.65, 1.65]" />
65+
<TresMeshNormalMaterial />
66+
</TresMesh>
67+
68+
<TresMesh :position="[0, 0, 5]">
69+
<TresBoxGeometry :args="[1.65, 1.65, 1.65]" />
70+
<TresMeshNormalMaterial />
71+
</TresMesh>
72+
73+
<Suspense>
74+
<Environment background preset="shangai" />
75+
</Suspense>
76+
77+
<ContactShadows
78+
:opacity=".65"
79+
:position-y="-1"
80+
:scale="35"
81+
:blur="1"
82+
/>
83+
84+
<Suspense>
85+
<EffectComposerPmndrs>
86+
<FishEyePmndrs
87+
:blendFunction="Number(blendFunction)"
88+
:lensS="[lensSX, lensSY]"
89+
:lensF="[lensFX, lensFY]"
90+
:scale="scale"
91+
/>
92+
</EffectComposerPmndrs>
93+
</Suspense>
94+
</TresCanvas>
95+
</template>

playground/src/router.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ export const postProcessingRoutes = [
5353
makeRoute('Brightness Contrast', '🔆', false),
5454
makeRoute('Vignette', '🕶️', false),
5555
makeRoute('Barrel blur', '🌀', false),
56+
makeRoute('Fish Eye', '👁️', false),
5657
makeRoute('On-demand', '🔄', false),
5758
]
5859

src/core/pmndrs/FishEyePmndrs.vue

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<script lang="ts" setup>
2+
import type { BlendFunction } from 'postprocessing'
3+
import { Vector2 } from 'three'
4+
import { FishEyeEffect } from './custom/fish-eye/index'
5+
import { makePropWatchers } from '../../util/prop'
6+
import { useEffectPmndrs } from './composables/useEffectPmndrs'
7+
import { computed } from 'vue'
8+
9+
export interface FishEyePmndrsProps {
10+
/**
11+
* The blend function for the effect.
12+
* Determines how this effect blends with other effects.
13+
*/
14+
blendFunction?: BlendFunction
15+
16+
/**
17+
* The lens scale.
18+
* A Vector2 value or an array of two numbers.
19+
*/
20+
lensS?: Vector2 | [number, number]
21+
22+
/**
23+
* The lens factor.
24+
* A Vector2 value or an array of two numbers.
25+
*/
26+
lensF?: Vector2 | [number, number]
27+
28+
/**
29+
* The scale of the effect.
30+
* A number value.
31+
*/
32+
scale?: number
33+
}
34+
35+
const props = defineProps<FishEyePmndrsProps>()
36+
37+
const computedLensS = computed(() =>
38+
Array.isArray(props.lensS) ? new Vector2(...props.lensS) : props.lensS,
39+
)
40+
const computedLensF = computed(() =>
41+
Array.isArray(props.lensF) ? new Vector2(...props.lensF) : props.lensF,
42+
)
43+
44+
const { pass, effect } = useEffectPmndrs(
45+
() =>
46+
new FishEyeEffect({
47+
...props,
48+
lensS: computedLensS.value,
49+
lensF: computedLensF.value,
50+
}),
51+
props,
52+
)
53+
54+
defineExpose({ pass, effect })
55+
56+
makePropWatchers(
57+
[
58+
[() => props.blendFunction, 'blendMode.blendFunction'],
59+
[() => computedLensS.value, 'lensS'],
60+
[() => computedLensF.value, 'lensF'],
61+
[() => props.scale, 'scale'],
62+
],
63+
effect,
64+
() => new FishEyeEffect(),
65+
)
66+
</script>

0 commit comments

Comments
 (0)