国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

3D沉浸式旅游網(wǎng)站開發(fā)案例復(fù)盤【Three.js】

這篇具有很好參考價(jià)值的文章主要介紹了3D沉浸式旅游網(wǎng)站開發(fā)案例復(fù)盤【Three.js】。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

Plongez dans Lyon網(wǎng)站終于上線了。 我們與 Danka 團(tuán)隊(duì)和 Nico Icecream 共同努力,打造了一個(gè)令我們特別自豪的流暢的沉浸式網(wǎng)站。

這個(gè)網(wǎng)站是專為 ONLYON Tourism 和會(huì)議而建,旨在展示里昂最具標(biāo)志性的活動(dòng)場(chǎng)所。觀看簡(jiǎn)短的介紹視頻后,用戶可以進(jìn)入城市的交互式風(fēng)景如畫的地圖,所有場(chǎng)館都建模為 3D 對(duì)象。 每個(gè)建筑物都可以點(diǎn)擊,進(jìn)入一個(gè)詳細(xì)說明位置信息的專用頁面。

3D沉浸式旅游網(wǎng)站開發(fā)案例復(fù)盤【Three.js】,3d,旅游,javascript

推薦:用 NSDT編輯器 快速搭建可編程3D場(chǎng)景。

1、打造沉浸式體驗(yàn)

主要網(wǎng)站導(dǎo)航體驗(yàn)依賴于卡通般的 WebGL 場(chǎng)景,其中包含大量景觀元素、云彩、動(dòng)畫車輛、波光粼粼的河流,當(dāng)然還有建筑物。

總而言之,它由 63 個(gè)幾何圖形、48 個(gè)紋理、32234 個(gè)三角形(以及一些后期處理魔法)組成。 當(dāng)你處理大量對(duì)象時(shí),必須組織代碼架構(gòu)并使用一些技巧來優(yōu)化性能。

3D沉浸式旅游網(wǎng)站開發(fā)案例復(fù)盤【Three.js】,3d,旅游,javascript

2、3D場(chǎng)景

所有模型均由才華橫溢的 3D 藝術(shù)家 Nicolas Dufoure(又名 Icecream)在 3ds Max 中創(chuàng)建,然后使用 Blender 導(dǎo)出為 GTLF 對(duì)象。如果你有一些現(xiàn)成的3D模型可以利用,那么可以使用這個(gè)在線3D格式轉(zhuǎn)換工具將它們轉(zhuǎn)換成GLTF模型,這會(huì)節(jié)省不少時(shí)間。

2.1 藝術(shù)指導(dǎo)和視覺構(gòu)成

Nico 和 Danka 團(tuán)隊(duì)從地圖的早期迭代開始了項(xiàng)目的創(chuàng)作過程,并很快確定了低多邊形和豐富多彩的藝術(shù)方向。

3D沉浸式旅游網(wǎng)站開發(fā)案例復(fù)盤【Three.js】,3d,旅游,javascript

與客戶品牌調(diào)色板相匹配的早期地圖迭代之一

我們知道必須添加兩打可點(diǎn)擊的建筑物,因此我們必須在視覺構(gòu)圖、導(dǎo)航便利性和性能之間找到適當(dāng)?shù)钠胶狻?/p>

3D沉浸式旅游網(wǎng)站開發(fā)案例復(fù)盤【Three.js】,3d,旅游,javascript

左:第一個(gè)場(chǎng)景合成測(cè)試渲染,右:早期 webgl 壓力測(cè)試

為了將繪制的三角形數(shù)量保持在最低限度,我們還很快決定限制場(chǎng)景左側(cè)和右側(cè)遠(yuǎn)側(cè)的 3D 對(duì)象的數(shù)量。 但過了一段時(shí)間,我們意識(shí)到我們實(shí)際上必須阻止用戶看到這些區(qū)域。

3D沉浸式旅游網(wǎng)站開發(fā)案例復(fù)盤【Three.js】,3d,旅游,javascript

這個(gè)地方看起來很空,不是嗎?

2.2 相機(jī)操作

為了避免平移、縮放和動(dòng)畫之間的任何沖突,我很早就決定從頭開始編寫相機(jī)控件的代碼。 事實(shí)證明這非常方便,因?yàn)橹鬄橄鄼C(jī)可能的位置添加閾值并不困難。

3D沉浸式旅游網(wǎng)站開發(fā)案例復(fù)盤【Three.js】,3d,旅游,javascript

