Threejs02 - 贴图、阴影和灯光

1,166 阅读7分钟

一、贴图

要渲染3D物体,必不可少的要素:场景、相机、物体(包括几何体和材质)、渲染器、控制器。基本概念可参考这篇文章。搭建一个场景代码如下:

import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
// 场景
const scene = new THREE.Scene();
// 相机
const camera = new THREE.PerspectiveCamera(
  75,
  window.innerWidth / window.innerHeight,
  0.1,
  1000
);
camera.position.set(0, 0, 10);
scene.add(camera);
// 添加物体
const box = new THREE.BoxGeometry(1, 1, 1, 100, 100, 100);
// 材质:MeshStandardMaterial标准网格材质,是基于物理渲染的,能够基于物理模型处理灯光和阴影,更接近物理世界的真实场景。
const material = new THREE.MeshStandardMaterial({color: 0xffff00})
// 物体
const cube = new THREE.Mesh(box, material);
scene.add(cube)
// 渲染器
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// 轨道控制器
const controls = new OrbitControls(camera, renderer.domElement);
// 轨道控制器的阻尼感
controls.enableDamping = true;
//辅助坐标轴
const axesHelp = new THREE.AxesHelper();
scene.add(axesHelp);
//渲染函数
function render() {
    renderer.render(scene, camera);
    requestAnimationFrame(render);
  }
render();

看下效果:

image.png
物体出来了,但是是黑的,因为有些材质受光照影响、有些材质不受光照影响。这里使用的标准网格材质是基于物理渲染的(BPR),受光照影响,需要有光才能看到。
添加灯光:

//环境光会均匀的照亮场景中的所有物体。
const light = new THREE.AmbientLight(0x404040);
scene.add(light);
// 平行光
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5);
// 设置光的位置
directionalLight.position.set(5, 5, 5);
// 添加到场景中
scene.add(directionalLight);

添加灯光后,物体可以看到了。
image.png\

接下来给这个物体添加贴图,来达到我们想要的3D场景。

1、map贴图

给这个物体贴个门上去。
首先要创建一个纹理加载器,将需要贴的图片引入进来

// 纹理加载器 加载文件
const texture = new THREE.TextureLoader(textureManage);
// 加载图片
const doorColorTexture = texture.load(
  require("../assets/images/textures/door/color.jpg")
);

设置贴图

const material = new THREE.MeshStandardMaterial({
  color: 0xffff00,
  map: doorColorTexture,
});

看下效果:

image.png
这个门就贴上去了,但是我们只想要中间的门,不需要两边的墙。可以加个透明度贴图,墙的部分是黑色,门的部分是白色的一张灰度图。

2、alphMap透明度贴图

黑色为完全透明,白色为完全不透明。注意透明度贴图需要设置材质的transparent属性为true,才会有效果。

// alpha贴图是一张灰度纹理,用于控制整个表面的不透明度。(黑色:完全透明;白色:完全不透明)
const alphaMap = texture.load(
  require("../assets/images/textures/door/alpha.jpg")
);

const material = new THREE.MeshStandardMaterial({
  color: 0xffff00,
  map: doorColorTexture,
  alphaMap: alphaMap,
  transparent: true,
});

效果:

image.png

3、aoMap环境遮挡贴图

在场景中为物体表面添加更加真实的阴影效果

    const aoMap = texture.load(
      require("../assets/images/textures/door/ambientOcclusion.jpg")
    );

    const material = new THREE.MeshStandardMaterial({
      color: 0xffff00,
      map: doorColorTexture,
      alphaMap: alphaMap,
      aoMap: aoMap,
      //环境遮挡效果的强度。默认值为1。零是不遮挡效果
      aoMapIntensity: 1,
    });

上面是加了aoMap的效果,下面是没加的效果,可以看出区别。平行光可能效果不明显,但还是能看出一点去区别的。
image.png

image.png

4、displacementMap位移贴图

增加图像凹凸效果。这个贴图需要设置几何体的分段数才会有效果。new THREE.BoxGeometry(1,1,1,100,100,100)。后三个参数就是分段数。

// 位移贴图,增加图像凸凹效果
const displacementMap = texture.load(
  require("../assets/images/textures/door/height.jpg")
);
const material = new THREE.MeshStandardMaterial({
  color: 0xffff00,
  map: doorColorTexture,
  alphaMap: alphaMap,
  transparent: true,
  aoMap: aoMap,
  //位移贴图 。需要设置分段数 才能显示效果 new THREE.BoxGeometry(1,1,1,100,100,100);后三个参数是分段值
  displacementMap: displacementMap,
  displacementScale: 0.1,
});

效果图:可以看出加了位移贴图后,物体有了厚度。通过调节displacementScale大小,来调节厚度。 注:位移纹理是一种灰度图像,根据其亮度值改变顶点的位置。影响displacementScale数值设定的因素有几个:
1、纹理分辨率:高分辨率纹理可以获得更精细的位移效果,但同时也需要更大的显存和计算量。在加载纹理时,选择合适分辨率的图像。
2、模型复杂度:模型的顶点数量和拓扑结构会影响位移效果的表现。高密度的模型可以获得更好的位移效果,但同样会增加渲染负担。
3、材质属性:位移纹理的颜色和对比度以及其他材质属性(如漫反射、镜面反射等)都会影响 displacementScale 的数值设定。你可能需要调整这些属性以达到理想的视觉效果。
4、光照条件:场景中的光源类型和数量会影响渲染效果,可能需要根据具体的光照环境调整 displacementScale 的数值。
5、观察距离和角度:不同的观察距离和视角可能需要不同的 displacementScale 数值以获得最佳效果。
6、性能考虑:较高的 displacementScale 值会增加渲染负担,因此在实际应用中可能需要权衡性能与视觉效果。

