Appearance
上个文章介绍了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>