第4.2章:无光照着色器教程

无光照着色器(Unlit Shader)是最基础的着色器类型,不计算任何光照效果,主要用于显示基础颜色和纹理。本章将详细讲解builtin-unlit着色器的原理和使用方法,以及如何创建自定义的无光照着色器。

🎯 学习目标

通过本章学习,你将掌握:

  • builtin-unlit着色器的结构和原理
  • 基础颜色和纹理显示的实现方法
  • 透明度和混合模式的使�?- 无光照着色器的性能优势
  • 无光照着色器的适用场景和最佳实�?

📖 builtin-unlit着色器解析

着色器基础结构

Cocos Creator的builtin-unlit着色器是所有无光照渲染的基础模板�?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# builtin-unlit.effect的基础结构
CCEffect %{
techniques:
- name: opaque
passes:
- vert: builtin/internal/sprite-vs:vert
frag: builtin/internal/sprite-fs:frag
properties:
mainTexture: { value: grey }
mainColor: { value: [1, 1, 1, 1], editor: { type: color } }
colorScaleAndCutoff: { value: [1, 1, 1, 0.5] }
embeddedMacros:
CC_USE_EMBEDDED_ALPHA: false
}%

顶点着色器分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// builtin-unlit顶点着色器核心代码
CCProgram sprite-vs %{
precision highp float;
#include <builtin/uniforms/cc-global>
#include <builtin/uniforms/cc-local>

in vec3 a_position;
in vec2 a_texCoord;
in vec4 a_color;

out vec2 v_uv;
out vec4 v_color;

vec4 vert () {
vec4 pos = vec4(a_position, 1);

// 变换到裁剪空�? pos = cc_matViewProj * cc_matWorld * pos;

// 传递UV坐标和颜�? v_uv = a_texCoord;
v_color = a_color;

return pos;
}
}%

片元着色器分析

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
// builtin-unlit片元着色器核心代码
CCProgram sprite-fs %{
precision mediump float;
#include <builtin/uniforms/cc-global>

in vec2 v_uv;
in vec4 v_color;

uniform sampler2D mainTexture;
uniform Constant {
vec4 mainColor;
vec4 colorScaleAndCutoff;
};

vec4 frag () {
// 纹理采样
vec4 texColor = texture(mainTexture, v_uv);

// 颜色混合
vec4 color = texColor * mainColor * v_color;

// Alpha裁剪
if (color.a < colorScaleAndCutoff.w) {
discard;
}

return color;
}
}%

🎨 基础颜色和纹理显�?

纯色显示

创建一个只显示颜色不使用纹理的无光照着色器�?

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
CCEffect %{
techniques:
- name: solid-color
passes:
- vert: vs:vert
frag: fs:frag
properties:
mainColor: { value: [1, 0, 0, 1], editor: { type: color } }
}%

CCProgram vs %{
precision highp float;
#include <builtin/uniforms/cc-global>
#include <builtin/uniforms/cc-local>

in vec3 a_position;

vec4 vert () {
vec4 pos = vec4(a_position, 1);
return cc_matViewProj * cc_matWorld * pos;
}
}%

CCProgram fs %{
precision mediump float;

uniform Constant {
vec4 mainColor;
};

vec4 frag () {
return mainColor;
}
}%

纹理采样和UV操作

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
// 高级纹理采样片元着色器
CCProgram texture-fs %{
precision mediump float;

in vec2 v_uv;

uniform sampler2D mainTexture;
uniform TextureParams {
vec4 mainColor;
vec2 tilingOffset; // xy: tiling, zw: offset
float rotation; // 纹理旋转角度
};

// UV变换函数
vec2 transformUV(vec2 uv) {
// 应用平移和缩�? uv = uv * tilingOffset.xy + tilingOffset.zw;

// 应用旋转
float cosA = cos(rotation);
float sinA = sin(rotation);
mat2 rotMat = mat2(cosA, -sinA, sinA, cosA);
uv = (uv - 0.5) * rotMat + 0.5;

return uv;
}

vec4 frag () {
vec2 uv = transformUV(v_uv);
vec4 texColor = texture(mainTexture, uv);
return texColor * mainColor;
}
}%

多纹理混�?

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
// 双纹理混合着色器
CCProgram dual-texture-fs %{
precision mediump float;

in vec2 v_uv;

uniform sampler2D texture1;
uniform sampler2D texture2;
uniform BlendParams {
vec4 color1;
vec4 color2;
float blendFactor;
int blendMode; // 0: multiply, 1: add, 2: overlay
};

vec4 frag () {
vec4 tex1 = texture(texture1, v_uv) * color1;
vec4 tex2 = texture(texture2, v_uv) * color2;

vec4 result;
if (blendMode == 0) {
// 乘法混合
result = mix(tex1, tex1 * tex2, blendFactor);
} else if (blendMode == 1) {
// 加法混合
result = mix(tex1, tex1 + tex2, blendFactor);
} else {
// 覆盖混合
result = mix(tex1, tex2, blendFactor);
}

return result;
}
}%

