vertigoHaloComponent.vue 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. <script setup lang="ts">
  2. import { useLoop } from '@tresjs/core'
  3. import * as THREE from 'three'
  4. import CustomShaderMaterial from 'three-custom-shader-material/vanilla'
  5. import {
  6. computed,
  7. onBeforeUnmount,
  8. shallowRef,
  9. watch,
  10. } from 'vue'
  11. import panelFragmentShader from '../shader/vertigoHaloComponent/panel.frag?raw'
  12. import panelVertexShader from '../shader/vertigoHaloComponent/panel.vert?raw'
  13. type VertigoHaloMaterial = CustomShaderMaterial<typeof THREE.MeshBasicMaterial>
  14. const props = withDefaults(
  15. defineProps<{
  16. accentColor?: string
  17. baseColor?: string
  18. edgeGlow?: number
  19. flashIntensity?: number
  20. gradientSpeed?: number
  21. highlightColor?: string
  22. opacity?: number
  23. pulseIntensity?: number
  24. ringFrequency?: number
  25. rotationSpeed?: number
  26. segmentCount?: number
  27. size?: number
  28. tunnelSpeed?: number
  29. voidColor?: string
  30. }>(),
  31. {
  32. accentColor: '#ff2244',
  33. baseColor: '#cc1133',
  34. edgeGlow: 1,
  35. flashIntensity: 1,
  36. gradientSpeed: 0.2,
  37. highlightColor: '#c8956c',
  38. opacity: 0.92,
  39. pulseIntensity: 1,
  40. ringFrequency: 10,
  41. rotationSpeed: 0.35,
  42. segmentCount: 16,
  43. size: 4,
  44. tunnelSpeed: 0.15,
  45. voidColor: '#220033',
  46. },
  47. )
  48. const clampNumber = (value: number | undefined, min: number, max: number, fallback: number) => {
  49. if (!Number.isFinite(value)) {
  50. return fallback
  51. }
  52. return THREE.MathUtils.clamp(value as number, min, max)
  53. }
  54. const setMaterialColor = (uniform: THREE.IUniform<THREE.Color>, value: string | undefined, fallback: string) => {
  55. try {
  56. uniform.value.set(value ?? fallback)
  57. }
  58. catch {
  59. uniform.value.set(fallback)
  60. }
  61. }
  62. const safeOpacity = computed(() => clampNumber(props.opacity, 0.05, 1, 0.92))
  63. const safeTunnelSpeed = computed(() => clampNumber(props.tunnelSpeed, 0, 1.5, 0.15))
  64. const safeRotationSpeed = computed(() => clampNumber(props.rotationSpeed, 0, 1.5, 0.35))
  65. const safeRingFrequency = computed(() => clampNumber(props.ringFrequency, 4, 24, 10))
  66. const safeSegmentCount = computed(() => Math.round(clampNumber(props.segmentCount, 6, 32, 16)))
  67. const safePulseIntensity = computed(() => clampNumber(props.pulseIntensity, 0, 2, 1))
  68. const safeFlashIntensity = computed(() => clampNumber(props.flashIntensity, 0, 2, 1))
  69. const safeEdgeGlow = computed(() => clampNumber(props.edgeGlow, 0, 2, 1))
  70. const safeGradientSpeed = computed(() => clampNumber(props.gradientSpeed, -1, 1, 0.2))
  71. const createPanelMaterial = (): VertigoHaloMaterial => {
  72. return new CustomShaderMaterial({
  73. baseMaterial: THREE.MeshBasicMaterial,
  74. color: 0xFFFFFF,
  75. depthTest: true,
  76. depthWrite: false,
  77. fragmentShader: panelFragmentShader,
  78. side: THREE.DoubleSide,
  79. silent: true,
  80. toneMapped: false,
  81. transparent: true,
  82. uniforms: {
  83. uAccentColor: { value: new THREE.Color(props.accentColor) },
  84. uBaseColor: { value: new THREE.Color(props.baseColor) },
  85. uEdgeGlow: { value: safeEdgeGlow.value },
  86. uFlashIntensity: { value: safeFlashIntensity.value },
  87. uGradientSpeed: { value: safeGradientSpeed.value },
  88. uHighlightColor: { value: new THREE.Color(props.highlightColor) },
  89. uOpacity: { value: safeOpacity.value },
  90. uPulseIntensity: { value: safePulseIntensity.value },
  91. uRingFrequency: { value: safeRingFrequency.value },
  92. uRotationSpeed: { value: safeRotationSpeed.value },
  93. uSegmentCount: { value: safeSegmentCount.value },
  94. uTime: { value: 0 },
  95. uTunnelSpeed: { value: safeTunnelSpeed.value },
  96. uVoidColor: { value: new THREE.Color(props.voidColor) },
  97. },
  98. vertexShader: panelVertexShader,
  99. })
  100. }
  101. const panelMaterial = shallowRef<VertigoHaloMaterial | null>(createPanelMaterial())
  102. const syncMaterialUniforms = () => {
  103. if (!panelMaterial.value) {
  104. return
  105. }
  106. const uniforms = panelMaterial.value.uniforms
  107. setMaterialColor(uniforms.uAccentColor, props.accentColor, '#ff2244')
  108. setMaterialColor(uniforms.uBaseColor, props.baseColor, '#cc1133')
  109. setMaterialColor(uniforms.uHighlightColor, props.highlightColor, '#c8956c')
  110. setMaterialColor(uniforms.uVoidColor, props.voidColor, '#220033')
  111. uniforms.uEdgeGlow.value = safeEdgeGlow.value
  112. uniforms.uFlashIntensity.value = safeFlashIntensity.value
  113. uniforms.uGradientSpeed.value = safeGradientSpeed.value
  114. uniforms.uOpacity.value = safeOpacity.value
  115. uniforms.uPulseIntensity.value = safePulseIntensity.value
  116. uniforms.uRingFrequency.value = safeRingFrequency.value
  117. uniforms.uRotationSpeed.value = safeRotationSpeed.value
  118. uniforms.uSegmentCount.value = safeSegmentCount.value
  119. uniforms.uTunnelSpeed.value = safeTunnelSpeed.value
  120. }
  121. watch(
  122. [
  123. () => props.accentColor,
  124. () => props.baseColor,
  125. () => props.gradientSpeed,
  126. () => props.highlightColor,
  127. () => props.voidColor,
  128. safeEdgeGlow,
  129. safeFlashIntensity,
  130. safeGradientSpeed,
  131. safeOpacity,
  132. safePulseIntensity,
  133. safeRingFrequency,
  134. safeRotationSpeed,
  135. safeSegmentCount,
  136. safeTunnelSpeed,
  137. ],
  138. syncMaterialUniforms,
  139. { immediate: true },
  140. )
  141. const { onBeforeRender } = useLoop()
  142. onBeforeRender(({ elapsed }) => {
  143. if (!panelMaterial.value) {
  144. return
  145. }
  146. panelMaterial.value.uniforms.uTime.value = elapsed
  147. })
  148. onBeforeUnmount(() => {
  149. panelMaterial.value?.dispose()
  150. panelMaterial.value = null
  151. })
  152. </script>
  153. <template>
  154. <TresGroup>
  155. <TresMesh :position="[0, 0, 0]" :rotation="[-Math.PI / 2, 0, 0]" :render-order="4">
  156. <TresPlaneGeometry :args="[5, 5, 1, 1]" />
  157. <primitive
  158. v-if="panelMaterial"
  159. :object="panelMaterial"
  160. attach="material"
  161. />
  162. </TresMesh>
  163. </TresGroup>
  164. </template>