lanxin 6 месяцев назад
Сommit
b11d8583b6

+ 3 - 0
.vscode/settings.json

@@ -0,0 +1,3 @@
+{
+  "liveServer.settings.port": 5501
+}

Разница между файлами не показана из-за своего большого размера
+ 1556 - 0
OrbitControls.js


+ 81 - 0
controls.js

@@ -0,0 +1,81 @@
+
+import * as THREE from './three.module.js'
+import { OrbitControls } from './OrbitControls.js'
+document.addEventListener('DOMContentLoaded', () => {
+  const modelViewer = document.getElementById('sphere')
+  let controls, threeScene
+
+  // 等待model-viewer内部Three.js场景初始化完成
+  modelViewer.addEventListener('load', () => {
+    // 延迟执行以确保Three.js场景完全初始化
+    setTimeout(() => {
+      threeScene = modelViewer[Object.getOwnPropertySymbols(modelViewer).find(e => e.description === 'scene')]
+      controls = modelViewer[Object.getOwnPropertySymbols(modelViewer).find(e => e.description === 'controls')]
+
+      // controls.camera.up.copy(new THREE.Vector3(0, 0, 1))
+      console.log(controls.camera.up)
+
+      controls.camera.up.copy(new THREE.Vector3(1, 0, 0))
+      controls.options.minPolarAngle = Math.PI / 180 * 1
+      controls.options.maximumPolarAngle = Math.PI - Math.PI / 180 * 1
+      controls.options.minimumPolarAngle = Math.PI / 180 * 1
+      controls.options.maxPolarAngle = Math.PI - Math.PI / 180 * 1
+      // 限制水平旋转范围
+      controls.options.minAzimuthAngle = -Math.PI / 2 // 左侧90度
+      controls.options.maxAzimuthAngle = Math.PI / 2  // 右侧90度
+      controls.options.maximumAzimuthAngle = Math.PI
+      controls.options.maximumAzimuthalAngle = Math.PI   // 右侧90度
+      controls.options.minimumAzimuthAngle = -Math.PI  // 左侧90度
+      controls.options.minimumAzimuthalAngle = -Math.PI
+
+
+      const move = controls.onPointerMove
+      // 重写事件处理函数,变换坐标
+      controls.onPointerMove = (event) => {
+        const pointer =
+          this.pointers.find((pointer) => pointer.id === event.pointerId)
+        if (pointer == null) {
+          return
+        }
+
+        // In case no one gave us a pointerup or pointercancel event.
+        if (event.pointerType === 'mouse' && event.buttons === 0) {
+          this.onPointerUp(event)
+          return
+        }
+
+        const numTouches = this.pointers.length
+        let dx = (event.clientX - pointer.clientX) / numTouches
+        let dy = (event.clientY - pointer.clientY) / numTouches;
+
+
+        [dx, dy] = this.applyRotationMapping(dx, dy)
+
+
+      }
+
+      // 刷新控制器状态
+      controls.update()
+
+      console.log(threeScene)
+      console.log(controls)
+
+
+
+
+
+    }, 1000) // 延迟1秒确保场景完全初始化
+  })
+})
+
+function applyRotationMapping(dx, dy) {
+  const angle = this.rotationAngle * Math.PI / 180
+  const cos = Math.cos(angle)
+  const sin = Math.sin(angle)
+
+  // 应用旋转变换矩阵
+  const newDx = dx * cos - dy * sin
+  const newDy = dx * sin + dy * cos
+
+  return [newDx, newDy]
+}

+ 108 - 0
index.html

