Skip to content

小汽车的渲染效果

🕒 Published at:

上个文章介绍了PBR材质的其中一个材质 MeshStandardMaterial

这次做个小汽车的案例,用到了PBR材质的另一个材质 MeshPhysicalMaterial

MeshPhysicalMaterial和MeshStandardMaterial都是拥有金属度metalness、粗糙度roughness属性的PBR材质,MeshPhysicalMaterial是在MeshStandardMaterial基础上扩展出来的子类,除了继承了MeshStandardMaterial的金属度、粗糙度等属性,还新增了清漆.clearcoat、透光率.transmission、反射率.reflectivity、光泽.sheen、折射率.ior等等各种用于模拟生活中不同材质的属性。

清漆层属性.clearcoat

清漆层属性.clearcoat可以用来模拟物体表面一层透明图层,就好比你在物体表面刷了一层透明清漆,喷了点水。.clearcoat的范围0到1,默认0。

清漆层粗糙度.clearcoatRoughness

清漆层粗糙度.clearcoatRoughness属性表示物体表面透明涂层.clearcoat对应的的粗糙度,.clearcoatRoughness的范围是为0.0至1.0。默认值为0.0。

js
const material = new THREE.MeshPhysicalMaterial( {
	clearcoat: 1.0,//物体表面清漆层或者说透明涂层的厚度
	clearcoatRoughness: 0.1,//透明涂层表面的粗糙度
} );

车外壳PBR材质设置

在设置车外壳清漆层之前,先创建一个MeshPhysicalMaterial材质,并设置好环境贴图、金属度、粗糙度,属性值先根据文档说明给一个大概的值,具体可以通过gui交互界面可视化调试。

js
const mesh = gltf.scene.getObjectByName('外壳01');
mesh.material = new THREE.MeshPhysicalMaterial({
        color: mesh.material.color, //默认颜色
        metalness: 0.9,//车外壳金属度
        roughness: 0.5,//车外壳粗糙度
        envMap: textureCube, //环境贴图
        envMapIntensity: 2.5, //环境贴图对Mesh表面影响程度
})

车外壳油漆效果

车外壳油漆效果,你可以通过PBR材质的清漆层属性.clearcoat和清漆层粗糙度.clearcoatRoughness属性模拟。

属性值先根据文档说明给一个大概的值,具体可以通过gui交互界面可视化调试。

js
const mesh = gltf.scene.getObjectByName('外壳01');
mesh.material = new THREE.MeshPhysicalMaterial( {
	clearcoat: 1.0,//物体表面清漆层或者说透明涂层的厚度
	clearcoatRoughness: 0.1,//透明涂层表面的粗糙度
} );

透光率(透射度).transmission

为了更好的模拟玻璃、半透明塑料一类的视觉效果,可以使用物理透明度.transmission属性代替Mesh普通透明度属性.opacity。

使用.transmission属性设置Mesh透明度,即便完全透射的情况下仍可保持高反射率。

物理光学透明度.transmission的值范围是从0.0到1.0。默认值为0.0。

js
const mesh = gltf.scene.getObjectByName('玻璃01')
mesh.material = new THREE.MeshPhysicalMaterial({
    transmission: 1.0, //玻璃材质透光率,transmission替代opacity 
})

折射率.ior

非金属材料的折射率从1.0到2.333。默认值为1.5。

不同材质的折射率,你可以百度搜索。

js
new THREE.MeshPhysicalMaterial({
    ior:1.5,//折射率
})

玻璃透光率.transmission设置

先设置玻璃金属度和粗糙度

js
const mesh = gltf.scene.getObjectByName('玻璃01')
mesh.material = new THREE.MeshPhysicalMaterial({
    metalness: 0.0,//玻璃非金属 
    roughness: 0.0,//玻璃表面光滑
    envMap:textureCube,//环境贴图
    envMapIntensity: 1.0, //环境贴图对Mesh表面影响程度
})

设置透光率.transmission和折射率.ior。

js
new THREE.MeshPhysicalMaterial({
    transmission: 1.0, //玻璃材质透光率,transmission替代opacity 
    ior:1.5,//折射率
})

通过上诉所学的概念实现一个小汽车效果

进度条效果

js
loader.load("../汽车.glb", function (gltf) {
    model.add(gltf.scene);
}, function (xhr) {
    // 控制台查看加载进度xhr
    // 通过加载进度xhr可以控制前端进度条进度   
    const percent = xhr.loaded / xhr.total;
    console.log('加载进度' + percent);
})

完整代码

html
<template>
  <div id="canvas"></div>
  <div class="jindutiao" v-if="percentage.value !== 100">
    <el-progress
      :percentage="percentage"
      :stroke-width="25"
      status="success"
      :text-inside="true"
      striped
      striped-flow
      :duration="10"
    />
  </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';
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';

//创建一个GUI对象,你可以看到浏览器右上角多了一个交互界面,GUI本质上就是一个前端js库。

const route = useRoute();

