【three.js】实现玻璃材质时,出现黑色/白色像素噪点

three.js小白的学习之路。

最近在使用blender绘制一些厂房模型,不可避免的要开几个窗户,增加几个玻璃。

其实在几个月之前也遇到过类似的问题,就是在blender中将玻璃材质的折射、投射、金属度和粗糙度等参数调整好之后,在blender中看没有任何问题,但是导出glb,加载到three.js中渲染就会出现一些白色或者黑色的噪点,就类似于下面的这种情况:

在没有遮挡的时候是正常的玻璃材质,在后面有遮挡的时候就会出现黑色噪点。

TypeScript 复制代码
{
  const geo = new Three.PlaneGeometry(500, 500);
  const mat = new Three.MeshBasicMaterial({
    color: "gray",
  });
  const mesh = new Three.Mesh(geo, mat);
  mesh.position.set(0, -100, 0);
  mesh.rotation.x = -Math.PI / 2;
  scene.add(mesh);
}

{
  const geometry = new Three.BoxGeometry(100, 100, 100);
  const material = new Three.MeshPhysicalMaterial({
    color: "white",
    metalness: 0,
    roughness: 0,
    transmission: 0.9, // 透光率
    ior: 1.8, // 折射
  });
  const mesh = new Three.Mesh(geometry, material);
  mesh.position.set(0, 0, 0);
  scene.add(mesh);

  const gui = new GUI();
  gui.add(material, "metalness", 0, 1, 0.01);
  gui.add(material, "roughness", 0, 1, 0.01);
  gui.add(material, "transmission", 0, 1, 0.01);
  gui.add(material, "ior", 0, 2.333, 0.01);
}

可以通过GUI调试查看更改各个参数时,玻璃材质会体现出什么样的效果。

原因分析

在three.js的官网,关于ior折射以及transmission透光的解释是这样的:

ior: number,表示非金属材料的折射率,默认1.5,范围1.0-2.333。

transmission: number,表示投射程度,当其非0时,不透明度应该设置为1,默认0,范围0-1。

可以看出,上述两个属性是用来反射某个环境的,高度依赖于环境的反射和折射来模拟真实的效果。

blender中导出的模型是没有设置玻璃所要反射的环境的(blender中在渲染模式下,是有世界环境的),或者如上述的例子中,也是没有设置材质的环境的。这就导致玻璃材质不知道要反射什么东西,无法正确计算环境反射,导致渲染异常。

解决方法

既然需要反射的环境,就给他一个环境就可以了。这个参数是 envMap。

envmap: Texture,确保物理渲染正确的环境贴图,默认 null。

envMapIntensity: number,通过乘以对应的颜色值来计算最终的环境贴图,默认1。

我们加载一个环境贴图,并赋值给对应材质的envMap:

TypeScript 复制代码
const textureCube = new Three.CubeTextureLoader().load([px, mx, py, my, pz, mz]);

// ..................
const material = new Three.MeshPhysicalMaterial({
    color: "white",
    metalness: 0,
    roughness: 0,
    transmission: 0.9, // 透光率
    ior: 1.8, // 折射
    envMap: textureCube,
  });

效果如下:

可以看到,增加了envMap属性之后,就没有之前看到的黑色噪点了。问题解决。

这样会引入一个新的问题,我找不到贴切的纹理贴图怎么办?或者,本意只是说弄几个玻璃意思意思,拥有玻璃的特质,透过玻璃可以看到内部即可,不是真的要渲染周围的环境。

这就需要用到第二个参数,envMapIntensity,我们添加一个gui测一下:

TypeScript 复制代码
const material = new Three.MeshPhysicalMaterial({
    color: "white",
    metalness: 0,
    roughness: 0,
    transmission: 0.9, // 透光率
    ior: 1.8, // 折射
    envMap: textureCube,
    envMapIntensity: 1,
  });

  const gui = new GUI();
  gui.add(material, "metalness", 0, 1, 0.01);
  gui.add(material, "roughness", 0, 1, 0.01);
  gui.add(material, "transmission", 0, 1, 0.01);
  gui.add(material, "ior", 0, 2.333, 0.01);
  gui.add(material, "envMapIntensity", 0, 1, 0.01);

效果如下:

可以看到,在envMapIntensity设置成0时,依然保留了玻璃的特性,但是不会再反应周围的环境了,有点类似于透明度opacity设置成0,但是又能清晰地看到有这么一块东西。

其他的实现玻璃效果的方法

