Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(app): Add Environment, allowing it to mount sub-components and Lightformer via FBO #382

Merged
merged 8 commits into from
Apr 18, 2024
31 changes: 31 additions & 0 deletions docs/guide/staging/environment.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,33 @@ You can use one of the available presets by passing the `preset` prop:
<EnvironmentPresetsDemo/>
</DocsDemo>

## Lightformer
hawk86104 marked this conversation as resolved.
Show resolved Hide resolved

You can incorporate `Lightformer` into the environment just like a slot.

```html
<script setup>
import { Enviroment, LightFormer } from '@tres/cientos'
</script>

<template>
<Environment>
<Lightformer :intensity="0.75" :position="[0, 5, -9]" />
<Lightformer from="ring" :rotation-y="-Math.PI / 2" :scale="[10, 10, 1]"/>
</Environment>
</template>
```

### Props for Lightformer

Lightformer inherits from mesh, and its extension parameters include:
| Prop | Description | Default |
| :----------- | :------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
| `from` | 'circle' , 'ring' , 'rect' , any:other Mesh.The type of Lightformer | `rect` |
| `intensity` | number : the intensity of the light. | 1 |
| `color` | the color of the light. | `0xffffff` |
| `args` | the arguments of the Geometry | When using other geometries, set the corresponding arguments. |

## Props

| Prop | Description | Default |
Expand All @@ -79,3 +106,7 @@ You can use one of the available presets by passing the `preset` prop:
| `background` | If `true`, the environment map will be used as the scene background. | `false` |
| `blur` | Blur factor between 0 and 1. (only works with three 0.146 and up) | 0 |
| `preset` | Preset environment map. | `undefined` |
| `resolution` | The resolution of the WebGLCubeRenderTarget. | 256 |
| `near` | The near of the CubeCamera. | 1 |
| `far` | The far of the CubeCamera. | 1000 |
| `frames` | The frames of the cubeCamera.update. | Infinity |
15 changes: 11 additions & 4 deletions playground/src/pages/staging/EnvironmentDemo.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { TresCanvas } from '@tresjs/core'
import { BasicShadowMap, SRGBColorSpace, NoToneMapping } from 'three'

import { OrbitControls, useProgress, Environment, TorusKnot } from '@tresjs/cientos'
import { OrbitControls, useProgress, Environment, Lightformer, TorusKnot } from '@tresjs/cientos'
import { TresLeches, useControls } from '@tresjs/leches'
import '@tresjs/leches/styles'

Expand All @@ -17,7 +17,7 @@ const gl = {
toneMapping: NoToneMapping,
}