@@ -0,0 +1,108 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <title>three.js webgl - loaders - gltf</title>
+    <meta charset="utf-8" />
+    <meta
+      name="viewport"
+      content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"
+    />
+    <style>
+      body {
+        margin: 0;
+        padding: 0;
+      }
+      #container {
+        width: 100vw;
+        height: 100vh;
+        display: flex;
+        align-items: center;
+      }
+      #sphere {
+        width: 100%;
+        min-width: 20%;
+        height: 100%;
+        flex-shrink: 0;
+      }
+      #sphere2 {
+        width: 0;
+        height: 100%;
+        flex: 1;
+        flex-shrink: 0;
+      }
+      .splitBox {
+        width: 1%;
+        height: 100%;
+        cursor: col-resize;
+        align-items: center;
+        justify-content: center;
+        background-color: #ccc;
+        display: none;
+      }
+      .splitLine {
+        width: 6px;
+        height: 5%;
+        background-color: #428cee;
+      }
+      .buttonBox {
+        position: absolute;
+        top: 10px;
+        right: 10px;
+        display: flex;
+        flex-direction: column;
+        gap: 10px;
+      }
+      #ec-button,
+      #strength-button {
+        width: 120px;
+        height: 45px;
+        border-radius: 5px;
+        font-size: 18px;
+        font-weight: bold;
+        z-index: 1000;
+        cursor: pointer;
+        border: 1px solid #acacac;
+        display: none;
+      }
+      #ec-button:hover,
+      #strength-button:hover {
+        border: 1px solid black;
+      }
+    </style>
+  </head>
+  <body>
+    <div id="container">
+      <div class="buttonBox">
+        <button id="ec-button">效果对比</button
+        ><button id="strength-button">微痕增强</button>
+      </div>
+
+      <model-viewer
+        id="sphere"
+        src="./static/gltf-13/low-13.gltf"
+        camera-controls
+        
+        tone-mapping="aces"
+        shadow-intensity="0"
+        interaction-prompt="none"
+        zoom-sensitivity="2.5"
+      >
+      </model-viewer>
+      <div class="splitBox">
+        <div class="splitLine"></div>
+      </div>
+    </div>
+    <!-- <script type="module" src="./mobile.js"></script> -->
+    <script type="importmap">
+      {
+        "imports": {
+          "three": "https://cdn.jsdelivr.net/npm/three@0.160.0/examples/js/controls/TrackballControls.js"
+        }
+      }
+    </script>
+
+    <script type='module' src="./controls.js"></script>
+    <script type="module" src="./main.js"></script>
+    <script type="module" src="./model-viewer.min.js"></script>
+  </body>
+</html>

+ 261 - 0
main.js

