前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >不用3d引擎实现炫酷“真”裸眼3d效果!

不用3d引擎实现炫酷“真”裸眼3d效果!

原创
作者头像
治电小白菜
修改2025-01-03 09:45:13
修改2025-01-03 09:45:13
1580
举报
文章被收录于专栏:技术综合技术综合

常见方法

可以看到网上大部分网页实现裸眼3d效果有两种

  1. 使用three.js渲染3d模型,然后实现视角切换。
  2. 通过几个图片做出视差偏移效果。

第一种对性能要求比较大,毕竟需要页面进行3d渲染;第二种属于一种伪3d效果,图片都是平面的,并不会因为视角的切换看到另一面的东西。

我的方案

demo地址:https://klren0312.github.io/glasses-free-3d-view/

通过鼠标从左往右移动,来对一张大的宫格图的不同位置进行切换,来实现不同视角的切换。

实现

1. 生成一张类似的宫格图

首先,访问链接下载blender插件: https://github.com/regcs/AliceLG/releases

在blender中安装插件,Edit->Preferences->Add-ons->Install

安装后记得安装依赖,会有红色按钮,点击后安装依赖,然后重启blender。会在工具栏看到插件

然后,需要设置渲染图片的输出地址

设置裁切面起始和结束的位置,以及焦距平面的位置,保证你的模型在这个范围里

然后点击Render Quilt渲染宫格图

点击保存即可拿到图片啦

2. 分析宫格图

可以看到他是以右上角作为最左边的一个视角图,再往下到左下角作为最右边的一个视角图。

3. 编写代码

1)图片展示区域

transform-style: preserve-3d; 设置元素的子元素是位于 3D 空间中。

代码语言:html
复制
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>3d image block</title>
  <style>
    html,
    body {
      width: 100%;
      height: 100%;
      background: #02020c;
    }

    .image-container {
      margin-top: 20px;
      display: flex;
      justify-content: center;
      background: #000;
    }

    .image-block {
      width: 480px;
      height: 636px;
      overflow: hidden;
      transform-style: preserve-3d;
      border-radius: 5px;
    }

    .inner {
      width: 100%;
      height: 100%;
      transform-style: preserve-3d;
    }

    .img {
      background-size: 100% 100%;
      opacity: 1;
    }

  </style>
</head>

<body>
  <!-- ======================= 3d image block start ==========================  -->
  <div id="js-container" class="image-container">
    <div id="js-block" class="image-block">
      <div class="inner">
        <div class="img" id="js-img"></div>
      </div>
    </div>
  </div>
2)js代码

上面分析的宫格图顺序,可以通过getCoordinates方法来进行宫格图定位,通过传入行列数和当前序号,来找到当前图片的位置。

代码语言:js
复制
/**
 * 获取坐标
 * @param {*} rows 行数
 * @param {*} cols 列数
 * @param {*} index 索引
 * @returns 
 */
function getCoordinates(rows, cols, index) {
    const adjustedIndex = index + skipImages // 调整索引以跳过前几张图
    const row = Math.floor(adjustedIndex / cols)
    const col = cols - 1 - (adjustedIndex % cols)
    return { rowNum: row, colNum: col }
}

监听mousemove事件,计算出鼠标移动的速度

代码语言:js
复制
/**
 * 鼠标移动
 */
block.addEventListener('mousemove', function (e) {
    const rect = block.getBoundingClientRect()
    const currentMouseX = e.clientX - rect.left
    const currentTime = Date.now()

    // 计算鼠标速度
    if (lastTime !== 0) {
        const deltaX = currentMouseX - lastMouseX
        const deltaTime = currentTime - lastTime
        mouseSpeed = deltaX / deltaTime // 速度 = 距离 / 时间

        // 限制最大速度
        if (mouseSpeed > maxSpeed) {
            mouseSpeed = maxSpeed
        } else if (mouseSpeed < -maxSpeed) {
            mouseSpeed = -maxSpeed
        }
    }

    lastMouseX = currentMouseX
    lastTime = currentTime
    targetX = currentMouseX
})

然后通过requestAnimationFrame来进行每帧的图片切换,并添加惯性,让视角切换更流畅。通过添加div的旋转角度,让3d效果更佳明显。

代码语言:js
复制
/**
 * 动画, 每帧执行
 */
function animate() {
    // 使用缓动函数平滑速度变化
    velocity += (targetX - currentX) * 0.05 * Math.abs(mouseSpeed)
    velocity *= friction
    currentX += velocity

    // 计算当前索引
    const blockWidth = block.offsetWidth
    const unitWidth = Math.round(blockWidth / totalViews)
    let theIndex = Math.round(currentX / unitWidth)

    // 确保索引在有效范围内
    if (theIndex < 0) {
    theIndex = 0
    } else if (theIndex > totalViews - 1) {
    theIndex = totalViews - 1
    }

    const { rowNum, colNum } = getCoordinates(row, col, theIndex)

    // 设置旋转角度和视点图
    const container = document.getElementById('js-container')
    const rotateY = (currentX - blockWidth / 2) / blockWidth * 30
    block.style.transform = `perspective(${container.offsetHeight ? container.offsetHeight * 2 : 2400}px) rotateY(${rotateY}deg)`
    img.style.transform = `translate(calc(-100% / ${col} * ${colNum}),calc(-100% / ${row} * ${rowNum}))`

    // 继续动画
    animationFrameId = requestAnimationFrame(animate)
}

