如何用Three.js + Blender打造一个web 3D展览馆

news2025/1/11 5:54:44

作者:vivo 互联网前端团队- Wei Xing 

运营活动新玩法层出不穷,web 3D炙手可热,本文将一步步带大家了解如何利用Three.js和Blender来打造一个沉浸式web 3D展览馆。

一、前言

3D展览馆是什么,先来预览下效果:

图片

看起来像个3D冒险类手游,用户可以操纵屏幕中央的虚拟摇杆,以第一人称视角在房间内自由移动、看展览。

1.1 为什么做3D展览馆

首先介绍一个背景,我们的工作内容是做游戏中心的用户运营活动,会做些好玩的活动让用户参与,并get一些福利。

当时的活动背景是我司一年一度的vivo游戏节,并且元宇宙是大热词。所以做它的原因有几个:

  • vivo游戏节主题

  • 契合元宇宙热点

  • 新玩法、新体验

1.2 技术选型

用到的组合方案:Three.js + Blender

  • why Three.js

开源的3D框架有很多,但最常用的有两种:Three.js、Babylon.js,我们只需要从中二选一。分析后发现两者各有优势:

图片

考虑到3D展览馆的几个基本特性:

  1. 简单的小型3D场景,没有复杂的交互(对镜头的要求不高)

  2. 投放在移动设备,需要尽可能小的包体,以提升性能

  3. 工期短,需要快速上手及更多的案例参考

Three.js包体更小、有更多参考案例、上手更快,所以虽然Babylon.js有它的优势,但Three.js更适合这个项目。

  • why Blender

Blender是一款轻量的开源3D建模软件,有很多好用的免费插件,而且Blender能导出GLTF / GLB模型(后面会对GLTF / GLB模型做简介),匹配Three.js的使用方式,整体更简单好用一些。

所以,就是它了。

二、实践部分

2.1 了解GLTF / GLB模型

在进入开发之前,先简单了解Blender和GLTF / GLB模型。

  • 简单了解 Blender

首先,Blender大概长这样,图中是设计师交付的3D展览馆稿子。简单理解为,左侧是模型的层次结构,中间是模型的预览效果,右侧是模型的属性面板。

一般来说,作为开发者我们不需要掌握太多Blender相关知识,只需知道如何看懂模型结构、导出GLTF / GLB模型以及烘焙的基本原理即可。

图片

  • GLTF / GLB模型

GLTF(Graphics Language Transmission Format)是一种标准的3D模型文件格式,它以JSON的形式存储3D模型信息,例如模型的层次结构、材质、动画、纹理等。

模型中依赖的静态资源,比如图片,可以通过外部URI的方式来引入,也可以转成base64直接插入在GLTF文件中。

它包含两种形式的后缀,分别是.gltf(JSON/ASCII).glb(Binary)。.gltf是以JSON的形式存储信息。.glb则是.gltf的扩展格式,它以二进制的形式存储信息,因此导出的模型体积也更小一些。如果我们不需要通过JSON对.gltf模型进行直接修改,建议使用.glb模型,它更小、加载更快。

  • Blender导出GLTF / GLB模型

在blender中,可以直接将模型导出为GLTF / GLB格式,三种选项的差别不再赘述,我们先简单选择最高效的.glb格式。

图片

有了模型之后,我们可以开始通过Three.js创建场景,并导入这个模型了。

2.2 Three.js 加载模型

为了防止篇幅过长,这里假设大家已经掌握了Three.js的一些基本语法。文章重点放在如何加载模型,并一步步进行调优和实现最终的3D展览馆效果。

怎么加载一个模型?

(1)创建一个空场景

首先创建一个空场景scene,后续所有的模型或材质都会被添加到这个场景中。

import * as THREE from 'three'

// 1. 创建场景
const scene = new THREE.Scene(); 


// 2. 创建镜头
const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
// 3. 创建Renderer
const renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );

(2)导入GLTF / GLB模型

通过GLTFLoader导入.glb模型,并添加到场景中。

import GLTFLoader from 'GLTFLoader'
const loader = new GLTFLoader()
loader.load('path/to/gallery.glb',
  gltf => {
    scene.add(gltf.scene) // 添加到场景中
  } 
}

(3)开始渲染

