作为一个被threejs折磨了很久程序猿,在图形优化这条道路上颇有一些心得,因此有一点点心得分享一下。
如何解决场景发暗问题
光源问题
一般情况下场景中会添加半球光和点光源(其他光源类似)下面就分这两种光源来说:
-
HemisphereLight(半球光)
- 注意半球光的ground color颜色是不是偏暗
- 尝试调节光的强度
-
**PointLight **
- 注意光的颜色,黑色光场景肯定发暗
- 注意distance参数,有时候你将物体距离光源太远就会又这个问题
- 注意decay参数这个光的强度随着距离衰减的量,同样光源距离物体太远也会又发暗的情况
使用了OutlinePass
这个本身应该算是threejs本身的一个目前的解决方式就是继承重写render方法,代码直接放出来,后续可以直接采用(其实就是同步了一下颜色空间)。
import {OutlinePass} from "three/examples/jsm/postprocessing/OutlinePass";
export default class MyOutlinePass extends OutlinePass {
constructor(resolution, scene, camera, selectedObjects) {
super(resolution, scene, camera, selectedObjects);
}
render(renderer, writeBuffer, readBuffer, deltaTime, maskActive) {
if (this.selectedObjects.length > 0) {
renderer.getClearColor(this._oldClearColor);
this.oldClearAlpha = renderer.getClearAlpha();
const oldAutoClear = renderer.autoClear;
renderer.autoClear = false;
if (maskActive) renderer.state.buffers.stencil.setTest(false);
renderer.setClearColor(0xffffff, 1);
// Make selected objects invisible
this.changeVisibilityOfSelectedObjects(false);
const currentBackground = this.renderScene.background;
this.renderScene.background = null;
// 1. Draw Non Selected objects in the depth buffer
this.renderScene.overrideMaterial = this.depthMaterial;
renderer.setRenderTarget(this.renderTargetDepthBuffer);
renderer.clear();
renderer.render(this.renderScene, this.renderCamera);
// Make selected objects visible
this.changeVisibilityOfSelectedObjects(true);
this._visibilityCache.clear();
// Update Texture Matrix for Depth compare
this.updateTextureMatrix();
// Make non selected objects invisible, and draw only the selected objects, by comparing the depth buffer of non selected objects
this.changeVisibilityOfNonSelectedObjects(false);
this.renderScene.overrideMaterial = this.prepareMaskMaterial;
this.prepareMaskMaterial.uniforms['cameraNearFar'].value.set(this.renderCamera.near, this.renderCamera.far);
this.prepareMaskMaterial.uniforms['depthTexture'].value = this.renderTargetDepthBuffer.texture;
this.prepareMaskMaterial.uniforms['textureMatrix'].value = this.textureMatrix;
renderer.setRenderTarget(this.renderTargetMaskBuffer);
renderer.clear();
renderer.render(this.renderScene, this.renderCamera);
this.renderScene.overrideMaterial = null;
this.changeVisibilityOfNonSelectedObjects(true);
this._visibilityCache.clear();
this.renderScene.background = currentBackground;
// 2. Downsample to Half resolution
this.fsQuad.material = this.materialCopy;
this.copyUniforms['tDiffuse'].value = this.renderTargetMaskBuffer.texture;
renderer.setRenderTarget(this.renderTargetMaskDownSampleBuffer);
renderer.clear();
this.fsQuad.render(renderer);
this.tempPulseColor1.copy(this.visibleEdgeColor);
this.tempPulseColor2.copy(this.hiddenEdgeColor);
if (this.pulsePeriod > 0) {
const scalar = (1 + 0.25) / 2 + Math.cos(performance.now() * 0.01 / this.pulsePeriod) * (1.0 - 0.25) / 2;
this.tempPulseColor1.multiplyScalar(scalar);
this.tempPulseColor2.multiplyScalar(scalar);
}
// 3. Apply Edge Detection Pass
this.fsQuad.material = this.edgeDetectionMaterial;
this.edgeDetectionMaterial.uniforms['maskTexture'].value = this.renderTargetMaskDownSampleBuffer.texture;
this.edgeDetectionMaterial.uniforms['texSize'].value.set(this.renderTargetMaskDownSampleBuffer.width, this.renderTargetMaskDownSampleBuffer.height);
this.edgeDetectionMaterial.uniforms['visibleEdgeColor'].value = this.tempPulseColor1;
this.edgeDetectionMaterial.uniforms['hiddenEdgeColor'].value = this.tempPulseColor2;
renderer.setRenderTarget(this.renderTargetEdgeBuffer1);
renderer.clear();
this.fsQuad.render(renderer);
// 4. Apply Blur on Half res
this.fsQuad.material = this.separableBlurMaterial1;
this.separableBlurMaterial1.uniforms['colorTexture'].value = this.renderTargetEdgeBuffer1.texture;
this.separableBlurMaterial1.uniforms['direction'].value = OutlinePass.BlurDirectionX;
this.separableBlurMaterial1.uniforms['kernelRadius'].value = this.edgeThickness;
renderer.setRenderTarget(this.renderTargetBlurBuffer1);
renderer.clear();
this.fsQuad.render(renderer);
this.separableBlurMaterial1.uniforms['colorTexture'].value = this.renderTargetBlurBuffer1.texture;
this.separableBlurMaterial1.uniforms['direction'].value = OutlinePass.BlurDirectionY;
renderer.setRenderTarget(this.renderTargetEdgeBuffer1);
renderer.clear();
this.fsQuad.render(renderer);
// Apply Blur on quarter res
this.fsQuad.material = this.separableBlurMaterial2;
this.separableBlurMaterial2.uniforms['colorTexture'].value = this.renderTargetEdgeBuffer1.texture;
this.separableBlurMaterial2.uniforms['direction'].value = OutlinePass.BlurDirectionX;
renderer.setRenderTarget(this.renderTargetBlurBuffer2);
renderer.clear();
this.fsQuad.render(renderer);
this.separableBlurMaterial2.uniforms['colorTexture'].value = this.renderTargetBlurBuffer2.texture;
this.separableBlurMaterial2.uniforms['direction'].value = OutlinePass.BlurDirectionY;
renderer.setRenderTarget(this.renderTargetEdgeBuffer2);
renderer.clear();
this.fsQuad.render(renderer);
// Blend it additively over the input texture
this.fsQuad.material = this.overlayMaterial;
this.overlayMaterial.uniforms['maskTexture'].value = this.renderTargetMaskBuffer.texture;
this.overlayMaterial.uniforms['edgeTexture1'].value = this.renderTargetEdgeBuffer1.texture;
this.overlayMaterial.uniforms['edgeTexture2'].value = this.renderTargetEdgeBuffer2.texture;
this.overlayMaterial.uniforms['patternTexture'].value = this.patternTexture;
this.overlayMaterial.uniforms['edgeStrength'].value = this.edgeStrength;
this.overlayMaterial.uniforms['edgeGlow'].value = this.edgeGlow;
this.overlayMaterial.uniforms['usePatternTexture'].value = this.usePatternTexture;
if (maskActive) renderer.state.buffers.stencil.setTest(true);
renderer.setRenderTarget(readBuffer);
this.fsQuad.render(renderer);
renderer.setClearColor(this._oldClearColor, this.oldClearAlpha);
renderer.autoClear = oldAutoClear;
}
if (this.renderToScreen) {
this.fsQuad.material = this.materialCopy;
//关键是下面这句
readBuffer.texture.encoding = renderer.outputEncoding;
this.copyUniforms['tDiffuse'].value = readBuffer.texture;
renderer.setRenderTarget(null);
this.fsQuad.render(renderer);
}
}
}
:exclamation:注意使用这种接方法解决的时候, .babelrc文件中 moudule:false必须去掉,不然build之后会报错。
导入模型部分发暗
这种情况常见于UI建模好之后发给你的模型。模型中有金属材质的部分会变黑,这又两个原因,因此一下两步基本可以解决问题。
-
导入three editor重新设置为 MeshStandardMaterial(先切换为其他任意材质,然后切回),这样就能解决无法调整Roughness和Meltalness的问题。
-
Roughness:可以调节粗糙度,你可以理解为此值越大对光和场景的反应就越小
-
Meltalness:此值越大表面就越光滑,可以想象抛光的金属一样
-
- 其次是尝试调节color参数,如果color原本为黑色,那肯定也会发黑。
-
未设置Environment
其实这个参数可以在两个地方设置一个在是Scene下设置,一个是在模型的Material属性下设置(Env Map)
具体设置代码如下:
new RGBELoader() .setPath(`${__static}/texture/`) .load("footprint_court_2k.hdr", function (hdrEquirect) { const envMap = pmremGenerator.fromEquirectangular(hdrEquirect).texture; pmremGenerator.dispose(); hdrEquirect.mapping = THREE.EquirectangularReflectionMapping; scene.environment = envMap; });稍微解释一下为什么要设置这个能解决模型发黑的问题,其实Env Map翻译过来就是环境映射,而给Scene设置Env就是相当于给全局的材质设置Env Map。而MeshStandard的金属材质需要设置Env Map才能表示出金属的反光效果(因为没环境贴图,金属材质也不知道反光啥是不是)。
解决模型表面纹理模糊的问题
如果本身模型纹理贴图像素足够高的话,其实不应该会模糊。但是如果像素太高模型势必就会很大,因此针对此我们可以尝试开启邻近过滤和线性过滤(效果不好)或者设置贴图重复。
代码如下
item.material.map.warpS = RepeatWrapping
item.material.map.warpS = RepeatWrapping
item.material.map.repeat.set(20, 20)
相机拉远模型模糊问题
首先这个问题也可以划分为两类问题,第一就是本身渲染的分辨率就不高导致模型边缘有锯齿,第二就是就是Mipmap问题
模型抗锯齿
首先,什么是锯齿,以及为什么会出现锯齿。
我们的屏幕,是以一个个正方形的像素点组成的,而正方形的特性导致了在倾斜的线上,边缘必定会出现一个个突起的阶梯状“毛刺”,比如图下这种
这种阶梯状的“毛刺”就是典型的锯齿。而有了锯齿也就有了抗锯齿(Anti-Aliasing).
抗锯齿的一般过程就是将这个毛刺的边缘柔化,使图像边缘看起来更平滑。如图:
抗锯齿有很多中,目前three里面的支持的有一下几种FXAA,TAA,SMAA,SSAA。
FXAA
快速近似抗锯齿(英语:Fast Approximate Anti-Aliasing,FXAA),由nVIDIA员工Timothy Lottes开发的一种反锯齿算法。[1]
FXAA占用很少的资源,便可得到良好的反锯齿效果,因为它不是分析3D模型本身,而是分析像素。如果把右图的FXAA图形放大,会发现FXAA只是把影像模糊化,但以正常大小观看,效果就跟四倍MSAA十分接近。《上古卷轴V:天际》是首款使用此技术的游戏。
优缺点:
消耗低,速度快;但是是一种粗糙的模糊处理。
TAA (Temporal Anti-Aliasing, TAA)
TAA(Temporal Anti-Aliasing)是时间性抗锯齿,是最常用的图像增强算法之一,这是一种基于着色器的算法,使用运动矢量组合两帧,以确定在何处对前一帧进行采样。在每一帧对屏幕区域内的像素进行一个抖动操作,这样当连续的多个帧的数据混合起来以后,就相当于对每个像素进行了多次采样,他将采样点从单帧分布到多个帧上,使得每一帧并不需要多次采样增加计算量,但TAA往往会盲目地跟随移动物体的运动矢量,从而造成屏幕上的细节模糊不清。
大家可能还听过TXAA,实际上就是TAA+MSAA(多重采样抗锯齿Multi-Sample Anti-Aliasing)。
SMAA(增强的亚像素形态抗锯齿)
SMAA的核心原理来源于MLAA。MLAA的基本思路是:检测每帧图像上的边缘(通常可对亮度、颜色、深度或者法线进行边缘检测),然后对这些边缘进行模式识别,归类出Z、U、L三种形状,根据形状对边缘进行重新矢量化(re-vectorization),并对边缘上的像素根据覆盖面积计算混合权重,将其与周围的颜色进行混合,从而达到平滑锯齿的目的。
SSAA
超采样反走样(Super Sampling AA),古老的全图抗锯齿。把图片放进缓存并放大,把放大后的图像像素采样临近2个或4个像素,混合,生成的最终像素,令图形的边缘色彩过渡趋于平滑,最后把图像还原回原来大小。缺点:很吃性能。
一般情况下建议开启FXAA和SMAA,这意味着用较低的性能消耗去换更好的画面效果
如何开启抗锯齿
当你不适用 EffectComposer的时候直接这样用
new THREE.WebGLRenderer({antialias: true});
//记得设置这个
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
因为没有研究源码,我也不知道这个背后具体使用了那种技术,因此我建议使用EffectComposer中的SMAA或者FXAA。其中FXAA可以直接使用,但是SMAA需要进行如下改造,思路同OutlinePass,源码如下:
import {SMAAPass} from "three/examples/jsm/postprocessing/SMAAPass";
export default class MySMAAPass extends SMAAPass{
constructor(width,height) {
super(width,height);
}
render(renderer, writeBuffer, readBuffer, deltaTime, maskActive) {
// pass 1
this.uniformsEdges[ 'tDiffuse' ].value = readBuffer.texture;
this.fsQuad.material = this.materialEdges;
renderer.setRenderTarget( this.edgesRT );
if ( this.clear ) renderer.clear();
this.fsQuad.render( renderer );
// pass 2
this.fsQuad.material = this.materialWeights;
renderer.setRenderTarget( this.weightsRT );
if ( this.clear ) renderer.clear();
this.fsQuad.render( renderer );
// pass 3
this.uniformsBlend[ 'tColor' ].value = readBuffer.texture;
this.fsQuad.material = this.materialBlend;
if ( this.renderToScreen ) {
readBuffer.texture.encoding = renderer.outputEncoding;
renderer.setRenderTarget( null );
this.fsQuad.render( renderer );
} else {
renderer.setRenderTarget( writeBuffer );
if ( this.clear ) renderer.clear();
this.fsQuad.render( renderer );
}
}
}
用MySMAAPass替代SMAAPASS即可。
下面来对比一下效果
无抗锯齿
开始SMAA
composer = new EffectComposer(renderer)
const renderPass = new RenderPass(scene, camera)
const pass = new MySMAAPass( params.container.innerWidth * renderer.getPixelRatio(), params.container.innerHeight * renderer.getPixelRatio() );
composer.addPass( pass );
开启FXAA
composer = new EffectComposer(renderer)
const renderPass = new RenderPass(scene, camera)
composer.addPass(renderPass)
// antialias
const fxaaPass = new ShaderPass(FXAAShader);
fxaaPass.material.uniforms['resolution'].value.x = 1 / (params.container.clientWidth * renderer.getPixelRatio());
fxaaPass.material.uniforms['resolution'].value.y = 1 / (params.container.clientHeight * renderer.getPixelRatio());
composer.addPass(fxaaPass)
总结
介于我在刚刚解决了SMAA的色差问题,所以以后大家可以使用SMAA,如果不想引入重写的MySMAAPass那就是使用FXAA也可以。
图像抗锯齿
邻近过滤和线性过滤
当你在玩吃鸡的时候可能会发现一个现象,一个人在距离你5米的地方,可以观察到敌人的一举一动,包括身上的装备等等,但当你拿到98k时,你会蹲在远处狙击别人,这时不开镜的你看500米远处的敌人只能看到一个黑点,这其中远近看到的区别就有应用到纹理过滤
游戏中A物体的游戏设计纹理贴图是400*400Texel(纹理像素)
- 当你(摄像机)跟A物体的距离为0时,在你屏幕上显示的像素就是40*400,跟贴图的纹理像素大小相同,此时不需要做特殊处理
- 假设你离A物体10米远时,屏幕显示的pix为200200,当200200的像素要显示400400纹理像素的物体时,此时的一颗像素需要映射22的纹理像素,这个时候就有个问题了,这颗像素要显示什么颜色?只能用合适的算法在这2*2里的纹理像素中计算得出,在这里引用OpenGL中的邻近过滤和线性过滤:
NEAREST(也叫邻近过滤,Nearest Neighbor Filtering)是OpenGL默认的纹理过滤方式。当设置为GL_NEAREST的时候,OpenGL会选择中心点最接近纹理坐标的那个像素。下图中你可以看到四个像素,加号代表纹理坐标。左上角那个纹理像素的中心距离纹理坐标最近,所以它会被选择为样本颜色:
LINEAR(也叫线性过滤,(Bi)linear Filtering)它会基于纹理坐标附近的纹理像素,计算出一个插值,近似出这些纹理像素之间的颜色。一个纹理像素的中心距离纹理坐标越近,那么这个纹理像素的颜色对最终的样本颜色的贡献越大。下图中你可以看到返回的颜色是邻近像素的混合色:
效果对比
此时的你来到远离市区500米的山上,想要狙击别人,你只能看到一个个很小的人影,在不开镜的情况下只能看到人的跑动。
此时敌人在屏幕显示为2020,400*400的纹理像素映射在2020的像素内,一颗像素需要映射20*20的纹理像素,如果直接进行纹理过滤,那么在使用线性过滤的情况下,只会使用纹理坐标映射点的周围4颗纹理像素进行计算,那么其他的396颗纹理像素就没了参考价值,考虑极端情况下,如果纹理坐标映射点在人的头发,那么不就只是显示黑色了? ,那么在最终的显示效果上可能会产生锯齿或者摩尔纹,摩尔纹长这样:
这视觉效果必须得改善,那我不能参考所有需要映射的纹理像素进行平均取色吗?可以,但是你的GPU允许你这么做吗,有兴趣可以做个实验,自己采样所有纹理像素进行纹理过滤,此时你如果远处的模型较多的情况下,
使用原生的线性过滤:60FPS,渲染一个物体每颗像素的颜色需要计算4个纹理像素的插值。 采样所有纹理像素进行纹理过滤:1FPS,渲染一个物体每颗像素的颜色需要采样计算400个纹理像素的插值。可以想象性能换质量的消耗实在太大,在如今的GPU算力下是施行不了的。那有办法不损耗算力,又可以提升显示质量吗?可以,使用Mipmap.
Mipmap
想象一下,假设我们有一个包含着上千物体的大房间,每个物体上都有纹理。有些物体会很远,但其纹理会拥有与近处物体同样高的分辨率。由于远处的物体可能只产生很少的片段,OpenGL从高分辨率纹理中为这些片段获取正确的颜色值就很困难,因为它需要对一个跨过纹理很大部分的片段只拾取一个纹理颜色。在小物体上这会产生不真实的感觉,更不用说对它们使用高分辨率纹理浪费内存的问题了。
OpenGL使用一种叫做多级渐远纹理(Mipmap)的概念来解决这个问题,它简单来说就是一系列的纹理图像,后一个纹理图像是前一个的二分之一。多级渐远纹理背后的理念很简单:距观察者的距离超过一定的阈值,OpenGL会使用不同的多级渐远纹理,即最适合物体的距离的那个。由于距离远,解析度不高也不会被用户注意到。同时,多级渐远纹理另一加分之处是它的性能非常好。
Mipmap中每一个层级的小图都是主图的一个特定比例的缩小细节的复制品。虽然在某些必要的视角,主图仍然会被使用,来渲染完整的细节。但是当贴图被缩小或者只需要从远距离观看时,mipmap就会转换到适当的层级。事实上,在三线性过滤(trilinear filtering)起作用时,会在两个相近的层级之间切换。
因为mipmap贴图需要被读取的像素远少于普通贴图,所以渲染的速度得到了提升。而且操作的时间减少了,因为mipmap的图片已经是做过抗锯齿处理的,从而减少了实时渲染的负担。放大和缩小也因为mipmap而变得更有效率。
如果贴图的基本尺寸是256x256像素的话,它mipmap就会有8个层级。每个层级是上一层级的四分之一的大小,依次层级大小就是:128x128;64x64;32x32;16x16;8x8;4x4;2x2;1x1(一个像素)。例如在一个场景中,渲染贴图需要填满的空间大小是40x40像素的话,如果没有三线性过滤,那32x32 会被放大显示,或者有三线性过滤,会在64x64和32x32之间切换。最简单的生成贴图的方法就是依次做平均,当然也可以用更加高级的算法。
在threejs中使用MIPMAP
pmremGenerator = new PMREMGenerator(renderer);
pmremGenerator.compileEquirectangularShader();
const envMap = pmremGenerator.fromEquirectangular(hdrEquirect).texture;
pmremGenerator.dispose();
hdrEquirect.mapping = THREE.EquirectangularReflectionMapping;
scene.environment = envMap;
上面是对Scene的environment的texture进行mipmap操作,我们可以对比一下效果
不开启mipmap
开启mimap
解决加载速度过慢问题
threejs load 模型的时候其实是分为两步了。第一步是IO流读取文件,第二步就是解码文件转换为3d的对象。我们能够操作的主要实在第一步,也就是减少文件体积(在尽量不影响效果的情况下)。因此我们需要一系列glb文件的压缩技术。总体被我分成如下两步
压缩文件
这里我就省略其他压缩技术,只介绍我们目前采用的技术。首先需要安装两个库
pnpm add -g gltfpack
pnpm add -g gltf-pipeline
EXT_meshopt_compression
此插件假定缓冲区视图数据针对 GPU 效率进行了优化——使用量化并使用最佳数据顺序进行 GPU 渲染——并在 bufferView 数据之上提供一个压缩层。每个 bufferView 都是独立压缩的,这允许加载器最大程度地将数据直接解压缩到 GPU 存储中。
除了优化压缩率之外,压缩格式还具有两个特性——非常快速的解码(使用 WebAssembly SIMD,解码器在现代桌面硬件上以约 1 GB/秒的速度运行),以及与通用压缩兼容的字节存储。也就是说,不是尽可能地减少编码大小,而是以通用压缩器可以进一步压缩它的方式构建比特流。
使用如下:
gltfpack -i male.glb -o male-processed.glb -cc
threejs中对应修改
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
import { MeshoptDecoder } from 'three/examples/jsm/libs/meshopt_decoder.module.js'
const loader = new GLTFLoader()
loader.setMeshoptDecoder(MeshoptDecoder)
loader.load(MODEL_FILE_PATH, (gltf) => {
// ....
})
其他压缩技术
KHR_draco_mesh_compression, KHR_mesh_quantization
拆分文件
glb文件可以拆分为.gltf/.glb文件+二进制文件+纹理图片,那么,我们就可以将其拆分出来,并对纹理图片进行单独的压缩,来进行性能的优化。
使用如下:
gltf-pipeline -i male.glb -o male-processed.glb -s
在threejs中无需任何改变
压缩纹理图片
上一个步骤我们将纹理图片拆分了出来,现在我们可以针对纹理图片进行压缩。压缩方式主要分为两种,两种对应的工具可以网上自行查找,这里我推荐utools内的插件:图片批处理。可以一次性完成裁剪和压缩并且替换的工作。
裁剪
将长宽裁剪为512512或者256256
压缩
采用图像压缩技术对png图片进行压缩
至于效果就拿加热炉来说,UI减面之后大约40MB,经过gltf压缩大约25MB,再经过拆分以及纹理图片的裁剪和压缩,现在大概2~3MB,加载时间400ms,还是在大批量模型同时加载的情况下。
总结
当然以上方法不是万能药,大量的压缩和裁剪势必会导致模型在近距离观看的时候不够细致,因此我们要根据实际场景进行搭配使用,包括可以细化每个模型的纹理图片裁剪的大小(但是长宽必须为2的n次方)。
图形学相关专业名词解释
color
这个就不解释了,不懂的自己去面壁
Emissive
自发光属性,模型默认是不发光的,如果一个模型是发光的,比如电源上一个电源灯,你可以把电源灯的材质设置为对应的发光颜色。
Roughness(粗糙度)
材料多么粗糙。0.0表示光滑的镜面反射,1.0表示完全分散。默认值为1.0。如果还提供了Roughnessmap,则两个值将乘以。
Metalness(金属度)
材料有多像金属。诸如木材或石材等非金属材料使用0.0,金属使用1.0,两者之间没有任何(通常)。默认值为0.0。可用于生锈的金属外观,值在0.0到1.0之间。如果还提供了MetalNessMap,则两个值将乘以。
Vertex Colors(顶点色)
定义是否使用顶点着色。默认值为false。
Map(贴图)
就是模型表面贴图,可以指定为颜色和一些图片素材
Emissive Map(自发光贴图)
自发光贴图,一个Mesh不同区域有的发光,有的不发光,这时候不能使用自发光性.emissive整体设置同样发光效果,可以通过自发光贴图属性.emissiveMap来解决。如果物体发光的电部分单独使用一个Mesh表示,这种情况下可以设置.emissive即可,不需要美术导出自发光贴图,一般美术出图的时候可能会把多个零件合并为一个网格模型Mesh
Alpha Map(透明的贴图)
Alpha图是一种灰度纹理,可控制整个表面上的不透明度(黑色:完全透明;白色:完全不透明
Bump Map(凹凸贴图)
创建凸点图的纹理。黑白值映射到相对于灯光的感知深度。凹凸实际上不会影响物体的几何形状,而只会影响照明。如果定义了NormalMap,将忽略这一点。
Normal Map(法线贴图)
用一张纹理存储法线信息,这样shader就可以得到每个像素的法线信息,通过计算光照,在视觉上产生凹凸不平的效果(当然这也就是说,在没有光照的情况下是看不出立体感的o.o)
Displace Map(移位贴图)
让点的位置沿面法线移动一个贴图中定义的距离。它使得贴图具备了表现细节和深度的能力,且可以同时允许自我遮盖,自我投影和呈现边缘轮廓
Rough Map(粗糙度贴图)
可以精细控制模型各部分得粗糙度
Env Map(环境贴图)
环境图。为了确保物理上正确的渲染,您应仅添加由PMREMGENERATOR预处理的环境图。默认为null。
Light Map(灯光贴图)
灯光图是一种用于灯光图的数据结构,这是一种表面缓存的形式,其中虚拟场景中表面的亮度预先计算并存储在纹理地图中,以供以后使用。灯光图最常用于使用实时3D计算机图形(例如视频游戏)的应用程序中的静态对象,以便以相对较低的计算成本提供照明效果,例如全局照明。
AO Map(环境闭塞贴图)
计算机图形学中的一种着色和渲染技术,用来计算场景中每一点是如何接受环境光的。
环境光遮蔽是一种全局方法,意味着每个点的照明是场景中其他几何体的共同作用
Side
定义面部的哪一侧将被渲染 。
Flat Shading
定义材料是否用平面阴影渲染。默认值为false。
Blending
材质Material的.blending属性主要控制纹理融合的叠加方式,.blending属性的默认值是 THREE.NormalBlending,其它值THREE.AdditiveBlending、THREE.SubtractiveBlending等
THREE.NormalBlending:.blending属性默认值THREE.AdditiveBlending:加法融合模式THREE.SubtractiveBlending:减法融合模式THREE.MultiplyBlending:乘法融合模式THREE.CustomBlending:自定义融合模式,与.blendSrc,.blendDst或.blendEquation属性组合使用
.blendSrc、.blendSrc、.blendEquation等属性的介绍可以查看Threejs文档材质基类Material
Opacity
透明度
Transparent
定义该材料是否透明。这对渲染具有影响,因为透明物体需要特殊处理,并在非透明物体后进行渲染。设置为真时,通过设置其不透明度属性来控制材料的透明程度。默认值为false。
Alpha Test
设置运行alpha测试时要使用的alpha值。如果不透明度低于此值,则不会呈现材料。默认值为0。
Depth Test
depthTest是一个设置项,是否在绘制的时候检查,当前像素前面是否有别的像素,如果别的像素挡道了它,那它就不会绘制,也就是说,是否只绘制最前面的一层。
Depth Write
渲染该材料是否受深度缓冲有任何影响。默认是true。当绘制2D图层时,禁用depth write以便在不创建Z index伪像的情况下将几件东西分层。
Wire Frame
显示模型线框
three-jarvis的使用
这是我本人的一个开源项目,目的是帮助大家更好的调式threejs项目。目前还在开发中还有很多设想中的功能没有实现,但是目前已经足够满足时间大部分需求了。‘
alt+rightClick 选中物体
双击模型树的节点也可以选中物体
右上位置为当前选择模型的基本信息,可以使用拖动,键入的方式修改信息。