|
@@ -0,0 +1,334 @@
|
|
|
+---
|
|
|
+layout: /@/layouts/ExperimentLayout.astro
|
|
|
+thumbnail: /portal-journey.png
|
|
|
+title: Portal Journey
|
|
|
+author: Alvarosabu
|
|
|
+description: A basic example of how to create a scene with TresJS
|
|
|
+tags: ['gltf', 'shaders', 'useTexture', 'useGLTF', 'useTweakPane', 'baked']
|
|
|
+---
|
|
|
+
|
|
|
+import TheExperience from '/@/components/portal-journey/TheExperience.vue'
|
|
|
+import TheInfo from '/@/components/TheInfo.astro'
|
|
|
+
|
|
|
+<TheExperience client:only />
|
|
|
+
|
|
|
+<TheInfo >
|
|
|
+# { frontmatter.title }
|
|
|
+
|
|
|
+Famous portal scene from the amazing [Three.js Journey](https://threejs-journey.com/) course by [Bruno Simon](https://bruno-simon.com/)
|
|
|
+
|
|
|
+```vue
|
|
|
+<script setup lang="ts">
|
|
|
+import { OrbitControls } from '@tresjs/cientos'
|
|
|
+import ThePortal from './ThePortal.vue'
|
|
|
+import TheFireFlies from './TheFireFlies.vue'
|
|
|
+</script>
|
|
|
+
|
|
|
+<template>
|
|
|
+ <Suspense>
|
|
|
+ <TresCanvas clear-color="#201919" shadows alpha window-size power-preference="high-performance">
|
|
|
+ <OrbitControls />
|
|
|
+ <TresPerspectiveCamera :position="[5, 5, 5]" :fov="45" :aspect="1" :near="0.1" :far="1000" />
|
|
|
+ <TresScene>
|
|
|
+ <TresFog :args="['#201919', 0.1, 75]" />
|
|
|
+ <ThePortal />
|
|
|
+ <TheFireFlies />
|
|
|
+ <TresAmbientLight :position="[10, 10, 10]" :intensity="1.5" color="#00ff00" />
|
|
|
+ </TresScene>
|
|
|
+ </TresCanvas>
|
|
|
+ </Suspense>
|
|
|
+</template>
|
|
|
+```
|
|
|
+
|
|
|
+## ThePortal.vue
|
|
|
+
|
|
|
+```vue
|
|
|
+<script setup lang="ts">
|
|
|
+import { sRGBEncoding, DoubleSide, MeshBasicMaterial, ShaderMaterial, Color, Mesh } from 'three'
|
|
|
+import { useRenderLoop, useTexture } from '@tresjs/core'
|
|
|
+import { useGLTF, useTweakPane } from '@tresjs/cientos'
|
|
|
+
|
|
|
+import PortalVertex from './shaders/portal/vertex.glsl'
|
|
|
+import PortalFragment from './shaders/portal/fragment.glsl'
|
|
|
+
|
|
|
+const experiment = {
|
|
|
+ portalColorStart: '#7030eb',
|
|
|
+ portalColorEnd: '#ddc0ff',
|
|
|
+}
|
|
|
+
|
|
|
+const { pane } = useTweakPane()
|
|
|
+
|
|
|
+const portalCtrls = pane.addFolder({ title: 'Portal' })
|
|
|
+portalCtrls
|
|
|
+ .addInput(experiment, 'portalColorStart', {
|
|
|
+ label: 'color start',
|
|
|
+ min: 0,
|
|
|
+ max: 1,
|
|
|
+ step: 0.01,
|
|
|
+ })
|
|
|
+ .on('change', ({ value }) => {
|
|
|
+ portalLightMaterial.uniforms.uColorStart.value.set(value)
|
|
|
+ })
|
|
|
+portalCtrls
|
|
|
+ .addInput(experiment, 'portalColorEnd', {
|
|
|
+ label: 'color end',
|
|
|
+ min: 0,
|
|
|
+ max: 1,
|
|
|
+ step: 0.01,
|
|
|
+ })
|
|
|
+ .on('change', ({ value }) => {
|
|
|
+ portalLightMaterial.uniforms.uColorEnd.value.set(value)
|
|
|
+ })
|
|
|
+
|
|
|
+const { scene: portal } = await useGLTF(
|
|
|
+ 'https://raw.githubusercontent.com/Tresjs/assets/main/models/gltf/portal/portal.glb',
|
|
|
+ {
|
|
|
+ draco: true,
|
|
|
+ },
|
|
|
+)
|
|
|
+
|
|
|
+const bakedTexture = await useTexture([
|
|
|
+ 'https://raw.githubusercontent.com/Tresjs/assets/main/models/gltf/portal/baked.jpg',
|
|
|
+])
|
|
|
+
|
|
|
+bakedTexture.flipY = false
|
|
|
+bakedTexture.encoding = sRGBEncoding
|
|
|
+
|
|
|
+// Baked material
|
|
|
+const bakedMaterial = new MeshBasicMaterial({
|
|
|
+ map: bakedTexture,
|
|
|
+ side: DoubleSide,
|
|
|
+})
|
|
|
+
|
|
|
+const portalLightMaterial = new ShaderMaterial({
|
|
|
+ uniforms: {
|
|
|
+ uTime: { value: 0 },
|
|
|
+ uColorStart: { value: new Color(experiment.portalColorStart) },
|
|
|
+ uColorEnd: { value: new Color(experiment.portalColorEnd) },
|
|
|
+ },
|
|
|
+ vertexShader: PortalVertex,
|
|
|
+ fragmentShader: PortalFragment,
|
|
|
+ side: DoubleSide,
|
|
|
+})
|
|
|
+
|
|
|
+const portalObj = portal
|
|
|
+const bakedMesh = portalObj.children.find(child => child.name === 'baked')
|
|
|
+;(bakedMesh as Mesh).material = bakedMaterial
|
|
|
+const portalCircle = portalObj.children.find(child => child.name === 'portalCircle')
|
|
|
+;(portalCircle as Mesh).material = portalLightMaterial
|
|
|
+
|
|
|
+const { onLoop } = useRenderLoop()
|
|
|
+
|
|
|
+onLoop(({ _delta, elapsed }) => {
|
|
|
+ portalLightMaterial.uniforms.uTime.value = elapsed
|
|
|
+})
|
|
|
+</script>
|
|
|
+<template>
|
|
|
+ <TresMesh v-bind="portal" />
|
|
|
+</template>
|
|
|
+```
|
|
|
+
|
|
|
+### Shaders
|
|
|
+
|
|
|
+```glsl
|
|
|
+// portal/vertex.glsl
|
|
|
+varying vec2 vUv;
|
|
|
+
|
|
|
+void main()
|
|
|
+{
|
|
|
+ vec4 modelPosition = modelMatrix * vec4(position, 1.0);
|
|
|
+ vec4 viewPosition = viewMatrix * modelPosition;
|
|
|
+ vec4 projectionPosition = projectionMatrix * viewPosition;
|
|
|
+
|
|
|
+ gl_Position = projectionPosition;
|
|
|
+ vUv = uv;
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+```glsl
|
|
|
+// portal/fragment.glsl
|
|
|
+varying vec2 vUv;
|
|
|
+uniform float uTime;
|
|
|
+uniform vec3 uColorStart;
|
|
|
+uniform vec3 uColorEnd;
|
|
|
+
|
|
|
+// Classic Perlin 3D Noise
|
|
|
+// by Stefan Gustavson
|
|
|
+//
|
|
|
+vec4 permute(vec4 x){return mod(((x*34.0)+1.0)*x, 289.0);}
|
|
|
+vec4 taylorInvSqrt(vec4 r){return 1.79284291400159 - 0.85373472095314 * r;}
|
|
|
+vec3 fade(vec3 t) {return t*t*t*(t*(t*6.0-15.0)+10.0);}
|
|
|
+
|
|
|
+float cnoise(vec3 P){
|
|
|
+vec3 Pi0 = floor(P); // Integer part for indexing
|
|
|
+vec3 Pi1 = Pi0 + vec3(1.0); // Integer part + 1
|
|
|
+Pi0 = mod(Pi0, 289.0);
|
|
|
+Pi1 = mod(Pi1, 289.0);
|
|
|
+vec3 Pf0 = fract(P); // Fractional part for interpolation
|
|
|
+vec3 Pf1 = Pf0 - vec3(1.0); // Fractional part - 1.0
|
|
|
+vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x);
|
|
|
+vec4 iy = vec4(Pi0.yy, Pi1.yy);
|
|
|
+vec4 iz0 = Pi0.zzzz;
|
|
|
+vec4 iz1 = Pi1.zzzz;
|
|
|
+
|
|
|
+vec4 ixy = permute(permute(ix) + iy);
|
|
|
+vec4 ixy0 = permute(ixy + iz0);
|
|
|
+vec4 ixy1 = permute(ixy + iz1);
|
|
|
+
|
|
|
+vec4 gx0 = ixy0 / 7.0;
|
|
|
+vec4 gy0 = fract(floor(gx0) / 7.0) - 0.5;
|
|
|
+gx0 = fract(gx0);
|
|
|
+vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0);
|
|
|
+vec4 sz0 = step(gz0, vec4(0.0));
|
|
|
+gx0 -= sz0 * (step(0.0, gx0) - 0.5);
|
|
|
+gy0 -= sz0 * (step(0.0, gy0) - 0.5);
|
|
|
+
|
|
|
+vec4 gx1 = ixy1 / 7.0;
|
|
|
+vec4 gy1 = fract(floor(gx1) / 7.0) - 0.5;
|
|
|
+gx1 = fract(gx1);
|
|
|
+vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1);
|
|
|
+vec4 sz1 = step(gz1, vec4(0.0));
|
|
|
+gx1 -= sz1 * (step(0.0, gx1) - 0.5);
|
|
|
+gy1 -= sz1 * (step(0.0, gy1) - 0.5);
|
|
|
+
|
|
|
+vec3 g000 = vec3(gx0.x,gy0.x,gz0.x);
|
|
|
+vec3 g100 = vec3(gx0.y,gy0.y,gz0.y);
|
|
|
+vec3 g010 = vec3(gx0.z,gy0.z,gz0.z);
|
|
|
+vec3 g110 = vec3(gx0.w,gy0.w,gz0.w);
|
|
|
+vec3 g001 = vec3(gx1.x,gy1.x,gz1.x);
|
|
|
+vec3 g101 = vec3(gx1.y,gy1.y,gz1.y);
|
|
|
+vec3 g011 = vec3(gx1.z,gy1.z,gz1.z);
|
|
|
+vec3 g111 = vec3(gx1.w,gy1.w,gz1.w);
|
|
|
+
|
|
|
+vec4 norm0 = taylorInvSqrt(vec4(dot(g000, g000), dot(g010, g010), dot(g100, g100), dot(g110, g110)));
|
|
|
+g000 *= norm0.x;
|
|
|
+g010 *= norm0.y;
|
|
|
+g100 *= norm0.z;
|
|
|
+g110 *= norm0.w;
|
|
|
+vec4 norm1 = taylorInvSqrt(vec4(dot(g001, g001), dot(g011, g011), dot(g101, g101), dot(g111, g111)));
|
|
|
+g001 *= norm1.x;
|
|
|
+g011 *= norm1.y;
|
|
|
+g101 *= norm1.z;
|
|
|
+g111 *= norm1.w;
|
|
|
+
|
|
|
+float n000 = dot(g000, Pf0);
|
|
|
+float n100 = dot(g100, vec3(Pf1.x, Pf0.yz));
|
|
|
+float n010 = dot(g010, vec3(Pf0.x, Pf1.y, Pf0.z));
|
|
|
+float n110 = dot(g110, vec3(Pf1.xy, Pf0.z));
|
|
|
+float n001 = dot(g001, vec3(Pf0.xy, Pf1.z));
|
|
|
+float n101 = dot(g101, vec3(Pf1.x, Pf0.y, Pf1.z));
|
|
|
+float n011 = dot(g011, vec3(Pf0.x, Pf1.yz));
|
|
|
+float n111 = dot(g111, Pf1);
|
|
|
+
|
|
|
+vec3 fade_xyz = fade(Pf0);
|
|
|
+vec4 n_z = mix(vec4(n000, n100, n010, n110), vec4(n001, n101, n011, n111), fade_xyz.z);
|
|
|
+vec2 n_yz = mix(n_z.xy, n_z.zw, fade_xyz.y);
|
|
|
+float n_xyz = mix(n_yz.x, n_yz.y, fade_xyz.x);
|
|
|
+return 2.2 * n_xyz;
|
|
|
+}
|
|
|
+
|
|
|
+void main()
|
|
|
+{
|
|
|
+ float speed = 0.1;
|
|
|
+ // Displace the UV coordinates by a noise value
|
|
|
+ vec2 displacedUv = vUv + cnoise(vec3(vUv* 5.0, uTime * speed));
|
|
|
+ // Get the color from the texture
|
|
|
+ float strength = cnoise(vec3(displacedUv * 5.0, uTime * speed * 2.0));
|
|
|
+
|
|
|
+ // Outer Glow
|
|
|
+
|
|
|
+ float outerGlow = distance(vUv, vec2(0.5)) * 5.0 - 1.4;
|
|
|
+ strength += outerGlow;
|
|
|
+
|
|
|
+ strength += step(- 0.2, strength) * 0.8;
|
|
|
+
|
|
|
+ strength = clamp(strength, 0.0, 1.0);
|
|
|
+
|
|
|
+ vec3 color = mix(uColorStart, uColorEnd, strength);
|
|
|
+
|
|
|
+ gl_FragColor = vec4(color, 1.0);
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+## TheFireFlies.vue
|
|
|
+
|
|
|
+```vue
|
|
|
+<script setup lang="ts">
|
|
|
+import { useRenderLoop } from '@tresjs/core'
|
|
|
+import { AdditiveBlending } from 'three'
|
|
|
+import FirefliesVertex from './shaders/fireflies/vertex.glsl'
|
|
|
+import FirefliesFragment from './shaders/fireflies/fragment.glsl'
|
|
|
+
|
|
|
+const shader = {
|
|
|
+ transparent: true,
|
|
|
+ blending: AdditiveBlending,
|
|
|
+ depthWrite: false,
|
|
|
+
|
|
|
+ vertexShader: FirefliesVertex,
|
|
|
+ fragmentShader: FirefliesFragment,
|
|
|
+ uniforms: {
|
|
|
+ uSize: { value: 100 },
|
|
|
+ uPixelRatio: { value: Math.min(window.devicePixelRatio, 2) },
|
|
|
+ uTime: { value: 0 },
|
|
|
+ },
|
|
|
+}
|
|
|
+
|
|
|
+const firefliesCount = 60
|
|
|
+const positionArray = new Float32Array(firefliesCount * 3)
|
|
|
+const scaleArray = new Float32Array(firefliesCount)
|
|
|
+
|
|
|
+for (let i = 0; i < firefliesCount; i++) {
|
|
|
+ positionArray[i * 3 + 0] = (Math.random() - 0.5) * 4
|
|
|
+ positionArray[i * 3 + 1] = Math.random() * 4
|
|
|
+ positionArray[i * 3 + 2] = (Math.random() - 0.5) * 4
|
|
|
+ scaleArray[i] = Math.random()
|
|
|
+}
|
|
|
+
|
|
|
+const { onLoop } = useRenderLoop()
|
|
|
+
|
|
|
+onLoop(({ elapsed }) => {
|
|
|
+ shader.uniforms.uTime.value = elapsed
|
|
|
+})
|
|
|
+</script>
|
|
|
+<template>
|
|
|
+ <TresPoints>
|
|
|
+ <TresBufferGeometry :position="[positionArray, 3]" :a-scale="[scaleArray, 1]" />
|
|
|
+ <TresShaderMaterial v-bind="shader" />
|
|
|
+ </TresPoints>
|
|
|
+</template>
|
|
|
+```
|
|
|
+
|
|
|
+### Shaders
|
|
|
+
|
|
|
+```glsl
|
|
|
+// fireflies/vertex.glsl
|
|
|
+uniform float uPixelRatio;
|
|
|
+uniform float uSize;
|
|
|
+uniform float uTime;
|
|
|
+attribute float aScale;
|
|
|
+
|
|
|
+void main()
|
|
|
+{
|
|
|
+ vec4 modelPosition = modelMatrix * vec4(position, 1.0);
|
|
|
+ modelPosition.y += sin(uTime + modelPosition.x * 100.0) * aScale * 0.2;
|
|
|
+ vec4 viewPosition = viewMatrix * modelPosition;
|
|
|
+ vec4 projectionPosition = projectionMatrix * viewPosition;
|
|
|
+
|
|
|
+ gl_Position = projectionPosition;
|
|
|
+ gl_PointSize = aScale * uSize * uPixelRatio;
|
|
|
+ gl_PointSize *= (1.0 / - viewPosition.z);
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+```glsl
|
|
|
+// fireflies/fragment.glsl
|
|
|
+void main()
|
|
|
+{
|
|
|
+ float distanceToCenter = distance(gl_PointCoord, vec2(0.5));
|
|
|
+ float strength = 0.05 / distanceToCenter - 0.1;
|
|
|
+
|
|
|
+ gl_FragColor = vec4(1.0, 1.0, 1.0, strength);
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+</TheInfo>
|