three.js 渲染调优,如何提升3d场景更逼真的渲染效果

您所在的位置:网站首页 如何让3d渲染更真实 three.js 渲染调优,如何提升3d场景更逼真的渲染效果

three.js 渲染调优,如何提升3d场景更逼真的渲染效果

2023-12-18 15:38| 来源: 网络整理| 查看: 265

three.js就不介绍了,本章内容主要讲解怎么渲染出更逼真的3d场景效果、渲染出更真实的图片。一般用了three.js的人都想把渲染效果做的更好, 最终效果受很多情况影响,比如材质、灯光、环境、模型质量,还需要结合实际情况调节。从各个地方收集的信息写成笔记。

1、渲染参数调优 // ================================================================================ // 平行光参数优化(模拟太阳) // -------------------------------------------------------------------------------- const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5) directionalLight.castShadow = true directionalLight.shadow.mapSize.height = 512 * 2 directionalLight.shadow.mapSize.width = 512 * 2 // 解决暗影 // 0.00 0.05 最好的区间 directionalLight.shadow.bias = 0.05 // 平面 directionalLight.shadow.normalBias = 0.05 // 圆形表面,缩小受影响的网格,使其不会在自身上投射阴影 // ================================================================================ // ================================================================================ // Encoding // -------------------------------------------------------------------------------- // 环境贴图 const cubeTextureLoader = new THREE.CubeTextureLoader() const environmentMapTexture = cubeTextureLoader.load([ "/textures/environmentMaps/px.jpg", "/textures/environmentMaps/nx.jpg", "/textures/environmentMaps/py.jpg", "/textures/environmentMaps/ny.jpg", "/textures/environmentMaps/pz.jpg", "/textures/environmentMaps/nz.jpg", ]) environmentMapTexture.encoding = THREE.sRGBEncoding // gltf,模型启用阴影和环境贴图 const gltfLoader = new GLTFLoader() gltfLoader.load("/model.glb", (gltf) => { const model = gltf.scene model.traverse((child) => { if ( child instanceof THREE.Mesh && child.material instanceof THREE.MeshStandardMaterial ) { child.castShadow = true child.receiveShadow = true child.material.envMap = environmentMapTexture child.material.envMapIntensity = 3 // child.material.needsUpdate = true } }) }) // renderer renderer.outputEncoding = THREE.sRGBEncoding // ================================================================================ // ================================================================================ // Tonemapping // -------------------------------------------------------------------------------- // 色调映射参数 // THREE.NoToneMapping // THREE.LinearToneMapping // THREE.ReinhardToneMapping // THREE.CineonToneMapping // THREE.ACESFilmicToneMapping // 使用算法将HDR值转换为LDR值,使其介于0到1之间, 0 1 renderer.toneMapping = THREE.ACESFilmicToneMapping // 渲染器将允许多少光线进入 renderer.toneMappingExposure = 3 // ================================================================================ // ================================================================================ // Rendering // -------------------------------------------------------------------------------- renderer.physicallyCorrectLights = true // synchronise light values between 3D software and three.js // 启用阴影,调整阴影类型 renderer.shadowMap.enabled = true renderer.shadowMap.type = THREE.PCFSoftShadowMap // ================================================================================ 2、材质调优 // ================================================================================ // 纹理特征 // -------------------------------------------------------------------------------- // 不透明度仅在透明打开时有效 material.transparent = true material.opacity = 0.5 // 材质面,双面、前面、背面 material.side = THREE.DoubleSide || THREE.FrontSide || THREE.BackSide // 改变材质的平面着色需要重新编译材质 material.flatShading = true || false material.needsUpdate = true // ================================================================================ // ================================================================================ // 加载纹理 // -------------------------------------------------------------------------------- // 使用全局 LoadingManager 来相互化/合并所有纹理加载器 const loadingManager = new THREE.LoadingManager() loadManager.onStart = () => { console.log("loading started") } loadManager.onProgress = () => { console.log("loading") } loadManager.onLoad = () => { console.log("loading completed") } loadManager.onError = () => { console.log("loading failed") } const cubeTextureLoader = new THREE.CubeTextureLoader(loadManager) // 立方体贴图必须有 6 个面 const environmentMapTexture = cubeTextureLoader.load([ "/environmentMaps/px.png", // positive x "/environmentMaps/nx.png", // negative x "/environmentMaps/py.png", "/environmentMaps/ny.png", "/environmentMaps/pz.png", "/environmentMaps/nz.png", ]) const material = new THREE.MeshStandardMaterial({ envMap: environmentMapTexture, metalness: 0.7, roughness: 0.2, }) // or scene.background = environmentMapTexture // ================================================================================ // ================================================================================ // 使用纹理 // -------------------------------------------------------------------------------- const textureLoader = new THREE.TextureLoader(loadingManager) // 颜色(反照率)纹理 Color (Albedo) texture const colorTexture = textureLoader.load("texture.jpg") const material = new THREE.MeshStandardMaterial({ map: colorTexture, }) // 透明贴图纹理 Alpha texture const alphaTexture = textureLoader.load("alpha.jpg") const material = new THREE.MeshStandardMaterial({ transparent: true, alphaMap: alphaTexture, }) // 位移(高度)纹理 Displacement (height) texture // 需要几何体中有很多顶点才能准确置换材质的高度 const displacementTexture = textureLoader.load("displacement.jpg") const material = new THREE.MeshStandardMaterial({ displacementMap: displacementTexture, displacementScale: 0.35, }) // 普通纹理 Normal texture // 建议使用 PNG 将每个顶点的精确位置与纹理细节匹配 const normalTexture = textureLoader.load("normal.png") const material = new THREE.MeshStandardMaterial({ normalMap: normalTexture, }) material.normalScale.x = 0.5 // 0 1 material.normalScale.y = 0.5 // 0 1 // 环境遮挡纹理 const ambientOcclusionTexture = textureLoader.load("ambientOcclusion.jpg") const cube = new THREE.BoxBufferGeometry(1, 1, 1) const material = new THREE.MeshStandardMaterial({ aoMap: ambientOcclusionTexture, }) // AO 贴图需要将现有的 uv 坐标复制到 uv2 cube.geometry.setAttribute( "uv2", new THREE.BufferAttribute(cube.geometry.attributes.uv.array, 2) ) // 金属度和粗糙度纹理 Metalness & Roughness texture const metalnessTexture = textureLoader.load("metalness.jpg") const roughnessTexture = textureLoader.load("roughness.jpg") const material = new THREE.MeshStandardMaterial({ metalnessMap: metalnessTexture, roughnessMap: roughnessTexture, // 使用金属度和粗糙度贴图时,不应显式声明金属度和粗糙度值 }) // MatCap(材质捕获)纹理 MatCap (material capture) texture const matcapTexture = textureLoader.load("matcap.jpg") const material = new THREE.MeshMatcapMaterial({ matcap: matcapTexture, }) // 渐变纹理 // 渐变纹理可以是一个非常小的正方形图像,从黑色到白色的阴影 const gradientTexture = textureLoader.load("gradient.jpg") const material = new THREE.MeshToonMaterial({ gradientMap: gradientTexture, }) // 卡通外观 gradientTexture.minFilter = THREE.NearestFilter gradientTexture.magFilter = THREE.NearestFilter gradientTexture.generateMipmaps = false // ================================================================================ 3、阴影调优 import * as THREE from "three" // ================================================================================ // 渲染阴影 // -------------------------------------------------------------------------------- // 启用渲染阴影 renderer.shadowMap.enabled = true renderer.shadowMap.type = THREE.PCFSoftShadowMap // THREE.PCFShadowMap (default) // 阴影贴图大小在增加之前应该仔细考虑,因为它可能导致非常大的阴影贴图文件 // 尝试先收紧阴影的相机,直接观察物体 // 由于 mipmapping,必须是 2 的幂(缩小到 1x1) lightThatCastShadow.shadow.mapSize.height = 512 * 2 // 1024 x 1024 lightThatCastShadow.shadow.mapSize.width = 512 * 2 lightThatCastShadow.shadow.radius = 10 // 阴影半径不适用于 THREE.PCFSoftShadowMap // 不在一行中渲染阴影相机助手 lightThatCastShadow.visible = false // DirectionalLight 阴影 // -------------------------------------------------------------------------------- const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5) directionalLight.castShadow = true directionalLight.shadow.camera.top = 2 directionalLight.shadow.camera.bottom = -2 directionalLight.shadow.camera.left = -2 directionalLight.shadow.camera.right = 2 directionalLight.shadow.camera.near = 1 directionalLight.shadow.camera.far = 6 // DirectionalLightCameraHelper是正交相机 const directionalLightCameraHelper = new THREE.CameraHelper( directionalLight.shadow.camera ) scene.add(directionalLightCameraHelper) // spotLight 聚光灯阴影 // -------------------------------------------------------------------------------- const spotLight = new THREE.SpotLight(0xffffff, 0.4, 10, Math.PI * 0.3) spotLight.castShadow = true spotLight.shadow.camera.fov = 30 spotLight.shadow.camera.near = 1 spotLight.shadow.camera.far = 6 // SpotLightCameraHelper是一个透视相机 const spotLightCameraHelper = new THREE.CameraHelper(spotLight.shadow.camera) scene.add(spotLightCameraHelper) // PointLight 点光源阴影 // -------------------------------------------------------------------------------- const pointLight = new THREE.PointLight(0xffffff, 0.3) pointLight.castShadow = true pointLight.shadow.mapSize.height = 512 * 2 pointLight.shadow.mapSize.width = 512 * 2 pointLight.shadow.camera.near = 0.1 pointLight.shadow.camera.far = 5 // PointLight 阴影的 fov 不应改变,因为它被设置为捕捉场景各个方向的阴影 // PointLightCameraHelper 是许多透视相机的集合,但它只显示最后一个,它在y轴上指向下 const pointLightCameraHelper = new THREE.CameraHelper(pointLight.shadow.camera) scene.add(pointLightCameraHelper) // ================================================================================ // ================================================================================ // Baked 烘焙阴影 // -------------------------------------------------------------------------------- const textureLoader = new THREE.TextureLoader() // 静态烘焙阴影,用于保持静止的对象 const staticBakedShadow = textureLoader.load("/textures/bakedShadow.jpg") new THREE.MeshBasicMaterial({ map: staticBakedShadow }) // 动态动画烘焙阴影,用于在场景中移动的对象。 // 这需要阴影跟踪对象的位置,以便真实地为自己设置动画,例如它的不透明度和比例 const dynamicBakedShadow = textureLoader.load( "/textures/dynamicBakedShadow.jpg" ) // 动态烘焙阴影将跟踪的对象 const sphere = new THREE.Mesh( new THREE.SphereBufferGeometry(0.5, 32, 32), new THREE.MeshStandardMaterial() ) // 烘焙阴影 const sphereShadow = new THREE.Mesh( new THREE.PlaneBufferGeometry(1.5, 1.5), new THREE.MeshBasicMaterial({ color: 0x000000, // black shadow alphaMap: dynamicBakedShadow, transparent: true, }) ) // 在每一帧中,烘焙阴影跟随球体的位置,并且其不透明度动态跟踪到球体的 y 位置 requestAnimationFrame(() => { sphereShadow.position.x = sphere.position.x sphereShadow.position.z = sphere.position.z sphereShadow.material.opacity = 1 - sphere.position.y + 0.2 }) // ================================================================================ 4、灯光调优 环境光 均匀照亮整个场景 半球光 天空颜色和地板颜色之间的渐变光,照亮整个场景 定向光 相互平行的太阳光线,无论位置如何,都具有无限范围 点光源 无限小灯笼,从其位置向各个方向照明 聚光灯 手电筒,锥形从亮到暗 矩形区域光 摄影棚灯 ## 性能成本 AmbientLight RectAreaLightHelper } from "three/examples/jsm/helpers/RectAreaLightHelper" const rectAreaLightHelper = new RectAreaLightHelper(rectAreaLight) scene.add(rectAreaLightHelper) requestAnimationFrame(() => { rectAreaLightHelper.position.copy(rectAreaLight.position) // helper 复制自己的光源的位置 rectAreaLightHelper.quaternion.copy(rectAreaLight.quaternion) // helper 复制自身光照的旋转 rectAreaLightHelper.update() }) // ================================================================================ 5、光线投射(对象拾取) const raycaster = new THREE.Raycaster() // 默认情况下,raycaster 从 0 投射到 Infinity (near = 0, far = Infinity) // ================================================================================ // 自定义光线投射 // -------------------------------------------------------------------------------- const rayOrigin = new THREE.Vector3(-5, 0, 0) const rayDirection = new THREE.Vector3(10, 0, 0) // 在 0 1 之间变换光线方向,以确保光线方向的矢量仍然是 1 个单位长 rayDirection.normalize() raycaster.set(rayOrigin, rayDirection) // ================================================================================ // ================================================================================ // Raycaster 交点 // -------------------------------------------------------------------------------- // 铸造一个对象 raycaster.intersectObject(object) // 转换多个对象(对象必须在数组中) const objects = [object1, object2, object3] raycaster.intersectObjects(objects) // 相交对象 // distance - 光线投射器原点(通常是相机)和对象面部之间的距离长度 // face - 包含 Face3(a, b, c) 和人脸的法线 (x, y, z) // 对象 - 相交的对象 // point - 3D世界空间中的交点坐标(Vector3)(通常基于原点(0,0,0)) // uv - 交点的 uv // Raycaster 鼠标事件 const mouse = new THREE.Vector2() // stores two values, (x, y) let currentIntersect = null // objects currently intersected // 鼠标进入鼠标离开 window.addEventListener("mousemove", (e) => { const { clientX, clientY } = e // 根据three.js (x, y) 轴在 -1 和 1 之间归一化鼠标 mouse.x = (clientX / sizes.width) * 2 - 1 mouse.y = -(clientY / sizes.height) * 2 + 1 // 使用基于场景相机的鼠标坐标 raycaster.setFromCamera(mouse, camera) const intersects = raycaster.intersectObjects(objects) // 鼠标与对象相交 const isIntersecting = intersects.length > 0 if (isIntersecting) { // 鼠标进入 if (!currentIntersect) { currentIntersect = intersects[0].object // 鼠标进入时改变对象颜色为绿色 intersects[0].object.material.color.set("green") } } else { // 鼠标离开 if (currentIntersect) { currentIntersect = null // 在鼠标离开时将所有对象的颜色更改为蓝色(它们的默认状态) objects.forEach((obj) => obj.material.color.set("blue")) } } }) // 鼠标点击 window.addEventListener("click", () => { if (currentIntersect) { // 将点击的对象变为橙色 switch (currentIntersect) { case object1: object1.material.color.set("orange") break case object2: object2.material.color.set("orange") break case object3: object3.material.color.set("orange") break } } }) // ================================================================================ 6、其他笔记 //加载管理器 const loadingManager = new THREE.LoadingManager() loadingManager.onStart = () => { console.log('loading started') } loadingManager.onLoad = () => { console.log('loading finished') } loadingManager.onProgress = () => { console.log('loading progressing') } loadingManager.onError = () => { console.log('loading error') } const textureLoader = new THREE.TextureLoader(loadingManager) const colorTexture = textureLoader.load('/textures/minecraft.png') // colorTexture.repeat.x = 2 // colorTexture.repeat.y = 3 // uv重复方式、防止uv拉伸 // colorTexture.wrapS = THREE.RepeatWrapping // colorTexture.wrapT = THREE.RepeatWrapping // colorTexture.offset.x = 0.5 // colorTexture.offset.y = 0.5 // colorTexture.rotation = Math.PI * 0.25 // colorTexture.center.x = 0.5 // colorTexture.center.y = 0.5 colorTexture.generateMipmaps = false colorTexture.minFilter = THREE.NearestFilter //window resize窗口监听需要做的 const sizes = { width: window.innerWidth, height: window.innerHeight } window.addEventListener('resize', () => { // Update sizes sizes.width = window.innerWidth sizes.height = window.innerHeight // Update camera camera.aspect = sizes.width / sizes.height camera.updateProjectionMatrix() // Update renderer renderer.setSize(sizes.width, sizes.height) renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)) }) 7、色调映射 Tone mapping

