????????本文主要介绍 Filament 的材质系统,官方介绍详见 →?Filament Materials Guide。材质系统中会涉及到一些空间和变换的知识点,可以参考:【Unity3D】空间和变换、【Unity3D】Shader常量、变量、结构体、函数、【OpenGL ES】MVP矩阵变换、【OpenGL ES】透视变换原理。
? ? ? ? 需要注意的是,Unity 世界空间是左手坐标系,OpenGL 和 Filament 的世界空间是右手坐标系,Filament 的世界空间坐标轴如下。
????????读者如果对 Filament 不太熟悉,请回顾以下内容。
????????材质的格式是一种松散地基于 JSON 的格式,Filament 官方称之为 JSONish 格式。在顶层,材质定义由 JSON 对象表示的 3 个不同块组成,如下。其中,vertex 块是可选的,必须包含 material 和 fragment 块。
material {
// 材质属性
}
vertex {
// 顶点着色器(可选)
}
fragment {
// 片元着色器
}
?????????JSONish 格式具有以下特点。
? ? ? ? 材质属性(material)中可以定义材质名(name)、外部参数(parameters)、顶点属性参数(requires)、光照模型(shadingModel)、混合模式(blending)等。
material {
name : "Textured material",
parameters : [ // 外部参数
{
type : sampler2d,
name : texture
},
{
type : float,
name : metallic
},
{
type : float,
name : roughness
}
],
requires : [ // 顶点属性
uv0
],
shadingModel : lit, // 光照模型
blending : opaque // 混合模式
}
????????顶点着色器(vertex)中可以对顶点的属性进行变换,如下。
vertex {
void materialVertex(inout MaterialVertexInputs material) {
float3 p = getPosition().xyz;
float3 u = mulMat4x4Float3(getViewFromClipMatrix(), p).xyz;
material.eyeDirection.xyz = mulMat3x3Float3(getWorldFromViewMatrix(), u);
}
}
? ? ? ? 片元着色器(fragment)中可以计算光照模型所需的参数,如下。
fragment {
void material(inout MaterialInputs material) {
prepareMaterial(material);
material.baseColor = texture(materialParams_texture, getUV0());
material.metallic = materialParams.metallic;
material.roughness = materialParams.roughness;
}
}
? ? ? ? ?其中,materialParams_texture、materialParams.metallic、materialParams.roughness 是材质属性中定义的外部参数。
? ? ? ? 顶点着色器的输入结构体如下。
struct MaterialVertexInputs {
float4 color; // if the color attribute is required
float2 uv0; // if the uv0 attribute is required
float2 uv1; // if the uv1 attribute is required
float3 worldNormal; // only if the shading model is not unlit
float4 worldPosition; // always available (see note below about world-space)
mat4 clipSpaceTransform; // default: identity, transforms the clip-space position, only available for `vertexDomain:device`
// variable* names are replaced with actual names
float4 variable0; // if 1 or more variables is defined
float4 variable1; // if 2 or more variables is defined
float4 variable2; // if 3 or more variables is defined
float4 variable3; // if 4 or more variables is defined
};
? ? ? ? 片元着色器的输入结构体如下。
struct MaterialInputs {
float4 baseColor; // default: float4(1.0)
float4 emissive; // default: float4(0.0, 0.0, 0.0, 1.0)
float4 postLightingColor; // default: float4(0.0)
// no other field is available with the unlit shading model
float roughness; // default: 1.0
float metallic; // default: 0.0, not available with cloth or specularGlossiness
float reflectance; // default: 0.5, not available with cloth or specularGlossiness
float ambientOcclusion; // default: 0.0
// not available when the shading model is subsurface or cloth
float3 sheenColor; // default: float3(0.0)
float sheenRoughness; // default: 0.0
float clearCoat; // default: 1.0
float clearCoatRoughness; // default: 0.0
float3 clearCoatNormal; // default: float3(0.0, 0.0, 1.0)
float anisotropy; // default: 0.0
float3 anisotropyDirection; // default: float3(1.0, 0.0, 0.0)
// only available when the shading model is subsurface or refraction is enabled
float thickness; // default: 0.5
// only available when the shading model is subsurface
float subsurfacePower; // default: 12.234
float3 subsurfaceColor; // default: float3(1.0)
// only available when the shading model is cloth
float3 sheenColor; // default: sqrt(baseColor)
float3 subsurfaceColor; // default: float3(0.0)
// only available when the shading model is specularGlossiness
float3 specularColor; // default: float3(0.0)
float glossiness; // default: 0.0
// not available when the shading model is unlit
// must be set before calling prepareMaterial()
float3 normal; // default: float3(0.0, 0.0, 1.0)
// only available when refraction is enabled
float transmission; // default: 1.0
float3 absorption; // default float3(0.0, 0.0, 0.0)
float ior; // default: 1.5
float microThickness; // default: 0.0, not available with refractionType "solid"
}
Name | GLSL type | Description |
---|---|---|
bool2 | bvec2 | A vector of 2 booleans |
bool3 | bvec3 | A vector of 3 booleans |
bool4 | bvec4 | A vector of 4 booleans |
int2 | ivec2 | A vector of 2 integers |
int3 | ivec3 | A vector of 3 integers |
int4 | ivec4 | A vector of 4 integers |
uint2 | uvec2 | A vector of 2 unsigned integers |
uint3 | uvec3 | A vector of 3 unsigned integers |
uint4 | uvec4 | A vector of 4 unsigned integers |
float2 | float2 | A vector of 2 floats |
float3 | float3 | A vector of 3 floats |
float4 | float4 | A vector of 4 floats |
float4×4 | mat4 | A 4×4 float matrix |
float3×3 | mat3 | A 3×3 float matrix |
Name | Type | Description |
---|---|---|
PI | float | π |
HALF_PI | float | π / 2 |
saturate(float x) | float | 将 x 约束在 0 ~ 1 之间 |
pow5(float x) | float | x ^ 5 |
sq(float x) | float | x ^ 2 |
max3(float3 v) | float | 获取向量中最大的分量 |
mulMat4×4Float3(float4×4 m, float3 v) | float4 | m * v |
mulMat3×3Float3(float4×4 m, float3 v) | float4 | m * v |
Name | Type | Description |
---|---|---|
getViewFromWorldMatrix() | float4×4 | [世界空间->观察空间]的变换矩阵V |
getWorldFromViewMatrix() | float4×4 | [观察空间->世界空间]的变换矩阵V' |
getClipFromViewMatrix() | float4×4 | [观察空间->裁剪空间]的变换矩阵P |
getViewFromClipMatrix() | float4×4 | [裁剪空间->观察空间]的变换矩阵P' |
getClipFromWorldMatrix() | float4×4 | [世界空间->裁剪空间]的变换矩阵VP |
getWorldFromClipMatrix() | float4×4 | [裁剪空间->世界空间]的变换矩阵(VP)' |
getUserWorldFromWorldMatrix() | float4×4 | [世界空间->用户世界空间]的变换矩阵 |
getWorldFromModelMatrix() | float4×4 | [模型空间->世界空间]的变换矩阵M(Vertex only) |
getWorldFromModelNormalMatrix() | float3×3 | [模型空间->世界空间]的法线变换矩阵(Vertex only) |
Name | Type | Description |
---|---|---|
getResolution() | float4 | (width, height, 1 / width, 1 / height),单位:pixels |
getWorldCameraPosition() | float3 | 相机的世界空间坐标 |
getWorldOffset() | float3 | 获取 api 级世界空间的位置(已弃用,使用 getUserWorldPosition 替代) |
getTime() | float | 获取当前时间,范围:0 ~ 1,单位:s |
getUserTime() | float4 | (time, (double)time - time, 0, 0) |
getUserTimeMod(float m) | float | 获取当前时间,范围:0 ~ m,单位:s |
getExposure() | float | 照相机的曝光度 |
getEV100() | float | 相机在 ISO 100 下的曝光度 |
Name | Type | Description |
---|---|---|
getPosition() | float4 | 获取模型空间顶点坐标 |
getCustom0()?to?getCustom7() | float4 | 获取模型的 Custom0 ~?Custom7 的值 |
getVertexIndex() | int | 获取顶点的索引 |
Name | Type | Description |
---|---|---|
getWorldTangentFrame() | float3×3 | 世界空间列向量:tangent、bi-tangent、normal,如果材质没有计算凹凸贴图的切空间法线,或者材质不是各向异性的,那么在这个矩阵中只有法线是有效的。 |
getWorldPosition() | float3 | 片元的世界空间坐标 |
getUserWorldPosition() | float3 | 片元的用户世界空间坐标 |
getWorldViewVector() | float3 | 世界空间下,片元指向相机的单位方向向量 |
getWorldNormalVector() | float3 | 世界空间下,经凹凸映射后的片元的单位法线向量(必须在 prepareMaterial() 之后使用) |
getWorldGeometricNormalVector() | float3 | 世界空间下,凹凸映射前的片元的单位法线向量 (可以在 prepareMaterial() 之前使用) |
getWorldReflectedVector() | float3 | view 向量关于法线的反向量(必须在 prepareMaterial() 之后使用) |
getNormalizedViewportCoord() | float3 | 标准化的用户视口位置(即 NDC 坐标标准化为 [0,1] 的位置,[1,0] 的深度,可以在prepareMaterial() 之前使用)。因为用户视口比实际的物理视口小,所以在物理视口的不可见区域中,这些坐标可以为负或大于 1。 |
getNdotV() | float | 获取法线向量与观察向量的点积,即:dot(normal, view),返回结果严格大于 0(必须在 prepareMaterial() 之后使用) |
getColor() | float4 | 获取片元经光栅化插值后的颜色(当 required 中包含 color 时才可用) |
getUV0() | float2 | 获取 uv0 纹理坐标(当 required 中包含 uv0 时才可用) |
getUV1() | float2 | 获取 uv1 纹理坐标(当 required 中包含 uv1 时才可用) |
getMaskThreshold() | float | 获取遮罩阈值(当 blending 设置为 masked 才可用) |
inverseTonemap(float3) | float3 | 将逆色调映射操作应用于指定的线性 sRGB 颜色并返回线性 sRGB 颜色。此操作可能是近似的,并且与 “Filmic” 色调映射操作一起使用效果最好 |
inverseTonemapSRGB(float3) | float3 | 将逆色调映射操作应用于指定的非线性 sRGB 颜色并返回线性 sRGB 颜色。此操作可能是近似的,并且与 “Filmic” 色调映射操作一起使用效果最好 |
luminance(float3) | float | 计算指定的线性 sRGB 颜色的亮度 |
ycbcrToRgb(float, float2) | float3 | 将亮度和 CbCr 对转换为 sRGB 颜色 |
uvToRenderTargetUV(float2) | float2 | 转换 UV 坐标以允许从 RenderTarget 中采样 |
? ? ? ? 在?material 块中,通过 shadingModel 属性配置光照模型,取值主要有:lit、subsurface、cloth、unlit、specularGlossiness,默认取 lit。
????????lit model 官方介绍见 →?litmodel,可以配置的参数如下。
Property | Type | Range | Note | Definition |
---|---|---|---|---|
baseColor | float4 | [0..1] | Linear RGB | 非金属表面的漫射反照率和金属表面的镜面颜色 |
metallic | float | [0..1] | 0 or 1 | 表面是介电(0)还是导体(1) |
roughness | float | [0..1] | 感知表面的平滑度(1)或粗糙(0) | |
reflectance | float | [0..1] | Prefer values > 0.35 | 表面正入射菲涅耳反射率,控制了反射的强度 |
sheenColor | float3 | [0..1] | Linear RGB | 光泽层的强度 |
sheenRoughness | float | [0..1] | 光泽层的平滑度或粗糙度 | |
clearCoat | float | [0..1] | 0 or 1 | 透明涂层的强度 |
clearCoatRoughness | float | [0..1] | 可感知的透明涂层的平滑度或粗糙度 | |
anisotropy | float | [?1..1] | 当该值为正值时,各向异性在切线方向上 | 在正切或双切方向上各向异性的数量 |
anisotropyDirection | float3 | [0..1] | Linear RGB,在切空间中编码方向向量 | 切线空间的局部曲面方向 |
ambientOcclusion | float | [0..1] | 定义一个表面点可以接触到多少环境光,它是一个介于 0 和 1 之间的每像素阴影因子 | |
normal | float3 | [0..1] | Linear RGB,在切空间中编码方向向量 | 使用凹凸贴图(法线贴图)来扰动表面的细节法线。 |
bentNormal | float3 | [0..1] | Linear RGB,在切空间中编码方向向量 | 指向平均不包含方向的法线,可以用来改善间接照明的质量 |
clearCoatNormal | float3 | [0..1] | Linear RGB,在切空间中编码方向向量 | 使用凹凸贴图(法线贴图)来扰动透明涂层的细节法线 |
emissive | float4 | rgb=[0..n], a=[0..1] | Linear RGB,alpha 编码曝光权重 | 额外的漫射反照率来模拟发射表面(如霓虹灯等)这个属性在带有 bloom 通道的 HDR 管道中非常有用 |
postLightingColor | float4 | [0..1] | Linear RGB | 额外的颜色,可以与照明计算的结果混合,见 postLightingBlending |
ior | float | [1..n] | 可选,通常由反射率推断 | 折射率,折射物的折射率或作为反射率的替代品 |
transmission | float | [0..1] | 定义了有多少电介质的漫射光通过物体传播,它定义了物体的透明度 | |
absorption | float3 | [0..n] | 折射率物体的吸收系数 | |
microThickness | float | [0..n] | 折射率物体的薄层厚度 | |
thickness | float | [0..n] | 折射物体的固体体积的厚度 |
????????subsurface?model 官方介绍见 →?subsurfacemodel,官方文档只留着标题,无内容介绍。
????????cloth?model 官方介绍见 →?clothmodel,可以配置的参数如下。
Property | Type | Range | Note | Definition |
---|---|---|---|---|
sheenColor | float3 | [0..1] | Linear RGB | 高光色调创建双色高光织物(默认值:sqrt(baseColor)) |
subsurfaceColor | float3 | [0..1] | Linear RGB | 通过材料散射和吸收后的漫射色着色 |
????????unlit?model 官方介绍见 →?unlitmodel,可以配置的参数如下。
Property | Type | Range | Note | Definition |
---|---|---|---|---|
baseColor | float4 | [0..1] | Linear RGB | 表面漫反射色 |
emissive | float4 | rgb=[0..n], a=[0..1] | Linear RGB,alpha 编码曝光权重 | 额外的漫射颜色来模拟发射表面,该属性在带有 bloom pass 的 HDR 管道中非常有用 |
postLightingColor | float4 | [0..1] | Linear RGB | 额外的颜色与基色和发射色混合 |
????????specularGlossiness?官方介绍见 →?specularglossiness,可以配置的参数如下。
Property | Type | Range | Note | Definition |
---|---|---|---|---|
baseColor | float4 | [0..1] | Linear RGB | 表面漫反射色 |
specularColor | float3 | [0..1] | Linear RGB | 高光色调(默认为黑色) |
glossiness | float | [0..1] | Inverse of roughness | 光泽度(默认为 0) |
????????在?material 块中,通过 blending?属性配置混合模式,取值有:opaque、transparent、fade、add、multiply、screen、masked,默认取 opaque,官方介绍见 →?blending。
material {
blending?: opaque
}
????????postLightingColor 属性定义了如何将 postLightingColor 材质属性与光照计算结果混合,官方介绍见 →?postlightingblending。取值主要有:opaque、transparent、add、Screen,默认取值:transparent。
material {
postLightingBlending : add
}
????????transparency 控制透明物体的渲染方式,它仅在混合模式不是 opaque 且 refractionMode 为 none 时有效,这些方法都不能准确地渲染凹形物体,但在实践中它们往往足够好,官方介绍见 →?transparency。取值主要有:default、twoPassesOneSide、twoPassesTwoSides,默认取值:default。
material {
transparency : twoPassesOneSide
}
????????当混合模式设置为?masked?时,maskThreshold 用于控制片元不被丢弃的最小 alpha 值;当混合模式未被设置为 masked?时,maskThreshold 将被忽略,官方介绍见 →?maskThreshold。取值为 0.0 ~ 1.0 之间的浮点数,默认取值:0.4??????。
material {
blending : masked,
maskThreshold : 0.5
}
????????refractionMode 用于控制折射模式,官方介绍见 → refractionMode。取值主要有:none、cubemap、screenspace,默认取值:none。当 refractionMode 设置为非 none 时才激活折射。
????????cubemap 模式只使用 IBL cubemap 作为折射源,这是非常有效的,没有场景对象被折射,只有在 cubemap 中编码的远处环境被折射,该模式对于对象查看器来说是足够的。screenspace 模式采用更先进的屏幕空间折射算法,该算法允许场景中不透明的物体被折射。
????????在 cubemap 模式中,假定折射光线从物体的中心发出,厚度参数仅用于计算吸收,而对折射本身没有影响。在 screenspace 模式中,假定折射光线在离开折射介质时平行于观看方向。
material {
refractionMode : cubemap,
}
????????refractionType 用于设置折射模型,官方介绍见 → refractionType。取值主要有:solid、thin,默认取值:solid。当 refractionMode 设置为非 none 时 refractionType 才会生效。
????????solid 模型用于厚的物体,如水晶球、冰块或雕塑;thin 模型用于薄的物体,如窗户、装饰球或肥皂泡。
????????solid 模型假定所有的折射物体都是与入射点相切且半径厚度的球体。thin 模型假定所有的折射物体都是平面的、薄的、均匀厚度的。
material {
refractionMode : cubemap,
refractionType : thin,
}
????????culling 用于控制需要剔除哪些三角形,官方介绍见 → culling。取值主要有:none、front、back、frontAndBack,默认取值:back。
material {
culling : none
}
????????colorWrite 用于控制开启 / 禁用写入颜色缓冲区,官方介绍见 → colorWrite。取值有:true、false,默认取值:true。
material {
colorWrite : false
}
????????depthWrite 用于控制开启 / 禁用写入深度缓冲区,官方介绍见 → depthWrite。取值有:true、false,不透明物体默认取值:true,透明物体默认取值:false。
material {
depthWrite : false
}
????????depthCulling 用于控制开启 / 禁用深度测试?,官方介绍见 → depthCulling。取值有:true、false,默认取值:true。
????????当深度测试被禁用时,用此材质渲染的物体将始终出现在其他不透明物体的前面。
material {
depthCulling : false
}
????????doubleSided 用于控制开启 / 禁用双面渲染,官方介绍见 → doubleSided。取值有:true、false,默认取值:false。当设置为 true 时,culling 自动设置为 none。
????????如果三角形是面向背面的,则三角形的法线将翻转为面向正面。当显式设置为 false 时,这允许在运行时切换双面性。
material {
doubleSided : true
}
????????alphaToCoverage 用于控制开启 / 禁用 alpha 覆盖,官方介绍见 → alphaToCoverage。取值有:true、false,默认取值:false。
????????当覆盖的 alpha 被启用时,片元的覆盖是从它的 alpha 派生出来的。此属性仅在启用 MSAA 时才可用。注意:将混合模式设置为 masked 会自动启用 alpha 覆盖,如果不希望这样做,可以通过将 alpha 的覆盖设置为 false 来取消此行为。
material {
blending : masked,
alphaToCoverage : false
}
????????reflections 用于控制材质的镜面反射源,官方介绍见 → reflections。取值有:default、screenspace,默认取值:default。
????????当 reflections 设置为 default 时,反射仅来自基于图像的光照(image-based lights,IBL);当此 reflections 设置为 screenspace 时,反射除了来自 IBL 之外,还来自屏幕空间的颜色缓冲区。
material {
reflections : screenspace
}
????????shadowMultiplier 用于控制开启 / 禁用阴影,该属性仅在 unlit 光照模型下才可用,官方介绍见 → shadowMultiplier。取值有:true、false,默认取值:false。
? ? ? ? 当 shadowMultiplier 设置为 true 时,材质计算的最终颜色需要乘以阴影因子(或可见性),它允许创建透明的且接收阴影投射的物体(如:AR 中不可见的地面),它仅接收直射光(directional lights)的阴影。
material {
shadingModel : unlit,
shadowMultiplier : true,
blending : transparent
}
????????transparentShadow 用于控制开启 / 禁用透明阴影,官方介绍见 → transparentShadow。取值有:true、false,默认取值:false。
????????当 shadowMultiplier 设置为 true 时,Filament 使用抖动模式(dithering pattern)模拟透明阴影,它们在方差阴影地图(VSM)和模糊启用时效果最好。阴影的不透明度直接来源于材质的 baseColor 属性的 alpha 通道,透明阴影可以在不透明的物体上启用,使它们与不透明的折射 / 透射物体兼容。
material {
transparentShadow : true,
blending : transparent
}
fragment {
void material(inout MaterialInputs material) {
prepareMaterial(material);
material.baseColor = texture(materialParams_baseColor, getUV0());
}
}
????????clearCoatIorChange 用于控制开启 / 禁用清除折射率变化图层,官方介绍见 → clearCoatIorChange。取值有:true、false,默认取值:true。
????????当 clearCoatIorChange 设置为 true 时,会添加一个清除图层,它考虑到折射率(IoR)的变化来修改底层的镜面颜色,这会使 baseColor 变暗。当此效果被禁用时,baseColor 保持不变。
material {
clearCoatIorChange : false
}
????????multiBounceAmbientOcclusion 用于控制开启 / 禁用多反弹环境遮挡,官方介绍见 → multiBounceAmbientOcclusion。取值有:true、false,默认取值:false。
????????当将环境遮挡(ambient occlusion)应用于基于图像的光照(image-based lighting,IBL)时,多反弹环境遮挡(multi-bounce ambient occlusion)考虑了相互反射。开启此功能可避免遮挡区域过度变暗。它还考虑了表面颜色来生成彩色环境遮挡。
material {
multiBounceAmbientOcclusion : true
}
????????specularAmbientOcclusion 用于控制开启 / 禁用多反弹环境遮挡,官方介绍见 → multiBounceAmbientOcclusion。取值有:none、simple、 bentNormals,默认取值:none。
????????静态环境遮挡贴图和动态环境遮挡贴图(SSAO 等)适用于漫射间接光照。当将此属性设置为非 none 时,一个新的环境遮挡项将从表面粗糙度中衍生出来,并应用于镜面间接光照。这种效果有助于消除不需要的镜面反射。当这个值设置为 simple 时,Filament 使用一种便宜但近似的方法来计算高光环境遮挡项。如果将此值设置为 bentNormals, Filament 将使用更精确但更昂贵的方法。
material {
specularAmbientOcclusion : simple
}
????????specularAntiAliasing 用于控制开启 / 禁用镜面抗锯齿,官方介绍见 → specularAntiAliasing。取值有:true、false,默认取值:false。
????????当一个对象远离相机,开启抗锯齿可用减少镜面锯齿,并保留镜面高光的形状。这种抗锯齿方案对光滑材料(低粗糙度)特别有效,但增加了渲染成本。抗锯齿效果的强度可以使用另外两个属性来控制:specularAntiAliasingVariance 和 specularAntiAliasingThreshold。
material {
specularAntiAliasing : true
}
????????specularAntiAliasingVariance 用于设置应用镜面抗锯齿时使用的过滤器内核的屏幕空间方差,官方介绍见 → specularAntiAliasingVariance。取值类型是 float 型,取值范围是 0 ~ 1,默认取值:0.15。
????????较高的 specularAntiAliasingVariance?值将增加过滤器的效果,但可能增加不需要的区域的粗糙度。
material {
specularAntiAliasingVariance : 0.2
}
????????specularAntiAliasingThreshold 用于设置应用镜面抗锯齿时抑制估计误差的夹持阈值(clamping threshold),官方介绍见 → specularAntiAliasingThreshold。取值类型是 float 型,取值范围是 0 ~ 1,默认取值:0.2。
????????当设置为 0 时,镜面抗锯齿被禁用。
material {
specularAntiAliasingThreshold : 0.1
}
????????线性颜色空间的介绍见 → 【Unity3D】伽马校正,Filament 在线性颜色空间中使用 RGB 颜色,官方介绍见 →?linearcolors。
????????如果颜色数据来自纹理,请确保使用 sRGB 纹理,以从 sRGB 自动进行硬件转换为线性。如果颜色数据作为材质的参数传递,可以通过在每个颜色通道上运行以下算法将其从 sRGB 转换为线性。
float sRGB_to_linear(float color) {
return color <= 0.04045 ? color / 12.92 : pow((color + 0.055) / 1.055, 2.4);
}
????????可以使用以下两个更便宜但不太准确的方法。
// Cheaper
linearColor = pow(color, 2.2);
// Cheapest
linearColor = color * color;
????????如果一种颜色的 RGB 分量都乘以 alpha 通道,那么它使用了预乘 alpha,官方介绍见 →?pre-multipliedalpha。
// Compute pre-multiplied color
color.rgb *= color.a;
????????如果颜色是从纹理中取样的,可以简单地确保纹理数据在上传时进行了预乘。在 Android 上,从 Bitmap 上传的任何纹理默认都会进行预乘。?