🌟 透明度和混合模式

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
# 透明渲染Pass配置
CCEffect %{
techniques:
- name: transparent
passes:
- vert: vs:vert
frag: fs:frag
# 透明混合设置
blendState:
targets:
- blend: true
blendSrc: src_alpha
blendDst: one_minus_src_alpha
blendDstAlpha: one_minus_src_alpha
rasterizerState:
cullMode: none
depthStencilState:
depthTest: true
depthWrite: false
properties:
mainTexture: { value: white }
mainColor: { value: [1, 1, 1, 0.5], editor: { type: color } }
alphaThreshold: { value: 0.1, editor: { range: [0, 1] } }
}%

动态透明度效�?

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
// 时间驱动的透明度动�?CCProgram animated-alpha-fs %{
precision mediump float;
#include <builtin/uniforms/cc-global>

in vec2 v_uv;

uniform sampler2D mainTexture;
uniform AnimationParams {
vec4 mainColor;
float fadeSpeed;
float fadeMin;
float fadeMax;
};

vec4 frag () {
vec4 texColor = texture(mainTexture, v_uv);

// 基于时间的透明度动�? float timeAlpha = sin(cc_time.x * fadeSpeed) * 0.5 + 0.5;
float alpha = mix(fadeMin, fadeMax, timeAlpha);

vec4 color = texColor * mainColor;
color.a *= alpha;

return color;
}
}%

Alpha Test vs Alpha Blend

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
// Alpha Test实现(硬边缘透明�?CCProgram alpha-test-fs %{
precision mediump float;

in vec2 v_uv;

uniform sampler2D mainTexture;
uniform TestParams {
vec4 mainColor;
float alphaThreshold;
};

vec4 frag () {
vec4 color = texture(mainTexture, v_uv) * mainColor;

// Alpha Test - 硬边缘透明
if (color.a < alphaThreshold) {
discard;
}

return color;
}
}%

// Alpha Blend实现(渐变透明�?CCProgram alpha-blend-fs %{
precision mediump float;

in vec2 v_uv;

uniform sampler2D mainTexture;
uniform BlendParams {
vec4 mainColor;
float opacity;
};

vec4 frag () {
vec4 color = texture(mainTexture, v_uv) * mainColor;

// Alpha Blend - 渐变透明
color.a *= opacity;

return color;
}
}%

�?无光照着色器的性能优势

渲染性能对比

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
// 性能测试脚本
@ccclass('UnlitPerformanceTest')
export class UnlitPerformanceTest extends Component {
@property(MeshRenderer)
meshRenderer: MeshRenderer = null!;

@property([Material])
testMaterials: Material[] = [];

private currentMaterial: number = 0;
private frameCount: number = 0;
private fpsSum: number = 0;

start() {
this.schedule(this.switchMaterial, 3.0);
this.schedule(this.logPerformance, 1.0);
}

update() {
this.frameCount++;
this.fpsSum += 1.0 / director.getDeltaTime();
}

private switchMaterial() {
if (this.testMaterials.length > 0) {
this.meshRenderer.setMaterial(this.testMaterials[this.currentMaterial], 0);
console.log(`Switched to material: ${this.currentMaterial}`);

this.currentMaterial = (this.currentMaterial + 1) % this.testMaterials.length;
this.frameCount = 0;
this.fpsSum = 0;
}
}

private logPerformance() {
if (this.frameCount > 0) {
const avgFPS = this.fpsSum / this.frameCount;
console.log(`Average FPS: ${avgFPS.toFixed(2)}`);
}
}
}

移动端优�?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 移动端优化的无光照着色器
CCProgram mobile-optimized-fs %{
precision mediump float; // 使用中等精度

in vec2 v_uv;

uniform sampler2D mainTexture;
uniform Constants {
vec4 mainColor;
};

vec4 frag () {
// 简化的纹理采样,避免复杂计�? vec4 texColor = texture(mainTexture, v_uv);

// 直接相乘,避免分�? return texColor * mainColor;
}
}%

🎯 适用场景和最佳实�?

UI元素渲染

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# UI专用无光照着色器
CCEffect %{
techniques:
- name: ui-unlit
passes:
- vert: ui-vs:vert
frag: ui-fs:frag
rasterizerState:
cullMode: none
blendState:
targets:
- blend: true
blendSrc: src_alpha
blendDst: one_minus_src_alpha
depthStencilState:
depthTest: false
depthWrite: false
properties:
mainTexture: { value: white }
mainColor: { value: [1, 1, 1, 1], editor: { type: color } }
}%

特效和粒子系�?

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
// 特效专用无光照着色器
CCProgram effect-fs %{
precision mediump float;
#include <builtin/uniforms/cc-global>

in vec2 v_uv;
in vec4 v_color;

uniform sampler2D mainTexture;
uniform EffectParams {
vec4 tintColor;
float intensity;
float time;
};

vec4 frag () {
vec4 texColor = texture(mainTexture, v_uv);

// 特效颜色增强
vec4 color = texColor * v_color * tintColor;
color.rgb *= intensity;

// 基于时间的闪烁效�? float flicker = sin(time * 10.0) * 0.1 + 0.9;
color.rgb *= flicker;

return color;
}
}%

