three.js webgl_nodes_materials_instance_uniform 例子分析

在 three.js 的官方例子中 webgl_nodes_materials_instance_uniform
可以看到场景中 有一个格子背景,一个环绕运动的点光源,12个颜色各异的球体网格。

如果使用一个自己开发的简陋渲染程序,可能要编译 15 个着色器,但这里只编译了三个着色器,12个颜色各异的球体共用一个着色器。unity3d, unreal 等三维渲染引擎,都有一个MaterialInstance的概念。而three.js 中没有对应的概念。

什么是材质 Material ? 材质就是 顶点着色器 + 片元着色器 + 着色器需要的若干个参数。 假设场景中有一个球体 和 一个立方体;这两个物件,可以共用一个材质,那么这时 顶点着色器 和 片元着色器 都各需要编译一次。

现在需要把球体改为红色,立方体改为蓝色。这时采用简陋的渲染程序,片元着色器可能至少需要编译两次,才能为两个物件涂上不同的颜色。这里其实就有了一个性能浪费,材质三要素中的 着色器参数修改了后,两个物件就至少需要编译两次片元着色器 ,在成熟的完备的三维渲染引擎 unity3d, unreal 中,就有了材质实例 MaterialInstance 这个概念,两个物件的 顶点着色器 和 片元着色器 都相同,仅仅着色器参数不同,这时就不需要 编译着色器两次,编译一次就够了。MaterialInstance的底层实现比较麻烦,大概思路就是创建了一个浅拷贝 来使用不同的着色器参数。

MaterialInstance的意义就是会性能优化提供了一个新途径,减少引擎的 draw call 次数,提高帧率。three.js 虽然没有MaterialInstance 的概念,但是会尽量复用已经编译好的着色器,查看three的源码WebGLPrograms 中的 getProgramCacheKey 方法,可以知道,两个材质如果仅仅颜色不同,是不会被编译两次的;但如果一个透明,一个不透明;或者一个有贴图,一个没有;两个材质就行分别编译一次

NodeMaterial 框架的主要好处是,使three.js 中的材质系统 更加组件化 和 可配置。类似于 unreal,unity3d,blender中的材质编辑器,通过一些属性框,和属性框之间的各种连线,就可以配出一个材质。如果不使用 NodeMaterial,很多时候,就不得不使用 THREE.ShaderMaterial 或 THREE.RawShaderMaterial 定制自己的顶点着色器和片元着色器,定制自己的着色器虽然更灵活,但是有时比使用 NodeMaterial 框架麻烦,并且从性能上讲,没有重用着色器,性能上就比不过 使用NodeMaterial 框架。

回到官方例子 webgl_nodes_materials_instance_uniform,要着重关注,十二个球体,如何做到只使用一个材质,但是每个球体都具有不同颜色的。

关键代码:

update( frame ) {
	this.uniformNode.value.copy( frame.object.color );
}

const material = new MeshStandardNodeMaterial();
material.colorNode = add( instanceUniform, cubeTextureNode );
material.emissiveNode = mul( instanceUniform, cubeTextureNode );

mesh.color = new THREE.Color( Math.random() * 0xffffff );

如何确定,着色器只编译了三次?
在three.module.js文件中,这行代码打断点

const glFragmentShader = WebGLShader( gl, gl.FRAGMENT_SHADER, fragmentGlsl );

刷新网页,可以看到,这行代码所在函数只被调用了三次

Three.js NodeMaterial introduction - Don McCurdy

你可能感兴趣的:(javascript,webgl,three.js)