当前位置: 首页 > news >正文

3D沉浸式旅游网站开发案例复盘【Three.js】

Plongez dans Lyon网站终于上线了。 我们与 Danka 团队和 Nico Icecream 共同努力,打造了一个令我们特别自豪的流畅的沉浸式网站。

这个网站是专为 ONLYON Tourism 和会议而建,旨在展示里昂最具标志性的活动场所。观看简短的介绍视频后,用户可以进入城市的交互式风景如画的地图,所有场馆都建模为 3D 对象。 每个建筑物都可以点击,进入一个详细说明位置信息的专用页面。

在这里插入图片描述

推荐:用 NSDT编辑器 快速搭建可编程3D场景。

1、打造沉浸式体验

主要网站导航体验依赖于卡通般的 WebGL 场景,其中包含大量景观元素、云彩、动画车辆、波光粼粼的河流,当然还有建筑物。

总而言之,它由 63 个几何图形、48 个纹理、32234 个三角形(以及一些后期处理魔法)组成。 当你处理大量对象时,必须组织代码架构并使用一些技巧来优化性能。

在这里插入图片描述

2、3D场景

所有模型均由才华横溢的 3D 艺术家 Nicolas Dufoure(又名 Icecream)在 3ds Max 中创建,然后使用 Blender 导出为 GTLF 对象。如果你有一些现成的3D模型可以利用,那么可以使用这个在线3D格式转换工具将它们转换成GLTF模型,这会节省不少时间。

2.1 艺术指导和视觉构成

Nico 和 Danka 团队从地图的早期迭代开始了项目的创作过程,并很快确定了低多边形和丰富多彩的艺术方向。

在这里插入图片描述

与客户品牌调色板相匹配的早期地图迭代之一

我们知道必须添加两打可点击的建筑物,因此我们必须在视觉构图、导航便利性和性能之间找到适当的平衡。

在这里插入图片描述

左:第一个场景合成测试渲染,右:早期 webgl 压力测试

为了将绘制的三角形数量保持在最低限度,我们还很快决定限制场景左侧和右侧远侧的 3D 对象的数量。 但过了一段时间,我们意识到我们实际上必须阻止用户看到这些区域。

在这里插入图片描述

这个地方看起来很空,不是吗?

2.2 相机操作

为了避免平移、缩放和动画之间的任何冲突,我很早就决定从头开始编写相机控件的代码。 事实证明这非常方便,因为之后为相机可能的位置添加阈值并不困难。

在这里插入图片描述

白色三角形代表我们实际的相机范围

这样,我们成功地限制了相机的移动,同时仍然允许用户探索所有地图重要区域。

2.3 烘焙和压缩纹理

为了节省大量 GPU 工作负载,Nico 和我同意的另一件事是用全局照明和阴影烘焙所有纹理。

当然,这意味着更多的建模工作,如果你的场景需要频繁更改,这可能会很烦人。 但它减轻了 GPU 的大量计算负担(光照阴影、阴影贴图……),在我们的例子中,这绝对是值得的。
在这里插入图片描述
在这里插入图片描述

3D场景建模概述

当处理如此数量的纹理(通常为 1024x1024、2048x2048 甚至 4096x4096 像素宽)时,你应该考虑的另一件事是使用基础压缩纹理。

如果你从未听说过,基础纹理基本上比 jpeg/png 纹理占用更少的 GPU 内存。 当它们从 CPU 上传到 GPU 时,它们还可以降低主线程瓶颈。

你可以在这里非常轻松地生成基础纹理。

3、代码架构和组织

当需要处理如此多的资源时,组织代码的最佳方法是创建几个 javascript 类(或函数,当然取决于你)并将它们组织在目录和文件中。

通常,我是这样组织该项目的文件和文件夹的:

webgl
|-- data
|   |-- objects.js
|   |-- otherObjects.js
|-- shaders
|   |-- customShader.js
|   |-- anotherShader.js
|-- CameraController.js
|-- GroupRaycaster.js
|-- ObjectsLoader.js
|-- WebGLExperience.js
  • data文件夹包含单独文件中的 javascript 对象以及所有信息
  • shaders文件夹包含单独文件中的所有项目自定义着色器
  • CameraController.js:处理所有相机移动和控制的类
  • GroupRaycaster.js:处理所有“交互式”对象光线投射的类
  • ObjectsLoader.js:加载所有场景对象的类
  • WebGLExperience.js:初始化渲染器、相机、场景、后处理并处理所有其他类的主类

当然,你可以自由地以不同的方式组织它。 例如,有些人喜欢为渲染器、场景和相机创建单独的类。

3.1 核心的概念代码摘录

