深入学习Three.js核心对象之(三)Material详解【转载】
这次分析构成模型对象的另一个重要元素:Material(材质)。
主要介绍:
- Material的属性及WebGLRenderer的处理: 属性分类、预处理宏与自定义标记
- 部分属性解读(Todo): 融合属性、深度测试、模板测试、裁剪、多边形偏移等
本文所参考的Three.js版本为0.116.1
系列文章
- 深入学习Three.js核心对象之(一)Object3D
- 深入学习Three.js核心对象之(二)Geometry
- 深入学习Three.js核心对象之(三)Material
Material
一个表示物体的Mesh对象需要Geometry与Material对象,前者用于设置该物体的顶点、面和法线信息,后者用于设置片元着色时一些渲染属性,影响color等的计算。
Material的属性从顶层数据对象的角度来看可以认为是一种标记,在创建WebGLProgram实例时中会根据这些标记决定是否在片元着色器中添加指定的GLSL代码定义,影响最终的着色效果。
属性
Material属性主要包含:
- 基础属性: shading(平滑/平面着色)、wireframe(线框表示)、side、vertexColors、fog
- 融合属性(如何与背景融合): blending、blendSrc、blendDst、blendEquation…
- 深度测试属性: depthFunc、depthTest、depthWrite
- 模板测试属性: stencilFunc、stencilWriteMask、stencilRef、stencilFuncMask…
- 裁剪属性: clippingPlanes、clipIntersection、clipShadows
- 其他属性: precision(shader精度)、polygonOffset(多边形偏移,处理z-fighting)、dithering(颜色抖动,获取额外颜色信息,处理锯齿)
WebGLRenderer对于材质属性的处理
看看在WebGL渲染器的着色器中是如何处理材质属性的。
GLSL预处理宏
在编写着色器时,可以通过GLSL的预处理宏来加载模块化的着色器代码:
- #define: 定义预处理宏
- #ifdef: 若存在#ifdef后的宏定义,则保留#ifdef/#endif间包含的宏代码
- #if: 若满足#if后的条件,则保留#if/#endif间包含的宏代码
通过在代码中预加载部分代码(#ifdef),当需要使用这部分代码时通过在开头定义该宏(#define)来启用。
#define USE_COLOR;
#ifdef USE_COLOR
vColor.xyz = color.xyz;
#endif
这段代码处理后会保留颜色插值的那一行:
vColor.xyz = color.xyz;
简易处理流程
- WebGLRenderer在初始化时会创建初始的GLContext
- 处理GL上下文时会根据参数生成WebGLProgram
- 构建WebGLProgram对象时根据传入的材质属性处理最终的着色器字符串
主要看一下第3步,即WebProgram中对于材质属性和着色器代码的处理。
以雾化属性fog为例,看看它的处理过程。 假设此时material的fog属性为true,在生成program实例前会将材质属性存放在一个parameters对象中:
// src/renderers/webgl/WebGLPrograms.js
function WebGLPrograms( renderer, extensions, capabilities ) {
this.getParameters = function ( material, lights, shadows, scene, nClipPlanes, nClipIntersection, object ) {
var fog = scene.fog;
var parameters = {
fog: !! fog,
useFog: material.fog,
fogExp2: ( fog && fog.isFogExp2 ),
...
}
return parameters
}
}
在program中,最终的着色器代码由两部分组成:宏定义前缀(prefix*)与着色器代码(*Shader),如下:
// src/renderers/webgl/WebGLProgram.js
function WebGLProgram( renderer, cacheKey, parameters ) {
...
var vertexGlsl = prefixVertex + vertexShader;
var glVertexShader = WebGLShader( gl, gl.VERTEX_SHADER, vertexGlsl );
gl.attachShader( program, glVertexShader );
}
GLSL宏定义与宏代码
来看下GLSL宏定义与宏代码的处理:
- 宏定义: prefixVertex/prefixFragment可以看到,根据属性在保存宏定义前缀的字符串变量中(prefixVertex/prefixFragment)中添加对应的宏定义(#define USE_FOG):
// src/renderers/webgl/WebGLProgram.js function WebGLProgram( renderer, cacheKey, parameters ) { prefixVertex, prefixFragment; refixFragment包含类似的操作 ixVertex = [ ... ( parameters.useFog && parameters.fog ) ? '#define USE_FOG' : '', ( parameters.useFog && parameters.fogExp2 ) ? '#define FOG_EXP2' : '' lter( filterEmptyLine ).join( '\n' ); LSL 3.0 conversion ixVertex = [ '#version 300 es\n', '#define attribute in', '#define varying out', '#define texture2D texture' in( '\n' ) + '\n' + prefixVertex; }
- 宏代码: ShaderChunk此时宏定义已经准备好了,那么还需要对应的宏代码。
Three在
src/renderers/shaders/ShaderChunk
路径下放置了多个GLSL宏代码片段,并使用ShaderChunk对象来统一管理。// src/renderers/shaders/ShaderChunk.js import fog_vertex from './ShaderChunk/fog_vertex.glsl.js'; import fog_pars_vertex from './ShaderChunk/fog_pars_vertex.glsl.js'; import fog_fragment from './ShaderChunk/fog_fragment.glsl.js'; import fog_pars_fragment from './ShaderChunk/fog_pars_fragment.glsl.js'; ... export var ShaderChunk = { vertex: fog_vertex, pars_vertex: fog_pars_vertex, fragment: fog_fragment, pars_fragment: fog_pars_fragment, }
这些片段均为为单一功能提供的变量声明与计算处理。以雾化属性的片元着色器宏代码为例:
// fog_pars_fragment.glsl #ifdef USE_FOG orm vec3 fogColor; ing float fogDepth; ef FOG_EXP2 uniform float fogDensity; e uniform float fogNear; uniform float fogFar; if #endif // fog_fragment.glsl #ifdef USE_FOG ef FOG_EXP2 float fogFactor = 1.0 - exp( - fogDensity * fogDensity * fogDepth * fogDepth ); e float fogFactor = smoothstep( fogNear, fogFar, fogDepth ); if ragColor.rgb = mix( gl_FragColor.rgb, fogColor, fogFactor ); #endif
关于这里的代码段命名规则,经观察后应该是使用
{NAME}_{SHADER}
表示main函数中代码,使用{NAME}_pars_{SHADER}
表示开头的变量声明。现在我们找到了宏定义与宏代码,还有一个问题就是这些宏代码是如何并且在哪里被加载进来的。
- 预置着色方案对象: ShaderLib我们可以看到ShaderChunk提供的是细粒度的GLSL预处理代码,距离最终完整的shader代码还差很远。
Three将预置的着色方案代码放在了
src/renderers/shaders/ShaderLib
路径下,这些代码也是使用ShaderChunk统一保存。 在外层使用了一个ShaderLib对象来保存这些预置的着色方案(以lambert光照模型的材质为例):// src/renderers/shaders/ShaderLib.js var ShaderLib = { ert: { uniforms: mergeUniforms([ UniformsLib.common, UniformsLib.specularmap, UniformsLib.envmap, UniformsLib.aomap, UniformsLib.lightmap, UniformsLib.emissivemap, UniformsLib.fog, UniformsLib.lights, { emissive: { value: new Color( 0x000000 ) } } ]), vertexShader: ShaderChunk.meshlambert_vert, fragmentShader: ShaderChunk.meshlambert_frag, ... }
可以看看
ShaderChunk.meshlambert_vert
中的内容:#define LAMBERT varying vec3 vLightFront; varying vec3 vIndirectFront; #ifdef DOUBLE_SIDED ing vec3 vLightBack; ing vec3 vIndirectBack; #endif #include <common> ... void main() { lude <fog_vertex> }
在这里通过
#include
引入的东东即为ShaderChunk
路径下的GLSL宏代码。此时应该会产生一个问号,不要着急接着往下看。
#include
如果看过three中ShaderLib的着色器代码,会发现其中有大量#include <common>
这样的类C语句。本意应该是加载公共代码库,但问题在于GLSL中并不支持#include语句,因此猜测是有一些额外的处理。
在WebGLProgram中接着往下看的话,会发现有几行这样的代码:
vertexShader = resolveIncludes( vertexShader );
vertexShader = replaceLightNums( vertexShader, parameters );
vertexShader = replaceClippingPlaneNums( vertexShader, parameters );
fragmentShader = resolveIncludes( fragmentShader );
fragmentShader = replaceLightNums( fragmentShader, parameters );
fragmentShader = replaceClippingPlaneNums( fragmentShader, parameters );
这些函数均是使用正则处理的一些在GLSL中添加的自定义标记,看下resolveIncludes()函数就明白了:
// Resolve Includes
var includePattern = /^[ \t]*#include +<([\w\d./]+)>/gm;
function resolveIncludes( string ) {
return string.replace( includePattern, includeReplacer );
}
function includeReplacer( match, include ) {
var string = ShaderChunk[ include ];
if ( string === undefined ) {
throw new Error( 'Can not resolve #include <' + include + '>' );
}
return resolveIncludes( string );
}
其中ShaderChunk对象就是之前提到的包含多种GLSL宏代码片段的对象。这样一来,如果在shader中写了这样一行:
#include <common>
那么通过正则会滤出common
,并在ShaderChunk对象中根据该键寻找到对应的代码段(ShaderChunk['common']
),在此位置用该代码段替换该语句。
这下就一目了然了,#include
是为了方便代码添加与管理的一种自定义标记,而不是能让GPU直接处理的命令,虚惊一场。
属性解读
Todo。通过一个扩展的自定义材质来测试这些属性的效果
融合属性
深度测试
模板测试
裁剪属性
多边形偏移
其他
光照贴图类
- aomap: AO贴图
- envMap: 环境贴图
- emissiveMap: 自发光贴图
- bumpMap: 凹凸贴图
- normalMap: 法线贴图
参考
- 原文作者:yrq110
- 原文链接:http://yrq110.me/post/front-end/deep-in-threejs-core-objects-iii-material/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。
WEBGL学习网 » 深入学习Three.js核心对象之(三)Material详解【转载】