通过requestAnimationFrame来调用renderer.render方法,开始实时渲染场景。

function animate() {
      requestAnimationFrame( animate );
      renderer.render( scene, camera );
}
animate();

ok,这样我们就完成了3D模型的导入,但是发现整个场景一片漆黑。

图片

试试加个环境光。

const ambientLight = new THREE.AmbientLight(0xffffff, 1)

scene.add(ambientLight)

图片

ok,亮起来了,但是效果依然很差,很劣质。

原因是模型中的材质效果、光源、阴影、环境纹理,这些全都丢失了,所以当我们导入模型时,看到的就是一堆简陋的纯色形状。

所以我们要一步步将这些丢失东西找回,还原设计稿。

2.3 还原设计稿

接下来一步步还原设计稿。

(1)加上光源

查看Blender模型,看到设计稿中添加了一堆点光源、平行光源。

图片

点光源可以理解为房间中的灯泡,光线强弱随着距离衰减;

平行光源可以理解为太阳的直射光,它和点光源不同,光线强弱不随着距离衰减。

于是我们也增加一些光源:

// 一些灯光选项
// 如果是平行光则没有distance、decay选项
const lightOptions = [

  {
    type: 'point', // 灯光类型:1. point点光源、2. directional平行光源
    color: 0xfff0bf, // 灯光颜色
    intensity: 0.4, // 灯光强度
    distance: 30,    // 光照距离
    decay: 2,    // 衰减速度
    position: { // 光源位置
      x: 2,
      y: 6,
      z: 0
    }
  },
  ...

]

function createLights() {
  pointLightOptions.forEach(option => {
    const light = option.type === 'point' ?
        new THREE.PointLight(option.color, option.intensity, option.distance, option.decay) :
        new THREE.DirectionalLight(option.color, option.intensity)
    const position = option.position
    light.position.set(position.x, position.y, position.z)
    scene.add(light)
  })
}

createLights()

可以看到场景比之前好了一些,有了光源后,模型变得立体和真实了,多了一些反色的光泽。

图片

图片

但是我们注意到,画面中的logo、长椅的两侧都是黑色的,并且旁边的球体、椅子等都显得不够真实。

所以,我们需要进行下一步调整:调整模型材质、增加环境纹理。

(2)调整模型材质,增加环境纹理

先简单了解一下材质和环境纹理。

  • 材质(material)

材质就像物体的皮肤,我们可以调整皮肤的光泽、金属度、粗糙度、透明与否等属性,让物体有不同的视觉效果。

一般从blender导出的模型中,已经包含了一些材质属性,但是Three.js中的材质属性和Blender中的属性并非完全的映射关系,模型在导入到Three.js后,效果和设计稿会有差异。这时候我们需要手动调整材质的属性,来达到和设计稿近似的效果。

  • 环境纹理(environment map)

环境纹理就是让模型映射周围的环境,让场景或物体更真实。例如我们要渲染一个立方体,把立方体放进一个屋子里,这个屋子的环境就会影响立方体的渲染效果。

比如镜面的物体被贴上环境纹理后,就可以实时反射周围的环境镜像,看起来很real。

设计稿中也是将一个大厅作为了环境纹理,让场景更真实。

图片

环境纹理分为:球形纹理和立方体形纹理。两者都可以,这里我们采用一张大厅的球形纹理作为环境贴图。

图片

以画面中的vivo游戏节logo为例,我们通过调整它的材质和环境纹理,让它变得更真实。

  1. 根据在blender中的命名,找到logo模型

  2. 调整logo的表面粗糙度和金属度

  3. 加载并设置环境纹理贴图

图片

const loader = new GLTFLoader()
loader.load('path/to/gallery.glb',
  gltf => {
      // 1. 根据Blender中物体的名字,找到logo模型
      gltf.scene.traverse(child => {
        if (isLogo(child)) {
       initLogo(child)   // 2. 调整材质
       setEnvMap(child)  // 3. 设置环境纹理
      }
      })
    scene.add(gltf.scene)
  } 
}


// 判断是否为Logo
const isLogo = object.name === 'logo'

function initLogo(object) {
  object.material.roughness = 0   // 调整表面粗糙度
  object.material.metalness = 1   // 调整金属度
}
// 加载环境纹理