那么让我们进入代码本身吧!

以下是一些文件实际外观的详细示例。

Obects.js :

import { customFragmentShader } from "../shaders/customShader";const sceneObjects = [{subPath: "path/to/",gltf: "object1.gltf"},{subPath: "anotherPath/to/",gltf: "object2.gltf",fragmentShader: customFragmentShader,uniforms: {uTime: {value: 0,}}}
];export default sceneObjects;

ObjectsLoader.js:

import { LoadingManager } from "three";import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { BasisTextureLoader } from "three/examples/jsm/loaders/BasisTextureLoader";export default class ObjectsLoader {constructor({renderer, // our threejs rendererbasePath = '/', // common base path for all your assetsonLoading = () => {}, // onLoading callbackonComplete = () => {} // onComplete callback}) {this.renderer = renderer;this.basePath = basePath;this.loadingManager = new LoadingManager();this.basisLoader = new BasisTextureLoader(this.loadingManager);// you can also host those files locally if you wantthis.basisLoader.setTranscoderPath("/node_modules/three/examples/js/libs/basis/");this.basisLoader.detectSupport(this.renderer);this.loadingManager.addHandler(/\.basis$/i, this.basisLoader);this.loader = new GLTFLoader(this.loadingManager);this.loader.setPath(this.basePath);this.onLoading = onLoading;this.onComplete = onComplete;this.objects = [];this.state = {objectsLoaded: 0,totalObjects: 0,isComplete: false,};this.loadingManager.onProgress = (url, itemsLoaded, itemsTotal) => {const percent = Math.ceil((itemsLoaded / itemsTotal) * 100);// loading callbackthis.onLoading && this.onLoading(percent);if(percent === 100 && !this.state.isComplete) {this.state.isComplete = true;this.isLoadingComplete();}};this.loadingManager.onError = (url) => {console.warn('>>> error while loading: ', url);};}loadObject({object,parent, // could be our main scene or a grouponSuccess = () => {} // callback for each object loaded if needed}) {if(!object || !object.gltf) return;if('requestIdleCallback' in window) {window.requestIdleCallback(() => {this.startLoading({object,parent,onSuccess});});}else {this.startLoading({object,parent,onSuccess});}}startLoading({object,parent,onSuccess}) {this.state.totalObjects++;// if object has a subpathif(object.subPath) {this.loader.setPath(this.basePath + object.subPath);}this.loader.load(object.gltf, (gltf) => {const sceneObject = {gltf,};// ... do whatever you want with your gltf scene here// ... like using a ShaderMaterial if object.fragmentShader is defined for example!parent.add(gltf.scene);this.objects.push(sceneObject);onSuccess && onSuccess(sceneObject);// check if we've load everythingthis.state.objectsLoaded++;this.isLoadingComplete();}, (xhr) => {},(error) => {console.warn( 'An error happened', error );this.state.objectsLoaded++;this.isLoadingComplete();});}isLoadingComplete() {if(this.state.isComplete && this.state.objectsLoaded === this.state.totalObjects) {setTimeout(() => {this.onComplete && this.onComplete();}, 0);}}
}

WebGLExperience.js:

import {WebGLRenderer,Scene,sRGBEncoding,Group
} from "three";import ObjectsLoader from "./ObjectsLoader";
import CameraController from "./CameraController";
import GroupRaycaster from "./GroupRaycaster";import sceneObjects from "./data/objects";/***
Project architecture example:
webgl
|-- data
|   |-- objects.js
|   |-- otherObjects.js
|-- shaders
|   |-- customShader.js
|   |-- anotherShader.js
|-- CameraController.js
|-- GroupRaycaster.js
|-- ObjectsLoader.js
|-- WebGLExperience.js
*/export default class WebGLExperience {constructor({// add params here if neededcontainer = document.body,}) {this.container = container;// update on resizethis.width = window.innerWidth;this.height = window.innerHeight;this.initRenderer();this.initScene();this.initCamera();this.loadObjects();this.initRaycasting();}/*** EVENTS CALLBACKS ***/onLoading(callback) {if(callback) {this.onLoadingCallback = callback;}return this;}onComplete(callback) {if(callback) {this.onCompleteCallback = callback;}return this;}/*** THREEJS SETUP ***/initRenderer() {this.renderer = new WebGLRenderer({antialias: true,alpha: true,});// important when dealing with GLTFs!this.renderer.outputEncoding = sRGBEncoding;this.renderer.setSize( this.width, this.height );this.renderer.setClearColor( 0xffffff, 1 );this.renderer.outputEncoding = sRGBEncoding;// append the canvasthis.container.appendChild( this.renderer.domElement );}initScene() {// scenethis.scene = new Scene();}initCamera() {// creates the camera and handles the controls & movementsthis.cameraController = new CameraController({webgl: this,});this.camera = this.cameraController.camera;}/*** RAYCASTING ***/initRaycasting() {this.raycaster = new GroupRaycaster({camera: this.camera,width: this.width,height: this.height,onMouseEnteredObject: (object) => {// raycasted object mouse enter event},onMouseLeavedObject: (object) => {// raycasted object mouse leave event},onObjectClicked: (object) => {// raycasted object mouse click event}});}/*** LOAD OBJECTS ***/loadObjects() {this.objectsLoader = new ObjectsLoader({renderer: this.renderer,basePath: '/assets/', // whateveronLoading: (percent) => {console.log(percent);// callbackthis.onLoadingCallback && this.onLoadingCallback(percent);},onComplete: () => {// loading complete...console.log("loading complete!");// callbackthis.onCompleteCallback && this.onCompleteCallback();}});// create a new group where we'll add all our objectsthis.objectGroup = new Group();this.scene.add(this.objectGroup);// load the objectssceneObjects.forEach(object => {this.objectsLoader.loadObject({object,parent: this.objectGroup,onSuccess: (loadedObject) => {console.log(loadedObject);}});});}/*** RENDERING ***/// ...other methods to handle rendering, interactions, etc.
}

3.2 与 Nextjs / React 集成

由于该项目使用 Nextjs,我们需要在 React 组件内实例化我们的 WebGLExperience 类。

我们只需创建一个 WebGLCanvas 组件并将其放在路由器外部,以便它始终位于 DOM 中。

WebGLCanvas.jsx:

import React, {useRef, useState, useEffect} from 'react';
import WebGLExperience from '../../webgl/WebGLExperience';import styles from './WebGLCanvas.module.scss';export default function WebGLCanvas() {const container = useRef();const [ webglXP, setWebglXP ] = useState();// set up webgl context on inituseEffect(() => {const webgl = new WebGLExperience({container: container.current,});setWebglXP(webgl);}, []);// now we can watch webglXP inside a useEffect hook// and do what we want with it// (watch for events callbacks for example...)useEffect(() => {if(webglXP) {webglXP.onLoading((percent) => {console.log('loading', percent);}).onComplete(() => {// do what you want (probably dispatch a context event)});}}, [webglXP]);return (<div className="WebGLCanvas" ref={container} />);
};

4、自定义着色器

显然我必须为这个网站从头开始编写一些自定义着色器。
以下是最有趣的一些细分。

4.1 着色器块

如果你仔细查看上面的示例代码,会发现我允许每个对象在需要时使用自己的自定义着色器。

事实上,场景中的每个网格体都使用 ShaderMaterial,因为当你单击建筑物时,灰度滤镜将应用于所有其他场景网格体:
在这里插入图片描述

应用了灰度滤镜的位置页面屏幕截图

这种效果的实现要归功于这段超级简单的 glsl 代码:

const grayscaleChunk = `vec4 textureBW = vec4(1.0);textureBW.rgb = vec3(gl_FragColor.r * 0.3 + gl_FragColor.g * 0.59 + gl_FragColor.b * 0.11);gl_FragColor = mix(gl_FragColor, textureBW, uGrayscale);
`;

由于所有对象都必须遵守此行为,因此我将其实现为“着色器块”,就像 Three.js 最初在内部构建自己的着色器的方式一样。

例如,使用的最基本场景的网格片段着色器如下所示:

varying vec2 vUv;uniform sampler2D map;
uniform float uGrayscale;void main() {gl_FragColor = texture2D(map, vUv);#include <grayscale_fragment>
}

然后我们只获取材质的 onBeforeCompile 方法的一部分:

material.onBeforeCompile = shader => {shader.fragmentShader = shader.fragmentShader.replace("#include <grayscale_fragment>",grayscaleChunk);
};

这样,如果我必须调整灰度效果,我只需修改一个文件,它就会更新我的所有片段着色器。

4.2 云

正如我上面提到的,我们决定不在场景中放置任何真实的灯光。 但由于云层正在(缓慢)移动,因此需要对其应用某种动态闪电。

为此,我需要做的第一件事是将顶点世界位置和法线传递给片段着色器:

varying vec3 vNormal;
varying vec3 vWorldPos;void main() {vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);gl_Position = projectionMatrix * mvPosition;vWorldPos = (modelMatrix * vec4(position, 1.0)).xyz;vNormal = normal;
}

然后在片段着色器中,我使用它们根据一些uniforms计算漫反射闪电:

varying vec3 vNormal;
varying vec3 vWorldPos;uniform float uGrayscale;uniform vec3 uCloudColor; // emissive color
uniform float uRoughness; // material roughness
uniform vec3 uLightColor; // light color
uniform float uAmbientStrength; // ambient light strength
uniform vec3 uLightPos; // light world space position// get diffusion based on material's roughness
// see https://learnopengl.com/PBR/Theory
float getRoughnessDiff(float diff) {float diff2 = diff * diff;float r2 = uRoughness * uRoughness;float r4 = r2 * r2;float denom = (diff2 * (r4 - 1.0) + 1.0);denom = 3.141592 * denom * denom;return r4 / denom;
}void main() {// ambient lightvec3 ambient = uAmbientStrength * uLightColor;// get light diffusionfloat diff = max(dot(normalize((uLightPos - vWorldPos)), vNormal), 0.0);// apply roughnessfloat roughnessDiff = getRoughnessDiff(diff);vec3 diffuse = roughnessDiff * uLightColor;vec3 result = (ambient + diffuse) * uCloudColor;gl_FragColor = vec4(result, 1.0);#include <grayscale_fragment>
}

这是一种从头开始应用基本闪电阴影的廉价方法,而且结果足够令人信服。

4.3 水中倒影

我花更多时间写的片段着色器无疑是波光粼粼的水。

起初,我愿意采用与 Bruno Simon 在 Madbox 网站上所做的类似的方法,但他使用额外的网格和一组自定义 UV 来实现。

由于 Nico 已经忙于所有建模工作,我决定尝试另一种方法。 我为自己创建了一个额外的纹理来计算波的方向:

在这里插入图片描述

左:水纹理,右:水流方向纹理

这里,水流方向被编码在绿色通道中:50% 的绿色表示水流直行,60% 的绿色表示水稍微向左流动,40% 表示水稍微向右流动,等等 在…

为了创建波浪,我使用了带有阈值的 2D perlin 噪声。 我使用了其他一些 2D 噪声来确定水会发光的区域,使它们向相反的方向移动,瞧!

varying vec2 vUv;uniform sampler2D map;
uniform sampler2D tFlow;
uniform float uGrayscale;
uniform float uTime;uniform vec2 uFrequency;
uniform vec2 uNaturalFrequency;
uniform vec2 uLightFrequency;
uniform float uSpeed;
uniform float uLightSpeed;
uniform float uThreshold;
uniform float uWaveOpacity;// see https://gist.github.com/patriciogonzalezvivo/670c22f3966e662d2f83#classic-perlin-noise
// for cnoise functionvec2 rotateVec2ByAngle(float angle, vec2 vec) {return vec2(vec.x * cos(angle) - vec.y * sin(angle),vec.x * sin(angle) + vec.y * cos(angle));
}void main() {vec4 flow = texture2D(tFlow, vUv);float sideStrength = flow.g * 2.0 - 1.0;vec2 wavesUv = rotateVec2ByAngle(sideStrength * PI, vUv) * uFrequency;float mainFlow = uTime * uSpeed * (1.0 - sideStrength);float sideFlow = uTime * sideStrength * uSpeed;wavesUv.x -= sideFlow;wavesUv.y += mainFlow;// make light areas travel towards the userfloat waveLightStrength = cnoise(wavesUv);// make small waves with noisevec2 naturalNoiseUv = rotateVec2ByAngle(sideStrength * PI, vUv * uNaturalFrequency);float naturalStrength = cnoise(naturalNoiseUv);// apply a threshold to get small waves moving towards the userfloat waveStrength = step(uThreshold, clamp(waveLightStrength - naturalStrength, 0.0, 1.0));// a light mowing backward to improve overall effectfloat light = cnoise(vUv * uLightFrequency + vec2(uTime * uLightSpeed));// get our final waves colorsvec4 color = vec4(1.0);color.rgb = mix(vec3(0.0), vec3(1.0), 1.0 - step(waveStrength, 0.01));// exagerate effectfloat increasedShadows = pow(abs(light), 1.75);color *= uWaveOpacity * increasedShadows;// mix with original texturevec4 text = texture2D(map, vUv);gl_FragColor = text + color;#include <grayscale_fragment>
}

如果你想测试一下,这里有一个 Shadertoy 上的演示。

为了帮助我调试这个问题,我使用了 GUI 来实时调整所有值并找到最有效的值(当然,我已经使用该 GUI 来帮助我调试很多其他事情) 。

在这里插入图片描述

4.4 后期处理

最后有一个使用 Threejs 内置 ShaderPass 类应用的后处理通道。 它处理出现的动画,在某个位置聚焦时在相机移动上添加一点鱼眼,并负责小级别校正(亮度、对比度、饱和度和曝光)。

在这里插入图片描述

在放大/缩小动画期间应用轻微的后处理变形效果

PostFXShader.js:

const PostFXShader = {uniforms: {'tDiffuse': { value: null },'deformationStrength': { value: 0 },'showScene': { value: 0 },// color manipulations'brightness': { value: 0 },'contrast': { value: 0.15 },'saturation': { value: 0.1 },'exposure': { value: 0 },},vertexShader: /* glsl */`varying vec2 vUv;void main() {vUv = uv;gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );}`,fragmentShader: `varying vec2 vUv;uniform sampler2D tDiffuse;uniform float showScene;uniform float deformationStrength;uniform float brightness;uniform float contrast;uniform float saturation;uniform float exposure;vec3 adjustBrightness(vec3 color, float value) {return color + value;}vec3 adjustContrast(vec3 color, float value) {return 0.5 + (1.0 + value) * (color - 0.5);}vec3 adjustExposure(vec3 color, float value) {return color * (1.0 + value);}vec3 adjustSaturation(vec3 color, float value) {// https://www.w3.org/TR/WCAG21/#dfn-relative-luminanceconst vec3 luminosityFactor = vec3(0.2126, 0.7152, 0.0722);vec3 grayscale = vec3(dot(color, luminosityFactor));return mix(grayscale, color, 1.0 + value);}void main() {vec2 texCoords = vUv;vec2 normalizedCoords = texCoords * 2.0 - 1.0;float distanceToCenter = distance(normalizedCoords, vec2(0.0));vec2 distortedCoords = normalizedCoords * (1.0 - distanceToCenter * deformationStrength);vec2 offset = normalizedCoords * sin(distanceToCenter * 3.0 - showScene * 3.0) * (1.0 - showScene) * 0.1;texCoords = (distortedCoords + 1.0) * 0.5 + offset;vec4 texture = texture2D(tDiffuse, texCoords);float showEffect = clamp(showScene - length(offset) * 10.0 / sqrt(2.0), 0.0, 1.0);vec4 grayscale = vec4(1.0);grayscale.rgb = vec3(texture.r * 0.3 + texture.g * 0.59 + texture.b * 0.11);texture.rgb = mix(grayscale.rgb, texture.rgb, showEffect);texture.a = showEffect * 0.9 + 0.1;texture.rgb *= texture.a;texture.rgb = adjustBrightness(texture.rgb, brightness);texture.rgb = adjustContrast(texture.rgb, contrast);texture.rgb = adjustExposure(texture.rgb, exposure);texture.rgb = adjustSaturation(texture.rgb, saturation);gl_FragColor = texture;}`
};export { PostFXShader };

在某些时候,我们还尝试添加散景通道,但它对性能要求太高,因此我们很快就放弃了它。

5、使用 Spector 进行调试

你始终可以通过安装spector.js扩展并检查WebGL上下文来深入查看使用的所有着色器。

如果你从未听说过,spector.js 适用于每个 WebGL 网站。 如果想检查一些 WebGL 效果是如何实现的,它总是超级方便!
在这里插入图片描述

使用spector.js 调试片段着色器

6、性能优化

我使用了一些技巧来优化体验性能。 以下是最重要的两个:

首先,这应该成为一种习惯:仅在需要时渲染场景。

这可能听起来很愚蠢,但它仍然经常被低估。 如果你的场景被覆盖层、页面或其他任何东西隐藏,就不要绘制它!

renderScene() {if(this.state.shouldRender) this.animate();
}

我使用的另一个技巧是根据用户 GPU 和屏幕尺寸来调整场景的像素比。

这个想法是首先使用 detector-gpu 检测用户的 GPU。 一旦我们获得了 GPU 估计的 fps,我们就会使用实际屏幕分辨率来计算实际条件下该 fps 测量值的增强估计。 然后,我们可以根据每次调整大小时的这些数字来调整渲染器像素比:

setGPUTier() {// GPU test(async () => {this.gpuTier = await getGPUTier({glContext: this.renderer.getContext(),});this.setImprovedGPUTier();})();
}// called on resize as well
setImprovedGPUTier() {const baseResolution = 1920 * 1080;this.gpuTier.improvedTier = {fps: this.gpuTier.fps * baseResolution / (this.width * this.height)};this.gpuTier.improvedTier.tier = this.gpuTier.improvedTier.fps >= 60 ? 3 :this.gpuTier.improvedTier.fps >= 30 ? 2 :this.gpuTier.improvedTier.fps >= 15 ? 1 : 0;this.setScenePixelRatio();
}

另一种常见的方法是持续监控给定时间段内的平均 FPS,并根据结果调整像素比。

其他优化包括使用或不使用多重采样渲染目标,具体取决于 GPU 和 WebGL2 支持(使用 FXAA 通道作为后备)、使用鼠标事件发射器、触摸和调整大小事件、使用 gsap 股票代码作为应用程序的唯一 requestAnimationFrame 循环等 。

7、结束语

总而言之,我们在构建家乡的交互式地图时度过了一段愉快的时光。

正如我们所见,打造像这样的沉浸式 WebGL 体验(需要实时渲染很多内容)并不困难。 但它确实需要一些组织和一个包含多个文件的干净代码库,可以轻松调试、添加或删除功能。

通过该架构,还可以非常轻松地添加或删除场景对象(因为这只是编辑 Javascript 对象的问题),从而在需要时可以方便地进行进一步的站点更新。


原文链接:WebGL旅游网站案例研究 — BimAnt

相关文章:

3D沉浸式旅游网站开发案例复盘【Three.js】

Plongez dans Lyon网站终于上线了。 我们与 Danka 团队和 Nico Icecream 共同努力&#xff0c;打造了一个令我们特别自豪的流畅的沉浸式网站。 这个网站是专为 ONLYON Tourism 和会议而建&#xff0c;旨在展示里昂最具标志性的活动场所。观看简短的介绍视频后&#xff0c;用户…...

IO的几个模型

I/O模型名词介绍 说到I/O模型&#xff0c;都会牵扯到同步、异步、阻塞、非阻塞这几个词&#xff0c;以下讲解这几个词的概念。 阻塞和非阻塞 阻塞和非阻塞指的是一直等还是可以去做其他事。 阻塞&#xff08;blocking&#xff09;&#xff1a;调用结果返回之前&#xff0c;…...

中路对线发现正在攻防演练中投毒的红队大佬

背景 2023年8月14日晚&#xff0c;墨菲安全实验室发布《首起针对国内金融企业的开源组件投毒攻击事件》NPM投毒事件分析文章&#xff0c;紧接着我们在8月17日监控到一个新的npm投毒组件包 hreport-preview&#xff0c;该投毒组件用来下载木马文件的域名地址竟然是 img.murphys…...

【LINUX相关】生成随机数(srand、/dev/random 和 /dev/urandom )

目录 一、问题背景二、修改方法2.1 修改种子2.2 使用linux中的 /dev/urandom 生成随机数 三、/dev/random 和 /dev/urandom 的原理3.1 参考连接3.2 重难点总结3.2.1 生成随机数的原理3.2.2 随机数生成器的结构3.2.3 二者的区别和选择 四、在代码的使用方法 一、问题背景 在一个…...

spark使用心得

spark入门 启停spark sbin/start-all.shsbin/stop-all.shspark-shell 进入spark/bin目录&#xff0c;执行&#xff1a; ./spark-shell 输出中有这么一行&#xff1a; Spark context Web UI available at http://xx.xx.xx.188:4040意味着我们可以从web页面查看spark的运行情…...

什么是边车

名词和概念定义 Sidecar&#xff1a;边车。微服务中数据平面的进程&#xff0c;负责转发应用、服务请求&#xff0c;并支持限流、熔断、负载均衡等特性。 Control-plane: 控制平面。微服务的配置中心&#xff0c;负责配置下发、数据搜集、服务发现等功能。 应用: 应用是指服务…...

vue项目打包成exe文件

1. 获取electron-quick-start demo git clone https://github.com/electron/electron-quick-start2. 安装依赖包 npm install 或 npm i // 安装依赖时可能会遇到node版本的问题&#xff0c;需要切换node版本的可以先看下nvm&#xff0c;简单易操作3. 打包项目&#xff08;需要…...

基于MFCC特征提取和GMM训练的语音信号识别matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 4.1 MFCC特征提取 4.2 Gaussian Mixture Model&#xff08;GMM&#xff09; 4.3. 实现过程 4.4 应用领域 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3…...

client-go实战之十二:选主(leader-election)

欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码)&#xff1a;https://github.com/zq2599/blog_demos 本篇概览 本文是《client-go实战》系列的第十二篇&#xff0c;又有一个精彩的知识点在本章呈现&#xff1a;选主(leader-election)在解释什么是选主之前&…...

2023年即将推出的CSS特性对你影响大不大?

Google开发者大会每年都会提出有关于 Web UI 和 CSS 方面的新特性&#xff0c;今年又上新了许多新功能&#xff0c;今天就从中找出了影响最大的几个功能给大家介绍一下 :has :has() 可以通过检查父元素是否包含特定子元素或这些子元素是否处于特定状态来改变样式&#xff0c;也…...

opencv实战项目-停车位计数

手势识别系列文章目录 手势识别是一种人机交互技术&#xff0c;通过识别人的手势动作&#xff0c;从而实现对计算机、智能手机、智能电视等设备的操作和控制。 1. opencv实现手部追踪&#xff08;定位手部关键点&#xff09; 2.opencv实战项目 实现手势跟踪并返回位置信息&a…...

NLP文本匹配任务Text Matching [无监督训练]:SimCSE、ESimCSE、DiffCSE 项目实践

NLP文本匹配任务Text Matching [无监督训练]&#xff1a;SimCSE、ESimCSE、DiffCSE 项目实践 文本匹配多用于计算两个文本之间的相似度&#xff0c;该示例会基于 ESimCSE 实现一个无监督的文本匹配模型的训练流程。文本匹配多用于计算两段「自然文本」之间的「相似度」。 例如…...

复习vue3,简简单单记录

这里的知识是结合视频以及其他文章一起学习&#xff0c;仅用于个人复习记录 ref 和reactive ref 用于基本类型 reactive 用于引用类型 如果使用ref 传递对象&#xff0c;修改值时候需要写为obj.value.attr 方式修改属性值 如果使用reactive 处理对象&#xff0c;直接obj.att…...

【自用】云服务器 docker 环境下 HomeAssistant 安装 HACS 教程

一、进入 docker 中的 HomeAssistant 1.查找 HomeAssistant 的 CONTAINER ID 连接上云服务器&#xff08;宿主机&#xff09;后&#xff0c;终端内进入 root &#xff0c;输入&#xff1a; docker ps找到了 docker 的 container ID 2.config HomeAssistant 输入下面的命令&…...

使用dockerfile手动构建JDK11镜像运行容器并校验

Docker官方维护镜像的公共仓库网站 Docker Hub 国内无法访问了&#xff0c;大部分镜像无法下载&#xff0c;准备逐步构建自己的镜像库。【转载aliyun官方-容器镜像服务 ACR】Docker常见问题 阿里云容器镜像服务ACR&#xff08;Alibaba Cloud Container Registry&#xff09;是面…...

编程语言学习笔记-架构师和工程师的区别,PHP架构师之路

&#x1f3c6;作者简介&#xff0c;黑夜开发者&#xff0c;全栈领域新星创作者✌&#xff0c;CSDN博客专家&#xff0c;阿里云社区专家博主&#xff0c;2023年6月CSDN上海赛道top4。 &#x1f3c6;数年电商行业从业经验&#xff0c;历任核心研发工程师&#xff0c;项目技术负责…...

Streamlit 讲解专栏(十):数据可视化-图表绘制详解(上)

文章目录 1 前言2 st.line_chart&#xff1a;绘制线状图3 st.area_chart&#xff1a;绘制面积图4 st.bar_chart&#xff1a;绘制柱状图5 st.pyplot&#xff1a;绘制自定义图表6 结语 1 前言 在数据可视化的世界中&#xff0c;绘制清晰、易于理解的图表是非常关键的。Streamlit…...

其他行业跳槽转入计算机领域简单看法

其他行业跳槽转入计算机领域简单看法 本人选择从以下几个方向谈谈自己的想法和观点。 先看一下总体图&#xff0c;下面会详细分析 如何规划才能实现转码 自我评估和目标设定&#xff1a;首先&#xff0c;你需要评估自己的技能和兴趣&#xff0c;确定你希望在计算机领域从事…...

Unity制作一个简单的登入注册页面

1.创建Canvas组件 首先我们创建一个Canvas画布&#xff0c;我们再在Canvas画布底下创建一个空物体&#xff0c;取名为Resgister。把空物体的锚点设置为全屏撑开。 2.我们在Resgister空物体底下创建一个Image组件&#xff0c;改名为bg。我们也把它 的锚点设置为全屏撑开状态。接…...

常用游戏运营指标DAU、LTV及参考范围

文章目录 前言运营指标指标范围参考值留存指标的意义总结 前言 作为游戏人免不了听到 DAU 、UP值、留存 等名词&#xff0c;并且有些名词听起来还很像&#xff0c;特别是一款上线的游戏&#xff0c;这些游戏运营指标是衡量游戏业务绩效和用户参与度的重要数据&#xff0c;想做…...

RTX 4090D 24G镜像一文详解:PyTorch 2.8预装xFormers/FlashAttention-2实战

RTX 4090D 24G镜像一文详解&#xff1a;PyTorch 2.8预装xFormers/FlashAttention-2实战 1. 镜像概述与核心优势 PyTorch 2.8深度学习镜像为RTX 4090D 24GB显卡量身打造&#xff0c;经过CUDA 12.4深度优化&#xff0c;提供开箱即用的高性能计算环境。这个镜像特别适合需要处理…...

OpenClaw环境迁移:GLM-4.7-Flash配置的备份与恢复方案

OpenClaw环境迁移&#xff1a;GLM-4.7-Flash配置的备份与恢复方案 1. 为什么需要环境迁移&#xff1f; 上周我的主力开发机突然硬盘故障&#xff0c;导致所有OpenClaw配置丢失。最痛心的是花了两周调试的GLM-4.7-Flash对接设置全部归零——包括精心调整的温度参数、自定义提示…...

OpenClaw负载测试:GLM-4.7-Flash并发处理能力评估

OpenClaw负载测试&#xff1a;GLM-4.7-Flash并发处理能力评估 1. 测试背景与目标 上周在尝试用OpenClaw自动化处理一批市场调研报告时&#xff0c;遇到了一个典型问题&#xff1a;当我同时提交20份PDF文件让AI助手提取关键数据时&#xff0c;系统开始出现响应延迟和部分任务超…...

OpenClaw+GLM-4.7-Flash:自动化电子书生成与排版工具

OpenClawGLM-4.7-Flash&#xff1a;自动化电子书生成与排版工具 1. 为什么需要自动化电子书制作 作为一个经常需要整理技术文档的开发者&#xff0c;我过去制作电子书的流程堪称"手工活地狱"&#xff1a;先在多个网页间复制粘贴内容&#xff0c;用Word调整格式&…...

G5080 G6080 G7080 G1810 G2810 ,MG3680,ts3380最新清零软件5B00,5B01,5B02,1700,1701,1702,1704,P07,E08废墨收集器已满

下载地址&#xff1a;链接:https://pan.baidu.com/s/1j7Nwv715wX1JL3qidnGyXA?pwd0000 提取码:0000 常见 佳能打印机 型号&#xff1a; G5080 G6080 G7080 G1810 G2810 G3810 G4810 G1800 G2800 G3800 G4800 G5010 G6010 G7010 G1010 G2010 G3010 G4010 G1000 G2000 G3000 G40…...

GPT-5-Codex CLI实战:如何用UIUIApi中转服务稳定获取API Key(避坑指南)

GPT-5-Codex CLI高效实践&#xff1a;国内开发者API接入全流程解析 最近在技术社区里&#xff0c;关于GPT-5-Codex的讨论热度持续攀升。作为一名长期关注AI编程工具的开发者&#xff0c;我发现很多同行在尝试接入这项服务时遇到了各种技术障碍。本文将分享一套经过实战验证的完…...

借助aibye智能工具高效完善毕业论文任务书范文,整合7大优质平台的AI修改功能提升学术写作质量

工具名称 核心功能 生成速度 适用场景 独特优势 aibiye 论文初稿生成 20-30分钟 全学科通用 自动插入图表公式 aicheck 初稿查重 20-30分钟 急需查重场景 独创降AIGC算法 askpaper 初稿生成 20-30分钟 理工科专业 支持代码片段 秒篇 快速生成 10-15分钟 …...

HY-Motion 1.0保姆级教程:解决CUDA OOM、Prompt截断等常见问题

HY-Motion 1.0保姆级教程&#xff1a;解决CUDA OOM、Prompt截断等常见问题 1. 前言&#xff1a;为什么需要这篇教程 你是不是也遇到过这样的情况&#xff1a;好不容易下载了HY-Motion 1.0这个强大的3D动作生成模型&#xff0c;准备大展身手&#xff0c;结果一运行就遇到CUDA内…...

vLLM-v0.17.1入门必看:从零部署支持多LoRA的开源推理框架

vLLM-v0.17.1入门必看&#xff1a;从零部署支持多LoRA的开源推理框架 1. vLLM框架简介 vLLM是一个专为大型语言模型(LLM)设计的高性能推理和服务库&#xff0c;最新发布的v0.17.1版本带来了多项重要改进&#xff0c;特别是增强了对多LoRA适配器的支持。这个开源项目最初由加州…...

团队用ai写代码越来越猛但为什么改个功能像在拆炸弹背后是流程断了

最近不少团队反馈&#xff0c;AI Coding 跑得飞快&#xff0c;两周就能堆出新功能&#xff0c;可一旦要改个按钮颜色&#xff0c;整个系统却像在拆炸弹。这种“改功能崩塌”的怪圈&#xff0c;正让许多管理者头疼&#xff1a;明明用了最先进的工具&#xff0c;交付反而更慢了。…...