欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Three.js+React制作3D夢中海島效果

 更新時間:2022年05月27日 09:55:13   作者:Dragonir  
深居內(nèi)陸的人們,大概每個人都有過大海之夢吧。本文使用React+Three.js技術(shù)棧,實現(xiàn)3D海洋和島嶼,感興趣的小伙伴可以跟隨小編一起學習一下

背景

深居內(nèi)陸的人們,大概每個人都有過大海之夢吧。夏日傍晚在沙灘漫步奔跑;或是在海上沖浪游泳;或是在海島游玩探險;亦或靜待日出日落……本文使用 React + Three.js 技術(shù)棧,實現(xiàn) 3D 海洋和島嶼,主要包含知識點包括:Tone Mapping、Water 類、Sky 類、Shader 著色、ShaderMaterial 著色器材質(zhì)、Raycaster 檢測遮擋以及 Three.js 的其他基礎知識,讓我們在這個夏天通過此頁面共赴大海之約。

效果

本頁面僅適配PC端,大屏訪問效果更佳。

在線預覽地址1:https://3d-eosin.vercel.app/#/ocean

在線預覽地址2:https://dragonir.github.io/3d/#/ocean

實現(xiàn)

素材準備

開發(fā)之前,需要準備頁面所需的素材,本文用到的海島素材是在 sketchfab.com 找的免費模型。下載好素材之后,在 Blender 中打開,按自己的想法調(diào)整模型的顏色、材質(zhì)、大小比例、角度、位置等信息,刪減不需要的模塊、縮減面數(shù)以壓縮模型體積,最后刪除相機、光照、UV、動畫等多余信息,只導出模型網(wǎng)格備用。

資源引入

首先,引入開發(fā)所需的必備資源,OrbitControls 用于鏡頭軌道控制;GLTFLoader 用于加載 gltf 格式模型;Water 是 Three.js 內(nèi)置的一個類,可以生成類似水的效果;Sky 可以生成天空效果;TWEEN 用來生成補間動畫;Animations 是對 TWEEN 控制鏡頭補間動畫方法的封裝;waterTexture 、flamingoModel、islandModel 三者分別是水的法向貼圖、飛鳥模型、海島模型;vertexShader 和 fragmentShader 是用于生成彩虹的 Shader 著色器。

import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { Water } from 'three/examples/jsm/objects/Water';
import { Sky } from 'three/examples/jsm/objects/Sky';
import { TWEEN } from "three/examples/jsm/libs/tween.module.min";
import Animations from '@/assets/utils/animations';
import waterTexture from '@/containers/Ocean/images/waternormals.jpg';
import islandModel from '@/containers/Ocean/models/island.glb';
import flamingoModel from '@/containers/Ocean/models/flamingo.glb';
import vertexShader from '@/containers/Ocean/shaders/rainbow/vertex.glsl';
import fragmentShader from '@/containers/Ocean/shaders/rainbow/fragment.glsl';

頁面結(jié)構(gòu)

頁面主要由3部分構(gòu)成:canvas.webgl 用于渲染 WEBGL 場景;div.loading 用于模型加載完成前顯示加載進度;div.point 用于添加交互點,省略部分是其他幾個交互點信息。

render () {
  return (
    <div className='ocean'>
      <canvas className='webgl'></canvas>
      {this.state.loadingProcess === 100 ? '' : (
        <div className='loading'>
          <span className='progress'>{this.state.loadingProcess} %</span>
        </div>
      )}
      <div className="point point-0">
        <div className="label label-0">1</div>
        <div className="text">燈塔:矗立在海岸的巖石之上,白色的塔身以及紅色的塔屋,在湛藍色的天空和深藍色大海的映襯下,顯得如此醒目和美麗。</div>
      </div>
      // ...
    </div>
  )
}

場景初始化

在這部分,先定義好需要的狀態(tài)值,loadingProcess 用于顯示頁面加載進度。

state = {
  loadingProcess: 0
}

定義一些全局變量和參數(shù),初始化場景、相機、鏡頭軌道控制器、燈光、頁面縮放監(jiān)聽等。