综上所述,在设置 displacementScale 时,需要考虑多种因素并进行调整。通过尝试不同的数值和观察相应的效果,你可以找到最适合你项目需求的设定。

image.png

5、roughnessMap粗糙度贴图

物体表面的粗糙度,决定了光的反射效果。0.0表示平滑的镜面反射,1.0表示完全漫反射。通过roughness设置粗糙度

const roughnessMap = texture.load(
  require("../assets/images/textures/door/roughness.jpg")
);
const material = new THREE.MeshStandardMaterial({
  color: 0xffff00,
  map: doorColorTexture,
  alphaMap: alphaMap,
  transparent: true,
  aoMap: aoMap,
  //位移贴图 。需要设置分段数 才能显示效果 new THREE.BoxGeometry(1,1,1,100,100,100);后三个参数是分段值
  displacementMap: displacementMap,
  displacementScale: 0.1,
  roughnessMap: roughnessMap,
  roughness: 0,
});

roughness为0的效果:表面很光滑,平行光照上去是镜面反射。有强烈的镜面高光。

image.png
roughness为1的效果:表面粗糙,发生散射现象,物体表面看起来更加柔和和有纹理感,较暗的镜面高光。
image.png

6、metalnessMap金属度贴图

用于改变材质的金属度。

// 金属度贴图
const metalnessMap = texture.load(
  require("../assets/images/textures/door/metalness.jpg")
);

const material = new THREE.MeshStandardMaterial({
color: 0xffff00,
map: doorColorTexture,
alphaMap: alphaMap,
transparent: true,
aoMap: aoMap,
//环境遮挡效果的强度。默认值为1。零是不遮挡效果
// aoMapIntensity: 0.5,
//替换贴图 需要设置分段数 才能显示效果 new THREE.BoxGeometry(1,1,1,100,100,100);后三个参数是分段值
displacementMap: displacementMap,
displacementScale: 0.1,
roughnessMap: roughnessMap,
roughness: 0,
metalnessMap: metalnessMap,
// 金属度
metalness: 1,
});

效果:可以看出金属部分有了金属感

image.png

7、normalMap法线贴图

增强物体表面细节。

// 法线贴图
const normalMap = texture.load(
require("../assets/images/textures/door/normal.jpg")
);

const material = new THREE.MeshStandardMaterial({
color: 0xffff00,
map: doorColorTexture,
alphaMap: alphaMap,
transparent: true,
aoMap: aoMap,
//环境遮挡效果的强度。默认值为1。零是不遮挡效果
// aoMapIntensity: 0.5,
//替换贴图 需要设置分段数 才能显示效果 new THREE.BoxGeometry(1,1,1,100,100,100);后三个参数是分段值
displacementMap: displacementMap,
displacementScale: 0.1,
roughnessMap: roughnessMap,
roughness: 0,
metalnessMap: metalnessMap,
metalness: 1,
normalMap: normalMap,
});

效果:是不更具真实感了呢。

image.png

二、灯光和阴影

想要呈现更具真实感的3D场景,阴影是必不可缺的。threejs中的阴影通常是指投射阴影,它是由场景中的光源、遮挡物、投射物三者共同作用产生的。光源照在遮挡物上,遮挡物背后的区域形成阴影。要实现投射阴影,需要物体材质满足对光的反应开启光源的投射阴影(castShadow)、物体材质的castShadow物体接收阴影渲染器开启阴影计算

1、光源

有光源才能产生阴影。threejs提供了多种类型的光源,如平行光、点光源、聚光灯。
平行光:来自远处的光,照射范围广。
点光源:如灯泡,距离越远,光线越暗。
聚光灯:如手电筒,有一个光源,照亮一块区域。

//平行光
const directionalLight = new THREE.DirectionalLight(0xffffff,0.5);
//聚光灯
// const spotLight = new THREE.SpotLight(0xffffff,0.5);
// 点光源
// const pointLight = new THREE.PointLight(0xff0000,0.5);
directionalLight.position.set(5,5,5);
scene.add(directionalLight);

2、设置光源的投射阴影

directionalLight.castShadow = true

3、设置物体的投射阴影。

//创建一个球体
const sphereGeometry = new THREE.SphereGeometry(1, 20, 20);
// 物体材质要对光有反应。MeshBasicMaterial这个材质不受光照影响
const material = new THREE.MeshStandardMaterial();
const sphere = new THREE.Mesh(sphereGeometry, material);
// 设置物体的投射阴影
sphere.castShadow = true;
//设置阴影边缘模糊度
directionalLight.shadow.radius = 20
//设置阴影的分辨率
directionalLight.shadow.mapSize.set(4096,4096);
scene.add(sphere)

4、接收阴影

创建一个平面,并设置接收阴影来接收上面球体的阴影。

// 创建平面
const planeGeometry = new THREE.PlaneGeometry(10, 10);
const plane = new THREE.Mesh(planeGeometry, material);
plane.position.set(0, -1, 0);
plane.rotation.x = -Math.PI / 2;
// 接收阴影
plane.receiveShadow = true;

scene.add(plane);

5、开启渲染器阴影计算

renderer.shadowMap.enabled = true;

注:上面的所有的参数都可以添加一个图形界面(dat.gui)来调整参数,找到最合适的参数大小

最后可以得到这样的效果(平行光):

image.png

聚光灯效果 :

image.png

源码和素材地址:threejs基础