let envMap
const envmaploader = new THREE.PMREMGenerator(renderer)

const setEnvMap = (object) => {
      if(envMap) {
        object.material.envMap = envMap.texture
      } else {
        textureLoader.load('path/to/envMap.jpg',
             texture => {
             texture.encoding = THREE.sRGBEncoding
             envMap = envmaploader.fromCubemap(texture)
                 object.material.envMap = envMap.texture
             })
      }
}

经过上面的处理后,可以看到原先黑色的logo有了金属光泽,并且会反射周围的环境纹理。

其它物体经过类似的处理后,也变得更真实一些。

图片

图片

图片

图片

现在整个场景更接近了设计稿一些,但场景中少了阴影,显得很干瘪。

加上阴影。

(3)增加阴影

增加阴影分四步:

  1. 对renderer开启阴影支持:renderer.shadowMap.enabled = true

  2. 对光源设置:castShadow = true

  3. 对需要投影的物体设置:castShadow = true

  4. 对需要被投影的平面或物体(比如地板)设置:receiveShadow = true

// 1. renderer
const renderer = new THREE.WebGLRenderer()
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;

// 2. light
const light = new THREE.DirectionalLight()
light.castShadow = true;

// 3. object
gltf.scene.traverse(function (child) {
   if (child.isMesh) {
     child.castShadow = true;
   }
});
// 4. floor
floor.receiveShadow = true

图片

添加阴影后,有质的提升,发现整个场景立体了很多,此时还原度已经很高。

如果不考虑性能损耗,这个场景的样式已经可以投入使用了。(后续会提到性能优化)

小结一下,刚刚做的几件事:

  1. 添加光源

  2. 调整模型材质、增加环境纹理

  3. 增加阴影

现在3D展览馆场景已经还原的差不多了,接下来要构造一个虚拟移动摇杆,控制第一人称镜头的移动和转向,实现沉浸式逛展的效果。

2.4 虚拟移动摇杆

要实现通过虚拟移动摇杆控制镜头的移动和转向,我们需要三个东西:

  • 一个移动摇杆(handler)

  • 一个长方体(player):用于承载第一人称视角

  • 一个镜头(camera):之前已经创建过了

有人会问为什么需要一个player,通过摇杆直接控制镜头不就行了吗?其实player的作用是用于做碰撞检测,当player遇到凳子、墙壁等障碍物时,需要停止镜头移动。直接控制镜头,是无法做碰撞检测的。

所以,实际上镜头移动的逻辑是:

用户操纵摇杆 → 更新player位置和朝向 →从而同步更新camera位置和朝向

(1)创建移动摇杆

移动摇杆的实现原理很简单,这里仅做简述。

核心在于创建一个圆盘,监听触摸手势,并根据手势的方向来实时更新move参数,控制镜头的移动和转向。

const speed = 8 // 移动速度
const turnSpeed = 3  // 转向速度
// move option,用于调整第一人称镜头的移动和转向
const move = {
      turn: 0,  // 旋转角度
      forward: 0     // 前进距离
}