const clock = new THREE.Clock();
const raycaster = new THREE.Raycaster()
const sizes = {
  width: window.innerWidth,
  height: window.innerHeight
}
const renderer = new THREE.WebGLRenderer({
  canvas: document.querySelector('canvas.webgl'),
  antialias: true
});
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
renderer.setSize(sizes.width, sizes.height);
// 設置渲染效果
renderer.toneMapping = THREE.ACESFilmicToneMapping;
// 創(chuàng)建場景
const scene = new THREE.Scene();
// 創(chuàng)建相機
const camera = new THREE.PerspectiveCamera(55, sizes.width / sizes.height, 1, 20000);
camera.position.set(0, 600, 1600);
// 添加鏡頭軌道控制器
const controls = new OrbitControls(camera, renderer.domElement);
controls.target.set(0, 0, 0);
controls.enableDamping = true;
controls.enablePan = false;
controls.maxPolarAngle = 1.5;
controls.minDistance = 50;
controls.maxDistance = 1200;
// 添加環(huán)境光
const ambientLight = new THREE.AmbientLight(0xffffff, .8);
scene.add(ambientLight);
// 添加平行光
const dirLight = new THREE.DirectionalLight(0xffffff, 1);
dirLight.color.setHSL(.1, 1, .95);
dirLight.position.set(-1, 1.75, 1);
dirLight.position.multiplyScalar(30);
scene.add(dirLight);
// 頁面縮放監(jiān)聽并重新更新場景和相機
window.addEventListener('resize', () => {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
}, false);

Tone Mapping

可以注意到,本文使用了 renderer.toneMapping = THREE.ACESFilmicToneMapping 來設置頁面渲染效果。目前 Three.js 中有以下幾種 Tone Mapping 值,它們定義了 WebGLRenderer 的 toneMapping 屬性,用于在近似標準計算機顯示器或移動設備的低動態(tài)范圍 LDR 屏幕上展示高動態(tài)范圍 HDR 外觀。大家可以修改不同的值看看渲染效果有何不同。

  • THREE.NoToneMapping
  • THREE.LinearToneMapping
  • THREE.ReinhardToneMapping
  • THREE.CineonToneMapping
  • THREE.ACESFilmicToneMapping

使用 Three.js 自帶的 Water 類創(chuàng)建海洋,首先創(chuàng)建一個平面網(wǎng)格 waterGeometry,讓后將它傳遞給 Water,并配置相關(guān)屬性,最后將海洋添加到場景中。

const waterGeometry = new THREE.PlaneGeometry(10000, 10000);
const water = new Water(waterGeometry, {
  textureWidth: 512,
  textureHeight: 512,
  waterNormals: new THREE.TextureLoader().load(waterTexture,  texture => {
    texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
  }),
  sunDirection: new THREE.Vector3(),
  sunColor: 0xffffff,
  waterColor: 0x0072ff,
  distortionScale: 4,
  fog: scene.fog !== undefined
});
water.rotation.x = - Math.PI / 2;
scene.add(water);

Water 類

參數(shù)說明

  • textureWidth:畫布寬度
  • textureHeight:畫布高度
  • waterNormals:法向量貼圖
  • sunDirection:陽光方向
  • sunColor:陽光顏色
  • waterColor:水顏色
  • distortionScale:物體倒影分散度
  • fog:霧
  • alpha:透明度

天空

接著,使用 Three.js 自帶的天空類 Sky 創(chuàng)建天空,通過修改著色器參數(shù)設置天空樣式,然后創(chuàng)建太陽并添加到場景中。

const sky = new Sky();
sky.scale.setScalar(10000);
scene.add(sky);
const skyUniforms = sky.material.uniforms;
skyUniforms['turbidity'].value = 20;
skyUniforms['rayleigh'].value = 2;
skyUniforms['mieCoefficient'].value = 0.005;
skyUniforms['mieDirectionalG'].value = 0.8;
// 太陽
const sun = new THREE.Vector3();
const pmremGenerator = new THREE.PMREMGenerator(renderer);
const phi = THREE.MathUtils.degToRad(88);
const theta = THREE.MathUtils.degToRad(180);
sun.setFromSphericalCoords(1, phi, theta);
sky.material.uniforms['sunPosition'].value.copy(sun);
water.material.uniforms['sunDirection'].value.copy(sun).normalize();
scene.environment = pmremGenerator.fromScene(sky).texture;

Sky 類

