react-three 实现3D游戏(3)——海洋、天空和云雾
概述
在之前的章节中,我们的环境已经加载出来了,也添加了环境的音效,但是孤零零的小岛难免让人感到单调。这次我们来丰富下游戏世界的场景,添加一些细节。
海洋
关于水的纹理相关资料很多,我们直接使用官方的示例。
🔗 react-three的示例:Water shader 🔗 Three.js官网的示例:shader Ocean
我们直接从官网中获得海面的法线纹理贴图,把他放到public目录的textures文件夹中
在models下新建文件ocean.tsx,在这个文件中我们做3件事:
- 加载法线纹理贴图
- 配置水面的可选参数(这里照搬了示例中,你可以自行调试)
- 在帧渲染中,改变uniforms的时间值,让水面流动
附代码如下:
/**
* 海洋组件
* 参数:
* range: 水面的范围
*/
extend({ Water });
export default function Ocean({ range }: { range: number }) {
// 水面波纹的法线贴图
const waterNormals = useTexture("/textures/waters.jpeg")
waterNormals.wrapS = waterNormals.wrapT = THREE.RepeatWrapping;
const geometry = useMemo(() => new THREE.PlaneGeometry(range, range), [range]);
const options: WaterOptions = useMemo(
() => ({
textureWidth: 512,
textureHeight: 512,
waterNormals, // 法线贴图
sunDirection: new THREE.Vector3(),// 太阳的照射方向
sunColor: 0xFFFACD, // 太阳的颜色
// waterColor: 0x001e0f, // 水的颜色
waterColor: "skyblue", // 水的颜色
distortionScale: 3.7, // 水面扭曲指数
}),
waterNormals]
);
useFrame((state, delta) => {
if (!state || !ref?.current) return;
ref.current.material.uniforms.time.value += delta;
});
return (
<water
ref={ref}
args={[geometry, options]}
rotation-x={-Math.PI / 2}
position={[0, -5.5, 0]}
/>
);
}
这里如果组件water
组件标红,需要在vite-env.d.ts中添加全局类型声明:
declare global {
namespace JSX {
interface IntrinsicElements {
water: ReactThreeFiber.Object3DNode<Water, typeof Water>;
}
}
}
雾气
在react-three中添加雾气相当简单,只需要添加:<fog attach="fog" args={[0xfff0ea, 1, 200]} />
代码到Canvas组件下,其中args的参数是雾效设置:
- 雾的颜色:
0xfff0ea
,即十六进制颜色值0xfff0ea
,这个值代表着一种浅黄色。 - 近截面(
near
):100
,表示雾效的起始位置在相机的位置后1个单位处。 - 远截面(
far
):200
,表示雾效的结束位置在相机的位置后200个单位处。
天空
海洋有了,我们还需要一个更加逼真的天空。原来的渐变色背景,可以不动,我们为场景添加一个天空盒,Sky组件。
import { KeyboardControls, Sky } from '@react-three/drei'
<Canvas>
<Sky
distance={500} // 天空盒的覆盖范围
sunPosition={[20, 30, 10]} // 太阳位置
azimuth={180} //太阳水平方向位置
inclination={180} // 太阳倾斜角度,值越大太阳越高
mieCoefficient={0.005} // Mie 散射系数,影响天空的亮度和颜色
mieDirectionalG={0.7} // Mie 方向系数,影响天空的光散射角度
rayleigh={3} // Rayleigh 散射系数,影响天空的亮度和颜色
turbidity={10} // 浊度,影响天空的清晰度和颜色
/>
</Canvas>
关于天空,可以参考threejs官网的示例:sky
通过它你可以更清楚的了解Sky组件各个参数的作用。 你可以自己调整这些参数,直到感觉适合你的项目。
如果你对Sky组件效果不满意,可以自己创建天空盒,最简单的方式用一个全景图作为纹理。创建一个球形geometry,将全景纹理双面渲染到球体上。
具体可以参考以下官方示例: 🔗 react-three 天空盒示例
云朵
我们使用react-three/drei中提供的Cloud
组件
注意,如果你不使用本地的纹理图片,该组件默认加载外网的在线纹理,但在中国大陆地区该纹理无法加载成功。
从示例中直接获取云朵的纹理图片,把它放到public文件下的textures文件夹中。
在models文件夹下新建clouds.tsx文件
import { Clouds, Cloud } from "@react-three/drei";
import * as THREE from "three";
/**
* 云层
* 参数:
* num: 云朵的数量
*/
export default function CloudLayer({num=30}:{num?:number}) {
return <Clouds material={THREE.MeshLambertMaterial} limit={200} range={200} texture="./textures/cloud.png" >
<Cloud position={[0, 100, 0]} concentrate="random" growth={10} color="white" opacity={1.25} seed={0.3} bounds={[200, 1, 200]} volume={20} segments={num} />
</Clouds>
}
这就是全部代码了,让我解释下Cloud的参数:
- concentrate:用于管理云朵们的分布状态,有3个值:
- random,随机分布
- inside, 中心聚集
- outside, 四周分布
- growth:动画的速度的系数值
- color:云朵的颜色
- opacity: 透明度
- seed: 随机种子,当随机种子一样时云朵形状一样。(默认随机取值)
- bounds:边界,它的值构成一个长方体,云朵在这个长方体内分布。如上述值表示长200,宽200,高为1的长方体。
- volume: 云的体积大小
- segments: 云朵的数量
Clouds的参数:
- limit:云朵数量的最大上限
- range:云朵渲染的数量
- texture:云的纹理
🔗 官方文档地址
挂载组件
在models文件夹下的index.tsx中挂载天空、雾气、海洋、云朵等环境。
import { Sky } from '@react-three/drei'
import Ocean from './ocean';
import CloudLayer from './cloudLayer';
...
<Suspense fallback={null}>
<Sky distance={500} sunPosition={[20, 30, 10]} />
<fog attach="fog" args={[0xfff0ea, 1, 200]} />
<Ocean range={500} />
<CloudLayer />
...
结语
本次完成
- 添加海洋、雾气
- 添加天空、云层
最终效果
耳边响起舒缓的音乐,伴随着浪涛声,伫立欣赏远处天空的云朵,真让人有种心旷神怡之感啊!
水流心不惊,云在意俱迟。 一心不赘物,古今自逍遥。
项目地址
结语
后续计划添加一个用于显示玩家位置的小地图。 如果大家有什么想法的话,欢迎在评论区提出你的宝贵想法和意见。
转载自:https://juejin.cn/post/7362547962605174821