第5.1章:Surface Shader详解 Surface Shader是Cocos Creator提供的高级着色器功能,它简化了复杂光照模型的实现,让开发者能够专注于表面材质的定义,而不需要处理复杂的光照计算。本章将详细介绍Surface Shader的原理、使用方法和高级技巧。
🎯 学习目标 通过本章学习,你将掌握:
Surface Shader的核心概念与架构 如何编写和使用Surface Shader 光照模型的自定义和扩展 Surface Shader的高级特性和优化 实际项目中的应用案例 调试和性能调优技巧 💡 Surface Shader架构 核心概念 Surface Shader将着色器分为两个主要部分:
Surface Function : 定义表面材质属性Lighting Model : 光照计算1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 graph TD A[Surface Shader] --> B[Surface Function] A --> C[Lighting Model] B --> D[Albedo] B --> E[Normal] B --> F[Metallic] B --> G[Roughness] B --> H[Emission] B --> I[Alpha] C --> J[Lambert] C --> K[BlinnPhong] C --> L[Standard PBR] C --> M[Custom Model] D --> N[Final Color] E --> N F --> N G --> N H --> N I --> N J --> N K --> N L --> N M --> N
与传统着色器的对比 特性 传统着色器 Surface Shader 复杂度 需要完整实现 只需定义表面属性 光照计算 手动实现 自动处理 多光源支持 复杂实现 内置支持 阴影处理 需要额外代码 自动处理 维护性 难以维护 易于维护 性能控制 完全控制 优化良好
🔧 基础Surface Shader 简单的Surface Shader 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 CCEffect %{ techniques: - name: opaque passes: - vert: surface-vs:vert frag: surface-fs:frag properties: mainColor: { value: [1 , 1 , 1 , 1 ], editor: { type: color } } mainTexture: { value: white } migration: properties: mainColor: { formerlySerializedAs: tintColor } }% CCProgram surface-vs %{ precision highp float ; #include <builtin/uniforms/cc-global> #include <builtin/uniforms/cc-local> #include <common/common-vs> #include <builtin/uniforms/cc-shadow> in vec3 a_position; in vec3 a_normal; in vec2 a_texCoord; out vec3 v_position; out vec3 v_normal; out vec2 v_uv; vec4 vert () { StandardVertInput In; In.position = a_position; In.normal = a_normal; In.texCoord = a_texCoord; StandardVertOutput Out = CCVertInput(In); v_position = Out.worldPos; v_normal = Out.worldNormal; v_uv = Out.texCoord; return Out.clipPos; } }% CCProgram surface-fs %{ precision mediump float ; #include <builtin/uniforms/cc-global> #include <common/common-fs> #include <common/lighting/functions> in vec3 v_position; in vec3 v_normal; in vec2 v_uv; uniform sampler2D mainTexture; uniform Constants { vec4 mainColor; }; void surf (out StandardSurface s) { vec4 texColor = texture (mainTexture, v_uv); s.albedo = texColor.rgb * mainColor.rgb; s.alpha = texColor.a * mainColor.a; s.metallic = 0.0 ; s.roughness = 0.5 ; s.normal = v_normal; s.emissive = vec3 (0.0 ); s.occlusion = 1.0 ; } vec4 frag () { StandardSurface s; surf(s); vec4 color = CCStandardSurfaceShading(s); return CCFragOutput(color); } }%
Surface数据结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 struct StandardSurface { vec3 albedo; vec3 normal; vec3 emissive; float metallic; float roughness; float occlusion; float alpha; vec3 position; }; struct AdvancedSurface { vec3 specular; float clearCoat; float clearCoatRoughness; vec3 subsurface; float transmission; float thickness; float anisotropy; };
🎨 自定义光照模型 卡通风格光照 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 vec3 ToonLighting(StandardSurface s, vec3 lightDir, vec3 lightColor, vec3 viewDir) { float NdotL = dot (s.normal, lightDir); float toonFactor; if (NdotL > 0.8 ) { toonFactor = 1.0 ; } else if (NdotL > 0.4 ) { toonFactor = 0.6 ; } else if (NdotL > 0.0 ) { toonFactor = 0.3 ; } else { toonFactor = 0.1 ; } vec3 diffuse = s.albedo * lightColor * toonFactor; vec3 halfVector = normalize (lightDir + viewDir); float specular = pow (max (0.0 , dot (s.normal, halfVector)), 32.0 ); specular = step (0.5 , specular) * 0.8 ; return diffuse + vec3 (specular); } vec4 frag () { StandardSurface s; surf(s); vec3 lightDir = normalize (cc_mainLitDir.xyz); vec3 lightColor = cc_mainLitColor.rgb * cc_mainLitColor.w; vec3 viewDir = normalize (cc_cameraPos.xyz - v_position); vec3 finalColor = ToonLighting(s, lightDir, lightColor, viewDir); finalColor += s.albedo * cc_ambientSky.rgb * s.occlusion; finalColor += s.emissive; return vec4 (finalColor, s.alpha); }
多种光照模型切换 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #define LIGHTING_LAMBERT 0 #define LIGHTING_BLINN_PHONG 1 #define LIGHTING_PBR 2 #define LIGHTING_TOON 3 #define LIGHTING_CUSTOM 4 uniform Constants { vec4 mainColor; int lightingModel; float toonSteps; float rimPower; }; vec3 CalculateLighting(StandardSurface s, vec3 lightDir, vec3 lightColor, vec3 viewDir) { #if LIGHTING_MODEL == LIGHTING_LAMBERT return LambertLighting(s, lightDir, lightColor); #elif LIGHTING_MODEL == LIGHTING_BLINN_PHONG return BlinnPhongLighting(s, lightDir, lightColor, viewDir); #elif LIGHTING_MODEL == LIGHTING_PBR return PBRLighting(s, lightDir, lightColor, viewDir); #elif LIGHTING_MODEL == LIGHTING_TOON return ToonLighting(s, lightDir, lightColor, viewDir); #elif LIGHTING_MODEL == LIGHTING_CUSTOM return CustomLighting(s, lightDir, lightColor, viewDir); #else return LambertLighting(s, lightDir, lightColor); #endif }
🚀 高级特性实现 多层纹理混合 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 uniform sampler2D albedoMap;uniform sampler2D detailMap;uniform sampler2D maskMap;uniform sampler2D normalMap;uniform Constants { vec4 mainColor; vec4 detailColor; float detailScale; float normalStrength; float mixFactor; }; void surf (out StandardSurface s) { vec2 detailUV = v_uv * detailScale; vec4 baseColor = texture (albedoMap, v_uv); vec4 detailSample = texture (detailMap, detailUV); vec4 mask = texture (maskMap, v_uv); vec3 blendedAlbedo = mix ( baseColor.rgb * mainColor.rgb, detailSample.rgb * detailColor.rgb, mask.r * mixFactor ); vec3 normalSample = texture (normalMap, v_uv).rgb * 2.0 - 1.0 ; normalSample.xy *= normalStrength; vec3 normal = normalize (v_normal); vec3 tangent = normalize (cross (normal, vec3 (0.0 , 1.0 , 0.0 ))); vec3 bitangent = cross (normal, tangent); mat3 TBN = mat3 (tangent, bitangent, normal); s.albedo = blendedAlbedo; s.normal = normalize (TBN * normalSample); s.metallic = mask.g; s.roughness = mask.b; s.occlusion = mask.a; s.alpha = baseColor.a * mainColor.a; s.emissive = vec3 (0.0 ); }
动态效果Surface Shader 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 uniform Constants { vec4 mainColor; float waveSpeed; float waveAmplitude; float noiseScale; float glowIntensity; }; float noise(vec2 p) { return fract (sin (dot (p, vec2 (127.1 , 311.7 ))) * 43758.5453123 ); } float smoothNoise(vec2 p) { vec2 i = floor (p); vec2 f = fract (p); float a = noise(i); float b = noise(i + vec2 (1.0 , 0.0 )); float c = noise(i + vec2 (0.0 , 1.0 )); float d = noise(i + vec2 (1.0 , 1.0 )); vec2 u = f * f * (3.0 - 2.0 * f); return mix (a, b, u.x) + (c - a) * u.y * (1.0 - u.x) + (d - b) * u.x * u.y; } void surf (out StandardSurface s) { vec2 animatedUV = v_uv; float time = cc_time.x * waveSpeed; animatedUV.x += sin (time + v_uv.y * 10.0 ) * waveAmplitude; animatedUV.y += cos (time + v_uv.x * 8.0 ) * waveAmplitude * 0.5 ; vec4 texColor = texture (mainTexture, animatedUV); float noiseValue = smoothNoise(v_uv * noiseScale + time); s.albedo = texColor.rgb * mainColor.rgb; s.metallic = 0.5 + 0.3 * sin (time + noiseValue * 6.28 ); s.roughness = 0.2 + 0.3 * noiseValue; float glowPulse = 0.5 + 0.5 * sin (time * 3.0 ); s.emissive = texColor.rgb * glowIntensity * glowPulse * noiseValue; s.normal = v_normal; s.alpha = texColor.a * mainColor.a; s.occlusion = 1.0 ; }
🎮 实际应用案例 水面Surface Shader 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 uniform sampler2D waterNormal;uniform sampler2D foamTexture;uniform samplerCube skybox;uniform WaterProperties { vec4 waterColor; vec4 deepWaterColor; float waveSpeed; float waveScale; float normalStrength; float foamThreshold; float reflectionStrength; float fresnelPower; }; vec3 calculateWaterNormal(vec2 uv, float time) { vec2 uv1 = uv * waveScale + vec2 (time * waveSpeed, 0.0 ); vec2 uv2 = uv * waveScale * 0.7 + vec2 (0.0 , time * waveSpeed * 0.8 ); vec3 normal1 = texture (waterNormal, uv1).rgb * 2.0 - 1.0 ; vec3 normal2 = texture (waterNormal, uv2).rgb * 2.0 - 1.0 ; return normalize (normal1 + normal2); } void surf (out StandardSurface s) { float time = cc_time.x; vec3 waterNorm = calculateWaterNormal(v_uv, time); waterNorm.xy *= normalStrength; vec3 viewDir = normalize (cc_cameraPos.xyz - v_position); float fresnel = pow (1.0 - max (0.0 , dot (viewDir, waterNorm)), fresnelPower); vec3 reflectDir = reflect (-viewDir, waterNorm); vec3 reflection = textureLod (skybox, reflectDir, 0.0 ).rgb; float depth = smoothNoise(v_uv * 5.0 + time * 0.1 ); float foam = smoothstep (foamThreshold, foamThreshold + 0.1 , depth); vec4 foamColor = texture (foamTexture, v_uv * 8.0 + time * 0.2 ); vec3 baseColor = mix (deepWaterColor.rgb, waterColor.rgb, depth); baseColor = mix (baseColor, foamColor.rgb, foam * foamColor.a); baseColor = mix (baseColor, reflection, fresnel * reflectionStrength); s.albedo = baseColor; s.normal = normalize (v_normal + waterNorm); s.metallic = 0.0 ; s.roughness = 0.1 + foam * 0.4 ; s.alpha = 0.8 + fresnel * 0.2 ; s.emissive = vec3 (0.0 ); s.occlusion = 1.0 ; }
皮肤Surface Shader 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 uniform sampler2D skinAlbedo;uniform sampler2D skinNormal;uniform sampler2D skinSSS;uniform SkinProperties { vec4 skinColor; vec4 sssColor; float sssStrength; float sssRadius; float normalStrength; float roughness; float specularStrength; }; vec3 calculateSSS(vec3 normal, vec3 lightDir, vec3 viewDir, vec3 sssMap) { float backLight = max (0.0 , dot (-normal, lightDir)); float viewScatter = pow (max (0.0 , dot (-viewDir, lightDir)), sssRadius); float thickness = 1.0 - sssMap.r; vec3 sss = sssColor.rgb * (backLight + viewScatter) * thickness * sssStrength; return sss; } void surf (out StandardSurface s) { vec4 albedoSample = texture (skinAlbedo, v_uv); vec4 sssSample = texture (skinSSS, v_uv); vec3 normalSample = texture (skinNormal, v_uv).rgb * 2.0 - 1.0 ; normalSample.xy *= normalStrength; vec3 normal = normalize (v_normal); s.normal = normalize (normal + normalSample); vec3 lightDir = normalize (cc_mainLitDir.xyz); vec3 viewDir = normalize (cc_cameraPos.xyz - v_position); vec3 sss = calculateSSS(s.normal, lightDir, viewDir, sssSample.rgb); s.albedo = albedoSample.rgb * skinColor.rgb; s.metallic = 0.0 ; s.roughness = roughness; s.alpha = albedoSample.a * skinColor.a; s.emissive = sss; s.occlusion = sssSample.g; }
🛠️ Surface Shader管理系统 动态Surface Shader切换 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 @ccclass ('SurfaceShaderManager' )export class SurfaceShaderManager extends Component { @property ([EffectAsset ]) surfaceShaders : EffectAsset [] = []; @property (MeshRenderer ) targetRenderer : MeshRenderer = null !; private currentShaderIndex : number = 0 ; private materialInstances : Map <string , MaterialInstance > = new Map (); start ( ) { this .preloadSurfaceShaders (); } private preloadSurfaceShaders ( ) { this .surfaceShaders .forEach ((shader, index ) => { const material = new Material (); material.initialize ({ effectAsset : shader, technique : 0 }); const instance = new MaterialInstance ({ parent : material }); this .materialInstances .set (shader.name , instance); }); } switchToShader (shaderName: string ) { const instance = this .materialInstances .get (shaderName); if (instance) { this .targetRenderer .setMaterialInstance (instance, 0 ); console .log (`Switched to Surface Shader: ${shaderName} ` ); } } adjustShaderParameters (params: Record<string , any > ) { const currentInstance = this .targetRenderer .getMaterialInstance (0 ); if (currentInstance) { Object .entries (params).forEach (([key, value] ) => { currentInstance.setProperty (key, value); }); } } applyPreset (presetName: string ) { const presets = { metal : { metallic : 1.0 , roughness : 0.1 , mainColor : new Color (200 , 200 , 200 , 255 ) }, wood : { metallic : 0.0 , roughness : 0.8 , mainColor : new Color (139 , 107 , 66 , 255 ) }, glass : { metallic : 0.0 , roughness : 0.0 , mainColor : new Color (255 , 255 , 255 , 100 ) } }; const preset = presets[presetName]; if (preset) { this .adjustShaderParameters (preset); } } }
Surface Shader性能分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 @ccclass ('SurfaceShaderProfiler' )export class SurfaceShaderProfiler extends Component { @property (Label ) performanceLabel : Label = null !; @property ([Material ]) testMaterials : Material [] = []; private frameCount : number = 0 ; private totalTime : number = 0 ; private currentMaterialIndex : number = 0 ; start ( ) { this .startProfiling (); } private startProfiling ( ) { this .schedule (this .switchMaterial , 2.0 ); this .schedule (this .updatePerformanceInfo , 0.1 ); } private switchMaterial ( ) { if (this .testMaterials .length === 0 ) return ; const material = this .testMaterials [this .currentMaterialIndex ]; const renderer = this .getComponent (MeshRenderer ); if (renderer && material) { const startTime = performance.now (); renderer.setMaterial (material, 0 ); const endTime = performance.now (); console .log (`Material switch time: ${endTime - startTime} ms` ); this .currentMaterialIndex = (this .currentMaterialIndex + 1 ) % this .testMaterials .length ; } } private updatePerformanceInfo ( ) { this .frameCount ++; this .totalTime += 0.1 ; if (this .totalTime >= 1.0 ) { const fps = this .frameCount / this .totalTime ; const currentMaterial = this .testMaterials [this .currentMaterialIndex ]; this .performanceLabel .string = `FPS: ${fps.toFixed(1 )} \n` + `Material: ${currentMaterial?.name || 'Unknown' } \n` + `Shader Complexity: ${this .getShaderComplexity(currentMaterial)} ` ; this .frameCount = 0 ; this .totalTime = 0 ; } } private getShaderComplexity (material : Material ): string { if (!material) return 'Unknown' ; const defines = material.passes [0 ]?.defines || {}; const defineCount = Object .keys (defines).length ; if (defineCount < 5 ) return 'Low' ; if (defineCount < 10 ) return 'Medium' ; return 'High' ; } }
🐛 调试和优化 Surface Shader调试功能 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 #define DEBUG_NONE 0 #define DEBUG_ALBEDO 1 #define DEBUG_NORMAL 2 #define DEBUG_METALLIC 3 #define DEBUG_ROUGHNESS 4 #define DEBUG_AO 5 #define DEBUG_EMISSION 6 uniform Constants { vec4 mainColor; int debugMode; }; void surf (out StandardSurface s) { #if DEBUG_MODE == DEBUG_ALBEDO s.albedo = s.albedo; s.emissive = vec3 (0.0 ); #elif DEBUG_MODE == DEBUG_NORMAL s.albedo = s.normal * 0.5 + 0.5 ; s.emissive = vec3 (0.0 ); #elif DEBUG_MODE == DEBUG_METALLIC s.albedo = vec3 (s.metallic); s.emissive = vec3 (0.0 ); #elif DEBUG_MODE == DEBUG_ROUGHNESS s.albedo = vec3 (s.roughness); s.emissive = vec3 (0.0 ); #elif DEBUG_MODE == DEBUG_AO s.albedo = vec3 (s.occlusion); s.emissive = vec3 (0.0 ); #elif DEBUG_MODE == DEBUG_EMISSION s.albedo = vec3 (0.0 ); s.emissive = s.emissive; #endif }
性能优化技巧 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 uniform Constants { vec4 mainColor; float roughness; float metallic; float roughnessSquared; float oneMinusMetallic; }; #if USE_NORMAL_MAP vec3 normalSample = texture (normalMap, v_uv).rgb * 2.0 - 1.0 ; s.normal = normalize (TBN * normalSample); #else s.normal = v_normal; #endif #if QUALITY_LEVEL >= 2 s.normal = calculateDetailNormal(v_uv); s.emissive = calculateComplexEmission(v_uv); #elif QUALITY_LEVEL >= 1 s.normal = calculateSimpleNormal(v_uv); s.emissive = vec3 (0.0 ); #else s.normal = v_normal; s.emissive = vec3 (0.0 ); #endif
🚀 下一步学习 掌握Surface Shader学习后,建议继续学习:
第6.1章:光照模型详解 - 高级光照模型第11.3章:着色器优化技术详解 - 着色器优化技巧第3.3章:着色器调试与开发工具 - 着色器调试工具📖 总结 通过本章学习,我们深入应用掌握了:
✅ Surface Shader的核心概念与架构 ✅ 如何编写和使用Surface Shader ✅ 自定义光照模型的实现方法 ✅ 高级特性和动态效果的实现 ✅ 实际项目中的应用案例 ✅ 调试和性能优化技巧 Surface Shader是一个强大的工具,让着色器开发变得更加简单和直观。通过合理使用Surface Shader,我们可以实现各种各样的材质效果,同时保持代码的简洁性和可维护性。
重要提醒 :Surface Shader虽然降低了开发难度,但仍需要注意性能优化。在移动端开发时要根据设备性能合理选择复杂度和渲染质量。