const { background, blur, preset } = useControls({
const { background, blur, preset, lightformers } = useControls({
background: true,
blur: {
value: 0,
Expand All @@ -42,6 +42,7 @@ const { background, blur, preset } = useControls({
],
value: 'sunset',
},
lightformers: false
})

const environmentRef = ref(null)
Expand Down Expand Up @@ -88,8 +89,14 @@ const { progress, hasFinishLoading } = await useProgress()
<Environment
:background="background.value"
:blur="blur.value"
:preset="preset.value"
/>
:preset="preset.value">
<TresGroup v-if="lightformers.value">
<Lightformer :intensity="0.75" :rotation-x="Math.PI / 2" :position="[0, 5, -9]" :scale="[10, 10, 1]" />
<Lightformer :intensity="4" :rotation-y="Math.PI / 2" :position="[-5, 1, -1]" :scale="[20, 0.1, 1]" />
<Lightformer :rotation-y="Math.PI / 2" :position="[-5, -1, -1]" :scale="[20, 0.5, 1]" />
<Lightformer :rotation-y="-Math.PI / 2" :position="[10, 1, 0]" :scale="[20, 11, 1]" />
</TresGroup>
</Environment>
</Suspense>
<TorusKnot>
<TresMeshStandardMaterial
Expand Down
2 changes: 2 additions & 0 deletions src/core/staging/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Environment from './useEnvironment/component.vue'
import Lightformer from './useEnvironment/lightformer/index.vue'
import Backdrop from './Backdrop.vue'
import ContactShadows from './ContactShadows.vue'
import Precipitation from './Precipitation.vue'
Expand All @@ -18,4 +19,5 @@ export {
Sparkles,
Stars,
Ocean,
Lightformer,
}
78 changes: 74 additions & 4 deletions src/core/staging/useEnvironment/component.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
<script setup lang="ts">
import type { Ref } from 'vue'
import { ref, watch } from 'vue'
import { ref, useSlots, onUnmounted, watch, toRaw } from 'vue'
import { WebGLCubeRenderTarget, CubeCamera, HalfFloatType } from 'three'
import type { CubeTexture, Texture } from 'three'
import { useTresContext, useRenderLoop } from '@tresjs/core'
import type { EnvironmentOptions } from './const'

import EnvSence from './envSence'
import { useEnvironment } from '.'

const props = withDefaults(defineProps<EnvironmentOptions>(), {
Expand All @@ -12,10 +14,78 @@ const props = withDefaults(defineProps<EnvironmentOptions>(), {
files: [],
path: '',
preset: undefined,
resolution: 256,
near: 1,
far: 1000,
frames: Infinity,
})

const texture: Ref<Texture | CubeTexture | null> = ref(null)
defineExpose(texture)
defineExpose({ texture })

const { extend, renderer, scene } = useTresContext()
let slots = null as any
let fbo = ref(null as null | WebGLCubeRenderTarget)
let cubeCamera = null as null | CubeCamera

const envSence = ref<EnvSence | null>(null)
onUnmounted(() => {
envSence.value?.destructor()
fbo.value?.dispose()
})
const { onBeforeLoop } = useRenderLoop()
let count = 1
onBeforeLoop(() => {
if (cubeCamera && envSence.value && fbo.value) {
if (props.frames === Infinity || count < props.frames) {
cubeCamera.update(renderer.value, toRaw(envSence.value.virtualScene))
count++
}
}
})
const useEnvironmentTexture = (await useEnvironment(props, fbo as any)).texture
const setTextureEnvAndBG = (fbo: WebGLCubeRenderTarget) => {
if (fbo) {
scene.value.environment = fbo.texture
if (props.background) {
scene.value.background = fbo.texture
}
} else {
scene.value.environment = useEnvironmentTexture.value
if (props.background) {
scene.value.background = useEnvironmentTexture.value
}
}
}
watch(useEnvironmentTexture, (value) => {
if (fbo.value) {
setTextureEnvAndBG(fbo.value)
}
}, { immediate: true, deep: true })

texture.value = await useEnvironment(props).texture
watch(useSlots().default, (value) => {
if (value) {
slots = value
if (Array.isArray(slots)&&slots.length>0) {
if (typeof slots[0]?.type !== 'symbol') {
extend({ EnvSence })
fbo.value = new WebGLCubeRenderTarget(props.resolution)
fbo.value.texture.type = HalfFloatType
cubeCamera = new CubeCamera(props.near, props.far, fbo.value)
setTextureEnvAndBG(fbo.value)
return
}
}
}
fbo.value?.dispose()
fbo.value = null
setTextureEnvAndBG()
}, { immediate: true, deep: true })
texture.value = useEnvironmentTexture
</script>

<template>
<TresEnvSence v-if="fbo" ref="envSence">
<slot />
</TresEnvSence>
</template>
29 changes: 28 additions & 1 deletion src/core/staging/useEnvironment/const.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

export interface EnvironmentOptions {
/**
* If true, the environment will be set as the scene's background.
Expand Down Expand Up @@ -34,6 +33,34 @@ export interface EnvironmentOptions {
* @type {EnvironmentPresetsType}
*/
preset?: EnvironmentPresetsType
/**
* The resolution of the WebGLCubeRenderTarget.
*
* @type {number}
* @default 256
*/
resolution?: number
/**
* The near of the CubeCamera.
*
* @type {number}
* @default 1
*/
near?: number
/**
* The far of the CubeCamera.
*
* @type {number}
* @default 1000
*/
far?: number
/**
* The frames of the cubeCamera.update.
*
* @type {number}
* @default Infinity
*/
frames?: number
}

export const environmentPresets = {
Expand Down
28 changes: 28 additions & 0 deletions src/core/staging/useEnvironment/envSence.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Scene, Object3D, Mesh } from 'three'

class EnvSence extends Object3D {
virtualScene = null as unknown as Scene
constructor() {
super()
this.virtualScene = new Scene()
}

add(...object: Object3D[]): this {
this.virtualScene.add(...object)
return this
}

destructor() {
this.virtualScene.traverse((object) => {
if (object instanceof Mesh) {
object.geometry.dispose()
object.material.dispose()
if (object.material.map) object.material.map.dispose()
this.virtualScene.remove(object)
}
})
this.virtualScene = null as unknown as Scene
}
}

export default EnvSence
6 changes: 4 additions & 2 deletions src/core/staging/useEnvironment/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useLoader, useTresContext } from '@tresjs/core'
import type {
CubeTexture,
Texture,
WebGLCubeRenderTarget
} from 'three'
import {
CubeReflectionMapping,
Expand Down Expand Up @@ -31,7 +32,7 @@ import { environmentPresets } from './const'

// eslint-disable-next-line max-len
const PRESET_ROOT = 'https://raw.githubusercontent.com/Tresjs/assets/main/textures/hdr/'
export async function useEnvironment(options: Partial<EnvironmentOptions>): Promise<Texture | CubeTexture> {
export async function useEnvironment(options: Partial<EnvironmentOptions>, fbo: Ref<WebGLCubeRenderTarget | undefined>): Promise<Texture | CubeTexture> {
const { scene } = useTresContext()

const {
Expand Down Expand Up @@ -82,7 +83,8 @@ export async function useEnvironment(options: Partial<EnvironmentOptions>): Prom

watch(() => [background.value, texture.value], ([_background, _texture]) => {
if (scene.value) {
scene.value.background = _background ? _texture : undefined as unknown as Texture
let bTexture = fbo?.value? fbo.value.texture : _texture
scene.value.background = _background ? bTexture : undefined as unknown as Texture
}
}, {
immediate: true,
Expand Down
60 changes: 60 additions & 0 deletions src/core/staging/useEnvironment/lightformer/index.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<script setup lang="ts">
/*
** This section of code is inspired by the Lightformer component from the drei library:
** https://github.com/pmndrs/drei/blob/master/src/core/Lightformer.tsx
** The Lightformer component in drei provides functionality for creating light probes in a scene.
*/
import { defineProps, ref, watchEffect } from 'vue'
import type { MeshBasicMaterial, Texture } from 'three'
import { Color, DoubleSide } from 'three'

JaimeTorrealba marked this conversation as resolved.
Show resolved Hide resolved
const props = withDefaults(defineProps<{
args?: any[]
from?: 'circle' | 'ring' | 'rect' | any
toneMapped?: boolean
map?: Texture
intensity?: number
color?: any
}>(), {
args: null as any,
from: 'rect',
toneMapped: false,
map: null as any,
intensity: 1,
color: new Color(0xffffff),
})

const material = ref<MeshBasicMaterial>()
watchEffect(() => {
if (material.value) {
material.value.color.multiplyScalar(props.intensity)
material.value.needsUpdate = true
}
})
</script>

<template>
<TresMesh>
<TresRingGeometry
v-if="from === 'circle'"
:args="[0, 1, 64]"
/>
<TresRingGeometry
v-else-if="from === 'ring'"
:args="[0.5, 1, 64]"
/>
<TresPlaneGeometry v-else-if="from === 'rect'" />
<props.from
v-else
:args="props"
/>

<TresMeshBasicMaterial
ref="material"
:toneMapped="toneMapped"
:map="map"
:side="DoubleSide"
:color="color"
/>
</TresMesh>
</template>