4. 效果展示

https://klren0312.github.io/glasses-free-3d-view/test.html

5. 完整代码

代码语言:html
复制
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>3d image block</title>
  <style>
    html,
    body {
      width: 100%;
      height: 100%;
      background: #02020c;
    }

    .image-container {
      margin-top: 20px;
      display: flex;
      justify-content: center;
      background: #000;
    }

    .image-block {
      width: 480px;
      height: 636px;
      overflow: hidden;
      transform-style: preserve-3d;
      border-radius: 5px;
    }

    .inner {
      width: 100%;
      height: 100%;
      transform-style: preserve-3d;
    }

    .img {
      background-size: 100% 100%;
      opacity: 1;
    }

  </style>
</head>

<body>
  <!-- ======================= 3d image block start ==========================  -->
  <div id="js-container" class="image-container">
    <div id="js-block" class="image-block">
      <div class="inner">
        <div class="img" id="js-img"></div>
      </div>
    </div>
  </div>
  <script>
    const img = document.getElementById('js-img')
    const block = document.getElementById('js-block')

    let row = 6 // 行数
    let col = 11 // 列数
    let skipImages = 0 // 需要跳过的图像数量
    let totalViews = row * col - skipImages // 总视图数

    let initialIndex = Math.floor(totalViews / 2) // 计算中间索引
    let { rowNum: initialRow, colNum: initialCol } = getCoordinates(row, col, initialIndex)

    let currentX = 0 // 当前位置
    let targetX = 0 // 目标位置
    let velocity = 0 // 速度
    let friction = 0.85 // 摩擦系数

    let lastMouseX = 0 // 上一次鼠标位置
    let lastTime = 0 // 上一次时间
    let mouseSpeed = 0 // 鼠标速度
    const maxSpeed = 40 // 增大最大速度限制
    let lastVelocity = 0 // 添加变量记录最后的速度
    let animationFrameId // requestAnimationFrame 的 ID

    // 设置显示的图片
    img.style.width = `calc(100% * ${col})`
    img.style.height = `calc(100% * ${row})`
    img.style.backgroundImage = `url(./images/test.png)`
    img.style.transform = `translate(calc(-100% / ${col} * ${initialCol}),calc(-100% / ${row} * ${initialRow}))`

    /**
     * 鼠标移动
     */
    block.addEventListener('mousemove', function (e) {
      const rect = block.getBoundingClientRect()
      const currentMouseX = e.clientX - rect.left
      const currentTime = Date.now()

      // 计算鼠标速度
      if (lastTime !== 0) {
        const deltaX = currentMouseX - lastMouseX
        const deltaTime = currentTime - lastTime
        mouseSpeed = deltaX / deltaTime // 速度 = 距离 / 时间

        // 限制最大速度
        if (mouseSpeed > maxSpeed) {
          mouseSpeed = maxSpeed
        } else if (mouseSpeed < -maxSpeed) {
          mouseSpeed = -maxSpeed
        }
      }

      lastMouseX = currentMouseX
      lastTime = currentTime
      targetX = currentMouseX
    })

    // 调用初始设置函数
    initialSetup()
    // 启动动画
    animate()

    /**
     * 初始化时设置中间图片和角度为0
     */
    function initialSetup() {
      img.style.transform = `translate(calc(-100% / ${col} * ${initialCol}),calc(-100% / ${row} * ${initialRow}))`
      block.style.transform = 'perspective(2400px) rotateY(0deg)'
    }

    /**
     * 动画, 每帧执行
     */
    function animate() {
      // 使用缓动函数平滑速度变化
      velocity += (targetX - currentX) * 0.05 * Math.abs(mouseSpeed)
      velocity *= friction
      currentX += velocity

      // 计算当前索引
      const blockWidth = block.offsetWidth
      const unitWidth = Math.round(blockWidth / totalViews)
      let theIndex = Math.round(currentX / unitWidth)

      // 确保索引在有效范围内
      if (theIndex < 0) {
        theIndex = 0
      } else if (theIndex > totalViews - 1) {
        theIndex = totalViews - 1
      }

      const { rowNum, colNum } = getCoordinates(row, col, theIndex)

      // 设置旋转角度和视点图
      const container = document.getElementById('js-container')
      const rotateY = (currentX - blockWidth / 2) / blockWidth * 30
      block.style.transform = `perspective(${container.offsetHeight ? container.offsetHeight * 2 : 2400}px) rotateY(${rotateY}deg)`
      img.style.transform = `translate(calc(-100% / ${col} * ${colNum}),calc(-100% / ${row} * ${rowNum}))`

      // 继续动画
      animationFrameId = requestAnimationFrame(animate)
    }

 

    /**
     * 获取坐标
     * @param {*} rows 行数
     * @param {*} cols 列数
     * @param {*} index 索引
     * @returns 
     */
    function getCoordinates(rows, cols, index) {
      const adjustedIndex = index + skipImages // 调整索引以跳过前几张图
      const row = Math.floor(adjustedIndex / cols)
      const col = cols - 1 - (adjustedIndex % cols)
      return { rowNum: row, colNum: col }
    }

  </script>
</body>

</html>

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 常见方法
  • 我的方案
  • 实现
    • 1. 生成一张类似的宫格图
    • 2. 分析宫格图
    • 3. 编写代码
      • 1)图片展示区域
      • 2)js代码
    • 4. 效果展示
    • 5. 完整代码
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档