-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #203 from Tresjs/shadertoy
feat(shadertoy-museum): add lab
- Loading branch information
Showing
23 changed files
with
6,193 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
<script setup lang="ts"> | ||
import Floor from './components/Floor.vue'; | ||
import Gallery from './components/Gallery.vue'; | ||
import Camera from './components/Camera.vue'; | ||
import ShaderToy from './components/ShaderToy.vue'; | ||
</script> | ||
|
||
<template> | ||
<Camera /> | ||
<ShaderToy /> | ||
<Lights /> | ||
<Effects /> | ||
|
||
<Suspense> | ||
<Floor /> | ||
</Suspense> | ||
<Suspense> | ||
<Gallery /> | ||
</Suspense> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
<script setup lang="ts"> | ||
import { PerspectiveCamera } from 'three'; | ||
import type { State } from '../index.vue'; | ||
const state = inject('state') as State | ||
const cam = shallowRef() | ||
watch(() => state.i, () => { | ||
const targetInfo = state.shaderToyTargets[state.i] ?? {} | ||
if (targetInfo.cameras && targetInfo.cameras.length > 0) { | ||
const otherCam = targetInfo.cameras[0] as PerspectiveCamera | ||
otherCam.getWorldPosition(cam.value.position) | ||
otherCam.getWorldQuaternion(cam.value.rotation) | ||
cam.value.setFocalLength(otherCam.getFocalLength()) | ||
} | ||
}) | ||
</script> | ||
|
||
<template> | ||
<Levioso :rotation-factor="0"> | ||
<TresPerspectiveCamera ref="cam" :position="[0, 5, 0]" :fov="60" :near="0.1" :far="1000" /> | ||
</Levioso> | ||
</template> |
26 changes: 26 additions & 0 deletions
26
components/content/shadertoy-museum/components/Effects.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
<script lang="ts" setup> | ||
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer' | ||
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass' | ||
import { OutputPass } from 'three/examples/jsm/postprocessing/OutputPass' | ||
import { UnrealBloomPass } from 'three-stdlib' | ||
import { extend, useLoop, useTres } from '@tresjs/core' | ||
import { shallowRef } from 'vue' | ||
extend({ EffectComposer, OutputPass, UnrealBloomPass, RenderPass }) | ||
const { renderer, scene, camera, sizes } = useTres() | ||
const composer = shallowRef<EffectComposer>() | ||
useLoop().render(({ elapsed }) => { | ||
if (composer.value) { | ||
composer.value!.render() | ||
} | ||
}) | ||
</script> | ||
|
||
<template> | ||
<TresEffectComposer ref="composer" :args="[renderer]" :set-size="[sizes.width.value, sizes.height.value]"> | ||
<TresRenderPass :args="[scene, camera]" attach="passes-0" /> | ||
<TresUnrealBloomPass :args="[undefined, 0.4, 0.3, 0.2]" attach="passes-1" /> | ||
<TresOutputPass attach="passes-2" :set-size="[sizes.width.value, sizes.height.value]" /> | ||
</TresEffectComposer> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
<script setup lang="ts"> | ||
import { useTexture } from '@tresjs/core' | ||
import MeshReflectionMaterial from '../meshReflectionMaterial/index.vue' | ||
import { Euler, Mesh, Vector3 } from 'three'; | ||
import type { State } from '../index.vue'; | ||
const normalMapSrc = '/textures/shadertoy-museum/normal.jpg' | ||
const normalMap = await useTexture([normalMapSrc]) | ||
const roughnessMapSrc = '/textures/shadertoy-museum/roughness.jpg' | ||
const roughnessMap = await useTexture([roughnessMapSrc]) | ||
const displacementMapSrc = '/textures/shadertoy-museum/displacement.png' | ||
const displacementMap = await useTexture([displacementMapSrc]) | ||
const state = inject('state') as State | ||
const position = shallowRef(new Vector3()) | ||
const rotation = shallowRef(new Euler()) | ||
const scale = shallowRef(new Vector3()) | ||
watch(() => state.i, () => { | ||
const targetInfo = state.shaderToyTargets[state.i] ?? {} | ||
if (targetInfo.floor) { | ||
position.value = targetInfo.floor.position | ||
rotation.value = targetInfo.floor.rotation | ||
scale.value = targetInfo.floor.scale | ||
} | ||
}) | ||
</script> | ||
|
||
<template> | ||
<TresMesh :position="position" :scale="scale" :rotation="rotation"> | ||
<TresPlaneGeometry /> | ||
<MeshReflectionMaterial :mix="0.5" :normal-map="normalMap" :roughness-map="roughnessMap" | ||
:displacement-map="displacementMap" /> | ||
</TresMesh> | ||
</template> |
131 changes: 131 additions & 0 deletions
131
components/content/shadertoy-museum/components/Gallery.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
<script setup lang="ts"> | ||
import { Box3, Camera, Color, Light, Mesh, MeshPhongMaterial, Quaternion, Vector3 } from 'three'; | ||
import { inject } from 'vue'; | ||
import { shaderToySrc } from '../fns/shaderToySrc'; | ||
import type { State } from '../index.vue'; | ||
import { shaderToyLights } from '../fns/shaderToyLights'; | ||
const state = inject('state') as State | ||
const { scene } = await useGLTF('/models/shadertoy-museum/gallery.glb', { draco: true }) | ||
const material = new MeshPhongMaterial({ color: new Color('#000022') }) | ||
scene.traverse(obj => { | ||
if ('material' in obj) { | ||
obj.material = material | ||
} | ||
if (obj.name.startsWith('ShaderToy')) { | ||
const name = obj.userData.name as keyof typeof shaderToySrc | ||
const shader = shaderToySrc[name] as string | ||
const lightFn = shaderToyLights[name] ?? (() => { }) | ||
const box = new Box3() | ||
box.setFromObject(obj) | ||
const size = new Vector3() | ||
box.getSize(size) | ||
if (!obj.userData.name) { | ||
throw ("Missing Blender property 'userData.name'.") | ||
} | ||
if (typeof obj.userData.name !== 'string') { | ||
throw ("Blender GLTF 'userData.name' should be a string.") | ||
} | ||
if (!(obj.userData.name in shaderToySrc)) { | ||
throw (`${obj.userData.name} not in shaderToySrc. Srcs: ${Object.keys(shaderToySrc).join(', ')}`) | ||
} | ||
const dimensions = new Vector3(1, 1, 0) | ||
dimensions.setFromMatrixPosition(obj.matrixWorld) | ||
dimensions.setFromMatrixScale(obj.matrixWorld) | ||
const scale = new Vector3(1, 1, 1) | ||
scale.setFromMatrixScale(obj.matrixWorld).multiplyScalar(2) | ||
scale.z = 0.001 | ||
const position = new Vector3(0, 0, 0) | ||
position.setFromMatrixPosition(obj.matrixWorld) | ||
const rotation = new Quaternion(0, 0, 0) | ||
rotation.setFromRotationMatrix(obj.matrixWorld) | ||
dimensions.multiplyScalar(128) | ||
dimensions.x = Math.floor(dimensions.x) | ||
dimensions.y = Math.floor(dimensions.y) | ||
const shaderDataStr = (shader.split('/** SHADERDATA')[1] ?? '*/').split('*/')[0] ?? '{}' | ||
const shaderMetaData = (() => { | ||
let data = { title: '', author: '', description: '', href: 'https://www.shadertoy.com/' } | ||
try { | ||
data = { ...data, ...JSON.parse(shaderDataStr) } | ||
} catch (_) { | ||
} | ||
return data | ||
})() | ||
state.shaderToyTargets.push( | ||
{ | ||
shader, | ||
...shaderMetaData, | ||
lightFn, | ||
name: obj.name, | ||
dimensions, | ||
cameras: (obj.children.filter(c => 'isPerspectiveCamera' in c && c.isPerspectiveCamera) ?? []) as Camera[], | ||
lights: (obj.children.filter(c => 'isLight' in c && c.isLight) ?? []) as Light[], | ||
target: (obj.children.filter(c => c.name.startsWith('Target')))[0] as Mesh, | ||
floor: (obj.children.filter(c => c.name.startsWith('Floor')))[0] as Mesh, | ||
} | ||
) | ||
} | ||
}) | ||
state.shaderToyTargets.sort((a, b) => (a.name).localeCompare(b.name)) | ||
for (const info of state.shaderToyTargets) { | ||
for (const light of info.lights) { | ||
light.getWorldPosition(light.position) | ||
light.removeFromParent() | ||
const userData = light.userData | ||
for (const key of Object.keys(userData)) { | ||
if ((typeof userData[key]) === 'string' && userData[key].startsWith('{')) { | ||
try { | ||
if (key === 'x' || key === 'y' || key === 'z') { | ||
const data = JSON.parse(userData[key]) | ||
data.init = light.position[key] | ||
userData[key] = data | ||
} | ||
if (key === 'intensity') { | ||
const data = JSON.parse(userData[key]) | ||
userData[key] = data | ||
} | ||
} catch (e) { | ||
console.error(e) | ||
} | ||
} | ||
} | ||
} | ||
for (const obj of [info.target, info.floor]) { | ||
obj.getWorldPosition(obj.position) | ||
obj.getWorldQuaternion(obj.quaternion) | ||
obj.getWorldScale(obj.scale) | ||
obj.removeFromParent() | ||
} | ||
} | ||
onMounted( | ||
() => setTimeout(() => { | ||
if (state.i < 0) { | ||
state.next() | ||
} | ||
}, 3000) | ||
) | ||
</script> | ||
|
||
<template> | ||
<TresGroup> | ||
<primitive :object="scene" /> | ||
</TresGroup> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
<script setup lang="ts"> | ||
import { Group, Light, Vector2 } from 'three'; | ||
import type { State } from '../index.vue'; | ||
import { lerp, pingpong } from 'three/src/math/MathUtils.js'; | ||
const state = inject('state') as State | ||
const lightsGroup = shallowRef(new Group()) | ||
watch(() => state.i, () => { | ||
const targetInfo = state.shaderToyTargets[state.i] ?? {} | ||
for (const light of lightsGroup.value.children) { | ||
(light as Light).removeFromParent() | ||
} | ||
if (targetInfo.lights && targetInfo.lights.length > 0) { | ||
for (const light of targetInfo.lights) { | ||
lightsGroup.value.add(light) | ||
} | ||
} | ||
}) | ||
const center = new Vector2(0.5, 0.5) | ||
useLoop().onBeforeRender( | ||
() => { | ||
const targetInfo = state.shaderToyTargets[state.i] ?? {} | ||
const elapsed = state.clock.getElapsedTime() | ||
if (targetInfo.lights) { | ||
for (const light of targetInfo.lights) { | ||
const d = light.userData | ||
if (d.x) { | ||
light.position.x = d.x.init + Math.cos(d.x.speed * elapsed + d.x.phase) * d.x.dist | ||
} | ||
if (d.y) { | ||
light.position.y = d.y.init + Math.cos(d.y.speed * elapsed + d.y.phase) * d.y.dist | ||
} | ||
if (d.z) { | ||
light.position.z = d.z.init + Math.cos(d.z.speed * elapsed + d.z.phase) * d.z.dist | ||
} | ||
if (d.intensity) { | ||
light.intensity = lerp(d.intensity.a, d.intensity.b, pingpong(elapsed * d.intensity.speed)) | ||
} | ||
targetInfo.lightFn(light as Light, center, elapsed) | ||
} | ||
} | ||
} | ||
) | ||
</script> | ||
|
||
<template> | ||
<TresGroup ref="lightsGroup"></TresGroup> | ||
</template> |
73 changes: 73 additions & 0 deletions
73
components/content/shadertoy-museum/components/ShaderToy.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
<script setup lang="ts"> | ||
import { DoubleSide, Material, MeshNormalMaterial, ShaderMaterial, Vector2 } from 'three'; | ||
import type { State } from '../index.vue'; | ||
const state = inject('state') as State | ||
const fragmentShader = ` | ||
precision mediump float; | ||
varying vec4 vFragColor; | ||
void main() { | ||
gl_FragColor = vec4(vec3(vFragColor), 1.0); | ||
} | ||
` | ||
const vertexShader = shallowRef(` | ||
uniform vec2 iResolution; | ||
uniform float iTime; | ||
varying vec4 vFragColor; | ||
void main() { | ||
gl_Position = vec4(1., 1., 0., 1.); | ||
vFragColor = vec4(1., 1., 0., 1.); | ||
} | ||
`) | ||
function getVertexShader(s: string) { | ||
return ` | ||
uniform vec2 iResolution; | ||
uniform float iTime; | ||
varying vec4 vFragColor; | ||
${s} | ||
void main() { | ||
mainImage(vFragColor, (position.xy + vec2(0.5, 0.5)) * iResolution); | ||
vec3 offset = vec3(normal) * clamp(vFragColor.a, 0., 1.); | ||
vec4 modelPosition = modelMatrix * vec4(position + offset, 1.0); | ||
vec4 viewPosition = viewMatrix * modelPosition; | ||
gl_Position = projectionMatrix * viewPosition; | ||
} | ||
` | ||
} | ||
const material = shallowRef(new MeshNormalMaterial() as Material) | ||
const uniforms = { | ||
iResolution: { value: new Vector2(256, 256) }, | ||
iTime: { value: 0 }, | ||
} | ||
useLoop().onBeforeRender( | ||
() => { | ||
uniforms.iTime.value = state.clock.getElapsedTime() | ||
}) | ||
watch(() => state.i, () => { | ||
const targetInfo = state.shaderToyTargets[state.i] ?? {} | ||
vertexShader.value = getVertexShader(targetInfo.shader) | ||
if (material.value) material.value.dispose() | ||
material.value = new ShaderMaterial({ vertexShader: getVertexShader(targetInfo.shader), fragmentShader, uniforms, side: DoubleSide }) | ||
}) | ||
</script> | ||
|
||
<template> | ||
<TresMesh :position="state.target.position" :scale="state.target.scale" :rotation="state.target.rotation" | ||
:material="material"> | ||
<TresPlaneGeometry :copy="state.target.geometry" /> | ||
</TresMesh> | ||
</template> |
Oops, something went wrong.