除了修改物理材质这一个方法,还有其他的方法生成玻璃效果,也是three.js提供的API。

1.cubeCamera

cubeCamera用于实拍当前场景,并将渲染结果生成在一个离屏渲染目标上,用于提供对应的纹理。这种就可以应用到监控视频等场景中。

使用方法:

TypeScript 复制代码
const cubeRenderTarget = new Three.WebGLCubeRenderTarget(512); // 参数是分辨率
const cubeCamera = new Three.CubeCamera(1, 1000, cubeRenderTarget);

const group = new Three.Group();

  const geo1 = new Three.PlaneGeometry(1000, 1000);
  const mat1 = new Three.MeshStandardMaterial({
    metalness: 1,
    roughness: 0,
    envMap: cubeRenderTarget.texture,
  });
  const plane= new Three.Mesh(geo1, mat1);
  group.add(plane);


  const geo2 = new Three.SphereGeometry(100);
  const mat2 = new Three.MeshStandardMaterial({ color: "blue" });
  const ball= new Three.Mesh(geo2, mat2);
  ball.position.set(0, 0, 500);
  group.add(ball);


scene.add(group);

const render = () => {
  requestAnimationFrame(render);
  renderer.render(scene, camera);

  cubeCamera.position.copy(plane.position);
  cubeCamera.update(renderer, scene);
};

这里我们生成一个小球,和一个平面,并处理好相应位置。使用cubeCamera,并与plane的位置相同,这样cubeCamera拍摄出来的场景就是从平面的角度所能看到的场景目标。

注意,要在render里面循环更新cubeCamera,因为移动控制器的时候,不更新的话,就无法正常渲染。

2.Reflector

如果你运行了cubeCamera的例子,你会发现一切都没啥问题。于是你想:我再整一个镜子,让这两个镜子面对面,是不是就会出现现实世界中的那种"你中有我,我中有你"的效果。

如果你试了,你会发现,完全不行。

为什么?因为从一个镜子的方向看,他可以看到另一个镜子,但是另一个镜子就是一个平面而已,只是我们后加了材质和环境贴图罢了,反之亦然。

有没有更好的方法?有的,就是Reflector。

TypeScript 复制代码
const group = new Three.Group();

  const geo1 = new Three.PlaneGeometry(1000, 1000);
  const mesh1 = new Reflector(geo1, {
    color: "blue",
    textureWidth: window.innerWidth * window.devicePixelRatio,
    textureHeight: window.innerHeight * window.devicePixelRatio,
  });
  mesh1.name = "mirror1";
  mesh1.position.z = -500;
  group.add(mesh1);

  const geo2 = new Three.PlaneGeometry(1000, 1000);
  const mesh2 = new Reflector(geo2, {
    color: "red",
    textureWidth: window.innerWidth * window.devicePixelRatio,
    textureHeight: window.innerHeight * window.devicePixelRatio,
  });
  mesh2.name = "mirror2";
  mesh2.position.z = 500;
  mesh2.rotateY(Math.PI);
  group.add(mesh2);

scene.add(group);

对比之前的创建方式,可以发现,Reflector替代了PBR物理渲染材质,而是直接通过geometry生成模型。

这样就可以实现上述的效果。

只不过这个方法更耗性能,并且也不是无限的透射出另一面镜子,一般就两三回。

相关推荐
z***396244 分钟前
Plugin ‘org.springframework.bootspring-boot-maven-plugin‘ not found(已解决)
java·前端·maven
e***582344 分钟前
Nginx 配置前端后端服务
运维·前端·nginx
小奶包他干奶奶1 小时前
Webpack学习——Plugin(插件)
前端·学习·webpack
张拭心1 小时前
AI 从业者需要铭记的时刻:2023年6月30日
前端·ai编程
我叫张小白。1 小时前
Vue3 Hooks:逻辑复用的解决方案
前端·javascript·vue.js·前端框架·vue
S***t7141 小时前
前端物联网开发
前端·物联网
我叫张小白。1 小时前
Vue3 Props 的使用:组件间数据传递的桥梁
前端·javascript·vue.js·vue3
r***86981 小时前
Nginx解决前端跨域问题
运维·前端·nginx
广州华水科技1 小时前
单北斗GNSS在桥梁变形监测中的关键应用与技术优势分析
前端
IT_陈寒1 小时前
Python 3.12新特性实战:10个让你效率翻倍的代码优化技巧
前端·人工智能·后端