Skip to content

Commit

Permalink
Merge pull request #203 from Tresjs/shadertoy
Browse files Browse the repository at this point in the history
feat(shadertoy-museum): add lab
  • Loading branch information
andretchen0 authored Nov 13, 2024
2 parents 23096b8 + 075edfd commit e4d1328
Show file tree
Hide file tree
Showing 23 changed files with 6,193 additions and 0 deletions.
20 changes: 20 additions & 0 deletions components/content/shadertoy-museum/TheExperience.vue
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>
24 changes: 24 additions & 0 deletions components/content/shadertoy-museum/components/Camera.vue
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 components/content/shadertoy-museum/components/Effects.vue
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>
38 changes: 38 additions & 0 deletions components/content/shadertoy-museum/components/Floor.vue
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 components/content/shadertoy-museum/components/Gallery.vue
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>
54 changes: 54 additions & 0 deletions components/content/shadertoy-museum/components/Lights.vue
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 components/content/shadertoy-museum/components/ShaderToy.vue
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>
Loading

0 comments on commit e4d1328

Please sign in to comment.