feat: 材质系统 v1——4 纹理槽(albedo/normal/specular/emissive)+ 法线贴图

- Material:单贴图 → 4 槽(binding=Slot 枚举),MakeCacheKey 组合键
  保持材质去重(同贴图组合共享描述符集)
- Model:读取 aiTextureType DIFFUSE/NORMALS/SPECULAR/EMISSIVE 四类路径;
  aiProcess_CalcTangentSpace 生成切线(Vertex +tangent 属性,location 3);
  每槽默认纹理兜底(白/平法线(128,128,255)/黑/黑,1x1)
- 纹理 sRGB 语义按槽位:颜色图 SRGB 变体,数据图(Normal/Specular)线性
  UNORM——Texture 路径加载加 srgb 参数;修复 InitFromPixels 图文 format
  不一致的 validation 警告
- UBO 尾部追加 cameraPos(Blinn-Phong V 向量)
- simple_shader.frag:TBN 法线贴图(无切线/平法线退化回几何法线)+
  Blinn-Phong 高光(specular 图强度系数)+ 自发光直加
- SunTemple 实测:148 张 DDS 全槽位加载(96 张线性变体)、1056 mesh、
  validation 零警告
This commit is contained in:
2026-09-23 14:50:10 +08:00
parent 8253c31ad5
commit c54f60c5ea
12 changed files with 229 additions and 92 deletions
+37 -4
View File
@@ -12,6 +12,7 @@ layout(location = 1) in vec3 fragNormal;
layout(location = 2) in vec3 fragWorldPos;
layout(location = 3) in float fragViewDepth;
layout(location = 4) in vec2 fragVelocity;
layout(location = 5) in vec3 fragTangent;
layout(location = 0) out vec4 outColor;
layout(location = 1) out vec2 outVelocity; // TAA velocity 附件
@@ -26,9 +27,15 @@ layout(set = 0, binding = 0) uniform UniformBufferObject {
mat4 projNoJitter;
mat4 prevView;
mat4 prevProj;
vec4 cameraPos;
} ubo;
layout(set = 1, binding = 0) uniform sampler2D texSampler;
// 材质纹理槽(binding = Material::Slot)
layout(set = 1, binding = 0) uniform sampler2D albedoMap;
layout(set = 1, binding = 1) uniform sampler2D normalMap;
layout(set = 1, binding = 2) uniform sampler2D specularMap;
layout(set = 1, binding = 3) uniform sampler2D emissiveMap;
layout(set = 2, binding = 0) uniform sampler2D shadowMap0; // CSM 级联 0(最近)
layout(set = 2, binding = 1) uniform sampler2D shadowMap1; // 级联 1
layout(set = 2, binding = 2) uniform sampler2D shadowMap2; // 级联 2(最远)
@@ -60,19 +67,45 @@ float ShadowFactor(vec3 N, float viewDepth) {
return (proj.z - bias > closest) ? 0.3 : 1.0;
}
// 法线贴图 → 世界空间法线(TBN 构建)。
// 无切线数据(零向量)或平法线贴图时退回几何法线。
vec3 MappedNormal(vec3 geomN, vec3 T, vec2 uv) {
if (dot(T, T) < 1e-4) return geomN;
vec3 N = geomN;
vec3 nT = normalize(T - N * dot(N, T)); // 正交化
vec3 B = cross(N, nT);
vec3 sampled = texture(normalMap, uv).xyz * 2.0 - 1.0;
if (dot(sampled, sampled) < 1e-4) return geomN;
return normalize(nT * sampled.x + B * sampled.y + N * sampled.z);
}
void main() {
vec3 albedo = texture(texSampler, fragUV).rgb;
vec3 N = normalize(fragNormal);
vec3 albedo = texture(albedoMap, fragUV).rgb;
float specIntensity = texture(specularMap, fragUV).r;
vec3 emissive = texture(emissiveMap, fragUV).rgb;
vec3 N = MappedNormal(normalize(fragNormal), fragTangent, fragUV);
vec3 V = normalize(ubo.cameraPos.xyz - fragWorldPos);
vec3 result = ubo.ambientColor.rgb * albedo;
for (int i = 0; i < MAX_LIGHTS; ++i) {
vec3 L = normalize(-ubo.lights[i].direction.xyz);
float diff = max(dot(N, L), 0.0);
// Blinn-Phong 高光(半程向量;specular 图做强度系数)
vec3 H = normalize(L + V);
float spec = pow(max(dot(N, H), 0.0), 32.0) * specIntensity;
float shade = (i == 0) ? ShadowFactor(N, fragViewDepth) : 1.0;
result += ubo.lights[i].color.rgb * ubo.lights[i].color.a * diff * albedo * shade;
result += ubo.lights[i].color.rgb * ubo.lights[i].color.a *
(albedo * diff + vec3(spec)) * shade;
}
// 自发光不经光照/阴影直接叠加
result += emissive;
outColor = vec4(result, 1.0);
outVelocity = fragVelocity;
}
+7
View File
@@ -10,12 +10,14 @@ struct Light {
layout(location = 0) in vec3 inPosition;
layout(location = 1) in vec3 inNormal;
layout(location = 2) in vec2 inUV;
layout(location = 3) in vec3 inTangent;
layout(location = 0) out vec2 fragUV;
layout(location = 1) out vec3 fragNormal;
layout(location = 2) out vec3 fragWorldPos;
layout(location = 3) out float fragViewDepth; // CSM:视图深度(级联选择)
layout(location = 4) out vec2 fragVelocity; // TAA:clip 空间运动向量
layout(location = 5) out vec3 fragTangent; // 世界空间切线(法线贴图)
// GPUDriven:模型矩阵从实例缓冲按实例索引取。
// 同一 mesh 的 N 个实体 = 1 次 drawIndexed(instanceCount=N),
@@ -39,6 +41,7 @@ layout(set = 0, binding = 0) uniform UniformBufferObject {
mat4 projNoJitter;
mat4 prevView;
mat4 prevProj;
vec4 cameraPos;
} ubo;
void main() {
@@ -60,6 +63,10 @@ void main() {
mat3 normalMatrix = mat3(transpose(inverse(model)));
fragNormal = normalize(normalMatrix * inNormal);
// 切线随模型变换(法线贴图 TBN;无切线数据保持零,frag 侧退回几何法线)
fragTangent = normalize(mat3(model) * inTangent);
// 零向量 normalize 产生 NaN,这里直接透传零,frag 判长度
fragTangent = dot(inTangent, inTangent) > 0.0 ? fragTangent : vec3(0.0);
fragUV = inUV;
}