// 创建一个handler,并监听手势,调整move option
const handler = new Handler()
handler.onTouchMove = () => { // update move option }

(2)创建player

首先创建一个player对象,它是一个1.2 * 2 * 1的透明长方体。

function createPlayer() {
  const box = new THREE.BoxGeometry(1.2, 2, 1)
  const mat = new THREE.MeshBasicMaterial({
    color: 0x000000,
    wireframe: true
  })

  const mesh = new THREE.Mesh(box, mat)
  box.translate(0, 1, 0)
  return mesh
}

const player = createPlayer() // 创建player
player.position.set(4.5, 2, 12)     // 设置player的初始位置

(3)updatePlayer & updateCamera

每次渲染(render)时,更新player的位置和朝向,并同步更新镜头的位置和朝向。

const clock = THREE.clock()

function render() {
  const dt = clock.delta()   // 获取每帧之间的时间间隔,根据时间间隔长短来更新player和camera的移动距离和转向的多少
  updatePlayer(dt)
  updateCamera(dt)
  renderer.render(scene, camera)
  window.requestAnimationFrame(render)
}

// 更新player的位置和朝向

function updatePlayer(dt) {
  const pos = player.position.clone()
  pos.y -= 1.5 // 降低高度,后续用于计算碰撞检测
  const dir = new THREE.Vector3()
  player.getWorldDirection(dir)
  dir.negate()
  if (move.forward < 0) dir.negate()
  // 调整镜头前进 or 后退

  if (move.forward !== 0) {
    player.translateZ(move.forward > 0 ? -dt * speed : dt * speed * 0.5)
  }
  // 调整镜头朝向

  if (move.turn !== 0) {
    player.rotateY(move.turn * 1.2 * dt)
  }
}

// 根据player的位置和朝向,同步更新camera的位置和朝向

function updateCamera(dt) {
  camera.position.lerp(activeCamera.getWorldPosition(new THREE.Vector3()), 0.08)
  const pos = player.position.clone()
  pos.y += 2.5
  camera.lookAt(pos)
}

注意:render方法中使用clock.delta()来计算每次渲染之间的时间间隔,并使用这个时间间隔来更新player和camera。因为在理想的60帧率情况下,两帧时间间隔为16.67ms,但实际上该数值会有波动,因此我们要根据实际的渲染时间间隔来更新player和camera,让镜头的移动和转向幅度更自然一些。

完成上述步骤后,我们就可以通过控制虚拟移动摇杆,来让镜头移动和转向了。

接下来加入碰撞检测,对镜头移动加点限制。

2.5 碰撞检测

碰撞检测的步骤也很简单:

  • 收集障碍物(colliders)

  • 检测碰撞(基于THREE.Raycaster)

(1)收集障碍物

模型加载完成后,遍历所有的child,如果child是一个物体(mesh),则把它加入到障碍物队列(colliders)中。

const colliders = []

loader.load('path/to/gallery.glb',
  gltf => {
    gltf.scene.traverse(child => {
        // 收集障碍物
        if(isMesh(child)) {
          colliders.push(child) 
        }
    })
  } 
})

(2)检测碰撞

调整刚刚的updatePlayer方法,在其中插入检测碰撞的逻辑。

碰撞检测逻辑基于THREE.Raycaster来实现,racaster可以理解为一个射线,当射线穿过了某个物体,我们就认为射线和物体相交了。

我们让射线的方向和player的朝向保持一致,并且在移动过程中不断判断射线前方/后面是否有相交的物体,如果有相交的物体,且和射线顶点距离distance < 2.5则认为遇到了障碍物,不能再继续前进。

function updatePlayer(dt) {
  const pos = player.position.clone()
  pos.y -= 1.5 // 降低高度,用于计算collision
  const dir = new THREE.Vector3()

  // 获取当前player的朝向
  player.getWorldDirection(dir)
  dir.negate()
  // 如果是向后退,需要对朝向取反
  if (move.forward < 0) dir.negate()

  // 利用Raycaster判断player是否和colliders有碰撞行为
  const raycaster = new THREE.Raycaster(pos, dir)
  let blocked = false

  if (colliders.length > 0) {
    const intersect = raycaster.intersectObjects(colliders)
    if (intersect.length > 0) {
      // 如果相交距离<2.5,表示前方或后面有障碍物
      if (intersect[0].distance < 2.5) {
        blocked = true
      }
    }
  }
  // 如果遇到障碍物,则停滞移动

  if (!blocked) {
    // 调整镜头前进 or 后退
    if (move.forward !== 0) {
      player.translateZ(move.forward > 0 ? -dt * speed : dt * speed * 0.5)
    }
  }

  // 调整镜头朝向
  if (move.turn !== 0) {
    player.rotateY(move.turn * 1.2 * dt)
  }
}

这样镜头的移动和碰撞检测就完成了。

当我们移动到椅子、墙壁等障碍物附近时,镜头会停止移动。镜头的移动范围也被我们限制在房间里,不会穿到房间外部。

图片

三、性能调优

3.1 纹理烘培

3D展览馆的基本功能已经完成了,但还没有做任何的性能调优。当我们把项目运行在手机上,会发现设备发热发烫,帧率很低,低端机型甚至无法运行。

经过分析,实时的光影渲染是罪魁祸首。

