Skip to content

环境贴图

🕒 Published at:

没有环境贴图的感觉

可以看到环境贴图对PBR材质渲染效果影响还是比较大,一般渲染PBR材质的模型,最好设置一个合适的环境贴图。

立方体纹理加载器CubeTextureLoader

  • TextureLoader返回Texture
  • CubeTextureLoader返回CubeTexture

通过前面学习大家知道,通过纹理贴图加载器TextureLoader的.load()方法加载一张图片可以返回一个纹理对象Texture。

立方体纹理加载器CubeTextureLoader的.load()方法是加载6张图片,返回一个立方体纹理对象CubeTexture。

立方体纹理对象CubeTexture的父类是纹理对象Texture。

CubeTextureLoader加载环境贴图

所谓环境贴图,就是一个模型周围的环境的图像,比如一间房子,房子的上下左右前后分别拍摄一张照片,就是3D空间中6个角度方向的照片。

js
// 加载环境贴图
// 加载周围环境6个方向贴图
// 上下左右前后6张贴图构成一个立方体空间
// 'px.jpg', 'nx.jpg':x轴正方向、负方向贴图  p:正positive  n:负negative
// 'py.jpg', 'ny.jpg':y轴贴图
// 'pz.jpg', 'nz.jpg':z轴贴图
const textureCube = new THREE.CubeTextureLoader()
    .setPath('./环境贴图/环境贴图0/')
    .load(['px.jpg', 'nx.jpg', 'py.jpg', 'ny.jpg', 'pz.jpg', 'nz.jpg']);
    // CubeTexture表示立方体纹理对象,父类是纹理对象Texture

MeshStandardMaterial环境贴图属性.envMap

实际生活中,一个物体表面,往往会反射周围的环境。人的眼睛看到的东西,往往反射有周围景物,所以three.js渲染模型,如果想渲染效果更好看,如果想更符合实际生活情况,也需要想办法让模型反射周围景物。

MeshStandardMaterial材质的环境贴图属性是.envMap,通过PBR材质的贴图属性可以实现模型表面反射周围景物,这样渲染效果更好。

js
// 加载环境贴图
// 递归遍历所有模型节点批量修改材质
loader.load('/ziyuan/金属.glb', function (gltf) {
    // 递归遍历所有模型节点批量修改材质
    gltf.scene.traverse(function (obj) {
    if (obj.isMesh) {
        //判断是否是网格模型
        // console.log('obj.material',obj.material);
        // 重新设置材质的金属度和粗糙度属性
        obj.material.metalness = 1.0; //金属度
        obj.material.roughness = 0.35; //表面粗糙度
        obj.material.envMap = textureCube; //设置环境贴图
        // envMapIntensity:控制环境贴图对mesh表面影响程度
        obj.material.envMapIntensity = 1; //默认值1, 设置为0.0,相当于没有环境贴图
        // 相当于下面这种写法

        // obj.material = new THREE.MeshStandardMaterial({
        //     color: obj.material.color, //读取材质原来的颜色
        //     metalness: 1.0, //金属度
        //     roughness: 0.5, //粗糙度
        //     envMap: textureCube, //设置pbr材质环境贴图
        //     // envMapIntensity:控制环境贴图对mesh表面影响程度
        //     envMapIntensity: 0.0, //默认值1, 设置为0.0,相当于没有环境贴图
        // })
    }
    });
    model.add(gltf.scene);
});

有环境贴图的效果

环境贴图反射率.envMapIntensity

MeshStandardMaterial的.envMapIntensity属性主要用来设置模型表面反射周围环境贴图的能力,或者说环境贴图对模型表面的影响能力。具体说.envMapIntensity相当于环境贴图的系数,环境贴图像素值乘以该系数后,在用于影响模型表面。

js
// envMapIntensity:控制环境贴图对mesh表面影响程度
//默认值1, 设置为0.0,相当于没有环境贴图
obj.material.envMapIntensity = 1.0;

粗糙度roughness为0

你可以尝试把粗糙度roughness设置为0,看看模型对环境贴图的反射效果。

js
// obj.material.roughness = 0.5; //表面粗糙度
obj.material.roughness = 0.0;//完全镜面反射,像镜子一样

镜面反射的感觉

选择合适的环境贴图

不同的明暗或景物的环境贴图对渲染效果的影响是不一样的,所以不仅要设置环境贴图,还要根据需要选择合适的环境贴图,一般实际开发使用美术提供的环境贴图即可。

纹理和渲染器颜色空间一致

js
//如果renderer.outputEncoding=THREE.sRGBEncoding;环境贴图需要保持一致
textureCube.encoding = THREE.sRGBEncoding;

场景环境属性.environment