@@ -0,0 +1,261 @@
+const modelViewerParameters = document.querySelector("model-viewer#sphere")
+const ecButton = document.querySelector("#ec-button")
+const strengthButton = document.querySelector("#strength-button")
+const splitBox = document.querySelector(".splitBox")
+const container = document.querySelector("#container")
+const buttonBox = document.querySelector(".buttonBox")
+
+// 需要切换的贴图
+let bmTexture = null
+let bmTexture2 = null
+// 金属度粗糙度贴图
+let mRTexture = null
+
+// 当前贴图
+let material = null
+
+
+// 进入页面,加载好贴图
+modelViewerParameters.addEventListener("load", () => {
+  strengthButton.style.display = "block"
+  ecButton.style.display = "block"
+  material = modelViewerParameters.model.materials[0]
+  // 设置金属度粗糙度
+  // material.pbrMetallicRoughness.setMetallicFactor(0);
+  // material.pbrMetallicRoughness.setRoughnessFactor(0.66)
+  // 设置baseColor贴图
+
+  bmTexture = material.pbrMetallicRoughness.baseColorTexture.texture
+  // 设置金属度粗糙度贴图
+  modelViewerParameters.createTexture("./static/gltf-13/2_r-4k.jpg").then((texture) => {
+    material.pbrMetallicRoughness.metallicRoughnessTexture.setTexture(texture)
+    mRTexture = texture
+  })
+  modelViewerParameters.createTexture("./static/gltf-13/bm-color.jpg").then((texture) => {
+    bmTexture2 = texture
+  })
+  modelViewerParameters.createTexture("./static/gltf-13/color_normal.jpg").then((texture) => {
+    material.normalTexture.setTexture(texture)
+  })
+
+})
+
+// 创建第二个模型
+const modelViewerParameters2 = modelViewerParameters.cloneNode(true)
+modelViewerParameters2.id = "sphere2"
+modelViewerParameters2.style.display = "none"
+document.querySelector("#container").appendChild(modelViewerParameters2)
+// 加载第二个模型
+modelViewerParameters2.addEventListener("load", () => {
+  const material2 = modelViewerParameters2.model.materials[0]
+  modelViewerParameters2.createTexture("./static/gltf-13/2_r-4k.jpg").then((texture) => {
+    material2.pbrMetallicRoughness.metallicRoughnessTexture.setTexture(texture)
+  })
+  modelViewerParameters2.createTexture("./static/gltf-13/bm-color.jpg").then((texture) => {
+    material2.pbrMetallicRoughness.baseColorTexture.setTexture(texture)
+  })
+  material2.pbrMetallicRoughness.setBaseColorFactor([0.3137, 0.3137, 0.3137])
+  console.log(material2)
+})
+
+const closeCompare = () => {
+  modelViewerParameters2.style.display = "none"
+  modelViewerParameters.style.width = "100%"
+  modelViewerParameters.style.height = "100%"
+  modelViewerParameters.style.maxWidth = "100%"
+  splitBox.style.display = "none"
+  ecButton.style.backgroundColor = "#f0f0f0"
+  container.style.flexDirection = "row"
+
+  isEcClick = true
+}
+
+const openComparePC = () => {
+  modelViewerParameters2.style.display = "block"
+  modelViewerParameters.style.width = "49.5%"
+  modelViewerParameters.style.maxWidth = "80%"
+  splitBox.style.display = "flex"
+  ecButton.style.backgroundColor = "#ccc"
+  startSync()
+  isEcClick = false
+}
+
+const openCompareM = () => {
+  modelViewerParameters2.style.display = "block"
+  modelViewerParameters2.style.width = "100%"
+  modelViewerParameters2.style.height = "0%"
+  modelViewerParameters.style.height = "50%"
+  container.style.flexDirection = "column"
+  ecButton.style.backgroundColor = "#ccc"
+
+  startSync()
+  isEcClick = false
+}
+
+// 点击切换展示第二个模型
+let isEcClick = true
+ecButton.addEventListener("click", () => {
+  if (isEcClick) {
+    if (isMobile) {
+      openCompareM()
+    } else {
+      openComparePC()
+    }
+  } else {
+    closeCompare()
+  }
+})
+
+// 点击增强按钮切换贴图
+let isStrengthClick = true
+strengthButton.addEventListener("click", () => {
+  console.log("点击了")
+  if (isStrengthClick) {
+    material.pbrMetallicRoughness.baseColorTexture.setTexture(bmTexture2)
+    material.pbrMetallicRoughness.setBaseColorFactor([0.3137, 0.3137, 0.3137])
+    strengthButton.style.backgroundColor = "#ccc"
+    isStrengthClick = false
+    closeCompare()
+  } else {
+    material.pbrMetallicRoughness.baseColorTexture.setTexture(bmTexture)
+    material.pbrMetallicRoughness.setBaseColorFactor([1, 1, 1])
+    strengthButton.style.backgroundColor = "#f0f0f0"
+    isStrengthClick = true
+  }
+
+})
+
+
+// 拖动分割线
+splitBox.addEventListener("mousedown", (e) => {
+  e.preventDefault() // 阻止默认行为
+
+  const containerWidth = container.offsetWidth
+  const startX = e.clientX
+  const startWidth = modelViewerParameters.offsetWidth
+
+  // 保存事件处理函数的引用以便后续移除
+  const onMouseMove = (e) => {
+    const moveX = e.clientX
+    const moveWidth = moveX - startX
+    const newWidth = startWidth + moveWidth
+
+    // 更新左右面板的宽度
+    modelViewerParameters.style.width = newWidth + "px"
+    modelViewerParameters2.style.width = (containerWidth - newWidth) + "px"
+  }
+
+  const onMouseUp = () => {
+    document.removeEventListener("mousemove", onMouseMove)
+    document.removeEventListener("mouseup", onMouseUp)
+  }
+
+  document.addEventListener("mousemove", onMouseMove)
+  document.addEventListener("mouseup", onMouseUp)
+})
+
+// 实时同步
+function startSync() {
+  modelViewerParameters.addEventListener('mousemove', () => {
+    modelViewerParameters.addEventListener('camera-change', handleCameraChange)
+    modelViewerParameters2.removeEventListener('camera-change', handleCameraChange2)
+  })
+  modelViewerParameters2.addEventListener('mousemove', () => {
+    modelViewerParameters2.addEventListener('camera-change', handleCameraChange2)
+    modelViewerParameters.removeEventListener('camera-change', handleCameraChange)
+  })
+  // 兼容移动端
+  modelViewerParameters.addEventListener('touchmove', () => {
+    modelViewerParameters.addEventListener('camera-change', handleCameraChange)
+    modelViewerParameters2.removeEventListener('camera-change', handleCameraChange2)
+  })
+  modelViewerParameters2.addEventListener('touchmove', () => {
+    modelViewerParameters2.addEventListener('camera-change', handleCameraChange2)
+    modelViewerParameters.removeEventListener('camera-change', handleCameraChange)
+  })
+  // 兼容鼠标滚轮
+  modelViewerParameters.addEventListener('wheel', () => {
+    modelViewerParameters.addEventListener('camera-change', handleCameraChange)
+    modelViewerParameters2.removeEventListener('camera-change', handleCameraChange2)
+  })
+  modelViewerParameters2.addEventListener('wheel', () => {
+    modelViewerParameters2.addEventListener('camera-change', handleCameraChange2)
+    modelViewerParameters.removeEventListener('camera-change', handleCameraChange)
+  })
+}
+
+// 相机变化处理
+function handleCameraChange() {
+  // 如果正在同步中,则不处理此次事件,避免循环触发
+  modelViewerParameters2.removeEventListener('camera-change', handleCameraChange2)
+  // 获取相机参数
+  const orbit = modelViewerParameters.getCameraOrbit()
+  const targetPoint = modelViewerParameters.getCameraTarget()
+  const fov = modelViewerParameters.getFieldOfView()
+
+  // 设置到目标viewer
+  modelViewerParameters2.cameraOrbit = orbit.toString()
+  modelViewerParameters2.cameraTarget = targetPoint.toString()
+  modelViewerParameters2.fieldOfView = `${fov}deg`
+  modelViewerParameters2.jumpCameraToGoal()
+  console.log(modelViewerParameters.cameraOrbit, 'modelViewerParameters.cameraOrbit')
+  console.log(modelViewerParameters2.cameraOrbit, 'modelViewerParameters2.cameraOrbit')
+}
+
+function handleCameraChange2() {
+  modelViewerParameters.removeEventListener('camera-change', handleCameraChange)
+  // 获取相机参数
+  const orbit = modelViewerParameters2.getCameraOrbit()
+  const targetPoint = modelViewerParameters2.getCameraTarget()
+  const fov = modelViewerParameters2.getFieldOfView()
+
+  // 设置到目标viewer
+  modelViewerParameters.cameraOrbit = orbit.toString()
+  modelViewerParameters.cameraTarget = targetPoint.toString()
+  modelViewerParameters.fieldOfView = `${fov}deg`
+  modelViewerParameters.jumpCameraToGoal()
+  // modelViewerParameters2.cameraOrbit = orbit.toString()
+  // modelViewerParameters2.cameraTarget = targetPoint.toString()
+  // modelViewerParameters2.fieldOfView = `${fov}deg`
+  // modelViewerParameters.jumpCameraToGoal()
+
+  console.log(modelViewerParameters.cameraOrbit, 'modelViewerParameters.cameraOrbit')
+  console.log(modelViewerParameters2.cameraOrbit, 'modelViewerParameters2.cameraOrbit')
+}
+
+
+let isMobile = false
+// 页面旋转和缩放处理函数
+function handleOrientationChange() {
+  const screenWidth = document.documentElement.clientWidth
+  const screenHeight = document.documentElement.clientHeight
+
+  console.log(screenWidth, screenHeight)
+
+  if (screenWidth <= screenHeight) {
+    // 竖屏模式
+    console.log('竖屏模式')
+    isMobile = true
+    modelViewerParameters.orientation = '-90deg 0deg 0deg'
+    modelViewerParameters2.orientation = '-90deg 0deg 0deg'
+    splitBox.style.display = 'none'
+    buttonBox.style.transform = "rotate(90deg) translate(-10px, -10px)"
+    buttonBox.style.transformOrigin = "center"
+    buttonBox.style.bottom = "10px"
+    buttonBox.style.top = "auto"
+    buttonBox.style.zIndex = "1"
+  } else {
+    isMobile = false
+    modelViewerParameters.orientation = '0deg 0deg 0deg'
+    modelViewerParameters2.orientation = '0deg 0deg 0deg'
+  }
+}
+
+
+// 页面加载完成后初始化
+window.addEventListener('load', () => {
+  handleOrientationChange()
+})
+
+// 监听窗口大小变化
+window.addEventListener('resize', handleOrientationChange)

