setupRenderer.ts 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. import type { TresContext } from 'src/composables/useTresContextProvider'
  2. import type { ColorRepresentation, Object3D, WebGLRenderer } from 'three'
  3. import { watch } from 'vue'
  4. import { useDevicePixelRatio } from '@vueuse/core'
  5. import { setPixelRatio } from '../utils'
  6. import { Mesh } from 'three'
  7. interface PropertyHandler<T = unknown> {
  8. set: (renderer: WebGLRenderer, value: T) => void
  9. immediate?: boolean
  10. }
  11. interface DirectProperty {
  12. key: keyof WebGLRenderer | 'shadowMap.enabled' | 'shadowMap.type' | 'physicallyCorrectLights'
  13. immediate?: boolean
  14. }
  15. // Properties that can be set directly on the renderer
  16. const directProperties: Record<string, DirectProperty> = {
  17. toneMapping: {
  18. key: 'toneMapping',
  19. immediate: true,
  20. },
  21. toneMappingExposure: {
  22. key: 'toneMappingExposure',
  23. immediate: true,
  24. },
  25. outputColorSpace: {
  26. key: 'outputColorSpace',
  27. immediate: true,
  28. },
  29. physicallyCorrectLights: {
  30. key: 'physicallyCorrectLights',
  31. immediate: true,
  32. },
  33. shadowMapType: {
  34. key: 'shadowMap.type',
  35. immediate: true,
  36. },
  37. shadows: {
  38. key: 'shadowMap.enabled',
  39. immediate: true,
  40. },
  41. }
  42. // Properties that use setter methods
  43. const rendererPropertyHandlers: Record<string, PropertyHandler<ColorRepresentation | boolean>> = {
  44. clearColor: {
  45. set: (renderer, value) => renderer.setClearColor(value as ColorRepresentation),
  46. immediate: true,
  47. },
  48. alpha: {
  49. set: (renderer, value) => renderer.setClearAlpha(value as boolean ? 1 : 0),
  50. immediate: true,
  51. },
  52. }
  53. // Modified setup function to handle both types of properties
  54. export function setupWebGLRenderer(
  55. initialRenderer: WebGLRenderer,
  56. options: Record<string, any>,
  57. ctx: TresContext,
  58. ) {
  59. const { pixelRatio } = useDevicePixelRatio()
  60. const { invalidate } = ctx
  61. function invalidateOnDemand() {
  62. if (options.renderMode === 'on-demand') {
  63. invalidate()
  64. }
  65. }
  66. // Watch DPR changes
  67. watch(() => options.dpr, (value) => {
  68. if (!value) { return }
  69. invalidateOnDemand()
  70. setPixelRatio(initialRenderer, pixelRatio.value, value as number)
  71. })
  72. // Watch size changes
  73. watch([ctx.sizes.width, ctx.sizes.height], () => {
  74. initialRenderer.setSize(ctx.sizes.width.value, ctx.sizes.height.value)
  75. invalidateOnDemand()
  76. }, {
  77. immediate: true,
  78. })
  79. // Watch properties that need setter methods
  80. Object.entries(rendererPropertyHandlers).forEach(([key, handler]) => {
  81. watch(
  82. () => options[key],
  83. (value) => {
  84. if (value === undefined) { return }
  85. handler.set(initialRenderer, value)
  86. invalidateOnDemand()
  87. },
  88. { immediate: handler.immediate },
  89. )
  90. })
  91. // Watch properties that can be set directly
  92. Object.entries(directProperties).forEach(([key, prop]) => {
  93. watch(
  94. () => options[key],
  95. (value) => {
  96. if (value === undefined) { return }
  97. // Handle nested properties (like shadowMap.type)
  98. const parts = prop.key.split('.')
  99. if (parts.length > 1) {
  100. // Handle shadowMap properties specifically
  101. if (parts[0] === 'shadowMap') {
  102. const shadowMapKey = parts[1] as keyof typeof initialRenderer.shadowMap
  103. initialRenderer.shadowMap[shadowMapKey] = value
  104. // Update materials when shadow properties change
  105. ctx.scene.value.traverse((child: Object3D) => {
  106. if (child instanceof Mesh) {
  107. const material = (child).material
  108. if (material) {
  109. material.needsUpdate = true
  110. }
  111. }
  112. })
  113. }
  114. }
  115. else {
  116. const key = prop.key as keyof WebGLRenderer
  117. // Check instance property first, then prototype
  118. const descriptor = Object.getOwnPropertyDescriptor(initialRenderer, key)
  119. || Object.getOwnPropertyDescriptor(Object.getPrototypeOf(initialRenderer), key)
  120. // Get safe properties from directProperties
  121. const safeToSetProperties = Object.values(directProperties)
  122. .map(prop => prop.key)
  123. .filter(key => !key.includes('.')) // Filter out nested properties like shadowMap.type
  124. if ((descriptor?.writable || safeToSetProperties.includes(key))) {
  125. const rendererKey = key as keyof Omit<WebGLRenderer, 'coordinateSystem' | 'info'>
  126. ;(initialRenderer as unknown as Record<string, unknown>)[rendererKey] = value
  127. }
  128. }
  129. invalidateOnDemand()
  130. },
  131. { immediate: prop.immediate },
  132. )
  133. })
  134. return initialRenderer
  135. }