diff --git a/src/geometry/box-brush-mesh.js b/src/geometry/box-brush-mesh.js index a0ac78f3..8ddcfe75 100644 --- a/src/geometry/box-brush-mesh.js +++ b/src/geometry/box-brush-mesh.js @@ -23,6 +23,7 @@ const EDGE_VERTEX_IDS = { edgeZ_negX_posY: ["negX_posY_negZ", "negX_posY_posZ"], edgeZ_posX_posY: ["posX_posY_negZ", "posX_posY_posZ"] }; +const WATER_TOP_FACE_RENDER_SEGMENTS = 12; function cloneVec3(vector) { return { x: vector.x, y: vector.y, z: vector.z }; } @@ -222,6 +223,29 @@ function computeFaceBounds(vertices) { } return { min, max }; } +function lerpNumber(start, end, amount) { + return start + (end - start) * amount; +} +function lerpVec3(start, end, amount) { + return { + x: lerpNumber(start.x, end.x, amount), + y: lerpNumber(start.y, end.y, amount), + z: lerpNumber(start.z, end.z, amount) + }; +} +function interpolateQuadSurfaceVertex(corners, u, v) { + const topEdge = lerpVec3(corners[0], corners[1], u); + const bottomEdge = lerpVec3(corners[3], corners[2], u); + return lerpVec3(topEdge, bottomEdge, v); +} +function pushRenderedFaceVertex(positions, normals, uvs, indices, vertex, normal, faceId, faceBounds, uvSize, uvState) { + const projectedUv = projectLocalVertexToFaceUv(vertex, faceId, faceBounds); + const transformedUv = transformProjectedFaceUv(projectedUv, uvSize, uvState); + positions.push(vertex.x, vertex.y, vertex.z); + normals.push(normal.x, normal.y, normal.z); + uvs.push(transformedUv.x, transformedUv.y); + indices.push(indices.length); +} export function getBoxBrushFaceVertexIds(faceId) { return FACE_VERTEX_IDS[faceId]; } @@ -257,6 +281,7 @@ export function buildBoxBrushDerivedMeshData(brush) { const normal = computeNewellNormal(faceVertices); const faceBounds = computeFaceBounds(faceVertices); const uvSize = getFaceUvSize(faceId, faceBounds); + const uvState = brush.faces[faceId].uv; const indexStart = indices.length; faceSurfaces.push({ faceId, @@ -264,15 +289,32 @@ export function buildBoxBrushDerivedMeshData(brush) { triangles, normal }); - for (const triangle of triangles) { - for (const vertexOffset of triangle) { - const vertex = faceVertices[vertexOffset]; - const projectedUv = projectLocalVertexToFaceUv(vertex, faceId, faceBounds); - const transformedUv = transformProjectedFaceUv(projectedUv, uvSize, brush.faces[faceId].uv); - positions.push(vertex.x, vertex.y, vertex.z); - normals.push(normal.x, normal.y, normal.z); - uvs.push(transformedUv.x, transformedUv.y); - indices.push(indices.length); + const useSubdividedWaterTopFace = brush.volume.mode === "water" && faceId === "posY" && brush.volume.water.surfaceDisplacementEnabled; + if (useSubdividedWaterTopFace) { + const faceCorners = faceVertices; + for (let row = 0; row < WATER_TOP_FACE_RENDER_SEGMENTS; row += 1) { + const v0 = row / WATER_TOP_FACE_RENDER_SEGMENTS; + const v1 = (row + 1) / WATER_TOP_FACE_RENDER_SEGMENTS; + for (let column = 0; column < WATER_TOP_FACE_RENDER_SEGMENTS; column += 1) { + const u0 = column / WATER_TOP_FACE_RENDER_SEGMENTS; + const u1 = (column + 1) / WATER_TOP_FACE_RENDER_SEGMENTS; + const quadVertices = [ + interpolateQuadSurfaceVertex(faceCorners, u0, v0), + interpolateQuadSurfaceVertex(faceCorners, u1, v0), + interpolateQuadSurfaceVertex(faceCorners, u1, v1), + interpolateQuadSurfaceVertex(faceCorners, u0, v1) + ]; + for (const vertex of [quadVertices[0], quadVertices[1], quadVertices[2], quadVertices[0], quadVertices[2], quadVertices[3]]) { + pushRenderedFaceVertex(positions, normals, uvs, indices, vertex, normal, faceId, faceBounds, uvSize, uvState); + } + } + } + } + else { + for (const triangle of triangles) { + for (const vertexOffset of triangle) { + pushRenderedFaceVertex(positions, normals, uvs, indices, faceVertices[vertexOffset], normal, faceId, faceBounds, uvSize, uvState); + } } } groups.push({ diff --git a/src/geometry/box-brush-mesh.ts b/src/geometry/box-brush-mesh.ts index b2efc08b..e35a3abc 100644 --- a/src/geometry/box-brush-mesh.ts +++ b/src/geometry/box-brush-mesh.ts @@ -36,6 +36,8 @@ const EDGE_VERTEX_IDS: Record = edgeZ_posX_posY: ["posX_posY_negZ", "posX_posY_posZ"] }; +const WATER_TOP_FACE_RENDER_SEGMENTS = 12; + export interface BoxBrushGeometryDiagnostic { code: string; message: string; @@ -280,6 +282,50 @@ function computeFaceBounds(vertices: Vec3[]): { min: Vec3; max: Vec3 } { return { min, max }; } +function lerpNumber(start: number, end: number, amount: number) { + return start + (end - start) * amount; +} + +function lerpVec3(start: Vec3, end: Vec3, amount: number): Vec3 { + return { + x: lerpNumber(start.x, end.x, amount), + y: lerpNumber(start.y, end.y, amount), + z: lerpNumber(start.z, end.z, amount) + }; +} + +function interpolateQuadSurfaceVertex( + corners: readonly [Vec3, Vec3, Vec3, Vec3], + u: number, + v: number +): Vec3 { + const topEdge = lerpVec3(corners[0], corners[1], u); + const bottomEdge = lerpVec3(corners[3], corners[2], u); + + return lerpVec3(topEdge, bottomEdge, v); +} + +function pushRenderedFaceVertex( + positions: number[], + normals: number[], + uvs: number[], + indices: number[], + vertex: Vec3, + normal: Vec3, + faceId: BoxFaceId, + faceBounds: { min: Vec3; max: Vec3 }, + uvSize: Vec2, + uvState: FaceUvState +) { + const projectedUv = projectLocalVertexToFaceUv(vertex, faceId, faceBounds); + const transformedUv = transformProjectedFaceUv(projectedUv, uvSize, uvState); + + positions.push(vertex.x, vertex.y, vertex.z); + normals.push(normal.x, normal.y, normal.z); + uvs.push(transformedUv.x, transformedUv.y); + indices.push(indices.length); +} + export function getBoxBrushFaceVertexIds(faceId: BoxFaceId): readonly [BoxVertexId, BoxVertexId, BoxVertexId, BoxVertexId] { return FACE_VERTEX_IDS[faceId]; } @@ -322,6 +368,7 @@ export function buildBoxBrushDerivedMeshData(brush: BoxBrush): DerivedBoxBrushMe const normal = computeNewellNormal(faceVertices); const faceBounds = computeFaceBounds(faceVertices); const uvSize = getFaceUvSize(faceId, faceBounds); + const uvState = brush.faces[faceId].uv as FaceUvState; const indexStart = indices.length; faceSurfaces.push({ @@ -331,15 +378,49 @@ export function buildBoxBrushDerivedMeshData(brush: BoxBrush): DerivedBoxBrushMe normal }); - for (const triangle of triangles) { - for (const vertexOffset of triangle) { - const vertex = faceVertices[vertexOffset]; - const projectedUv = projectLocalVertexToFaceUv(vertex, faceId, faceBounds); - const transformedUv = transformProjectedFaceUv(projectedUv, uvSize, brush.faces[faceId].uv as FaceUvState); - positions.push(vertex.x, vertex.y, vertex.z); - normals.push(normal.x, normal.y, normal.z); - uvs.push(transformedUv.x, transformedUv.y); - indices.push(indices.length); + const useSubdividedWaterTopFace = + brush.volume.mode === "water" && + faceId === "posY" && + brush.volume.water.surfaceDisplacementEnabled; + + if (useSubdividedWaterTopFace) { + const faceCorners = faceVertices as [Vec3, Vec3, Vec3, Vec3]; + + for (let row = 0; row < WATER_TOP_FACE_RENDER_SEGMENTS; row += 1) { + const v0 = row / WATER_TOP_FACE_RENDER_SEGMENTS; + const v1 = (row + 1) / WATER_TOP_FACE_RENDER_SEGMENTS; + + for (let column = 0; column < WATER_TOP_FACE_RENDER_SEGMENTS; column += 1) { + const u0 = column / WATER_TOP_FACE_RENDER_SEGMENTS; + const u1 = (column + 1) / WATER_TOP_FACE_RENDER_SEGMENTS; + const quadVertices: [Vec3, Vec3, Vec3, Vec3] = [ + interpolateQuadSurfaceVertex(faceCorners, u0, v0), + interpolateQuadSurfaceVertex(faceCorners, u1, v0), + interpolateQuadSurfaceVertex(faceCorners, u1, v1), + interpolateQuadSurfaceVertex(faceCorners, u0, v1) + ]; + + for (const vertex of [quadVertices[0], quadVertices[1], quadVertices[2], quadVertices[0], quadVertices[2], quadVertices[3]]) { + pushRenderedFaceVertex(positions, normals, uvs, indices, vertex, normal, faceId, faceBounds, uvSize, uvState); + } + } + } + } else { + for (const triangle of triangles) { + for (const vertexOffset of triangle) { + pushRenderedFaceVertex( + positions, + normals, + uvs, + indices, + faceVertices[vertexOffset], + normal, + faceId, + faceBounds, + uvSize, + uvState + ); + } } } diff --git a/src/rendering/water-material.js b/src/rendering/water-material.js index d207fc10..931f826d 100644 --- a/src/rendering/water-material.js +++ b/src/rendering/water-material.js @@ -718,7 +718,6 @@ export function createWaterMaterial(options) { varying vec2 vLocalSurfaceUv; varying vec3 vWaveNormal; - varying vec3 vWorldPos; varying vec3 vViewDir; varying vec4 vReflectionCoord; #include @@ -754,7 +753,6 @@ export function createWaterMaterial(options) { vec4 worldPos = modelMatrix * vec4(transformedPosition, 1.0); vec4 mvPosition = viewMatrix * worldPos; - vWorldPos = worldPos.xyz; vViewDir = normalize(cameraPosition - worldPos.xyz); vReflectionCoord = reflectionMatrix * worldPos; gl_Position = projectionMatrix * mvPosition; @@ -779,7 +777,6 @@ export function createWaterMaterial(options) { varying vec2 vLocalSurfaceUv; varying vec3 vWaveNormal; - varying vec3 vWorldPos; varying vec3 vViewDir; varying vec4 vReflectionCoord; #include diff --git a/src/rendering/water-material.ts b/src/rendering/water-material.ts index 95797e8f..1a288dab 100644 --- a/src/rendering/water-material.ts +++ b/src/rendering/water-material.ts @@ -975,7 +975,6 @@ export function createWaterMaterial(options: WaterMaterialOptions): WaterMateria varying vec2 vLocalSurfaceUv; varying vec3 vWaveNormal; - varying vec3 vWorldPos; varying vec3 vViewDir; varying vec4 vReflectionCoord; #include @@ -1011,7 +1010,6 @@ export function createWaterMaterial(options: WaterMaterialOptions): WaterMateria vec4 worldPos = modelMatrix * vec4(transformedPosition, 1.0); vec4 mvPosition = viewMatrix * worldPos; - vWorldPos = worldPos.xyz; vViewDir = normalize(cameraPosition - worldPos.xyz); vReflectionCoord = reflectionMatrix * worldPos; gl_Position = projectionMatrix * mvPosition; @@ -1036,7 +1034,6 @@ export function createWaterMaterial(options: WaterMaterialOptions): WaterMateria varying vec2 vLocalSurfaceUv; varying vec3 vWaveNormal; - varying vec3 vWorldPos; varying vec3 vViewDir; varying vec4 vReflectionCoord; #include diff --git a/src/runtime-three/runtime-host.js b/src/runtime-three/runtime-host.js index 98ef65ed..2f656784 100644 --- a/src/runtime-three/runtime-host.js +++ b/src/runtime-three/runtime-host.js @@ -628,8 +628,19 @@ export class RuntimeHost { const previousAutoClear = this.renderer.autoClear; const previousRenderTarget = this.renderer.getRenderTarget(); const previousFogDensity = this.underwaterSceneFog.density; + const previousReflectionStates = this.runtimeWaterContactUniforms.map((waterBinding) => ({ + binding: waterBinding, + enabled: waterBinding.reflectionEnabledUniform?.value ?? 0, + texture: waterBinding.reflectionTextureUniform?.value ?? null + })); try { this.underwaterSceneFog.density = 0; + for (const state of previousReflectionStates) { + if (state.binding.reflectionEnabledUniform !== null) { + state.binding.reflectionEnabledUniform.value = 0; + } + } + binding.reflectionTextureUniform.value = null; this.renderer.autoClear = true; this.renderer.setRenderTarget(binding.reflectionRenderTarget); this.renderer.clear(); @@ -640,6 +651,14 @@ export class RuntimeHost { this.renderer.autoClear = previousAutoClear; this.modelGroup.visible = previousModelGroupVisibility; this.underwaterSceneFog.density = previousFogDensity; + for (const state of previousReflectionStates) { + if (state.binding.reflectionEnabledUniform !== null) { + state.binding.reflectionEnabledUniform.value = state.enabled; + } + if (state.binding.reflectionTextureUniform !== null) { + state.binding.reflectionTextureUniform.value = state.texture; + } + } for (const hiddenWaterMesh of hiddenWaterMeshes) { hiddenWaterMesh.mesh.visible = hiddenWaterMesh.visible; } diff --git a/src/runtime-three/runtime-host.ts b/src/runtime-three/runtime-host.ts index f511d4f5..bc1c9749 100644 --- a/src/runtime-three/runtime-host.ts +++ b/src/runtime-three/runtime-host.ts @@ -884,9 +884,20 @@ export class RuntimeHost { const previousAutoClear = this.renderer.autoClear; const previousRenderTarget = this.renderer.getRenderTarget(); const previousFogDensity = this.underwaterSceneFog.density; + const previousReflectionStates = this.runtimeWaterContactUniforms.map((waterBinding) => ({ + binding: waterBinding, + enabled: waterBinding.reflectionEnabledUniform?.value ?? 0, + texture: waterBinding.reflectionTextureUniform?.value ?? null + })); try { this.underwaterSceneFog.density = 0; + for (const state of previousReflectionStates) { + if (state.binding.reflectionEnabledUniform !== null) { + state.binding.reflectionEnabledUniform.value = 0; + } + } + binding.reflectionTextureUniform.value = null; this.renderer.autoClear = true; this.renderer.setRenderTarget(binding.reflectionRenderTarget); this.renderer.clear(); @@ -896,6 +907,14 @@ export class RuntimeHost { this.renderer.autoClear = previousAutoClear; this.modelGroup.visible = previousModelGroupVisibility; this.underwaterSceneFog.density = previousFogDensity; + for (const state of previousReflectionStates) { + if (state.binding.reflectionEnabledUniform !== null) { + state.binding.reflectionEnabledUniform.value = state.enabled; + } + if (state.binding.reflectionTextureUniform !== null) { + state.binding.reflectionTextureUniform.value = state.texture; + } + } for (const hiddenWaterMesh of hiddenWaterMeshes) { hiddenWaterMesh.mesh.visible = hiddenWaterMesh.visible; diff --git a/src/viewport-three/viewport-host.ts b/src/viewport-three/viewport-host.ts index 7fb13046..9b3de0d9 100644 --- a/src/viewport-three/viewport-host.ts +++ b/src/viewport-three/viewport-host.ts @@ -3137,7 +3137,18 @@ export class ViewportHost { const previousAutoClear = this.renderer.autoClear; const previousRenderTarget = this.renderer.getRenderTarget(); + const previousReflectionStates = this.viewportWaterSurfaceBindings.map((waterBinding) => ({ + binding: waterBinding, + enabled: waterBinding.reflectionEnabledUniform?.value ?? 0, + texture: waterBinding.reflectionTextureUniform?.value ?? null + })); try { + for (const state of previousReflectionStates) { + if (state.binding.reflectionEnabledUniform !== null) { + state.binding.reflectionEnabledUniform.value = 0; + } + } + binding.reflectionTextureUniform.value = null; this.renderer.autoClear = true; this.renderer.setRenderTarget(binding.reflectionRenderTarget); this.renderer.clear(); @@ -3145,6 +3156,14 @@ export class ViewportHost { } finally { this.renderer.setRenderTarget(previousRenderTarget); this.renderer.autoClear = previousAutoClear; + for (const state of previousReflectionStates) { + if (state.binding.reflectionEnabledUniform !== null) { + state.binding.reflectionEnabledUniform.value = state.enabled; + } + if (state.binding.reflectionTextureUniform !== null) { + state.binding.reflectionTextureUniform.value = state.texture; + } + } for (const hiddenObject of hiddenObjects) { hiddenObject.object.visible = hiddenObject.visible;