Skip to content

Commit e4d1328

Browse files
authored
Merge pull request #203 from Tresjs/shadertoy
feat(shadertoy-museum): add lab
2 parents 23096b8 + 075edfd commit e4d1328

File tree

23 files changed

+6193
-0
lines changed

23 files changed

+6193
-0
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<script setup lang="ts">
2+
import Floor from './components/Floor.vue';
3+
import Gallery from './components/Gallery.vue';
4+
import Camera from './components/Camera.vue';
5+
import ShaderToy from './components/ShaderToy.vue';
6+
</script>
7+
8+
<template>
9+
<Camera />
10+
<ShaderToy />
11+
<Lights />
12+
<Effects />
13+
14+
<Suspense>
15+
<Floor />
16+
</Suspense>
17+
<Suspense>
18+
<Gallery />
19+
</Suspense>
20+
</template>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<script setup lang="ts">
2+
import { PerspectiveCamera } from 'three';
3+
import type { State } from '../index.vue';
4+
5+
const state = inject('state') as State
6+
const cam = shallowRef()
7+
8+
watch(() => state.i, () => {
9+
const targetInfo = state.shaderToyTargets[state.i] ?? {}
10+
11+
if (targetInfo.cameras && targetInfo.cameras.length > 0) {
12+
const otherCam = targetInfo.cameras[0] as PerspectiveCamera
13+
otherCam.getWorldPosition(cam.value.position)
14+
otherCam.getWorldQuaternion(cam.value.rotation)
15+
cam.value.setFocalLength(otherCam.getFocalLength())
16+
}
17+
})
18+
</script>
19+
20+
<template>
21+
<Levioso :rotation-factor="0">
22+
<TresPerspectiveCamera ref="cam" :position="[0, 5, 0]" :fov="60" :near="0.1" :far="1000" />
23+
</Levioso>
24+
</template>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<script lang="ts" setup>
2+
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer'
3+
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass'
4+
import { OutputPass } from 'three/examples/jsm/postprocessing/OutputPass'
5+
import { UnrealBloomPass } from 'three-stdlib'
6+
import { extend, useLoop, useTres } from '@tresjs/core'
7+
import { shallowRef } from 'vue'
8+
9+
extend({ EffectComposer, OutputPass, UnrealBloomPass, RenderPass })
10+
const { renderer, scene, camera, sizes } = useTres()
11+
const composer = shallowRef<EffectComposer>()
12+
13+
useLoop().render(({ elapsed }) => {
14+
if (composer.value) {
15+
composer.value!.render()
16+
}
17+
})
18+
</script>
19+
20+
<template>
21+
<TresEffectComposer ref="composer" :args="[renderer]" :set-size="[sizes.width.value, sizes.height.value]">
22+
<TresRenderPass :args="[scene, camera]" attach="passes-0" />
23+
<TresUnrealBloomPass :args="[undefined, 0.4, 0.3, 0.2]" attach="passes-1" />
24+
<TresOutputPass attach="passes-2" :set-size="[sizes.width.value, sizes.height.value]" />
25+
</TresEffectComposer>
26+
</template>
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<script setup lang="ts">
2+
import { useTexture } from '@tresjs/core'
3+
import MeshReflectionMaterial from '../meshReflectionMaterial/index.vue'
4+
import { Euler, Mesh, Vector3 } from 'three';
5+
import type { State } from '../index.vue';
6+
7+
const normalMapSrc = '/textures/shadertoy-museum/normal.jpg'
8+
const normalMap = await useTexture([normalMapSrc])
9+
10+
const roughnessMapSrc = '/textures/shadertoy-museum/roughness.jpg'
11+
const roughnessMap = await useTexture([roughnessMapSrc])
12+
13+
const displacementMapSrc = '/textures/shadertoy-museum/displacement.png'
14+
const displacementMap = await useTexture([displacementMapSrc])
15+
16+
const state = inject('state') as State
17+
const position = shallowRef(new Vector3())
18+
const rotation = shallowRef(new Euler())
19+
const scale = shallowRef(new Vector3())
20+
21+
watch(() => state.i, () => {
22+
const targetInfo = state.shaderToyTargets[state.i] ?? {}
23+
24+
if (targetInfo.floor) {
25+
position.value = targetInfo.floor.position
26+
rotation.value = targetInfo.floor.rotation
27+
scale.value = targetInfo.floor.scale
28+
}
29+
})
30+
</script>
31+
32+
<template>
33+
<TresMesh :position="position" :scale="scale" :rotation="rotation">
34+
<TresPlaneGeometry />
35+
<MeshReflectionMaterial :mix="0.5" :normal-map="normalMap" :roughness-map="roughnessMap"
36+
:displacement-map="displacementMap" />
37+
</TresMesh>
38+
</template>
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
<script setup lang="ts">
2+
import { Box3, Camera, Color, Light, Mesh, MeshPhongMaterial, Quaternion, Vector3 } from 'three';
3+
import { inject } from 'vue';
4+
import { shaderToySrc } from '../fns/shaderToySrc';
5+
import type { State } from '../index.vue';
6+
import { shaderToyLights } from '../fns/shaderToyLights';
7+
8+
const state = inject('state') as State
9+
10+
const { scene } = await useGLTF('/models/shadertoy-museum/gallery.glb', { draco: true })
11+
12+
const material = new MeshPhongMaterial({ color: new Color('#000022') })
13+
14+
scene.traverse(obj => {
15+
if ('material' in obj) {
16+
obj.material = material
17+
}
18+
19+
if (obj.name.startsWith('ShaderToy')) {
20+
const name = obj.userData.name as keyof typeof shaderToySrc
21+
const shader = shaderToySrc[name] as string
22+
const lightFn = shaderToyLights[name] ?? (() => { })
23+
const box = new Box3()
24+
box.setFromObject(obj)
25+
const size = new Vector3()
26+
box.getSize(size)
27+
28+
if (!obj.userData.name) {
29+
throw ("Missing Blender property 'userData.name'.")
30+
}
31+
if (typeof obj.userData.name !== 'string') {
32+
throw ("Blender GLTF 'userData.name' should be a string.")
33+
}
34+
if (!(obj.userData.name in shaderToySrc)) {
35+
throw (`${obj.userData.name} not in shaderToySrc. Srcs: ${Object.keys(shaderToySrc).join(', ')}`)
36+
}
37+
38+
const dimensions = new Vector3(1, 1, 0)
39+
dimensions.setFromMatrixPosition(obj.matrixWorld)
40+
dimensions.setFromMatrixScale(obj.matrixWorld)
41+
42+
const scale = new Vector3(1, 1, 1)
43+
scale.setFromMatrixScale(obj.matrixWorld).multiplyScalar(2)
44+
scale.z = 0.001
45+
46+
const position = new Vector3(0, 0, 0)
47+
position.setFromMatrixPosition(obj.matrixWorld)
48+
49+
const rotation = new Quaternion(0, 0, 0)
50+
rotation.setFromRotationMatrix(obj.matrixWorld)
51+
52+
dimensions.multiplyScalar(128)
53+
dimensions.x = Math.floor(dimensions.x)
54+
dimensions.y = Math.floor(dimensions.y)
55+
56+
const shaderDataStr = (shader.split('/** SHADERDATA')[1] ?? '*/').split('*/')[0] ?? '{}'
57+
const shaderMetaData = (() => {
58+
let data = { title: '', author: '', description: '', href: 'https://www.shadertoy.com/' }
59+
try {
60+
data = { ...data, ...JSON.parse(shaderDataStr) }
61+
} catch (_) {
62+
}
63+
64+
return data
65+
})()
66+
67+
state.shaderToyTargets.push(
68+
{
69+
shader,
70+
...shaderMetaData,
71+
lightFn,
72+
name: obj.name,
73+
dimensions,
74+
cameras: (obj.children.filter(c => 'isPerspectiveCamera' in c && c.isPerspectiveCamera) ?? []) as Camera[],
75+
lights: (obj.children.filter(c => 'isLight' in c && c.isLight) ?? []) as Light[],
76+
target: (obj.children.filter(c => c.name.startsWith('Target')))[0] as Mesh,
77+
floor: (obj.children.filter(c => c.name.startsWith('Floor')))[0] as Mesh,
78+
}
79+
)
80+
}
81+
})
82+
83+
state.shaderToyTargets.sort((a, b) => (a.name).localeCompare(b.name))
84+
85+
for (const info of state.shaderToyTargets) {
86+
for (const light of info.lights) {
87+
light.getWorldPosition(light.position)
88+
light.removeFromParent()
89+
90+
const userData = light.userData
91+
for (const key of Object.keys(userData)) {
92+
if ((typeof userData[key]) === 'string' && userData[key].startsWith('{')) {
93+
try {
94+
if (key === 'x' || key === 'y' || key === 'z') {
95+
const data = JSON.parse(userData[key])
96+
data.init = light.position[key]
97+
userData[key] = data
98+
}
99+
if (key === 'intensity') {
100+
const data = JSON.parse(userData[key])
101+
userData[key] = data
102+
}
103+
} catch (e) {
104+
console.error(e)
105+
}
106+
}
107+
}
108+
}
109+
110+
for (const obj of [info.target, info.floor]) {
111+
obj.getWorldPosition(obj.position)
112+
obj.getWorldQuaternion(obj.quaternion)
113+
obj.getWorldScale(obj.scale)
114+
obj.removeFromParent()
115+
}
116+
}
117+
118+
onMounted(
119+
() => setTimeout(() => {
120+
if (state.i < 0) {
121+
state.next()
122+
}
123+
}, 3000)
124+
)
125+
</script>
126+
127+
<template>
128+
<TresGroup>
129+
<primitive :object="scene" />
130+
</TresGroup>
131+
</template>
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<script setup lang="ts">
2+
import { Group, Light, Vector2 } from 'three';
3+
import type { State } from '../index.vue';
4+
import { lerp, pingpong } from 'three/src/math/MathUtils.js';
5+
6+
const state = inject('state') as State
7+
8+
const lightsGroup = shallowRef(new Group())
9+
10+
watch(() => state.i, () => {
11+
const targetInfo = state.shaderToyTargets[state.i] ?? {}
12+
13+
for (const light of lightsGroup.value.children) {
14+
(light as Light).removeFromParent()
15+
}
16+
17+
if (targetInfo.lights && targetInfo.lights.length > 0) {
18+
for (const light of targetInfo.lights) {
19+
lightsGroup.value.add(light)
20+
}
21+
}
22+
})
23+
24+
const center = new Vector2(0.5, 0.5)
25+
26+
useLoop().onBeforeRender(
27+
() => {
28+
const targetInfo = state.shaderToyTargets[state.i] ?? {}
29+
const elapsed = state.clock.getElapsedTime()
30+
if (targetInfo.lights) {
31+
for (const light of targetInfo.lights) {
32+
const d = light.userData
33+
if (d.x) {
34+
light.position.x = d.x.init + Math.cos(d.x.speed * elapsed + d.x.phase) * d.x.dist
35+
}
36+
if (d.y) {
37+
light.position.y = d.y.init + Math.cos(d.y.speed * elapsed + d.y.phase) * d.y.dist
38+
}
39+
if (d.z) {
40+
light.position.z = d.z.init + Math.cos(d.z.speed * elapsed + d.z.phase) * d.z.dist
41+
}
42+
if (d.intensity) {
43+
light.intensity = lerp(d.intensity.a, d.intensity.b, pingpong(elapsed * d.intensity.speed))
44+
}
45+
targetInfo.lightFn(light as Light, center, elapsed)
46+
}
47+
}
48+
}
49+
)
50+
</script>
51+
52+
<template>
53+
<TresGroup ref="lightsGroup"></TresGroup>
54+
</template>
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 { DoubleSide, Material, MeshNormalMaterial, ShaderMaterial, Vector2 } from 'three';
3+
import type { State } from '../index.vue';
4+
5+
const state = inject('state') as State
6+
7+
const fragmentShader = `
8+
precision mediump float;
9+
varying vec4 vFragColor;
10+
11+
void main() {
12+
gl_FragColor = vec4(vec3(vFragColor), 1.0);
13+
}
14+
`
15+
16+
const vertexShader = shallowRef(`
17+
uniform vec2 iResolution;
18+
uniform float iTime;
19+
varying vec4 vFragColor;
20+
21+
void main() {
22+
gl_Position = vec4(1., 1., 0., 1.);
23+
vFragColor = vec4(1., 1., 0., 1.);
24+
}
25+
`)
26+
27+
function getVertexShader(s: string) {
28+
return `
29+
uniform vec2 iResolution;
30+
uniform float iTime;
31+
32+
varying vec4 vFragColor;
33+
34+
${s}
35+
36+
void main() {
37+
mainImage(vFragColor, (position.xy + vec2(0.5, 0.5)) * iResolution);
38+
vec3 offset = vec3(normal) * clamp(vFragColor.a, 0., 1.);
39+
vec4 modelPosition = modelMatrix * vec4(position + offset, 1.0);
40+
vec4 viewPosition = viewMatrix * modelPosition;
41+
42+
gl_Position = projectionMatrix * viewPosition;
43+
}
44+
`
45+
}
46+
47+
const material = shallowRef(new MeshNormalMaterial() as Material)
48+
49+
const uniforms = {
50+
iResolution: { value: new Vector2(256, 256) },
51+
iTime: { value: 0 },
52+
}
53+
54+
useLoop().onBeforeRender(
55+
() => {
56+
uniforms.iTime.value = state.clock.getElapsedTime()
57+
})
58+
59+
watch(() => state.i, () => {
60+
const targetInfo = state.shaderToyTargets[state.i] ?? {}
61+
62+
vertexShader.value = getVertexShader(targetInfo.shader)
63+
if (material.value) material.value.dispose()
64+
material.value = new ShaderMaterial({ vertexShader: getVertexShader(targetInfo.shader), fragmentShader, uniforms, side: DoubleSide })
65+
})
66+
</script>
67+
68+
<template>
69+
<TresMesh :position="state.target.position" :scale="state.target.scale" :rotation="state.target.rotation"
70+
:material="material">
71+
<TresPlaneGeometry :copy="state.target.geometry" />
72+
</TresMesh>
73+
</template>

0 commit comments

Comments
 (0)