白色三角形代表我們實(shí)際的相機(jī)范圍

這樣,我們成功地限制了相機(jī)的移動(dòng),同時(shí)仍然允許用戶探索所有地圖重要區(qū)域。

2.3 烘焙和壓縮紋理

為了節(jié)省大量 GPU 工作負(fù)載,Nico 和我同意的另一件事是用全局照明和陰影烘焙所有紋理。

當(dāng)然,這意味著更多的建模工作,如果你的場(chǎng)景需要頻繁更改,這可能會(huì)很煩人。 但它減輕了 GPU 的大量計(jì)算負(fù)擔(dān)(光照陰影、陰影貼圖……),在我們的例子中,這絕對(duì)是值得的。
3D沉浸式旅游網(wǎng)站開發(fā)案例復(fù)盤【Three.js】,3d,旅游,javascript
3D沉浸式旅游網(wǎng)站開發(fā)案例復(fù)盤【Three.js】,3d,旅游,javascript

3D場(chǎng)景建模概述

當(dāng)處理如此數(shù)量的紋理(通常為 1024x1024、2048x2048 甚至 4096x4096 像素寬)時(shí),你應(yīng)該考慮的另一件事是使用基礎(chǔ)壓縮紋理。

如果你從未聽說過,基礎(chǔ)紋理基本上比 jpeg/png 紋理占用更少的 GPU 內(nèi)存。 當(dāng)它們從 CPU 上傳到 GPU 時(shí),它們還可以降低主線程瓶頸。

你可以在這里非常輕松地生成基礎(chǔ)紋理。

3、代碼架構(gòu)和組織

當(dāng)需要處理如此多的資源時(shí),組織代碼的最佳方法是創(chuàng)建幾個(gè) javascript 類(或函數(shù),當(dāng)然取決于你)并將它們組織在目錄和文件中。

通常,我是這樣組織該項(xiàng)目的文件和文件夾的:

webgl
|-- data
|   |-- objects.js
|   |-- otherObjects.js
|-- shaders
|   |-- customShader.js
|   |-- anotherShader.js
|-- CameraController.js
|-- GroupRaycaster.js
|-- ObjectsLoader.js
|-- WebGLExperience.js
  • data文件夾包含單獨(dú)文件中的 javascript 對(duì)象以及所有信息
  • shaders文件夾包含單獨(dú)文件中的所有項(xiàng)目自定義著色器
  • CameraController.js:處理所有相機(jī)移動(dòng)和控制的類
  • GroupRaycaster.js:處理所有“交互式”對(duì)象光線投射的類
  • ObjectsLoader.js:加載所有場(chǎng)景對(duì)象的類
  • WebGLExperience.js:初始化渲染器、相機(jī)、場(chǎng)景、后處理并處理所有其他類的主類

當(dāng)然,你可以自由地以不同的方式組織它。 例如,有些人喜歡為渲染器、場(chǎng)景和相機(jī)創(chuàng)建單獨(dú)的類。

3.1 核心的概念代碼摘錄

那么讓我們進(jìn)入代碼本身吧!

以下是一些文件實(shí)際外觀的詳細(xì)示例。

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 renderer
   basePath = '/', // common base path for all your assets
   onLoading = () => {}, // onLoading callback
   onComplete = () => {} // 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 want
   this.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 callback
     this.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 group
    onSuccess = () => {} // 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 subpath
   if(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 everything
     this.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 needed
   container = document.body,
 }) {
   this.container = container;

   // update on resize
   this.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 canvas
   this.container.appendChild( this.renderer.domElement );
 }

 initScene() {
   // scene
   this.scene = new Scene();
 }

 initCamera() {
   // creates the camera and handles the controls & movements
   this.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/', // whatever
     onLoading: (percent) => {
       console.log(percent);

       // callback
       this.onLoadingCallback && this.onLoadingCallback(percent);
     },
     onComplete: () => {
       // loading complete...
       console.log("loading complete!");

       // callback
       this.onCompleteCallback && this.onCompleteCallback();
     }
   });


   // create a new group where we'll add all our objects
   this.objectGroup = new Group();
   this.scene.add(this.objectGroup);

   // load the objects
   sceneObjects.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 集成

由于該項(xiàng)目使用 Nextjs,我們需要在 React 組件內(nèi)實(shí)例化我們的 WebGLExperience 類。