色调映射Tone mapping旨在将超高的动态范围HDR转换到我们日常显示的屏幕上的低动态范围LDR的过程。 说明一下HDR和LDR(摘自知乎LDR和HDR):

因为不同的厂家生产的屏幕亮度(物理)实际上是不统一的,那么我们在说LDR时,它是一个0到1范围的值,对应到不同的屏幕上就是匹配当前屏幕的最低亮度(0)和最高亮度(1)自然界中的亮度差异是非常大的。例如,蜡烛的光强度大约为15,而太阳光的强度大约为10w。这中间的差异是非常大的,有着超级高的动态范围。我们日常使用的屏幕,其最高亮度是经过一系列经验积累的,所以使用、用起来不会对眼睛有伤害;但自然界中的,比如我们直视太阳时,实际上是会对眼睛产生伤害的。 总结

上面是记录一些调渲染需要用到的参数笔记,那么怎么才能调节最好的效果,最重要的设置如下:

我们渲染采用最为专业的ACES色调映射(也是UE里面默认的色调映射),也只有ACES支持虚幻功能,虚幻绽放亮度。再然后我们需要把色彩空间编码改成THREE.sRGBEncoding即可。

在着色器中色值的提取与色彩的计算操作一般都是在线性空间。在webgl中,贴图或者颜色以srgb传入时,必须转换为线性空间。计算完输出后再将线性空间转为srgb空间。