Разница между файлами не показана из-за своего большого размера
+ 1082 - 0
model-viewer.min.js


BIN
static/gltf-13/1_normal.jpg


BIN
static/gltf-13/2_ao.jpg


BIN
static/gltf-13/2_r-4k.jpg


BIN
static/gltf-13/2_r.jpg


BIN
static/gltf-13/35-basecolor.jpg


BIN
static/gltf-13/bm-color.jpg


BIN
static/gltf-13/color_normal.jpg


BIN
static/gltf-13/low-13.bin


+ 162 - 0
static/gltf-13/low-13.gltf

@@ -0,0 +1,162 @@
+{
+    "asset" : {
+        "generator" : "Khronos glTF Blender I/O v3.2.43",
+        "version" : "2.0"
+    },
+    "scene" : 0,
+    "scenes" : [
+        {
+            "name" : "Scene",
+            "nodes" : [
+                0
+            ]
+        }
+    ],
+    "nodes" : [
+        {
+            "mesh" : 0,
+            "name" : "low-13",
+            "rotation" : [
+                0.7071068286895752,
+                0,
+                0,
+                0.7071067094802856
+            ]
+        }
+    ],
+    "materials" : [
+        {
+            "name" : "Default",
+            "normalTexture" : {
+                "index" : 0
+            },
+            "occlusionTexture" : {
+                "index" : 1
+            },
+            "pbrMetallicRoughness" : {
+                "baseColorTexture" : {
+                    "index" : 2
+                },
+                "metallicFactor" : 0,
+                "roughnessFactor" : 0.5
+            }
+        }
+    ],
+    "meshes" : [
+        {
+            "name" : "low-13",
+            "primitives" : [
+                {
+                    "attributes" : {
+                        "POSITION" : 0,
+                        "NORMAL" : 1,
+                        "TEXCOORD_0" : 2
+                    },
+                    "indices" : 3,
+                    "material" : 0
+                }
+            ]
+        }
+    ],
+    "textures" : [
+        {
+            "sampler" : 0,
+            "source" : 0
+        },
+        {
+            "sampler" : 0,
+            "source" : 1
+        },
+        {
+            "sampler" : 0,
+            "source" : 2
+        }
+    ],
+    "images" : [
+        {
+            "mimeType" : "image/jpeg",
+            "name" : "1_normal",
+            "uri" : "1_normal.jpg"
+        },
+        {
+            "mimeType" : "image/jpeg",
+            "name" : "2_ao",
+            "uri" : "2_ao.jpg"
+        },
+        {
+            "mimeType" : "image/jpeg",
+            "name" : "35-basecolor",
+            "uri" : "35-basecolor.jpg"
+        }
+    ],
+    "accessors" : [
+        {
+            "bufferView" : 0,
+            "componentType" : 5126,
+            "count" : 66152,
+            "max" : [
+                27.113290786743164,
+                3.5846190452575684,
+                36.51493835449219
+            ],
+            "min" : [
+                -27.133544921875,
+                -3.682961940765381,
+                -36.53605270385742
+            ],
+            "type" : "VEC3"
+        },
+        {
+            "bufferView" : 1,
+            "componentType" : 5126,
+            "count" : 66152,
+            "type" : "VEC3"
+        },
+        {
+            "bufferView" : 2,
+            "componentType" : 5126,
+            "count" : 66152,
+            "type" : "VEC2"
+        },
+        {
+            "bufferView" : 3,
+            "componentType" : 5125,
+            "count" : 390594,
+            "type" : "SCALAR"
+        }
+    ],
+    "bufferViews" : [
+        {
+            "buffer" : 0,
+            "byteLength" : 793824,
+            "byteOffset" : 0
+        },
+        {
+            "buffer" : 0,
+            "byteLength" : 793824,
+            "byteOffset" : 793824
+        },
+        {
+            "buffer" : 0,
+            "byteLength" : 529216,
+            "byteOffset" : 1587648
+        },
+        {
+            "buffer" : 0,
+            "byteLength" : 1562376,
+            "byteOffset" : 2116864
+        }
+    ],
+    "samplers" : [
+        {
+            "magFilter" : 9729,
+            "minFilter" : 9987
+        }
+    ],
+    "buffers" : [
+        {
+            "byteLength" : 3679240,
+            "uri" : "low-13.bin"
+        }
+    ]
+}

Разница между файлами не показана из-за своего большого размера
+ 48830 - 0
three.core.js


Разница между файлами не показана из-за своего большого размера
+ 17327 - 0
three.module.js