页面中有10+个光源,每个光源都在实时投射阴影(尤其是点光源十分消耗资源,引起卡顿)。但实际,场景中的光源和物体位置都没有发生改变,这意味着我们不需要计算实时阴影,只需要固定的阴影。

这点可以通过纹理烘焙来实现。并且在移动端,经过纹理烘焙的光影效果实际上要优于设备计算的实时光影效果。

  • 纹理烘焙(Texture Baking)

纹理烘焙,是指通过将场景效果预渲染到指定纹理上,生成一个模型贴图。在Blender中,我们可以选中任意对象进行烘焙。

图片

以3D展览馆的地板为例,我们可以通过纹理烘焙,将光影效果直接渲染到贴图上。

左图是原本的棋盘格纹理,右图是结合了光影效果的烘焙贴图。烘焙完成后,地板上的光影效果就被固定下来了,我们也不需要再做实时的光影渲染。

图片

图片

用同样的方式,将地板、墙壁、天花板等物体,一一进行烘焙处理,导出一个新的模型。由于光影效果已经被渲染到贴图上,我们可以将大部分光源去掉,只保留2-3个必要的点、平行光源和全局光。再次运行后,发现卡顿、发烫的问题已经不再明显。并且效果其实比实时渲染更精细一些。

图片

图片

这里没有对烘焙做过多介绍,要生成精致的烘焙结果还需要依赖对UV Map、烘焙参数的了解,虽然这些偏向于设计同学的工作,一般由他们来输出烘焙纹理。但是作为开发者,了解了这些后才能和UI更好地沟通和配合。

3.2 优化模型大小

模型大小约为23M,首次加载模型需要9s左右。(尤其是在做完纹理烘焙后,由于贴图变得复杂,模型更大了)

以下是几个优化模型大小的建议:

  1. 优先使用.glb而非.gltf格式。.glb是二进制格式,它比.gltf的JSON格式小25% - 30%左右。

  2. 将纹理(Texture)和模型分离,并行加载。23M的模型中,其实只有2.3M为模型大小,其余都为纹理贴图。将模型和纹理分开后,可以极大减少模型的加载速度。

  3. 使用Draco、gltfpack等工具或一些online compressor来压缩模型(Blender在导出gltf模型时,就带有基于Draco的压缩选项)。本项目通过该步骤压缩了50%的模型大小:3M → 1.2M。

  4. 压缩纹理(Texture)。本项目用到了5张的Texture,压缩后:18M→ 2M。

经过优化,初始模型大小由23M缩小为1.2M,首次加载时间由9s缩短到3s以内。

(左图为优化前,右图为优化后)

图片

图片

四、总结

现在,我们基本完成了整个3D展览馆的开发。虽然有一些细节没有在文中涉及到,但开发过程大致如此。

(1)了解Blender、GLTF / GLB模型

(2)js导入GLTF / GLB模型

(3)还原设计稿

  • 添加光源

  • 调整模型材质、增加环境纹理

  • 增加阴影

(4)实现虚拟移动摇杆,控制镜头移动

(5)增加碰撞检测

(6)性能调优:

  • 纹理烘培:通过纹理烘焙降低实时光影的性能损耗。

  • 优化包体大小:

- 优先使用.glb而非.gltf格式

- 纹理和模型分离

- 压缩模型

- 压缩纹理

五、其他

一些建议:

  • 设计师在Blender中命名物体、材质时要规范化,避免出现奇怪或没有标识意义的命名,因为在开发过程中会使用到,容易混淆。

  • 设计师在在Blender中复用材质要谨慎,避免开发在调整某个材质时,影响到其它使用到相同材质的物体(潜在bug)。

  • 模型加载缓慢时,可以增加loading进度条,缓解等待焦虑。Three.js loader支持加载进度查询。

  • Three.js在不同版本之间,接口频繁变更,在使用时注意版本差异,google问题时也要注意接口兼容性。

  • Three.js实现物体发光效果较繁琐,且消耗性能,设计时可尽量避免使用。

  • Three.js的镜头移动不够丝滑,注重镜头切换流畅性的项目,可以尝试用Babylon.js。

  • 部分浏览器不支持videoTexture(在模型中播放视频),谨慎设计该类型功能,或做好兼容处理。

