Threejs 优化指南

2,607 阅读20分钟

作为一个被threejs折磨了很久程序猿,在图形优化这条道路上颇有一些心得,因此有一点点心得分享一下。

如何解决场景发暗问题

光源问题

一般情况下场景中会添加半球光和点光源(其他光源类似)下面就分这两种光源来说:

  1. HemisphereLight(半球光)

    1. 注意半球光的ground color颜色是不是偏暗
    2. 尝试调节光的强度
  2. **PointLight **

    1. 注意光的颜色,黑色光场景肯定发暗
    2. 注意distance参数,有时候你将物体距离光源太远就会又这个问题
    3. 注意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建模好之后发给你的模型。模型中有金属材质的部分会变黑,这又两个原因,因此一下两步基本可以解决问题。

  1. 导入three editor重新设置为 MeshStandardMaterial(先切换为其他任意材质,然后切回),这样就能解决无法调整Roughness和Meltalness的问题。

    • Roughness:可以调节粗糙度,你可以理解为此值越大对光和场景的反应就越小

    • Meltalness:此值越大表面就越光滑,可以想象抛光的金属一样

2022-04-21-10-25-52-image.png

-   其次是尝试调节color参数,如果color原本为黑色,那肯定也会发黑。
  1. 未设置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(纹理像素)

  1. 当你(摄像机)跟A物体的距离为0时,在你屏幕上显示的像素就是40*400,跟贴图的纹理像素大小相同,此时不需要做特殊处理
  2. 假设你离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.AdditiveBlendingTHREE.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 选中物体

双击模型树的节点也可以选中物体

右上位置为当前选择模型的基本信息,可以使用拖动,键入的方式修改信息。