linear颜色空间:物理上的线性颜色空间,当计算机需要对sRGB像素运行图像处理算法时,一般会采用线性颜色空间计算。sRGB颜色空间: sRGB是当今一般电子设备及互联网图像上的标准颜色空间。较适应人眼的感光。sRGB的gamma与2.2的标准gamma非常相似,所以在从linear转换为sRGB时可通过转换为gamma2.2替代。gamma转换:线性与非线性颜色空间的转换可通过gamma空间进行转换。 最重要的几项设置!!! renderer.physicallyCorrectLights = true //正确的物理灯光照射 renderer.outputEncoding = THREE.sRGBEncoding //采用sRGBEncoding renderer.toneMapping = THREE.ACESFilmicToneMapping //aces标准 renderer.toneMappingExposure = 1.25 //色调映射曝光度 renderer.shadowMap.enabled = true //阴影就不用说了 renderer.shadowMap.type = THREE.PCFSoftShadowMap //阴影类型(处理运用Shadow Map产生的阴影锯齿) //此时模型效果很好了,但是如果使用了天空盒子,天空盒子贴图为rgb色彩空间, //此时天空会出现灰蒙蒙,失去了对比度一样,那是因为贴图色彩空间默认不是 //THREE.sRGBEncoding,所以得在场景里面所有的图都使用THREE.sRGBEncoding,具体操作如下 environmentMap.encoding = THREE.sRGBEncoding scene.background = environmentMap scene.environmentMap = environmentMap //之后就是材质贴图,three.js默认认将贴图编码格式定义为Three.LinearEncoding const textureLoader = new THREE.TextureLoader(); textureLoader.load( "./assets/texture/tv-processed0.png", function(texture){ texture.encoding = THREE.sRGBEncoding; }); //这样也行 basic.map.encoding = THREE.sRGBEncoding; //最后想要环境反射,就把环境贴图贴上去 const updateAllMaterials = () => { scene.traverse(child => { if (child instanceof THREE.Mesh && child.material instanceof THREE.MeshStandardMaterial) { child.material.envMap = environmentMap child.material.envMapIntensity = debugObject.envMapIntensity child.material.needsUpdate = true child.castShadow = true child.receiveShadow = true } }) } //顺便说一句,阴影看到奇怪的条纹,表示阴影失真 //在计算曲面是否处于阴影中时,由于精度原因,阴影失真可能会发生在平滑和平坦表面上,因此我们必须调整灯光阴影shadow的“偏移bias”和“法线偏移normalBias”属性来修复此阴影失真, //bias通常用于平面 //normalBias通常用于圆形表面 directionalLight.shadow.normalBias = 0.05

注意:并不是所有的贴图都需要采用THREE.sRGBEncoding,其实规则很直接,所有我们能够直接看到的纹理贴图,比如map,就应该使用THREE.sRGBEncoding作为编码;而其他的纹理贴图比如法向纹理贴图normalMap就该使用THREE.LinearEncoding。 你可能会问那模型上的各种纹理贴图都要一个个亲自去设置吗?大可不必,因为GLTFLoader会将加载的所有纹理自动进行正确的编码

当然也有人认为GammaEncoding优于sRGBEncoding,原本还可以通过gamma矫正,但是从three.js r136 版本之后已经移除了gamma矫正,不建议使用了,全部使用THREE.sRGBEncoding替代,目前three.js输出渲染编码默认为THREE.LinearEncoding,并且从three.js官方讨论得知,有人建议设置THREE.sRGBEncoding为默认值,色调映射也可能会移除其他类型,只留下ACES映射、关闭色调映射。

后续再更新



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3