天空材質(zhì)著色器參數(shù)說明

  • turbidity 渾濁度
  • rayleigh 視覺效果就是傍晚晚霞的紅光的深度
  • luminance 視覺效果整體提亮或變暗
  • mieCoefficient 散射系數(shù)
  • mieDirectionalG 定向散射值

首先,創(chuàng)建具有彩虹漸變效果的著色器 Shader, 然后使用著色器材質(zhì) ShaderMaterial, 創(chuàng)建圓環(huán) THREE.TorusGeometry 并添加到場景中。

頂點著色器 vertex.glsl

varying vec2 vUV;
varying vec3 vNormal;
void main () {
  vUV = uv;
  vNormal = vec3(normal);
  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}

片段著色器 fragment.glsl

varying vec2 vUV;
varying vec3 vNormal;
void main () {
  vec4 c = vec4(abs(vNormal) + vec3(vUV, 0.0), 0.1); // 設置透明度為0.1
  gl_FragColor = c;
}

彩虹漸變著色器效果

const material = new THREE.ShaderMaterial({
  side: THREE.DoubleSide,
  transparent: true,
  uniforms: {},
  vertexShader: vertexShader,
  fragmentShader: fragmentShader
});
const geometry = new THREE.TorusGeometry(200, 10, 50, 100);
const torus = new THREE.Mesh(geometry, material);
torus.opacity = .1;
torus.position.set(0, -50, -400);
scene.add(torus);

Shader 著色器

WebGL 中記述了坐標變換的機制就叫做著色器 Shader,著色器又有處理幾何圖形頂點的 頂點著色器 和處理像素的 片段著色器 兩種類型

準備頂點著色器和片元著色器

著色器的添加有多種方法,最簡單的方法就是把著色器記錄在 HTML 中。該方法利用HTML 的 script 標簽來實現(xiàn),如:

頂點著色器

<script id="vshader" type="x-shader/x-vertex"></script>

片段著色器

<script id="fshader" type="x-shader/x-fragment"></script>

也可以像本文中一樣,直接使用單獨創(chuàng)建 glsl 格式文件引入。

著色器的三個變量與運行方式

  • Uniforms:是所有頂點都具有相同的值的變量。 比如燈光,霧,和陰影貼圖就是被儲存在 uniforms 中的數(shù)據(jù)。uniforms 可以通過頂點著色器和片元著色器來訪問。
  • Attributes:是與每個頂點關(guān)聯(lián)的變量。例如,頂點位置,法線和頂點顏色都是存儲在 attributes 中的數(shù)據(jù)。attributes 只可以在頂點著色器中訪問。
  • Varyings:是從頂點著色器傳遞到片元著色器的變量。對于每一個片元,每一個varying 的值將是相鄰頂點值的平滑插值。

頂點著色器 首先運行,它接收 attributes, 計算每個單獨頂點的位置,并將其他數(shù)據(jù)varyings 傳遞給片段著色器。片段著色器 后運行,它設置渲染到屏幕的每個單獨的片段的顏色。

ShaderMaterial 著色器材質(zhì)

Three.js 所謂的材質(zhì)對象 Material 本質(zhì)上就是著色器代碼和需要傳遞的 uniform 數(shù)據(jù)光源、顏色、矩陣。Three.js 提供可直接渲染著色器語法的材質(zhì) ShaderMaterial 和 RawShaderMaterial。

  • RawShaderMaterial: 和原生 WebGL 中一樣,頂點著色器、片元著色器代碼基本沒有任何區(qū)別,不過頂點數(shù)據(jù)和 uniform 數(shù)據(jù)可以通過 Three.js 的 API 快速傳遞,要比使用 WebGL 原生的 API 與著色器變量綁定要方便得多。
  • ShaderMaterialShaderMaterial 比 RawShaderMaterial 更方便些,著色器中的很多變量不用聲明,Three.js 系統(tǒng)會自動設置,比如頂點坐標變量、投影矩陣、視圖矩陣等。

構(gòu)造函數(shù)

ShaderMaterial(parameters : Object)

parameters:可選,用于定義材質(zhì)外觀的對象,具有一個或多個屬性。

常用屬性

