|
@@ -3,7 +3,7 @@ import { onMounted, onUnmounted, ref, watch } from 'vue'
|
|
|
import * as THREE from 'three'
|
|
import * as THREE from 'three'
|
|
|
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
|
|
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
|
|
|
import { Sky } from 'three/examples/jsm/objects/Sky.js'
|
|
import { Sky } from 'three/examples/jsm/objects/Sky.js'
|
|
|
-import { Water, createWaterNormalTexture } from './Water'
|
|
|
|
|
|
|
+import { StylizedWaterMaterial } from './waterNew'
|
|
|
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
|
|
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
|
|
|
|
|
|
|
|
const containerRef = ref<HTMLDivElement>()
|
|
const containerRef = ref<HTMLDivElement>()
|
|
@@ -17,17 +17,24 @@ const cameraInfo = ref({
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
const waterParams = ref({
|
|
const waterParams = ref({
|
|
|
- alpha: 0.65,
|
|
|
|
|
- sunColor: '#ffffff',
|
|
|
|
|
- waterColor: '#78a6a0',
|
|
|
|
|
- distortionScale: 30.0,
|
|
|
|
|
- noiseScale: 1.0,
|
|
|
|
|
- fresnelBias: 0.15,
|
|
|
|
|
- fresnelPower: 4.0,
|
|
|
|
|
- fresnelStrength: 1.0,
|
|
|
|
|
|
|
+ alpha: 0.85,
|
|
|
|
|
+ waterColor: '#566c67',
|
|
|
|
|
+ deepColor: '#0a2a4a',
|
|
|
flowSpeed: 0.5,
|
|
flowSpeed: 0.5,
|
|
|
flowDirectionX: 1.0,
|
|
flowDirectionX: 1.0,
|
|
|
flowDirectionY: 1.0,
|
|
flowDirectionY: 1.0,
|
|
|
|
|
+ waveHeight: 0.6,
|
|
|
|
|
+ waveFrequency: 1.0,
|
|
|
|
|
+ foamIntensity: 0.25,
|
|
|
|
|
+ specIntensity: 2.0,
|
|
|
|
|
+ specPower: 64.0,
|
|
|
|
|
+ fresnelPower: 2.5,
|
|
|
|
|
+ fresnelIntensity: 1.0,
|
|
|
|
|
+ depthRange: 44.0,
|
|
|
|
|
+ waterNormalStrength: 2.0,
|
|
|
|
|
+ waterNormalTiling: 0.26,
|
|
|
|
|
+ collisionFoamThreshold: 0.10,
|
|
|
|
|
+ collisionFoamStrength: 0.5,
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
const showCoordinatePanel = ref(false)
|
|
const showCoordinatePanel = ref(false)
|
|
@@ -39,13 +46,13 @@ let camera: THREE.PerspectiveCamera
|
|
|
let renderer: THREE.WebGLRenderer
|
|
let renderer: THREE.WebGLRenderer
|
|
|
let controls: OrbitControls
|
|
let controls: OrbitControls
|
|
|
let sky: Sky
|
|
let sky: Sky
|
|
|
-let water: Water
|
|
|
|
|
let waterMesh: THREE.Mesh
|
|
let waterMesh: THREE.Mesh
|
|
|
let sunDirection: THREE.Vector3
|
|
let sunDirection: THREE.Vector3
|
|
|
let animationId: number
|
|
let animationId: number
|
|
|
let tilesetGroup: THREE.Group | null = null
|
|
let tilesetGroup: THREE.Group | null = null
|
|
|
let raycaster: THREE.Raycaster
|
|
let raycaster: THREE.Raycaster
|
|
|
let mouse: THREE.Vector2
|
|
let mouse: THREE.Vector2
|
|
|
|
|
+let depthRenderTarget: THREE.WebGLRenderTarget
|
|
|
|
|
|
|
|
function createSky() {
|
|
function createSky() {
|
|
|
sky = new Sky()
|
|
sky = new Sky()
|
|
@@ -67,40 +74,24 @@ function createSky() {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function createWaterSurface() {
|
|
function createWaterSurface() {
|
|
|
- const textureLoader = new THREE.TextureLoader()
|
|
|
|
|
- const waterNormals = createWaterNormalTexture(textureLoader)
|
|
|
|
|
-
|
|
|
|
|
- water = new Water(renderer, camera, scene, {
|
|
|
|
|
- textureWidth: 512,
|
|
|
|
|
- textureHeight: 512,
|
|
|
|
|
- waterNormals,
|
|
|
|
|
- alpha: waterParams.value.alpha,
|
|
|
|
|
- sunDirection,
|
|
|
|
|
- sunColor: new THREE.Color(waterParams.value.sunColor),
|
|
|
|
|
- waterColor: new THREE.Color(waterParams.value.waterColor),
|
|
|
|
|
- distortionScale: waterParams.value.distortionScale,
|
|
|
|
|
- noiseScale: waterParams.value.noiseScale,
|
|
|
|
|
- fresnelBias: waterParams.value.fresnelBias,
|
|
|
|
|
- fresnelPower: waterParams.value.fresnelPower,
|
|
|
|
|
- fresnelStrength: waterParams.value.fresnelStrength,
|
|
|
|
|
- flowDirection: new THREE.Vector2(waterParams.value.flowDirectionX, waterParams.value.flowDirectionY),
|
|
|
|
|
- side: THREE.DoubleSide,
|
|
|
|
|
- fog: true,
|
|
|
|
|
- })
|
|
|
|
|
-
|
|
|
|
|
waterMesh = new THREE.Mesh(
|
|
waterMesh = new THREE.Mesh(
|
|
|
- new THREE.PlaneGeometry(200, 200, 60, 60),
|
|
|
|
|
- water.material
|
|
|
|
|
|
|
+ new THREE.PlaneGeometry(200, 200, 120, 120),
|
|
|
|
|
+ StylizedWaterMaterial
|
|
|
)
|
|
)
|
|
|
- waterMesh.add(water)
|
|
|
|
|
waterMesh.rotation.x = -Math.PI / 2
|
|
waterMesh.rotation.x = -Math.PI / 2
|
|
|
waterMesh.position.set(840.85714, 7.47851, 2179.50782)
|
|
waterMesh.position.set(840.85714, 7.47851, 2179.50782)
|
|
|
waterMesh.receiveShadow = true
|
|
waterMesh.receiveShadow = true
|
|
|
scene.add(waterMesh)
|
|
scene.add(waterMesh)
|
|
|
|
|
+ StylizedWaterMaterial.uniforms.cameraPos.value.copy(camera.position)
|
|
|
|
|
+ StylizedWaterMaterial.uniforms.sunDirection.value.copy(sunDirection)
|
|
|
|
|
+ StylizedWaterMaterial.uniforms.cameraNear.value = camera.near
|
|
|
|
|
+ StylizedWaterMaterial.uniforms.cameraFar.value = camera.far
|
|
|
|
|
+ const container = containerRef.value!
|
|
|
|
|
+ StylizedWaterMaterial.uniforms.iResolution.value.set(container.clientWidth, container.clientHeight)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async function load3DTiles() {
|
|
async function load3DTiles() {
|
|
|
- const tilesetUrl = 'http://localhost:9003/model/scence/tileset.json'
|
|
|
|
|
|
|
+ const tilesetUrl = 'http://192.168.0.110:9003/model/scence/tileset.json'
|
|
|
const gltfLoader = new GLTFLoader()
|
|
const gltfLoader = new GLTFLoader()
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
@@ -237,6 +228,11 @@ function initScene() {
|
|
|
renderer.shadowMap.type = THREE.PCFShadowMap
|
|
renderer.shadowMap.type = THREE.PCFShadowMap
|
|
|
container.appendChild(renderer.domElement)
|
|
container.appendChild(renderer.domElement)
|
|
|
|
|
|
|
|
|
|
+ const pixelWidth = Math.floor(container.clientWidth * window.devicePixelRatio)
|
|
|
|
|
+ const pixelHeight = Math.floor(container.clientHeight * window.devicePixelRatio)
|
|
|
|
|
+ depthRenderTarget = new THREE.WebGLRenderTarget(pixelWidth, pixelHeight)
|
|
|
|
|
+ depthRenderTarget.depthTexture = new THREE.DepthTexture(pixelWidth, pixelHeight)
|
|
|
|
|
+
|
|
|
controls = new OrbitControls(camera, renderer.domElement)
|
|
controls = new OrbitControls(camera, renderer.domElement)
|
|
|
controls.enableDamping = true
|
|
controls.enableDamping = true
|
|
|
controls.dampingFactor = 0.03
|
|
controls.dampingFactor = 0.03
|
|
@@ -250,7 +246,7 @@ function initScene() {
|
|
|
}
|
|
}
|
|
|
controls.target.set(842.3117, 9.27789, 2178.09268)
|
|
controls.target.set(842.3117, 9.27789, 2178.09268)
|
|
|
controls.maxPolarAngle = Math.PI / 2.1
|
|
controls.maxPolarAngle = Math.PI / 2.1
|
|
|
- controls.minDistance = 20
|
|
|
|
|
|
|
+ controls.minDistance = 5
|
|
|
controls.maxDistance = 100
|
|
controls.maxDistance = 100
|
|
|
|
|
|
|
|
createSky()
|
|
createSky()
|
|
@@ -326,8 +322,18 @@ function onMouseClick(event: MouseEvent) {
|
|
|
function animate() {
|
|
function animate() {
|
|
|
animationId = requestAnimationFrame(animate)
|
|
animationId = requestAnimationFrame(animate)
|
|
|
sky.material.uniforms.time.value += 0.001
|
|
sky.material.uniforms.time.value += 0.001
|
|
|
- water.material.uniforms.time.value += 0.005 * waterParams.value.flowSpeed
|
|
|
|
|
- water.render()
|
|
|
|
|
|
|
+
|
|
|
|
|
+ waterMesh.visible = false
|
|
|
|
|
+ const prevRT = renderer.getRenderTarget()
|
|
|
|
|
+ renderer.setRenderTarget(depthRenderTarget)
|
|
|
|
|
+ renderer.render(scene, camera)
|
|
|
|
|
+ renderer.setRenderTarget(prevRT)
|
|
|
|
|
+ waterMesh.visible = true
|
|
|
|
|
+
|
|
|
|
|
+ StylizedWaterMaterial.uniforms.depthSampler.value = depthRenderTarget.depthTexture
|
|
|
|
|
+ StylizedWaterMaterial.uniforms.iTime.value += 0.016
|
|
|
|
|
+ StylizedWaterMaterial.uniforms.collisionFoamTime.value += 0.016
|
|
|
|
|
+ StylizedWaterMaterial.uniforms.cameraPos.value.copy(camera.position)
|
|
|
|
|
|
|
|
controls.update()
|
|
controls.update()
|
|
|
|
|
|
|
@@ -355,6 +361,10 @@ function onResize() {
|
|
|
camera.aspect = width / height
|
|
camera.aspect = width / height
|
|
|
camera.updateProjectionMatrix()
|
|
camera.updateProjectionMatrix()
|
|
|
renderer.setSize(width, height)
|
|
renderer.setSize(width, height)
|
|
|
|
|
+ const pixelWidth = Math.floor(width * window.devicePixelRatio)
|
|
|
|
|
+ const pixelHeight = Math.floor(height * window.devicePixelRatio)
|
|
|
|
|
+ depthRenderTarget.setSize(pixelWidth, pixelHeight)
|
|
|
|
|
+ StylizedWaterMaterial.uniforms.iResolution.value.set(width, height)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
onMounted(() => {
|
|
@@ -376,19 +386,28 @@ onUnmounted(() => {
|
|
|
|
|
|
|
|
renderer.dispose()
|
|
renderer.dispose()
|
|
|
controls.dispose()
|
|
controls.dispose()
|
|
|
|
|
+ depthRenderTarget.dispose()
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
watch(() => waterParams.value, (newParams) => {
|
|
watch(() => waterParams.value, (newParams) => {
|
|
|
- if (water && water.material && water.material.uniforms) {
|
|
|
|
|
- water.material.uniforms.alpha.value = newParams.alpha
|
|
|
|
|
- water.material.uniforms.sunColor.value.set(newParams.sunColor)
|
|
|
|
|
- water.material.uniforms.waterColor.value.set(newParams.waterColor)
|
|
|
|
|
- water.material.uniforms.distortionScale.value = newParams.distortionScale
|
|
|
|
|
- water.material.uniforms.noiseScale.value = newParams.noiseScale
|
|
|
|
|
- water.material.uniforms.fresnelBias.value = newParams.fresnelBias
|
|
|
|
|
- water.material.uniforms.fresnelPower.value = newParams.fresnelPower
|
|
|
|
|
- water.material.uniforms.fresnelStrength.value = newParams.fresnelStrength
|
|
|
|
|
- water.material.uniforms.flowDirection.value.set(newParams.flowDirectionX, newParams.flowDirectionY)
|
|
|
|
|
|
|
+ if (StylizedWaterMaterial && StylizedWaterMaterial.uniforms) {
|
|
|
|
|
+ StylizedWaterMaterial.uniforms.alpha.value = newParams.alpha
|
|
|
|
|
+ StylizedWaterMaterial.uniforms.flowSpeed.value = newParams.flowSpeed
|
|
|
|
|
+ StylizedWaterMaterial.uniforms.flowDirection.value.set(newParams.flowDirectionX, newParams.flowDirectionY)
|
|
|
|
|
+ StylizedWaterMaterial.uniforms.waveHeight.value = newParams.waveHeight
|
|
|
|
|
+ StylizedWaterMaterial.uniforms.waveFrequency.value = newParams.waveFrequency
|
|
|
|
|
+ StylizedWaterMaterial.uniforms.shallowColor.value.set(newParams.waterColor)
|
|
|
|
|
+ StylizedWaterMaterial.uniforms.deepColor.value.set(newParams.deepColor)
|
|
|
|
|
+ StylizedWaterMaterial.uniforms.foamIntensity.value = newParams.foamIntensity
|
|
|
|
|
+ StylizedWaterMaterial.uniforms.specIntensity.value = newParams.specIntensity
|
|
|
|
|
+ StylizedWaterMaterial.uniforms.specPower.value = newParams.specPower
|
|
|
|
|
+ StylizedWaterMaterial.uniforms.fresnelPower.value = newParams.fresnelPower
|
|
|
|
|
+ StylizedWaterMaterial.uniforms.fresnelIntensity.value = newParams.fresnelIntensity
|
|
|
|
|
+ StylizedWaterMaterial.uniforms.depthRange.value = newParams.depthRange
|
|
|
|
|
+ StylizedWaterMaterial.uniforms.waterNormalStrength.value = newParams.waterNormalStrength
|
|
|
|
|
+ StylizedWaterMaterial.uniforms.waterNormalTiling.value = newParams.waterNormalTiling
|
|
|
|
|
+ StylizedWaterMaterial.uniforms.collisionFoamThreshold.value = newParams.collisionFoamThreshold
|
|
|
|
|
+ StylizedWaterMaterial.uniforms.collisionFoamStrength.value = newParams.collisionFoamStrength
|
|
|
}
|
|
}
|
|
|
}, { deep: true })
|
|
}, { deep: true })
|
|
|
</script>
|
|
</script>
|
|
@@ -484,6 +503,20 @@ watch(() => waterParams.value, (newParams) => {
|
|
|
<span class="slider-value">{{ waterParams.alpha.toFixed(2) }}</span>
|
|
<span class="slider-value">{{ waterParams.alpha.toFixed(2) }}</span>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+ <div class="water-section">
|
|
|
|
|
+ <div class="section-label">浪高</div>
|
|
|
|
|
+ <div class="slider-item">
|
|
|
|
|
+ <input type="range" v-model.number="waterParams.waveHeight" min="0" max="2" step="0.05" />
|
|
|
|
|
+ <span class="slider-value">{{ waterParams.waveHeight.toFixed(2) }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="water-section">
|
|
|
|
|
+ <div class="section-label">波浪频率</div>
|
|
|
|
|
+ <div class="slider-item">
|
|
|
|
|
+ <input type="range" v-model.number="waterParams.waveFrequency" min="0.1" max="5" step="0.1" />
|
|
|
|
|
+ <span class="slider-value">{{ waterParams.waveFrequency.toFixed(1) }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
<div class="water-section">
|
|
<div class="water-section">
|
|
|
<div class="section-label">水流速度</div>
|
|
<div class="section-label">水流速度</div>
|
|
|
<div class="slider-item">
|
|
<div class="slider-item">
|
|
@@ -506,38 +539,52 @@ watch(() => waterParams.value, (newParams) => {
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
<div class="water-section">
|
|
<div class="water-section">
|
|
|
- <div class="section-label">水颜色</div>
|
|
|
|
|
|
|
+ <div class="section-label">浅水颜色</div>
|
|
|
<div class="color-item">
|
|
<div class="color-item">
|
|
|
<input type="color" v-model="waterParams.waterColor" />
|
|
<input type="color" v-model="waterParams.waterColor" />
|
|
|
<span class="color-value">{{ waterParams.waterColor }}</span>
|
|
<span class="color-value">{{ waterParams.waterColor }}</span>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
<div class="water-section">
|
|
<div class="water-section">
|
|
|
- <div class="section-label">太阳光颜色</div>
|
|
|
|
|
|
|
+ <div class="section-label">深水颜色</div>
|
|
|
<div class="color-item">
|
|
<div class="color-item">
|
|
|
- <input type="color" v-model="waterParams.sunColor" />
|
|
|
|
|
- <span class="color-value">{{ waterParams.sunColor }}</span>
|
|
|
|
|
|
|
+ <input type="color" v-model="waterParams.deepColor" />
|
|
|
|
|
+ <span class="color-value">{{ waterParams.deepColor }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="water-section">
|
|
|
|
|
+ <div class="section-label">水深范围</div>
|
|
|
|
|
+ <div class="slider-item">
|
|
|
|
|
+ <input type="range" v-model.number="waterParams.depthRange" min="1" max="50" step="0.5" />
|
|
|
|
|
+ <span class="slider-value">{{ waterParams.depthRange.toFixed(1) }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="water-section">
|
|
|
|
|
+ <div class="section-label">水法线强度</div>
|
|
|
|
|
+ <div class="slider-item">
|
|
|
|
|
+ <input type="range" v-model.number="waterParams.waterNormalStrength" min="0" max="2" step="0.05" />
|
|
|
|
|
+ <span class="slider-value">{{ waterParams.waterNormalStrength.toFixed(2) }}</span>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
<div class="water-section">
|
|
<div class="water-section">
|
|
|
- <div class="section-label">波纹强度</div>
|
|
|
|
|
|
|
+ <div class="section-label">水纹平铺</div>
|
|
|
<div class="slider-item">
|
|
<div class="slider-item">
|
|
|
- <input type="range" v-model.number="waterParams.distortionScale" min="0" max="100" step="1" />
|
|
|
|
|
- <span class="slider-value">{{ waterParams.distortionScale.toFixed(0) }}</span>
|
|
|
|
|
|
|
+ <input type="range" v-model.number="waterParams.waterNormalTiling" min="0.01" max="5" step="0.01" />
|
|
|
|
|
+ <span class="slider-value">{{ waterParams.waterNormalTiling.toFixed(2) }}</span>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
<div class="water-section">
|
|
<div class="water-section">
|
|
|
- <div class="section-label">噪声缩放</div>
|
|
|
|
|
|
|
+ <div class="section-label">高光强度</div>
|
|
|
<div class="slider-item">
|
|
<div class="slider-item">
|
|
|
- <input type="range" v-model.number="waterParams.noiseScale" min="0.1" max="5" step="0.1" />
|
|
|
|
|
- <span class="slider-value">{{ waterParams.noiseScale.toFixed(1) }}</span>
|
|
|
|
|
|
|
+ <input type="range" v-model.number="waterParams.specIntensity" min="0" max="5" step="0.1" />
|
|
|
|
|
+ <span class="slider-value">{{ waterParams.specIntensity.toFixed(1) }}</span>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
<div class="water-section">
|
|
<div class="water-section">
|
|
|
- <div class="section-label">菲涅尔偏移</div>
|
|
|
|
|
|
|
+ <div class="section-label">高光锐度</div>
|
|
|
<div class="slider-item">
|
|
<div class="slider-item">
|
|
|
- <input type="range" v-model.number="waterParams.fresnelBias" min="0" max="1" step="0.01" />
|
|
|
|
|
- <span class="slider-value">{{ waterParams.fresnelBias.toFixed(2) }}</span>
|
|
|
|
|
|
|
+ <input type="range" v-model.number="waterParams.specPower" min="1" max="256" step="1" />
|
|
|
|
|
+ <span class="slider-value">{{ waterParams.specPower.toFixed(0) }}</span>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
<div class="water-section">
|
|
<div class="water-section">
|
|
@@ -550,8 +597,29 @@ watch(() => waterParams.value, (newParams) => {
|
|
|
<div class="water-section">
|
|
<div class="water-section">
|
|
|
<div class="section-label">菲涅尔强度</div>
|
|
<div class="section-label">菲涅尔强度</div>
|
|
|
<div class="slider-item">
|
|
<div class="slider-item">
|
|
|
- <input type="range" v-model.number="waterParams.fresnelStrength" min="0" max="3" step="0.1" />
|
|
|
|
|
- <span class="slider-value">{{ waterParams.fresnelStrength.toFixed(1) }}</span>
|
|
|
|
|
|
|
+ <input type="range" v-model.number="waterParams.fresnelIntensity" min="0" max="3" step="0.1" />
|
|
|
|
|
+ <span class="slider-value">{{ waterParams.fresnelIntensity.toFixed(1) }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="water-section">
|
|
|
|
|
+ <div class="section-label">泡沫强度</div>
|
|
|
|
|
+ <div class="slider-item">
|
|
|
|
|
+ <input type="range" v-model.number="waterParams.foamIntensity" min="0" max="2" step="0.05" />
|
|
|
|
|
+ <span class="slider-value">{{ waterParams.foamIntensity.toFixed(2) }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="water-section">
|
|
|
|
|
+ <div class="section-label">碰撞泡沫阈值</div>
|
|
|
|
|
+ <div class="slider-item">
|
|
|
|
|
+ <input type="range" v-model.number="waterParams.collisionFoamThreshold" min="0" max="2" step="0.05" />
|
|
|
|
|
+ <span class="slider-value">{{ waterParams.collisionFoamThreshold.toFixed(2) }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="water-section">
|
|
|
|
|
+ <div class="section-label">碰撞泡沫强度</div>
|
|
|
|
|
+ <div class="slider-item">
|
|
|
|
|
+ <input type="range" v-model.number="waterParams.collisionFoamStrength" min="0" max="3" step="0.1" />
|
|
|
|
|
+ <span class="slider-value">{{ waterParams.collisionFoamStrength.toFixed(1) }}</span>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|