我們只需創(chuàng)建一個(gè) 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 init
 useEffect(() => {
   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、自定義著色器

顯然我必須為這個(gè)網(wǎng)站從頭開始編寫一些自定義著色器。
以下是最有趣的一些細(xì)分。

4.1 著色器塊

如果你仔細(xì)查看上面的示例代碼,會(huì)發(fā)現(xiàn)我允許每個(gè)對(duì)象在需要時(shí)使用自己的自定義著色器。

事實(shí)上,場(chǎng)景中的每個(gè)網(wǎng)格體都使用 ShaderMaterial,因?yàn)楫?dāng)你單擊建筑物時(shí),灰度濾鏡將應(yīng)用于所有其他場(chǎng)景網(wǎng)格體:
3D沉浸式旅游網(wǎng)站開發(fā)案例復(fù)盤【Three.js】,3d,旅游,javascript

應(yīng)用了灰度濾鏡的位置頁面屏幕截圖

這種效果的實(shí)現(xiàn)要?dú)w功于這段超級(jí)簡(jiǎn)單的 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);
`;

由于所有對(duì)象都必須遵守此行為,因此我將其實(shí)現(xiàn)為“著色器塊”,就像 Three.js 最初在內(nèi)部構(gòu)建自己的著色器的方式一樣。

例如,使用的最基本場(chǎng)景的網(wǎng)格片段著色器如下所示:

varying vec2 vUv;

uniform sampler2D map;
uniform float uGrayscale;

void main() {
 gl_FragColor = texture2D(map, vUv);

 #include <grayscale_fragment>
}

然后我們只獲取材質(zhì)的 onBeforeCompile 方法的一部分:

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

這樣,如果我必須調(diào)整灰度效果,我只需修改一個(gè)文件,它就會(huì)更新我的所有片段著色器。

4.2 云

正如我上面提到的,我們決定不在場(chǎng)景中放置任何真實(shí)的燈光。 但由于云層正在(緩慢)移動(dòng),因此需要對(duì)其應(yīng)用某種動(dòng)態(tài)閃電。

為此,我需要做的第一件事是將頂點(diǎn)世界位置和法線傳遞給片段著色器:

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;
}

然后在片段著色器中,我使用它們根據(jù)一些uniforms計(jì)算漫反射閃電:

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 light
 vec3 ambient = uAmbientStrength * uLightColor;

 // get light diffusion
 float diff = max(dot(normalize((uLightPos - vWorldPos)), vNormal), 0.0);
 // apply roughness
 float roughnessDiff = getRoughnessDiff(diff);

 vec3 diffuse = roughnessDiff * uLightColor;

 vec3 result = (ambient + diffuse) * uCloudColor;

 gl_FragColor = vec4(result, 1.0);

 #include <grayscale_fragment>
}

這是一種從頭開始應(yīng)用基本閃電陰影的廉價(jià)方法,而且結(jié)果足夠令人信服。

4.3 水中倒影

我花更多時(shí)間寫的片段著色器無疑是波光粼粼的水。

起初,我愿意采用與 Bruno Simon 在 Madbox 網(wǎng)站上所做的類似的方法,但他使用額外的網(wǎng)格和一組自定義 UV 來實(shí)現(xiàn)。

由于 Nico 已經(jīng)忙于所有建模工作,我決定嘗試另一種方法。 我為自己創(chuàng)建了一個(gè)額外的紋理來計(jì)算波的方向:

3D沉浸式旅游網(wǎng)站開發(fā)案例復(fù)盤【Three.js】,3d,旅游,javascript

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

這里,水流方向被編碼在綠色通道中:50% 的綠色表示水流直行,60% 的綠色表示水稍微向左流動(dòng),40% 表示水稍微向右流動(dòng),等等 在…

為了創(chuàng)建波浪,我使用了帶有閾值的 2D perlin 噪聲。 我使用了其他一些 2D 噪聲來確定水會(huì)發(fā)光的區(qū)域,使它們向相反的方向移動(dòng),瞧!

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 function

vec2 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 user
  float waveLightStrength = cnoise(wavesUv);

  // make small waves with noise
  vec2 naturalNoiseUv = rotateVec2ByAngle(sideStrength * PI, vUv * uNaturalFrequency);
  float naturalStrength = cnoise(naturalNoiseUv);

  // apply a threshold to get small waves moving towards the user
  float waveStrength = step(uThreshold, clamp(waveLightStrength - naturalStrength, 0.0, 1.0));

  // a light mowing backward to improve overall effect
  float light = cnoise(vUv * uLightFrequency + vec2(uTime * uLightSpeed));

  // get our final waves colors
  vec4 color = vec4(1.0);
  color.rgb = mix(vec3(0.0), vec3(1.0), 1.0 - step(waveStrength, 0.01));

  // exagerate effect
  float increasedShadows = pow(abs(light), 1.75);
  color *= uWaveOpacity * increasedShadows;

  // mix with original texture
  vec4 text = texture2D(map, vUv);

  gl_FragColor = text + color;

  #include <grayscale_fragment>
}

如果你想測(cè)試一下,這里有一個(gè) Shadertoy 上的演示。

為了幫助我調(diào)試這個(gè)問題,我使用了 GUI 來實(shí)時(shí)調(diào)整所有值并找到最有效的值(當(dāng)然,我已經(jīng)使用該 GUI 來幫助我調(diào)試很多其他事情) 。

3D沉浸式旅游網(wǎng)站開發(fā)案例復(fù)盤【Three.js】,3d,旅游,javascript

4.4 后期處理

最后有一個(gè)使用 Threejs 內(nèi)置 ShaderPass 類應(yīng)用的后處理通道。 它處理出現(xiàn)的動(dòng)畫,在某個(gè)位置聚焦時(shí)在相機(jī)移動(dòng)上添加一點(diǎn)魚眼,并負(fù)責(zé)小級(jí)別校正(亮度、對(duì)比度、飽和度和曝光)。

3D沉浸式旅游網(wǎng)站開發(fā)案例復(fù)盤【Three.js】,3d,旅游,javascript

在放大/縮小動(dòng)畫期間應(yīng)用輕微的后處理變形效果

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-luminance
      const 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 };

在某些時(shí)候,我們還嘗試添加散景通道,但它對(duì)性能要求太高,因此我們很快就放棄了它。

5、使用 Spector 進(jìn)行調(diào)試

你始終可以通過安裝spector.js擴(kuò)展并檢查WebGL上下文來深入查看使用的所有著色器。

如果你從未聽說過,spector.js 適用于每個(gè) WebGL 網(wǎng)站。 如果想檢查一些 WebGL 效果是如何實(shí)現(xiàn)的,它總是超級(jí)方便!
3D沉浸式旅游網(wǎng)站開發(fā)案例復(fù)盤【Three.js】,3d,旅游,javascript

使用spector.js 調(diào)試片段著色器

6、性能優(yōu)化

我使用了一些技巧來優(yōu)化體驗(yàn)性能。 以下是最重要的兩個(gè):

首先,這應(yīng)該成為一種習(xí)慣:僅在需要時(shí)渲染場(chǎng)景。

這可能聽起來很愚蠢,但它仍然經(jīng)常被低估。 如果你的場(chǎng)景被覆蓋層、頁面或其他任何東西隱藏,就不要繪制它!

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

我使用的另一個(gè)技巧是根據(jù)用戶 GPU 和屏幕尺寸來調(diào)整場(chǎng)景的像素比。

這個(gè)想法是首先使用 detector-gpu 檢測(cè)用戶的 GPU。 一旦我們獲得了 GPU 估計(jì)的 fps,我們就會(huì)使用實(shí)際屏幕分辨率來計(jì)算實(shí)際條件下該 fps 測(cè)量值的增強(qiáng)估計(jì)。 然后,我們可以根據(jù)每次調(diào)整大小時(shí)的這些數(shù)字來調(diào)整渲染器像素比:

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();
}

另一種常見的方法是持續(xù)監(jiān)控給定時(shí)間段內(nèi)的平均 FPS,并根據(jù)結(jié)果調(diào)整像素比。

其他優(yōu)化包括使用或不使用多重采樣渲染目標(biāo),具體取決于 GPU 和 WebGL2 支持(使用 FXAA 通道作為后備)、使用鼠標(biāo)事件發(fā)射器、觸摸和調(diào)整大小事件、使用 gsap 股票代碼作為應(yīng)用程序的唯一 requestAnimationFrame 循環(huán)等 。

7、結(jié)束語

總而言之,我們?cè)跇?gòu)建家鄉(xiāng)的交互式地圖時(shí)度過了一段愉快的時(shí)光。

正如我們所見,打造像這樣的沉浸式 WebGL 體驗(yàn)(需要實(shí)時(shí)渲染很多內(nèi)容)并不困難。 但它確實(shí)需要一些組織和一個(gè)包含多個(gè)文件的干凈代碼庫,可以輕松調(diào)試、添加或刪除功能。

通過該架構(gòu),還可以非常輕松地添加或刪除場(chǎng)景對(duì)象(因?yàn)檫@只是編輯 Javascript 對(duì)象的問題),從而在需要時(shí)可以方便地進(jìn)行進(jìn)一步的站點(diǎn)更新。


原文鏈接:WebGL旅游網(wǎng)站案例研究 — BimAnt文章來源地址http://www.zghlxwxcb.cn/news/detail-650637.html

到了這里,關(guān)于3D沉浸式旅游網(wǎng)站開發(fā)案例復(fù)盤【Three.js】的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 如何使用python開發(fā)網(wǎng)站?

    使用Python開發(fā)網(wǎng)站的步驟如下: 安裝Python和相關(guān)的Web框架,例如Django、Flask、Pyramid等。 設(shè)計(jì)網(wǎng)站的功能和頁面,使用HTML、CSS和JavaScript等技術(shù)進(jìn)行設(shè)計(jì)。 使用Python的Web框架搭建網(wǎng)站,例如Django中的 manage.py startapp 命令創(chuàng)建應(yīng)用,F(xiàn)lask中的 app.py 命令創(chuàng)建應(yīng)用。 在應(yīng)用中引入需

    2024年02月11日
    瀏覽(26)
  • 集團(tuán)企業(yè)網(wǎng)站建設(shè)開發(fā)

    集團(tuán)企業(yè)網(wǎng)站建設(shè)開發(fā)

    為集團(tuán)提供一個(gè)互聯(lián)網(wǎng)上的形象宣傳和信息發(fā)布、收集的重要平臺(tái) 利用最新的互聯(lián)網(wǎng)動(dòng)態(tài)數(shù)據(jù)庫交互能力,建立一套在互聯(lián)網(wǎng)上具有領(lǐng)先地位的集團(tuán)網(wǎng)站,將集團(tuán)和子公司網(wǎng)站做到有機(jī)的統(tǒng)一。集團(tuán)網(wǎng)站不但要把集團(tuán)的企業(yè)、產(chǎn)品等相關(guān)信息展示給我們的客戶、合作伙伴和業(yè)

    2024年02月14日
    瀏覽(53)
  • 前端開發(fā)網(wǎng)站推薦

    前端開發(fā)網(wǎng)站推薦

    以下是一些可以用來查找和比較前端框架的推薦網(wǎng)站: JavaScript框架比較: 這些網(wǎng)站提供了對(duì)不同JavaScript框架和庫的詳細(xì)比較和評(píng)估。 JavaScripting: 提供了大量的JavaScript庫和框架,以及它們的星級(jí)、更新頻率等信息。 Libraries.io: 可以查找各種編程語言的開源庫和框架,并查看

    2024年02月07日
    瀏覽(25)
  • 網(wǎng)站開發(fā)第一彈---HTML01

    網(wǎng)站開發(fā)第一彈---HTML01

    ???歡迎您來到我的MySQL基礎(chǔ)復(fù)習(xí)專欄 ☆* o(≧▽≦)o *☆哈嘍~我是小小惡斯法克?? ?博客主頁: 小小惡斯法克的博客 ??該系列文章專欄: 網(wǎng)站開發(fā)flask框架 ??文章作者技術(shù)和水平很有限,如果文中出現(xiàn)錯(cuò)誤,希望大家能指正?? ?? 感謝大家的關(guān)注!??? 目錄 1. 前后端

    2024年02月01日
    瀏覽(20)
  • 「Vue|網(wǎng)頁開發(fā)|前端開發(fā)」02 從單頁面到多頁面網(wǎng)站:使用路由實(shí)現(xiàn)網(wǎng)站多個(gè)頁面的展示和跳轉(zhuǎn)

    「Vue|網(wǎng)頁開發(fā)|前端開發(fā)」02 從單頁面到多頁面網(wǎng)站:使用路由實(shí)現(xiàn)網(wǎng)站多個(gè)頁面的展示和跳轉(zhuǎn)

    本文主要介紹如何使用路由控制來實(shí)現(xiàn)將一個(gè)單頁面網(wǎng)站擴(kuò)展成多頁面網(wǎng)站,包括頁面擴(kuò)展的邏輯,vue的官方路由vue-router的基本用法以及擴(kuò)展用法 「Vue|網(wǎng)頁開發(fā)|前端開發(fā)」01 快速入門:快速寫一個(gè)Vue的HelloWorld項(xiàng)目 我們?cè)谶M(jìn)行網(wǎng)站開發(fā)的時(shí)候,大多數(shù)都是需要有多個(gè)頁面

    2024年02月11日
    瀏覽(24)
  • 動(dòng)態(tài)網(wǎng)站開發(fā)02:Java Web概述

    動(dòng)態(tài)網(wǎng)站開發(fā)02:Java Web概述

    目標(biāo):了解XML的概念,能夠知道HTML用于做什么 1、XML XML是EXtensible Markup Language的縮寫,它是一種類似于HTML的標(biāo)記語言,稱為可擴(kuò)展標(biāo)記語言。XML用于提供數(shù)據(jù)描述格式,適用于不同應(yīng)用程序之間的數(shù)據(jù)交換,而且這種交換不以預(yù)先定義的一組數(shù)據(jù)結(jié)構(gòu)為前提,增強(qiáng)了可擴(kuò)展性

    2024年02月08日
    瀏覽(17)
  • 小例子——Flask網(wǎng)站開發(fā)(二)【保姆級(jí)】

    小例子——Flask網(wǎng)站開發(fā)(二)【保姆級(jí)】

    ? 問題一: ? ? ? ? ? ? ? ?如何實(shí)現(xiàn)Flask發(fā)送get請(qǐng)求? 下面是python代碼演示。 要使用Flask發(fā)送GET請(qǐng)求,首先需要安裝Flask庫,然后創(chuàng)建一個(gè)Flask應(yīng)用。 1. 首先在管理員窗口安裝Flask庫: ? 2. 創(chuàng)建一個(gè)名為`app.py`的文件,并添加以下代碼: ? 在這個(gè)示例中,我們創(chuàng)建了一個(gè)名

    2024年04月23日
    瀏覽(15)
  • 如何做好網(wǎng)站建設(shè)定制開發(fā)

    如何做好網(wǎng)站建設(shè)定制開發(fā)

    隨著互聯(lián)網(wǎng)的發(fā)展,如今的 網(wǎng)站建設(shè) 技術(shù)越來越先進(jìn),所以在網(wǎng)站建設(shè)定制開發(fā)中就出現(xiàn)了很多的模版,如:企業(yè)類型、產(chǎn)品信息、品牌介紹等。當(dāng)然網(wǎng)站建設(shè)定制開發(fā)不是說只要模版就行了,對(duì)于網(wǎng)站建設(shè)而言,模版只是作為輔助工具來使用,并不是主要的。 一款優(yōu)秀的

    2024年02月07日
    瀏覽(33)
  • Flask開發(fā)簡(jiǎn)易網(wǎng)站疑難點(diǎn)梳理

    Flask開發(fā)簡(jiǎn)易網(wǎng)站疑難點(diǎn)梳理

    Flask是python的web框架, 首先python本身開發(fā)效率就高,再因?yàn)镕lask是輕量級(jí)的,相比Django容易很多。 部署python項(xiàng)目時(shí)減少依賴包遺漏的情況,使用venv模塊為每個(gè)項(xiàng)目創(chuàng)建獨(dú)立的python環(huán)境,實(shí)現(xiàn)依賴的隔離。 在工程目錄下運(yùn)行:(xxxxxx是運(yùn)行環(huán)境目錄) 安裝第三方python庫和啟動(dòng)我

    2024年02月08日
    瀏覽(21)
  • Web 開發(fā)的 20 個(gè)實(shí)用網(wǎng)站

    Web 開發(fā)的 20 個(gè)實(shí)用網(wǎng)站

    作為一名前端開發(fā)工程師,我們一定使用過很多工具來提高自己的工作效率。它們可以是網(wǎng)站、文檔或 JavaScript 庫。 本文將分享20個(gè)實(shí)用網(wǎng)站。 https://jex.im/regulex/#!flags=re=%5E(a%7Cb)*%3F%24 我們可能會(huì)覺得正則表達(dá)式特別難學(xué)習(xí),但現(xiàn)在不一樣了??梢暬ぞ呤拐齽t表達(dá)式更容易

    2024年02月19日
    瀏覽(16)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包