网格模型可以通过材质的.envMap属性设置环境贴图,如果一个gltf模型中所有的Mesh都要设置环境贴图就需要递归遍历gltf模型,给里面每个Mesh的材质设置.envMap。

js
loader.load("../工厂.glb", function (gltf) {
    // 递归遍历批量设置环境贴图
    gltf.scene.traverse(function (obj) {
        if (obj.isMesh) { //判断是否是网格模型
            obj.material.envMap = textureCube; //设置环境贴图
        }
    });
})

如果你希望环境贴图影响场景中scene所有Mesh,可以通过Scene的场景环境属性.environment实现,把环境贴图对应纹理对象设置为.environment的属性值即可。

js
// 环境贴图纹理对象textureCube作为.environment属性值,影响所有模型
scene.environment = textureCube;

整个背景都是贴图的感觉

代码

html
<template>
  <div id="canvas"></div>
</template>

<script setup>
import * as THREE from 'three';
// 引入轨道控制器扩展库OrbitControls.js
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { onMounted, ref } from 'vue';
import { useRoute } from 'vue-router';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';

const route = useRoute();

onMounted(() => {
  // 创建场景
  const scene = new THREE.Scene();

  const loader = new GLTFLoader(); //创建一个GLTF加载器

  const model = new THREE.Group(); //声明一个组对象,用来添加加载成功的三维场景

  // 加载环境贴图
  // 加载周围环境6个方向贴图
  // 上下左右前后6张贴图构成一个立方体空间
  // 'px.jpg', 'nx.jpg':x轴正方向、负方向贴图  p:正positive  n:负negative
  // 'py.jpg', 'ny.jpg':y轴贴图
  // 'pz.jpg', 'nz.jpg':z轴贴图
  // CubeTexture表示立方体纹理对象,父类是纹理对象Texture
  const textureCube = new THREE.CubeTextureLoader()
    .setPath('/ziyuan/环境贴图/环境贴图1/')
    .load(['px.jpg', 'nx.jpg', 'py.jpg', 'ny.jpg', 'pz.jpg', 'nz.jpg']);

  textureCube.encoding = THREE.sRGBEncoding;

  const geometry = new THREE.SphereGeometry(15, 32, 16);
  const material = new THREE.MeshStandardMaterial({
    envMap: textureCube, //设置环境贴图
    metalness: 1, //金属度
    roughness: 0.0 //完全镜面反射,像镜子一样
  });

  scene.background = textureCube;

  const sphere = new THREE.Mesh(geometry, material);
  scene.add(sphere);

  // AxesHelper:辅助观察的坐标系
  //   const axesHelper = new THREE.AxesHelper(100);
  //   scene.add(axesHelper);

  // tip: 环境光
  const directionalLight = new THREE.DirectionalLight(0xffe1a4, 1);
  directionalLight.position.set(100, 60, 50);
  scene.add(directionalLight);

  const ambient = new THREE.AmbientLight(0xffffff, 0.4);
  scene.add(ambient);

  //   const dirLightHelper = new THREE.DirectionalLightHelper(directionalLight, 5, 0xff0000);
  //   scene.add(dirLightHelper);

  // width和height用来设置Three.js输出的Canvas画布尺寸(像素px)
  const width = window.innerWidth; //宽度
  const height = window.innerHeight; //高度

  /**
   * 透视投影相机设置
   */
  // 30:视场角度, width / height:Canvas画布宽高比, 1:近裁截面, 3000:远裁截面
  const camera = new THREE.PerspectiveCamera(60, width / height, 1, 1000);

  camera.position.set(12, 15, 89); //相机在Three.js三维坐标系中的位置
  camera.lookAt(0, 0, 0); //相机观察目标指向Three.js坐标系原点

  /**
   * 创建渲染器对象
   */
  const renderer = new THREE.WebGLRenderer({
    antialias: true // 开启抗锯齿
  });
  renderer.setSize(width, height); //设置three.js渲染区域的尺寸(像素px)
  renderer.render(scene, camera); //执行渲染操作
  renderer.outputEncoding = THREE.sRGBEncoding;

  renderer.outputColorSpace = THREE.SRGBColorSpace; //设置为SRGB颜色空间

  document.getElementById('canvas').appendChild(renderer.domElement);

  // 设置相机控件轨道控制器OrbitControls
  const controls = new OrbitControls(camera, renderer.domElement);

  function render() {
    renderer.render(scene, camera); //执行渲染操作
    // 查看相机位置 调整到一个合适的地方
    // console.log('camera.position',camera.position);
    requestAnimationFrame(render); //请求再次执行渲染函数render,渲染下一帧
  }
  render();

  // 画布跟随窗口变化
  window.onresize = function () {
    renderer.setSize(window.innerWidth, window.innerHeight);
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
  };
});
</script>