Files
netease-modsdk-wiki/docs/mcguide/16-美术/7-材质与着色器/材质配置说明.md
2025-03-17 13:24:39 +08:00

558 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
front:
hard: 入门
time: 分钟
---
# 材质配置说明
## 前言
本文将详细介绍材质文件的结构与配置方式。
## 加载的材质文件列表
材质文件存放于资源包的materials文件夹下我们打开resource_packs\vanilla\materials目录可看到包含有下面这些文件这些是原生微软定义的材质文件
![material_list](./images/material_list.png)
而resource_packs\vanilla_netease\materials目录下的材质文件则是网易对微软材质文件的修改与扩充。
下面我们就先以原生微软的材质文件进行讲解,首先,目录下面的文件基本都是以".material"为后缀的文件除此之外还有3个重要的json文件分别是common.jsonfancy.jsonsad.json。
我们先来看看sad.json和fancy.json他们是用于控制画质表现的内部各自定义了一个材质文件列表fancy.json通常比sad.json会多定义几个材质文件和可能会为某些材质文件多添加了一些额外的宏shader中可通过对这些宏的判断做特殊的处理
sad.json:
```json
[
{"path":"materials/sad.material"},
{"path":"materials/entity.material"},
{"path":"materials/terrain.material"},
{"path":"materials/portal.material"},
{"path":"materials/barrier.material"},
{"path":"materials/wireframe.material"}
]
```
fancy.json:
```json
[
{"path":"materials/fancy.material", "+defines":["FANCY"]},
{"path":"materials/entity.material", "+defines":["FANCY"]},
{"path":"materials/terrain.material", "+defines":["FANCY"]},
{"path":"materials/hologram.material"},
{"path":"materials/portal.material", "+defines":["FANCY"]},
{"path":"materials/barrier.material"},
{"path":"materials/wireframe.material"}
]
```
可见fancy.json比sad.json多定义了fancy.material,hologram.material材质文件另外也为多个材质文件定义了FANCY宏。
游戏内 设置/视频/精美贴图 这个开关就是控制sad与fancy的切换。当精美贴图开关打开时fancy.json中的材质文件就会生效而关闭时sad.json的材质文件就会生效
![fancy_switch](./images/fancy_switch.png)
为了实现更好的表现效果fancy.json中的材质文件通常具有较复杂的运算而sad.json中的材质则通常以牺牲一点渲染表现去换取更好的性能。
开发者若需要编写计算较为复杂shader则建议同时编写一个低消耗的版本然后分别把他们定义在fancy与sad内。让玩家在游戏中通过精美贴图选项自行控制是否开启相应效果。
接下来再看看common.json
```json
[
{"path":"materials/particles.material"},
{"path":"materials/shadows.material"},
{"path":"materials/sky.material"},
{"path":"materials/ui.material"},
{"path":"materials/ui3D.material"},
{"path":"materials/portal.material"},
{"path":"materials/barrier.material"},
{"path":"materials/wireframe.material"}
]
```
相比于sad与fancy可互相切换。common.json中定义的材质文件进入游戏后都将被加载。除了在common.json, sad.json, fancy.json中声明的材质文件其余的材质文件并不会被加载。
## 材质语法
我们使用其中一个材质文件entity.material来进行讲解打开文件我们可以看到文件是以materials开头然后定义了版本号version为1.0.0,这些都是固定格式,标识了这个材质文件的解析方式,我们可以暂时不理会不修改:
![material_head](./images/material_head.png)
接下来讲解材质中字段的格式我们用entity.material中定义的第一个材质来看
![entity_static](./images/entity_static.png)
可以看到材质里面每一个字段的定义都是键值对的形式,例如:
```json
[
"vertexShader": "shaders/entity.vertex",
]
```
冒号左边代表键为vertexShader,右边代表值为shaders/entity.vertex
另外还有列表形式的定义:
```json
[
"vertexFields": [
{ "field": "Position" },
{ "field": "Normal" },
{ "field": "UV0" }
],
]
```
用符号[ ]声明是列表然后里面是每个子元素的json定义。
## 材质所有的属性字段概况
我们把所有字段按功能分到以下几类中:
### 渲染状态
#### states
配置渲染环境,可以有以下的值:
```json
EnableAlphaToCoverage MSAA
Wireframe 线
Blending : blendSrc,blendDst
DisableColorWrite RGBA
DisableAlphaWrite alphaRGB
DisableRGBWrite RGBAlpha
DisableDepthTest
DisableDepthWrite
DisableCulling :
InvertCulling 使
StencilWrite
EnableStencilTest
```
### 着色器路径
#### vertexShader
顶点着色器的路径通常为shaders/XXX.vertex。
#### vrGeometryShader 或 geometryShader
几何着色器的路径通常为shaders/XXX.geometry移动端用不到无须考虑修改。
#### fragmentShader
片段着色器的路径通常为shaders/XXX.fragment。
### Shader宏定义
#### defines
为使用到的Shader定义宏。为了代码的复用我们很多不同的材质会使用相同的shader。此时若想shader里面某处根据当前材质执行不一样的逻辑则可以通过材质defines声明的宏去做判断。
我们可以用下面entity_for_skeleton这个材质做说明这里可以看出定义了USE_SKINNINGUSE_OVERLAYNETEASE_SKINNING三个宏。
```json
"entity_for_skeleton": {
"vertexShader": "shaders/entity.vertex",
"vrGeometryShader": "shaders/entity.geometry",
"fragmentShader": "shaders/entity.fragment",
"+defines": [ "USE_SKINNING", "USE_OVERLAY", "NETEASE_SKINNING" ],
"vertexFields": [
{ "field": "Position" },
{ "field": "Normal" },
{ "field": "BoneId0" },
{ "field": "UV0" }
],
"msaaSupport": "Both",
"+samplerStates": [
{
"samplerIndex": 0,
"textureFilter": "Point"
}
]
},
```
再看顶点着色器entity.vertex中会有通过#ifdef#else#endif来对宏进行判断并执行不同的逻辑分支宏的这些判断语句是编译期处理的不像传统shader中的if else编译期处理的在实际运行中不会产生逻辑分支性能不会因分支而下降。另外下面可以看出宏也可以做多层的判断先判断NETEASE_SKINNING宏在内部执行逻辑中又有LARGE_VERTEX_SHADER_UNIFORMS宏的判断
```json
#ifdef NETEASE_SKINNING
MAT4 boneMat = transpose(mat3x4ToMat4(BONES_70[int(BONEID_0)]));
entitySpacePosition = boneMat * POSITION;
entitySpaceNormal = boneMat * NORMAL;
#else
#if defined(LARGE_VERTEX_SHADER_UNIFORMS)
entitySpacePosition = BONES[int(BONEID_0)] * POSITION;
entitySpaceNormal = BONES[int(BONEID_0)] * NORMAL;
#else
entitySpacePosition = BONE * POSITION;
entitySpaceNormal = BONE * NORMAL;
#endif
#endif
```
### 运行时状态
#### depth 深度测试
##### depthFunc
深度检测通过函数,可以使用以下的值:
```json
Always :
Equal
NotEqual
Less
Greater
GreaterEqual
LessEqual
```
相关联的states渲染环境配置
```json
DisableDepthTest
DisableDepthWrite
```
#### Stencil 蒙版测试
##### stencilRef
与蒙版缓冲区比较或要被写入的值
##### stencilRefOverride
是否使用缓冲区当前的值作为stencilRef支持0或1
```json
1 使stencilRefstencilRefstencilRefOverride1
0 使stencilRefstencilRef
```
##### stencilReadMask
蒙版缓冲区的值与stencilRef值在比较前均会先与stencilReadMask进行位与运算
##### stencilWriteMask
stencilRef值在写入蒙版缓冲区前会与stencilWriteMask进行位与运算
##### frontFace, backFace
配置网格正面或反面使用什么蒙版测试函数,另外,判断的顺序为先蒙版检测,再深度检测,需要配置以下操作:
```json
stencilFunc : stencilRef使
Always :
Equal stencilRef
NotEqual stencilRef
Less stencilRef
Greater stencilRef
GreaterEqual stencilRef
LessEqual stencilRef
stencilFailOp stencilFunc
Keep
Replace stencilRefstencilWriteMask
stencilDepthFailOp : stencilFunc,
Keep
Replace stencilRefstencilWriteMask
stencilPassOp : stencilFunc
Keep
Replace stencilRefstencilWriteMask
```
相关联的states渲染环境配置
```json
StencilWrite
EnableStencilTest
```
最后我们看一段例子:
```json
"shadow_back": {
"+states": [
"StencilWrite",
"DisableColorWrite",
"DisableDepthWrite",
"InvertCulling",
"EnableStencilTest"
],
"vertexShader": "shaders/position.vertex",
"vrGeometryShader": "shaders/position.geometry",
"fragmentShader": "shaders/flat_white.fragment",
"frontFace": {
"stencilFunc": "Always",
"stencilFailOp": "Keep",
"stencilDepthFailOp": "Keep",
"stencilPassOp": "Replace"
},
"backFace": {
"stencilFunc": "Always",
"stencilFailOp": "Keep",
"stencilDepthFailOp": "Keep",
"stencilPassOp": "Replace"
},
"stencilRef": 1,
"stencilReadMask": 255,
"stencilWriteMask": 1,
"vertexFields": [
{ "field": "Position" }
],
"msaaSupport": "Both"
}
```
例子中 StencilWrite 代表支持蒙版缓冲区的写入EnableStencilTest代表开启蒙版测试frontFace的配置代表正面渲染的时候蒙版测试总是通过深度测试不通过则保持缓冲区值不变若深度测试也通过则会往缓冲区写入 stencil位与stencilWriteMask 的值,即 1 & 1 = 1值。backFace的配置也是类似。
#### Blend 半透明对象颜色混合
半透明对象的渲染需要配置混合因子最终输出的rgb颜色值 = 当前颜色值 * 源混合因子 + 缓冲区中的颜色值 * 目标混合因子
##### blendSrc
源混合因子
##### blendDst
目标混合因子
##### alphaSrc
计算alpha时的源混合因子通常不配置取默认值
##### alphaDst
计算alpha时的目标混合因子通常不配置取默认值
---
混合因子总共可以取下面的值:
```json
DestColor
SourceColor
Zero (0,0,0)
One (1,1,1)
OneMinusDestColor : (1,1,1) -
OneMinusSrcColor : (1,1,1) -
SourceAlpha alpha
DestAlpha alpha
OneMinusSrcAlpha 1 - alpha
```
在引擎中,默认值为:
```json
blendSrc SourceAlpha
blendDst OneMinusSrcAlpha
alphaSrc One
alphaDst OneMinusSrcAlpha
```
相关联的states渲染环境配置
```json
Blending : blendSrc,blendDst
DisableColorWrite RGBA
DisableAlphaWrite alphaRGB
DisableRGBWrite RGBAlpha
```
#### sample 纹理采样
##### samplerStates
配置采样状态值为一个列表根据需要采样的纹理个数为每一个纹理进行配置通常若顶点属性中声明了UV0, UV1代表需要采样两个纹理这里则需要配置两个元素。下面看子元素的定义
```json
{
"samplerIndex": 0,
"textureFilter": "Point",
"textureWrap": "Repeat"
}
```
每个属性的定义如下:
###### samplerIndex
数字代表当前正在设置第几张纹理的属性由0开始
###### textureFilter
纹理过滤模式默认为Point当实际显示的纹理贴图相比于原图进行了放大或缩小时新的分辨率贴图与原分辨率贴图上像素点的映射关系可以有以下的值
```json
Point
Bilinear : 线
Trilinear : 线
MipMapBilinear MipMap线
TexelAA 齿使
PCF 使
```
###### textureWrap
纹理包裹模式控制uv若在[0,1]之外的时候应该采样到什么样的纹理,可以有如下的值:
```json
Repeat [01]
Clamp 1.111-0.100
```
#### vertex 顶点属性
##### vertexFields
顶点属性,用于声明使用这个材质进行渲染的网格每个顶点保存有什么属性,由美术制作资源的时候决定,可能用到的有以下的值:
```json
Position
Color
Normal 线
UV0
UV1
UV2
BoneId0 ID
```
#### rasterizer 光栅化环境配置
##### msaaSupport
配置MSAA(多重采样抗锯齿)的支持(引擎中的默认值为NonMSAA)
```json
NonMSAA : MSAA使
MSAA : MSAA使
Both MSAA使使
```
##### 深度偏移
深度偏移主要用于解决z-fighting问题即当两个物体深度相近则渲染时可能会出现某些帧显示这个物体某些帧显示另一个物体这种闪烁现象。深度偏移的原理是把其中一个对象往深度大或者小的方向偏移一下使他们的深度不再一样。
可配置以下四个变量:
```json
depthBias
slopeScaledDepthBias
depthBiasOGL
slopeScaledDepthBiasOGL
```
具体偏移的深度为:
```json
offset = (slopeScaledDepthBias * m) + (depthBias * r)
```
在OGL平台上则为
```json
offset = (slopeScaledDepthBiasOGL * m) + (depthBiasOGL * r)
```
m是多边形的深度的斜率在光栅化阶段计算得出中的最大值。一个多边形越是与近裁剪面平行m就越接近0。
r是能产生在窗口坐标系的深度值中可分辨的差异的最小值r是由具体实现OpenGL的平台指定的一个常量。
---
相关联的states渲染环境配置
```json
Wireframe 线
DisableCulling :
InvertCulling 使
```
#### primitive 图元
##### primitiveMode
图元渲染模式引擎中的默认值为TriangleList
```json
None
QuadList
TriangleList : 使v0,v1,v2,使v3,v4,v5
TriangleStrip :
LineList : 线
Line : 线
```
### 材质变体
#### variants
用于快速基于大部分相同定义实现多种子材质。看下面entity_static这个实际例子
```json
"entity_static": {
"vertexShader": "shaders/entity.vertex",
"vrGeometryShader": "shaders/entity.geometry",
"fragmentShader": "shaders/entity.fragment",
"vertexFields": [
{ "field": "Position" },
{ "field": "Normal" },
{ "field": "UV0" }
],
"variants": [
{
"skinning": {
"+defines": [ "USE_SKINNING" ],
"vertexFields": [
{ "field": "Position" },
{ "field": "BoneId0" },
{ "field": "Normal" },
{ "field": "UV0" }
]
}
},
{
"skinning_color": {
"+defines": [ "USE_SKINNING", "USE_OVERLAY" ],
"+states": [ "Blending" ],
"vertexFields": [
{ "field": "Position" },
{ "field": "BoneId0" },
{ "field": "Color" },
{ "field": "Normal" },
{ "field": "UV0" }
]
}
}
],
"msaaSupport": "Both",
"+samplerStates": [
{
"samplerIndex": 0,
"textureFilter": "Point"
}
]
},
```
variants即为材质变体的声明上述声明了skinning和skinning_color两个子变体子变体中对外部某些字段进行了改写。实际使用中相当于快速定义了两个材质本体和变体间用点"."连接两个材质分别为entity_static.skinning和entity_static.skinning_color。
除此之外后续如果有其它材质继承自entity_static比如entity_dynamic则此材质也会同时继承有此两种变体分别为entity_dynamic.skinning和entity_dynamic.skinning_color。
## 材质合并规则
不同目录文件中声明了同一材质时的,在加载后会根据以下规则进行合并:
1.通常情况下后加载的文件的材质的字段会覆盖之前加载的
2.以下字段特殊,除了替换以外,还支持使用"+"添加与使用"-"删除属性的操作:
```json
defines
states
samplerStates
```
举一个的例子,例如包体文件中声明了这么一材质(省略无关代码),定义了三个宏:
```json
"testMat": {
"defines": [ "MACRO_1", "MACRO_2", "MACRO_3" ],
}
```
此时一个Mod也声明了此材质定义了另外三个宏
```json
"testMat": {
"defines": [ "MACRO_4", "MACRO_5", "MACRO_6" ],
}
```
上述情况下最终运行时相当于defines字段被覆盖实际运行时生效的宏只有: MACRO_4, MACRO_5, MACRO_6
若MOD中定义的时候使用了"+"符号:
```json
"testMat": {
"+defines": [ "MACRO_4", "MACRO_5", "MACRO_6" ],
}
```
相当于在原来的基础上添加定义,则实际运行时生效的宏有: MACRO_1, MACRO_2, MACRO_3, MACRO_4, MACRO_5, MACRO_6
若MOD中定义的时候使用了"-"符号:
```json
"testMat": {
"-defines": [ "MACRO_3"],
}
```
相当于在原来的基础上删除某些定义,则实际运行时生效的宏只有: MACRO_1, MACRO_2
若多个文件都对同一材质进行了定义,而且分别涉及有覆盖,添加,删除操作,则依次生效的顺序为:
先执行所有的覆盖操作,再执行所有的添加操作,最后执行所有的删除操作。
即如果有其中一个材质文件声明删除MACRO_3操作
```json
"testMat": {
"-defines": [ "MACRO_3"],
}
```
则无论其它文件怎么覆盖添加MACRO_3最终合成后这一个材质一定不会有MACRO_3宏。