attributes[Object]:接受如下形式的對象,{ attribute1: { value: []} } 指定要傳遞給頂點著色器代碼的 attributes;鍵為 attribute 修飾變量的名稱,值也是對象格式,如 { value: [] }, value 是固定名稱,因為 attribute 相對于所有頂點,所以應該回傳一個數(shù)組格式。只有 bufferGeometry 類型的能使用該屬性。

.uniforms[Object]:如下形式的對象:{ uniform1: { value: 1.0 }, uniform2: { value: 2.0 }} 指定要傳遞給shader 代碼的 uniforms;鍵為 uniform 的名稱,值是如下形式:{ value: 1.0 } 這里 value 是 uniform 的值。名稱必須匹配著色器代碼中 uniform 的 name,和 GLSL 代碼中的定義一樣。 注意,uniforms 逐幀被刷新,所以更新 uniform 值將立即更新 GLSL 代碼中的相應值。

.fragmentShader[String]:片元著色器的 GLSL 代碼,它也可以作為一個字符串直接傳遞或者通過 AJAX 加載。

.vertexShader[String]:頂點著色器的 GLSL 代碼,它也可以作為一個字符串直接傳遞或者通過 AJAX 加載。

接著,使用 GLTFLoader 加載島嶼模型并添加到場景中。加載之前可以使用 LoadingManager 來管理加載進度。

const manager = new THREE.LoadingManager();
manager.onProgress = async(url, loaded, total) => {
  if (Math.floor(loaded / total * 100) === 100) {
    this.setState({ loadingProcess: Math.floor(loaded / total * 100) });
    Animations.animateCamera(camera, controls, { x: 0, y: 40, z: 140 }, { x: 0, y: 0, z: 0 }, 4000, () => {
      this.setState({ sceneReady: true });
    });
  } else {
    this.setState({ loadingProcess: Math.floor(loaded / total * 100) });
  }
};
const loader = new GLTFLoader(manager);
loader.load(islandModel, mesh => {
  mesh.scene.traverse(child => {
    if (child.isMesh) {
      child.material.metalness = .4;
      child.material.roughness = .6;
    }
  })
  mesh.scene.position.set(0, -2, 0);
  mesh.scene.scale.set(33, 33, 33);
  scene.add(mesh.scene);
});

使用 GLTFLoader 加載島嶼模型添加到場景中,獲取模型自帶的動畫幀并進行播放,記得要在 requestAnimationFrame 中更新動畫??梢允褂?nbsp;clone 方法在場景中添加多只飛鳥。鳥模型來源于 Three.js 官網(wǎng)。

loader.load(flamingoModel, gltf => {
  const mesh = gltf.scene.children[0];
  mesh.scale.set(.35, .35, .35);
  mesh.position.set(-100, 80, -300);
  mesh.rotation.y = - 1;
  mesh.castShadow = true;
  scene.add(mesh);
  const mixer = new THREE.AnimationMixer(mesh);
  mixer.clipAction(gltf.animations[0]).setDuration(1.2).play();
  this.mixers.push(mixer);
});

交互點

添加交互點,鼠標 hover 懸浮時顯示提示語,點擊交互點可以切換鏡頭角度,視角聚焦到交互點對應的位置上。

const points = [
  {
    position: new THREE.Vector3(10, 46, 0),
    element: document.querySelector('.point-0')
  },
  // ...
];
document.querySelectorAll('.point').forEach(item => {
  item.addEventListener('click', event => {
    let className = event.target.classList[event.target.classList.length - 1];
    switch(className) {
      case 'label-0':
        Animations.animateCamera(camera, controls, { x: -15, y: 80, z: 60 }, { x: 0, y: 0, z: 0 }, 1600, () => {});
        break;
      // ...
    }
  }, false);
});

動畫

在 requestAnimationFrame 中更新水、鏡頭軌道控制器、相機、TWEEN、交互點等動畫。