参考:

  • Threejs+Blender打造3D全景VR画展「1」 – 码语派教室

  • three js blender animation - Google Search

  • Loading Animated Characters in React Three Fiber

  • 浅谈three.js中的needsUpdate - pissang - 博客园

  • Directional Light Shadow - Three.js Tutorials

  • Shadows not working

  • 自适应Shadow Bias算法

  • GLB Modify Material and add emission

  • How can I improve the performance of a three.js script?

  • HTML5 Audio events not triggering on Chrome

  • http://jeromeetienne.github.io/threex.videotexture/examples/videotexture.html

  • 部分代码参考自:GitHub - mayupi/3dvr-gallery: 3dvr gallery with threejs and blender

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/751896.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Maven -- <dependencyManagement>管理子项目版本

背景&#xff1a; 一个旧项目&#xff0c;想使用mybatis-plus&#xff0c;想着这是比较基础的依赖包&#xff0c;就在父项目中添加对应依赖&#xff0c;如下: <!-- 依赖声明 --><dependencyManagement><dependencies><!-- mybatis-plus 依赖配置 -->&l…

第四章:C++模板初阶

系列文章目录 文章目录 系列文章目录前言泛型编程函数模板函数模板概念 函数模板格式函数模板的原理函数模板的实例化模板参数的匹配原则 类模板类模板的定义格式类模板的实例化 总结 前言 C通过泛型编程来实现函数模板和类模板。 泛型编程 如何实现一个通用的交换函数呢&…

图像分割的简史:从CNN到掩模R-CNN

一、说明 在 Athelas&#xff0c;我们使用卷积神经网络 &#xff08;CNN&#xff09; 不仅仅是分类&#xff01;在这篇文章中&#xff0c;我们将看到如何在图像实例分割中使用CNN&#xff0c;并取得很好的效果。 自从 Alex Krizhevsky、Geoff Hinton 和 Ilya Sutskever 在 2012…

Windows bat实现删除文本中的 空行、空格、制表符、最后一行空行

文章目录 一、准备测试数据二、通过for /f实现1. 删除仅含换行符的空行2. 删除所有空白行3. 删除所有空白行以及非空白行左侧的空格和制表符4. 删除所有空白行以及非空白行两侧的空格和制表符5. 删除所有空白行以及所有空格和制表符 三、通过findstr实现1. 删除仅含换行符的空行…

Base64存储为文件

要将Base64编码的字符串存储为文件&#xff0c;您可以按照以下步骤进行操作&#xff1a; 将Base64编码的字符串解码为字节数组。您可以使用Java的Base64类进行解码&#xff0c;例如&#xff1a; import java.util.Base64;String base64String "your_base64_encoded_str…

unity 打包htv vive 客户端包,调不出SteamVR

原因一&#xff1a; 引用自&#xff1a;unity 运行包无法连接SteamVR问题_野区捕龙为宠的博客-CSDN博客 原因二&#xff1a; 1、打包出来的exe文件命名是中文名&#xff0c;如&#xff1a;危化品.exe 2、打包路径中有中文。 避免以上情况即可。 引用自&#xff1a;unity 打包…

算法训练营第三十八天||● 理论基础 ● 509. 斐波那契数 ● 70. 爬楼梯 ● 746. 使用最小花费爬楼梯

● 理论基础 五步走&#xff1a; 确定dp数组 递推公式 dp数组初始化 遍历顺序 打印dp数组 ● 509. 斐波那契数 这道题比较简单&#xff0c;但自己不看答案还做不出来&#xff0c;没想到用初始的方法 先看递归 只需要两行代码 确定终止条件 和逻辑 class Solution { p…

Java-API简析_java.lang.Process类(基于 Latest JDK)(浅析源码)

【版权声明】未经博主同意&#xff0c;谢绝转载&#xff01;&#xff08;请尊重原创&#xff0c;博主保留追究权&#xff09; https://blog.csdn.net/m0_69908381/article/details/131714752 出自【进步*于辰的博客】 因为我发现目前&#xff0c;我对Java-API的学习意识比较薄弱…

软考A计划-系统集成项目管理工程师--项目质量管理-尾

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例点击跳转>软考全系列 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff…

【FAQ】API6低代码开发问题汇总

