【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生成模型。

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

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

相关推荐
moeyui7052 小时前
Python文件编码读取和处理整理知识点
开发语言·前端·python
IT_陈寒2 小时前
WeaveFox 全栈创作体验:从想法到完整应用的零距离
前端·后端·程序员
pixle02 小时前
从零学习Node.js框架Koa 【一】 Koa 初探从环境搭建到第一个应用程序
前端·node.js·web·koa.js·web全栈·node服务端框架
抹茶生活2 小时前
CSS浮动样式
前端·css
匀泪2 小时前
CE(Linux的例行性工作)
前端·chrome
歪歪1002 小时前
解决多 Linux 客户端向 Windows 服务端的文件上传、持久化与生命周期管理问题
linux·运维·服务器·开发语言·前端·数据库·windows
不一样的少年_3 小时前
【前端效率工具】再也不用 APIfox 联调!零侵入 Mock,全程不改代码、不开代理
前端·javascript·浏览器
IT_陈寒3 小时前
JavaScript 性能优化实战:我通过这7个技巧将页面加载速度提升了65%
前端·人工智能·后端
JIngJaneIL3 小时前
数码商城系统|电子|基于SprinBoot+vue的商城推荐系统(源码+数据库+文档)
java·前端·数据库·vue.js·论文·毕设·数码商城系统