const animate = () => {
  requestAnimationFrame(animate);
  water.material.uniforms['time'].value += 1.0 / 60.0;
  controls && controls.update();
  const delta = clock.getDelta();
  this.mixers && this.mixers.forEach(item => {
    item.update(delta);
  });
  const timer = Date.now() * 0.0005;
  TWEEN && TWEEN.update();
  camera && (camera.position.y += Math.sin(timer) * .05);
  if (this.state.sceneReady) {
    // 遍歷每個點
    for (const point of points) {
      // 獲取2D屏幕位置
      const screenPosition = point.position.clone();
      screenPosition.project(camera);
      raycaster.setFromCamera(screenPosition, camera);
      const intersects = raycaster.intersectObjects(scene.children, true);
      if (intersects.length === 0) {
        // 未找到相交點,顯示
        point.element.classList.add('visible');
      } else {
        // 找到相交點
        // 獲取相交點的距離和點的距離
        const intersectionDistance = intersects[0].distance;
        const pointDistance = point.position.distanceTo(camera.position);
        // 相交點距離比點距離近,隱藏;相交點距離比點距離遠,顯示
        intersectionDistance < pointDistance ? point.element.classList.remove('visible') :  point.element.classList.add('visible');
      }
      const translateX = screenPosition.x * sizes.width * 0.5;
      const translateY = - screenPosition.y * sizes.height * 0.5;
      point.element.style.transform = `translateX(${translateX}px) translateY(${translateY}px)`;
    }
  }
  renderer.render(scene, camera);
}
animate();
}

Raycaster 檢測遮擋

仔細觀察,在上述更新交互點動畫的方法中,通過 raycaster 射線來檢查交互點是否被物體遮擋,如果被遮擋就隱藏交互點,否則顯示交互點,大家可以通過旋轉(zhuǎn)場景觀察到這一效果。

總結(jié)

本文包含的新知識點主要包括:

  • Tone Mapping
  • Water 類
  • Sky 類
  • Shader 著色器
  • ShaderMaterial 著色器材質(zhì)
  • Raycaster 檢測遮擋

以上就是Three.js+React制作3D夢中海島效果的詳細內(nèi)容,更多關(guān)于Three.js React 3D海島的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • DIV層之拖動、關(guān)閉、打開效果代碼

    DIV層之拖動、關(guān)閉、打開效果代碼

    非常不錯的效果,適合在當前頁打開測試窗口,圖片等
    2008-09-09
  • 支持移動端原生js輪播圖

    支持移動端原生js輪播圖

    這篇文章主要介紹了支持移動端原生js輪播圖的相關(guān)資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-02-02
  • JS實現(xiàn)簡單圖片輪播效果

    JS實現(xiàn)簡單圖片輪播效果

    這篇文章主要為大家詳細介紹了JS實現(xiàn)簡單圖片輪播效果,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-05-05
  • 非html5實現(xiàn)js版彈球游戲示例代碼

    非html5實現(xiàn)js版彈球游戲示例代碼

    彈球游戲,一般都是使用html5來實現(xiàn)的,其實不然,使用js也可以實現(xiàn)類似的效果,下面有個不錯的示例,感興趣的朋友可以參考下,希望對大家有所幫助
    2013-09-09
  • webpack4簡單入門實例

    webpack4簡單入門實例

    這篇文章主要介紹了webpack4簡單入門實例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-09-09
  • JS中Eval解析JSON字符串的一個小問題

    JS中Eval解析JSON字符串的一個小問題

    JSON (JavaScript Object Notation)一種簡單的數(shù)據(jù)格式,比xml更輕巧,下面通過本文給大家介紹JS中Eval解析JSON字符串的一個小問題,需要的朋友參考下吧
    2016-02-02
  • js利用FileReader讀取本地文件或者blob方式

    js利用FileReader讀取本地文件或者blob方式

    這篇文章主要介紹了js利用FileReader讀取本地文件或者blob方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-12-12
  • 微博@符號的用戶名提示效果。(想@到誰?)

    微博@符號的用戶名提示效果。(想@到誰?)

    相信你老早就在騰訊或者新浪的微博上體驗到了,@符號在微博時代的便捷呼叫。
    2010-11-11
  • 一文總結(jié)JS中邏輯運算符的特點

    一文總結(jié)JS中邏輯運算符的特點

    在JavaScript的眾多運算符里,提供了三個邏輯運算符&&、||和!,下面這篇文章主要給大家介紹了關(guān)于JS中邏輯運算符的特點,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下
    2022-03-03
  • Javascript刷新頁面的實例

    Javascript刷新頁面的實例

    這篇文章主要介紹了Javascript刷新頁面的實例的相關(guān)資料,希望通過本文能幫助到大家,需要的朋友可以參考下
    2017-09-09

最新評論