Tino Koch 1 月之前
父節點
當前提交
c9da4bbbd3
共有 3 個文件被更改,包括 55 次插入144 次删除
  1. 1 0
      src/components/TresCanvas.vue
  2. 53 144
      src/core/setupRenderer.ts
  3. 1 0
      src/utils/index.ts

+ 1 - 0
src/components/TresCanvas.vue

@@ -217,6 +217,7 @@ onUnmounted(unmountCanvas)
 <script lang="ts">
 <script lang="ts">
 export type WebGLRendererProps = TransformToMaybeRefOrGetter<Omit<WebGLRendererParameters, 'canvas'>>
 export type WebGLRendererProps = TransformToMaybeRefOrGetter<Omit<WebGLRendererParameters, 'canvas'>>
 
 
+// TODO move following type. Props shouldn't be defined by passing a ref.
 export interface TresCanvasProps extends /* @vue-ignore */ WebGLRendererProps {
 export interface TresCanvasProps extends /* @vue-ignore */ WebGLRendererProps {
   /**
   /**
    * WebGL Context options (Readonly because they are passed to the renderer constructor)
    * WebGL Context options (Readonly because they are passed to the renderer constructor)

+ 53 - 144
src/core/setupRenderer.ts

@@ -1,33 +1,26 @@
-import type { ColorRepresentation, Object3D, ToneMapping, WebGLRenderer } from 'three'
-import type { MaybeRefOrGetter, UnwrapRef, WatchHandle, WatchOptions } from 'vue'
+import type { Object3D, ToneMapping, WebGLRenderer } from 'three'
 import { computed, toValue, watch } from 'vue'
 import { computed, toValue, watch } from 'vue'
 import { useDevicePixelRatio } from '@vueuse/core'
 import { useDevicePixelRatio } from '@vueuse/core'
 import { setPixelRatio } from '../utils'
 import { setPixelRatio } from '../utils'
 
 
-import { ACESFilmicToneMapping, Mesh } from 'three'
+import { ACESFilmicToneMapping, Material, Mesh, PCFSoftShadowMap, SRGBColorSpace } from 'three'
 import type { TresCanvasProps } from '../components/TresCanvas.vue'
 import type { TresCanvasProps } from '../components/TresCanvas.vue'
 import type { TresRendererSetupContext } from 'src/composables/useRenderer'
 import type { TresRendererSetupContext } from 'src/composables/useRenderer'
 
 
-interface PropertyHandler<T = unknown> {
-  set: (renderer: WebGLRenderer, value: T) => void
-  immediate?: boolean
-}
-
-interface DirectProperty {
-  key: keyof WebGLRenderer | 'shadowMap.enabled' | 'shadowMap.type' | 'physicallyCorrectLights'
-  immediate?: boolean
-}
-
 type NamesOfPropsThatCannotChange = keyof Pick<
 type NamesOfPropsThatCannotChange = keyof Pick<
   TresCanvasProps,
   TresCanvasProps,
-  'antialias' |
+  'depth' |
+  'alpha' |
   'camera' | // this is handled in useCameras // TODO camera should be handled
   'camera' | // this is handled in useCameras // TODO camera should be handled
   'stencil' |
   'stencil' |
-  'logarithmicDepthBuffer' |
-  'preserveDrawingBuffer' |
+  'renderer' |
+  'antialias' |
+  'precision' |
   'powerPreference' |
   'powerPreference' |
-  'alpha' |
-  'renderer'
+  'premultipliedAlpha' |
+  'preserveDrawingBuffer' |
+  'logarithmicDepthBuffer' |
+  'failIfMajorPerformanceCaveat'
 >
 >
 
 
 type NamesOfPropsThatCanChange = keyof Omit<
 type NamesOfPropsThatCanChange = keyof Omit<
@@ -37,58 +30,6 @@ type NamesOfPropsThatCanChange = keyof Omit<
 
 
 // TODO test by using basic playground
 // TODO test by using basic playground
 
 
-// Properties that can be set directly on the renderer
-const directProperties: Record<string, DirectProperty> = {
-  toneMapping: {
-    key: 'toneMapping',
-    immediate: true,
-  },
-  toneMappingExposure: {
-    key: 'toneMappingExposure',
-    immediate: true,
-  },
-  outputColorSpace: {
-    key: 'outputColorSpace',
-    immediate: true,
-  },
-  physicallyCorrectLights: {
-    key: 'physicallyCorrectLights',
-    immediate: true,
-  },
-  shadowMapType: {
-    key: 'shadowMap.type',
-    immediate: true,
-  },
-  shadows: {
-    key: 'shadowMap.enabled',
-    immediate: true,
-  },
-}
-
-// Properties that use setter methods
-const rendererPropertyHandlers: Record<string, PropertyHandler<ColorRepresentation | boolean | number>> = {
-  clearColor: {
-    set: (renderer, value) => {
-      // Check if the color string includes alpha (8 characters hex)
-      if (typeof value === 'string' && value.length === 9 && value.startsWith('#')) {
-        // Extract alpha from the last 2 characters
-        const alpha = Number.parseInt(value.slice(7, 9), 16) / 255
-        renderer.setClearAlpha(alpha)
-        // Set color without alpha
-        renderer.setClearColor(value.slice(0, 7))
-      }
-      else {
-        renderer.setClearColor(value as ColorRepresentation)
-      }
-    },
-    immediate: true,
-  },
-  clearAlpha: {
-    set: (renderer, value) => renderer.setClearAlpha(value as number),
-    immediate: true,
-  },
-}
-
 // Modified setup function to handle both types of properties
 // Modified setup function to handle both types of properties
 export function setupWebGLRenderer( // TODO object format? // TODO name like comosable
 export function setupWebGLRenderer( // TODO object format? // TODO name like comosable
   initialRenderer: WebGLRenderer,
   initialRenderer: WebGLRenderer,
@@ -104,13 +45,6 @@ export function setupWebGLRenderer( // TODO object format? // TODO name like com
     }
     }
   }
   }
 
 
-  // Watch DPR changes
-  watch(() => options.dpr, (value) => {
-    if (!value) { return }
-    invalidateIfOnDemandMode()
-    setPixelRatio(initialRenderer, pixelRatio.value, value as number)
-  })
-
   // Watch size changes
   // Watch size changes
   watch([ctx.sizes.width, ctx.sizes.height], () => {
   watch([ctx.sizes.width, ctx.sizes.height], () => {
     initialRenderer.setSize(ctx.sizes.width.value, ctx.sizes.height.value)
     initialRenderer.setSize(ctx.sizes.width.value, ctx.sizes.height.value)
@@ -127,7 +61,7 @@ export function setupWebGLRenderer( // TODO object format? // TODO name like com
   }: {
   }: {
     getFromProps: (props: TresCanvasProps) => T | undefined // TODO check if there is a better type than any
     getFromProps: (props: TresCanvasProps) => T | undefined // TODO check if there is a better type than any
     setOnRenderer: (renderer: WebGLRenderer, value: T) => void
     setOnRenderer: (renderer: WebGLRenderer, value: T) => void
-    immediate?: boolean
+    immediate?: boolean // TODO think about turning this around
     defaultValue: T
     defaultValue: T
   }) =>
   }) =>
     watch(
     watch(
@@ -167,6 +101,13 @@ export function setupWebGLRenderer( // TODO object format? // TODO name like com
     }
     }
   })
   })
 
 
+  const forceMaterialUpdate = () =>
+    ctx.scene.value.traverse((child: Object3D) => {
+      if (child instanceof Mesh && child.material instanceof Material) {
+        child.material.needsUpdate = true
+      }
+    })
+
   const x: Record<NamesOfPropsThatCanChange, ReturnType<typeof createWatcher>> = {
   const x: Record<NamesOfPropsThatCanChange, ReturnType<typeof createWatcher>> = {
     toneMapping: createWatcher<ToneMapping>({
     toneMapping: createWatcher<ToneMapping>({
       getFromProps: ({ toneMapping }) => toValue(toneMapping),
       getFromProps: ({ toneMapping }) => toValue(toneMapping),
@@ -174,12 +115,6 @@ export function setupWebGLRenderer( // TODO object format? // TODO name like com
       immediate: true,
       immediate: true,
       defaultValue: ACESFilmicToneMapping,
       defaultValue: ACESFilmicToneMapping,
     }),
     }),
-    shadows: createWatcher({
-      getFromProps: ({ shadows }) => toValue(shadows),
-      setOnRenderer: (renderer, value) => renderer.shadowMap.enabled = value,
-      immediate: true,
-      defaultValue: false,
-    }),
     clearColor: createWatcher({
     clearColor: createWatcher({
       getFromProps: () => clearColorAndAlpha.value.color, // TODO name getFromProps doesn't fit anymore
       getFromProps: () => clearColorAndAlpha.value.color, // TODO name getFromProps doesn't fit anymore
       setOnRenderer: (renderer, value) => renderer.setClearColor(value),
       setOnRenderer: (renderer, value) => renderer.setClearColor(value),
@@ -192,68 +127,42 @@ export function setupWebGLRenderer( // TODO object format? // TODO name like com
       immediate: true,
       immediate: true,
       defaultValue: 1,
       defaultValue: 1,
     }),
     }),
-  }
-
-  // Watch properties that need setter methods
-  Object.entries(rendererPropertyHandlers).forEach(([key, handler]) => {
-    watch(
-      () => options[key],
-      (value) => {
-        if (value === undefined) { return }
-        handler.set(initialRenderer, value)
-        invalidateIfOnDemandMode()
+    dpr: createWatcher({
+      getFromProps: ({ dpr }) => toValue(dpr),
+      setOnRenderer: (renderer, value) => setPixelRatio(renderer, pixelRatio.value, value),
+      defaultValue: undefined,
+    }),
+    outputColorSpace: createWatcher({
+      getFromProps: ({ outputColorSpace }) => toValue(outputColorSpace),
+      setOnRenderer: (renderer, value) => renderer.outputColorSpace = value,
+      immediate: true,
+      defaultValue: SRGBColorSpace,
+    }),
+    toneMappingExposure: createWatcher({
+      getFromProps: ({ toneMappingExposure }) => toValue(toneMappingExposure),
+      setOnRenderer: (renderer, value) => renderer.toneMappingExposure = value,
+      immediate: true,
+      defaultValue: 1,
+    }),
+    shadows: createWatcher({
+      getFromProps: ({ shadows }) => toValue(shadows),
+      setOnRenderer: (renderer, value) => {
+        renderer.shadowMap.enabled = value
+        forceMaterialUpdate()
       },
       },
-      { immediate: handler.immediate },
-    )
-  })
-
-  // Watch properties that can be set directly
-  Object.entries(directProperties).forEach(([key, prop]) => {
-    watch(
-      () => options[key],
-      (value) => {
-        if (value === undefined) { return }
-        // Handle nested properties (like shadowMap.type)
-        const parts = prop.key.split('.')
-
-        if (parts.length > 1) {
-          // Handle shadowMap properties specifically
-          if (parts[0] === 'shadowMap') {
-            const shadowMapKey = parts[1] as keyof typeof initialRenderer.shadowMap
-            initialRenderer.shadowMap[shadowMapKey] = value
-
-            // Update materials when shadow properties change
-            ctx.scene.value.traverse((child: Object3D) => {
-              if (child instanceof Mesh) {
-                const material = (child).material
-                if (material) {
-                  material.needsUpdate = true
-                }
-              }
-            })
-          }
-        }
-        else {
-          const key = prop.key as keyof WebGLRenderer
-          // Check instance property first, then prototype
-          const descriptor = Object.getOwnPropertyDescriptor(initialRenderer, key)
-            || Object.getOwnPropertyDescriptor(Object.getPrototypeOf(initialRenderer), key)
-
-          // Get safe properties from directProperties
-          const safeToSetProperties = Object.values(directProperties)
-            .map(prop => prop.key)
-            .filter(key => !key.includes('.')) // Filter out nested properties like shadowMap.type
-
-          if ((descriptor?.writable || safeToSetProperties.includes(key))) {
-            const rendererKey = key as keyof Omit<WebGLRenderer, 'coordinateSystem' | 'info'>
-            ;(initialRenderer as unknown as Record<string, unknown>)[rendererKey] = value
-          }
-        }
-        invalidateIfOnDemandMode()
+      immediate: true,
+      defaultValue: false,
+    }),
+    shadowMapType: createWatcher({
+      getFromProps: ({ shadowMapType }) => toValue(shadowMapType),
+      setOnRenderer: (renderer, value) => {
+        renderer.shadowMap.type = value
+        forceMaterialUpdate()
       },
       },
-      { immediate: prop.immediate },
-    )
-  })
+      immediate: true,
+      defaultValue: PCFSoftShadowMap,
+    }),
+  }
 
 
   return initialRenderer
   return initialRenderer
 }
 }

+ 1 - 0
src/utils/index.ts

@@ -448,6 +448,7 @@ export function noop(fn: string): any {
   fn
   fn
 }
 }
 
 
+// TODO move following function?
 export function setPixelRatio(renderer: { setPixelRatio?: (dpr: number) => void, getPixelRatio?: () => number }, systemDpr: number, userDpr?: number | [number, number]) {
 export function setPixelRatio(renderer: { setPixelRatio?: (dpr: number) => void, getPixelRatio?: () => number }, systemDpr: number, userDpr?: number | [number, number]) {
   // NOTE: Optional `setPixelRatio` allows this function to accept
   // NOTE: Optional `setPixelRatio` allows this function to accept
   // THREE renderers like SVGRenderer.
   // THREE renderers like SVGRenderer.