天空盒渲�?

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
// 天空盒无光照着色器
CCProgram skybox-fs %{
precision mediump float;

in vec3 v_worldPos;

uniform samplerCube skyboxTexture;
uniform SkyboxParams {
vec4 tintColor;
float exposure;
float rotation;
};

vec4 frag () {
// 使用世界坐标作为采样方向
vec3 dir = normalize(v_worldPos);

// 应用旋转
float cosA = cos(rotation);
float sinA = sin(rotation);
mat3 rotMat = mat3(
cosA, 0, sinA,
0, 1, 0,
-sinA, 0, cosA
);
dir = rotMat * dir;

// 立方体贴图采�? vec4 skyColor = textureCube(skyboxTexture, dir);

// 应用色调和曝�? skyColor.rgb *= tintColor.rgb * exposure;

return skyColor;
}
}%

🛠�?实践项目

项目1:渐变背景着色器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 渐变背景无光照着色器
CCProgram gradient-fs %{
precision mediump float;

in vec2 v_uv;

uniform GradientParams {
vec4 topColor;
vec4 bottomColor;
float gradientPower;
float offset;
};

vec4 frag () {
// 计算渐变系数
float gradientFactor = pow(v_uv.y + offset, gradientPower);
gradientFactor = clamp(gradientFactor, 0.0, 1.0);

// 颜色插�? vec4 color = mix(bottomColor, topColor, gradientFactor);

return color;
}
}%

项目2:滚动纹理着色器

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
// 滚动纹理无光照着色器
CCProgram scrolling-fs %{
precision mediump float;
#include <builtin/uniforms/cc-global>

in vec2 v_uv;

uniform sampler2D mainTexture;
uniform ScrollParams {
vec4 mainColor;
vec2 scrollSpeed;
vec2 tiling;
};

vec4 frag () {
// 计算滚动偏移
vec2 offset = cc_time.x * scrollSpeed;

// 应用平铺和滚�? vec2 scrollUV = (v_uv * tiling) + offset;

// 纹理采样
vec4 texColor = texture(mainTexture, scrollUV);

return texColor * mainColor;
}
}%

项目3:多层视差背�?

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
// 多层视差背景着色器
CCProgram parallax-fs %{
precision mediump float;
#include <builtin/uniforms/cc-global>

in vec2 v_uv;

uniform sampler2D layer1;
uniform sampler2D layer2;
uniform sampler2D layer3;
uniform ParallaxParams {
vec4 layer1Color;
vec4 layer2Color;
vec4 layer3Color;
vec3 scrollSpeeds; // 不同层的滚动速度
};

vec4 frag () {
// 计算各层的UV偏移
vec2 uv1 = v_uv + vec2(cc_time.x * scrollSpeeds.x, 0.0);
vec2 uv2 = v_uv + vec2(cc_time.x * scrollSpeeds.y, 0.0);
vec2 uv3 = v_uv + vec2(cc_time.x * scrollSpeeds.z, 0.0);

// 采样各层纹理
vec4 color1 = texture(layer1, uv1) * layer1Color;
vec4 color2 = texture(layer2, uv2) * layer2Color;
vec4 color3 = texture(layer3, uv3) * layer3Color;

// 混合各层
vec4 result = color1;
result = mix(result, color2, color2.a);
result = mix(result, color3, color3.a);

return result;
}
}%

💡 优化建议

性能优化

  1. 减少纹理采样次数: 合并多个纹理到一张图集中
  2. *使用适当的精�?: 在移动端使用mediump而不是highp
  3. 避免分支语句: 在片元着色器中尽量避免if语句
  4. 合理使用Alpha Test: 只在必要时使用,因为会产生分�?

内存优化

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
// 纹理管理优化
@ccclass('TextureManager')
export class TextureManager extends Component {
private static _instance: TextureManager;
private _textureCache: Map<string, Texture2D> = new Map();

static getInstance(): TextureManager {
if (!this._instance) {
this._instance = new TextureManager();
}
return this._instance;
}

getTexture(path: string): Texture2D | null {
if (this._textureCache.has(path)) {
return this._textureCache.get(path)!;
}

// 加载纹理并缓�? resources.load(path, Texture2D, (err, texture) => {
if (!err && texture) {
this._textureCache.set(path, texture);
}
});

return null;
}

releaseTexture(path: string) {
if (this._textureCache.has(path)) {
this._textureCache.delete(path);
}
}
}

📚 总结

无光照着色器是着色器编程的基础,具有以下特点:

优势

  • 性能优越: 不需要光照计算,渲染速度�?- *简单易�?: 代码结构简单,易于学习和修�?- 兼容性好: 在各种设备上都有良好的性能表现
  • *用途广�?: 适用于UI、特效、背景等多种场景

适用场景

  • UI界面渲染
  • 特效和粒子系�?- 天空盒和背景
  • 简单的2D游戏
  • 性能要求较高的场�?
    掌握无光照着色器是学习更复杂着色器的重要基础,建议在实际项目中多加练习和应用�?

*下一步学�?