参考文档&#xff1a; 低代码开发参考文档&#xff1a; 文档中心:使用低代码进行开发 基于景区模板开发元服务&#xff1a; 文档中心:模板简介 使用API6低代码开发遇到的问题汇总情况如下&#xff1a; 1、低代码环境下&#xff0c;如何实现box-shadow阴影效果的配置&#…

第五章:DeepLabV1——深度卷积神经网络和全连接条件随机场的语义图像分割

0.摘要 深度卷积神经网络&#xff08;DCNN&#xff09;在图像分类和目标检测等高级视觉任务中表现出最先进的性能。本研究结合了DCNN和概率图模型的方法&#xff0c;用于解决像素级分类的任务&#xff08;也称为“语义图像分割”&#xff09;。我们发现&#xff0c;DCNN最后一层…

sping boot与JPA结合使用

1、pom.xml文件内容如下&#xff1a; <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http:/…

SOPC之NiosⅡ系统(二)

常用NIOS Ⅱ组件概括 目录 1.时钟组件 2.Nios Ⅱ处理器 2.1 Main 2.2 Vector 2.3 Caches and Memory Interfaces 2.4 Arithmetic Instructions 2.5 JTAG Debug 3.RAM组件 4.System ID组件 5.JTAG UART组件 6.PIO组件 6.1 基础设置(Basic Settings) 6.2 输出寄存器…

应用于智能眼镜上实现检测功能的距离传感芯片

随着智能穿戴设备的发展&#xff0c;各种新型的电子产品不断被研发出来&#xff0c;诸如智能头盔、智能手表、智能手环等等&#xff0c;智能眼镜就是其中一种&#xff0c;国内华为、小米相继推出智能产品。 智能眼镜具有和智能耳机相似的功能&#xff0c;不仅保留眼镜本身功能…

Twitter迎来史上最大危机,超级App能成为其救命武器吗?

前段时间闹得沸沸扬扬的“马扎大战”再出新剧情&#xff0c;继“笼斗”约架被马斯克妈妈及时叫停之后&#xff0c;马斯克在7月9日再次向扎克伯克打起嘴炮&#xff0c;这次不仅怒骂小扎是混蛋&#xff0c;还要公开和他比大小&#xff1f;&#xff01;&#xff01;此番马斯克的疯…

应用开发环境搭建

应用开发环境搭建 1、安装JDK linux上传安装包解压缩设置环境变量判断JDK是否安装成功 2、nginx下载选择Stable version版本下载到本地&#xff08;该版本为Linux版本&#xff09;&#xff0c;下载完成后直接在本地解压后放入linux系统中下载完成后&#xff0c;安装Nginx&#…

android studio 使用lib中的framework.jar编译

本文参考了网上搜索到的内容总结了一下&#xff0c;感谢大神们的无私奉献。 在App中的build.gradle中的android{}下添加&#xff1a; android{...gradle.projectsEvaluated {tasks.withType(JavaCompile) {Set<File> fileSet options.bootstrapClasspath.getFiles()Li…

想知道音频转文字怎么弄吗?看完这篇文章你就懂了

小伙伴们&#xff0c;你们有没有遇到过这样的情况&#xff1a;听到了一段有趣或重要的音频&#xff0c;但却无法方便地保存下来或与他人分享&#xff1f;别担心&#xff0c;现在有很多免费的音频转文字软件app可以帮助我们解决这个问题。不过&#xff0c;这些音频转文字软件app…

S32K系列MCU介绍和资料搜集

1. S32K系列微控制器概述 S32K系列微控制器&#xff0c;是NXP推出的专门面向汽车电子和工业应用场合的微控制器。基于ArmCortex-M系列的可扩展、低功耗微控制器&#xff0c;获得了AEC-Q100认证&#xff0c;具有高级功能安全、信息安全和软件支持&#xff0c;适用于工业和汽车A…

进程间通信之匿名管道(pipe)

文章目录 前言管道管道的创建管道的使用单进程使用管道进行通信多进程使用管道进行通信关闭管道的读端/写端 总结 前言 管道分为匿名管道和命名管道&#xff0c;匿名管道只能在有共同祖先的(有亲缘关系)进程中使用&#xff0c;而命名管道可以在任意进程中使用&#xff0c;以下…