const percentage = ref(0);

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

  // 创建材质子菜单
  // 创建材质子菜单
  const matFolder1 = gui.addFolder('外壳材质');
  matFolder1.close(); //关闭菜单
  const matFolder2 = gui.addFolder('玻璃材质');
  matFolder2.close(); //关闭菜单

  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; //设置纹理贴图编码方式和WebGL渲染器一致

  // 递归遍历所有模型节点批量修改材质
  loader.load(
    '/ziyuan/轿车.glb',
    function (gltf) {
      model.add(gltf.scene);
      // 注意如果车外壳或玻璃共享了材质,修改一个其他的也会变化和影响

      const mesh1 = gltf.scene.getObjectByName('外壳01');
      // mesh1.material.envMap = textureCube; //环境贴图
      // mesh1.material.envMapIntensity = 1.0; ////环境贴图对Mesh表面影响程度

      // // 设置清漆属性
      // mesh1.material.clearcoat = 1.0;
      // mesh1.material.clearcoatRoughness = 0.009;

      mesh1.material = new THREE.MeshPhysicalMaterial({
        color: mesh1.material.color, //默认颜色
        metalness: 0.9, //车外壳金属度
        roughness: 0.5, //车外壳粗糙度
        envMap: textureCube, //环境贴图
        envMapIntensity: 2.5, //环境贴图对Mesh表面影响程度
        clearcoat: 1,
        clearcoatRoughness: 0.009
      });

      const mesh2 = gltf.scene.getObjectByName('玻璃01');
      // mesh2.material.envMap = textureCube; //环境贴图
      // mesh2.material.envMapIntensity = 1.0; ////环境贴图对Mesh表面影响程度

      mesh2.material = new THREE.MeshPhysicalMaterial({
        metalness: 0.0, //玻璃非金属
        roughness: 0.0, //玻璃表面光滑
        envMap: textureCube, //环境贴图
        envMapIntensity: 1.0, //环境贴图对Mesh表面影响程度
        transmission: 1.0, //玻璃材质透光率,transmission替代opacity
        ior: 1.5 //折射率
      });

      // 查看threejs解析的PBR材质
      gltf.scene.traverse(function (obj) {
        if (obj.isMesh) {
          if (obj.material.name.includes('玻璃')) {
            obj.material = new THREE.MeshPhysicalMaterial({
              metalness: 0.0, //玻璃非金属
              roughness: 0.0, //玻璃表面光滑
              envMap: textureCube, //环境贴图
              envMapIntensity: 1.0, //环境贴图对Mesh表面影响程度
              transmission: 1.0, //玻璃材质透光率,transmission替代opacity
              ior: 1.5 //折射率
            });
          }
          // 玻璃
          console.log('obj.material', obj.material.name);
        }
      });
      console.log('外壳', mesh1.material);
      console.log('玻璃', mesh2.material);

      const obj = {
        color1: mesh1.material.color.getHex(), // 外壳颜色
        color2: mesh2.material.color.getHex() // 玻璃颜色
      };
      // 车壳材质gui界面
      matFolder1.addColor(obj, 'color1').onChange(function (value) {
        mesh1.material.color.set(value);
      });
      matFolder1.add(mesh1.material, 'metalness', 0, 1);
      matFolder1.add(mesh1.material, 'roughness', 0, 1);
      matFolder1.add(mesh1.material, 'clearcoat', 0, 1);
      matFolder1.add(mesh1.material, 'clearcoatRoughness', 0, 1);
      matFolder1.add(mesh1.material, 'envMapIntensity', 0, 10);

      // 玻璃材质gui界面
      matFolder2.addColor(obj, 'color2').onChange(function (value) {
        mesh2.material.color.set(value);
      });
      matFolder2.add(mesh2.material, 'metalness', 0, 1);
      matFolder2.add(mesh2.material, 'roughness', 0, 1);
      // matFolder2.add(mesh2.material, 'transmission', 0, 1);
      // matFolder2.add(mesh2.material, 'ior', 1, 2.333);
      matFolder2.add(mesh2.material, 'envMapIntensity', 0, 10);
    },
    function (xhr) {
      // 控制台查看加载进度xhr
      // 通过加载进度xhr可以控制前端进度条进度
      const percent = xhr.loaded / xhr.total;
      console.log('加载进度' + percent);
      percentage.value = percent * 100;
    }
  );

  scene.add(model); //网格模型添加到场景中

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

  const directionalLight = new THREE.DirectionalLight(0xffffff, 2);
  directionalLight.position.set(12.8, 60, 50);
  scene.add(directionalLight);
  // tip: 环境光
  const ambient = new THREE.AmbientLight(0xffffff, 0.4);
  scene.add(ambient);

  // 环境光子菜单
  const ambientFolder = gui.addFolder('环境光');
  ambientFolder.close(); //关闭菜单
  // 环境光强度
  ambientFolder.add(ambient, 'intensity', 0, 2);
  // 平行光子菜单
  const dirFolder = gui.addFolder('平行光');
  dirFolder.close(); //关闭菜单
  // 平行光强度
  dirFolder.add(directionalLight, 'intensity', 0, 2);
  const dirFolder2 = dirFolder.addFolder('位置'); //子菜单的子菜单
  dirFolder2.close(); //关闭菜单
  // 平行光位置
  dirFolder2.add(directionalLight.position, 'x', -400, 400);
  dirFolder2.add(directionalLight.position, 'y', -400, 400);
  dirFolder2.add(directionalLight.position, 'z', -400, 400);

  //   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(10, width / height, 1, 3000);

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

  /**
   * 创建渲染器对象
   */
  const renderer = new THREE.WebGLRenderer();
  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>

<style>
.jindutiao {
  position: absolute;
  left: 0;
  top: 0;
  width: 100vw;
  height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
}
.el-progress {
  width: 600px;
}
.el-progress-bar__innerText {
  color: #000 !important;
}
</style>