第9章 2D渲染与Sprite光照

第9章 2D渲染与Sprite光照

理论讲解

9.1 URP 2D Renderer配置

URP为2D渲染提供了专门的渲染器,称为Universal 2D Renderer。这个渲染器优化了2D渲染流程,支持2D光照、精灵遮罩等功能。与3D渲染不同,2D渲染更注重性能和简洁性,同时保持视觉效果。

Universal 2D Renderer的主要特性:

  1. 2D光照系统:支持点光源、方向光和聚光灯
  2. 精灵遮罩:支持Sprite Mask功能
  3. Sorting Layers:支持多层排序
  4. 优化的渲染流程:减少批处理和绘制调用

9.2 2D Lights系统

URP中的2D光照系统是专门为2D游戏设计的光照解决方案。与3D光照不同,2D光照在二维平面上工作,提供更高效的性能。

2D光源类型

  1. Light 2D (Point Light):从一点向四周发射光线
  2. Light 2D (Freeform Light):自定义形状的面光源
  3. Light 2D (Sprite Light):基于精灵纹理的光照
  4. Light 2D (Parametric Light):参数化形状的光照

光源属性

  • Light Type:光源类型(Point, Freeform, Sprite, Parametric)
  • Light Color:光照颜色
  • Alpha Blend On:是否启用Alpha混合
  • Intensity:光照强度
  • Volume Scale:体积缩放
  • Blend Style:混合样式(Additive, Multiply, Multiply RGB)

9.3 Normal Maps在2D中的应用

法线贴图在2D中用于创建更丰富的光照效果,使2D精灵看起来更有立体感。通过法线贴图,可以在2D环境中模拟3D光照效果。

法线贴图制作

  • 使用专门的2D法线贴图生成工具
  • 确保法线贴图格式正确(通常为RGB格式)
  • 与原精灵贴图保持相同尺寸

2D法线贴图应用

  • 在Sprite Renderer上应用法线贴图
  • 配置光照材质以支持法线贴图
  • 调整光照参数以获得最佳效果

9.4 Sprite Mask与Sorting Layers

Sprite Mask

Sprite Mask用于遮罩精灵,只显示在遮罩区域内的部分。在URP中,Sprite Mask与2D光照系统配合使用,可以创建复杂的遮罩效果。

Mask属性:

  • Front Sorting Layer:前景排序层
  • Front Sorting Order:前景排序顺序
  • Back Sorting Layer:背景排序层
  • Back Sorting Order:背景排序顺序

Sorting Layers

Sorting Layers用于控制渲染顺序,确保精灵按正确的前后关系显示:

  • Default:默认排序层
  • UI:UI排序层
  • Effects:特效排序层
  • Custom layers:自定义排序层

9.5 2D阴影投射

URP 2D支持2D阴影投射,通过Light 2D组件实现。2D阴影系统包括:

  • Shadow Casters:阴影投射器
  • Shadow Receiving:阴影接收
  • Shadow Distance:阴影距离

9.6 Pixel Perfect Camera

Pixel Perfect Camera确保精灵在屏幕上以精确的像素显示,避免模糊和失真。这对于像素艺术风格的游戏尤为重要。

Pixel Perfect Camera特性:

  • Assets Pixels Per Unit:每单位的像素数
  • Ref Resolution X/Y:参考分辨率
  • Upscale Render Texture:放大渲染纹理
  • Crop Frame X/Y:裁剪帧

代码示例

9.7 2D光照控制器

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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
using UnityEngine;
using UnityEngine.Rendering.Universal;

[RequireComponent(typeof(Light2D))]
public class Light2DController : MonoBehaviour
{
[Header("Light Configuration")]
public Light2D light2D;

[Header("Animation Settings")]
public bool animateIntensity = false;
public AnimationCurve intensityCurve = AnimationCurve.EaseInOut(0, 0.5f, 1, 1.5f);
public float animationDuration = 2f;

[Header("Color Settings")]
public Gradient colorGradient = new Gradient();
public float colorChangeDuration = 5f;

[Header("Flicker Settings")]
public bool enableFlicker = false;
public float flickerMin = 0.8f;
public float flickerMax = 1.2f;
public float flickerSpeed = 10f;

private float animationTimer = 0f;
private float colorTimer = 0f;
private float flickerTimer = 0f;

void Start()
{
if (light2D == null)
light2D = GetComponent<Light2D>();

InitializeColorGradient();
}

void Update()
{
if (light2D == null) return;

// 强度动画
if (animateIntensity)
{
animationTimer += Time.deltaTime;
float progress = (animationTimer % animationDuration) / animationDuration;
float intensity = intensityCurve.Evaluate(progress);
light2D.intensity = intensity;
}

// 颜色变化
if (colorGradient.colorKeys.Length > 1)
{
colorTimer += Time.deltaTime;
float colorProgress = (colorTimer % colorChangeDuration) / colorChangeDuration;
light2D.color = colorGradient.Evaluate(colorProgress);
}

// 闪烁效果
if (enableFlicker)
{
flickerTimer += Time.deltaTime * flickerSpeed;
float flickerValue = Mathf.Lerp(flickerMin, flickerMax,
Mathf.PerlinNoise(flickerTimer, 0));
light2D.intensity *= flickerValue;
}
}

private void InitializeColorGradient()
{
if (colorGradient.colorKeys.Length == 0)
{
GradientColorKey[] colorKeys = new GradientColorKey[2];
colorKeys[0] = new GradientColorKey(Color.white, 0.0f);
colorKeys[1] = new GradientColorKey(Color.red, 1.0f);
colorGradient.SetKeys(colorKeys, new GradientAlphaKey[2]
{
new GradientAlphaKey(1.0f, 0.0f),
new GradientAlphaKey(1.0f, 1.0f)
});
}
}

// 动态调整光照范围
public void SetLightRange(float range)
{
if (light2D != null)
{
light2D.pointLightOuterRadius = range;
}
}

// 切换光照类型
public void SetLightType(Light2D.LightType type)
{
if (light2D != null)
{
light2D.lightType = type;
}
}

// 启用/禁用光照
public void SetLightEnabled(bool enabled)
{
if (light2D != null)
{
light2D.enabled = enabled;
}
}

// 获取当前光照强度
public float GetCurrentIntensity()
{
return light2D != null ? light2D.intensity : 0f;
}
}

9.8 2D精灵光照材质管理器

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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering.Universal;

public class SpriteLightingManager : MonoBehaviour
{
[System.Serializable]
public class SpriteLightingConfig
{
public string name;
public Material material;
public bool useNormalMap = false;
public bool receiveShadows = true;
public bool castShadows = true;
}

[Header("Lighting Configurations")]
public List<SpriteLightingConfig> lightingConfigs = new List<SpriteLightingConfig>();

[Header("Default Settings")]
public Material defaultLitMaterial;
public Material defaultUnlitMaterial;

private Dictionary<string, Material> materialMap = new Dictionary<string, Material>();
private Dictionary<SpriteRenderer, Material> originalMaterials = new Dictionary<SpriteRenderer, Material>();

void Start()
{
InitializeMaterials();
}

private void InitializeMaterials()
{
// 构建材质映射表
foreach (var config in lightingConfigs)
{
if (config.material != null)
{
materialMap[config.name] = config.material;
}
}
}

// 应用光照材质到精灵渲染器
public void ApplyLightingToSprite(SpriteRenderer spriteRenderer, string configName)
{
if (materialMap.ContainsKey(configName))
{
// 保存原始材质
if (!originalMaterials.ContainsKey(spriteRenderer))
{
originalMaterials[spriteRenderer] = spriteRenderer.material;
}

spriteRenderer.material = materialMap[configName];
}
}

// 恢复原始材质
public void RestoreOriginalMaterial(SpriteRenderer spriteRenderer)
{
if (originalMaterials.ContainsKey(spriteRenderer))
{
spriteRenderer.material = originalMaterials[spriteRenderer];
originalMaterials.Remove(spriteRenderer);
}
}

// 批量应用光照材质
public void ApplyLightingToGroup(GameObject groupRoot, string configName)
{
var spriteRenderers = groupRoot.GetComponentsInChildren<SpriteRenderer>();
foreach (var spriteRenderer in spriteRenderers)
{
ApplyLightingToSprite(spriteRenderer, configName);
}
}

// 创建支持法线贴图的材质
public Material CreateNormalMappedMaterial(Texture normalMap, string shaderName = "Universal Render Pipeline/Lit 2D")
{
Shader shader = Shader.Find(shaderName);
if (shader == null)
{
Debug.LogError($"Shader {shaderName} not found!");
return null;
}

Material material = new Material(shader);
if (normalMap != null)
{
material.SetTexture("_BumpMap", normalMap);
material.EnableKeyword("_NORMALMAP");
}

return material;
}

// 动态调整材质参数
public void AdjustMaterialParameters(SpriteRenderer spriteRenderer,
float smoothness = 0.5f, float metallic = 0f, Color emissionColor = default(Color))
{
Material material = spriteRenderer.material;

if (material.HasProperty("_Smoothness"))
material.SetFloat("_Smoothness", smoothness);

if (material.HasProperty("_Metallic"))
material.SetFloat("_Metallic", metallic);

if (emissionColor != default(Color))
{
material.SetColor("_EmissionColor", emissionColor);
material.EnableKeyword("_EMISSION");
}
}

// 设置阴影投射和接收
public void SetShadowSettings(SpriteRenderer spriteRenderer, bool castShadows, bool receiveShadows)
{
var light2D = spriteRenderer.GetComponent<Light2D>();
if (light2D != null)
{
light2D.lightCastsShadows = castShadows;
light2D.lightOrder = receiveShadows ? 0 : -1; // 通过lightOrder控制接收
}
}
}

9.9 2D排序层管理器

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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
using System.Collections.Generic;
using UnityEngine;

public class SortingLayerManager : MonoBehaviour
{
[System.Serializable]
public class SortingLayerDefinition
{
public string layerName;
public int sortingOrder;
public string description;
}

[Header("Sorting Layer Definitions")]
public List<SortingLayerDefinition> layerDefinitions = new List<SortingLayerDefinition>();

[Header("Default Settings")]
public string defaultLayer = "Default";
public int defaultOrder = 0;

private Dictionary<string, int> layerOrderMap = new Dictionary<string, int>();
private Dictionary<SpriteRenderer, string> spriteOriginalLayers = new Dictionary<SpriteRenderer, string>();

void Start()
{
InitializeLayerMap();
}

private void InitializeLayerMap()
{
// 构建排序层映射
foreach (var layerDef in layerDefinitions)
{
layerOrderMap[layerDef.layerName] = layerDef.sortingOrder;
}

// 如果默认层未定义,使用默认值
if (!layerOrderMap.ContainsKey(defaultLayer))
{
layerOrderMap[defaultLayer] = defaultOrder;
}
}

// 设置精灵的排序层
public void SetSpriteSortingLayer(SpriteRenderer spriteRenderer, string layerName)
{
if (spriteRenderer != null && layerOrderMap.ContainsKey(layerName))
{
// 保存原始层信息
if (!spriteOriginalLayers.ContainsKey(spriteRenderer))
{
spriteOriginalLayers[spriteRenderer] = spriteRenderer.sortingLayerName;
}

spriteRenderer.sortingLayerName = layerName;
spriteRenderer.sortingOrder = layerOrderMap[layerName];
}
}

// 设置精灵的排序顺序
public void SetSpriteSortingOrder(SpriteRenderer spriteRenderer, int order)
{
if (spriteRenderer != null)
{
spriteRenderer.sortingOrder = order;
}
}

// 动态调整排序顺序(基于Z轴位置)
public void UpdateSortingOrderBasedOnPosition(SpriteRenderer spriteRenderer)
{
if (spriteRenderer != null)
{
// 使用Z轴位置作为排序依据,Z值越大越靠前
int order = (int)(-spriteRenderer.transform.position.z * 100);
spriteRenderer.sortingOrder = order;
}
}

// 批量设置排序层
public void SetGroupSortingLayer(GameObject groupRoot, string layerName)
{
var spriteRenderers = groupRoot.GetComponentsInChildren<SpriteRenderer>();
foreach (var spriteRenderer in spriteRenderers)
{
SetSpriteSortingLayer(spriteRenderer, layerName);
}
}

// 恢复原始排序层
public void RestoreOriginalLayer(SpriteRenderer spriteRenderer)
{
if (spriteOriginalLayers.ContainsKey(spriteRenderer))
{
spriteRenderer.sortingLayerName = spriteOriginalLayers[spriteRenderer];
spriteOriginalLayers.Remove(spriteRenderer);
}
}

// 获取排序层ID
public int GetSortingLayerID(string layerName)
{
return SortingLayer.NameToID(layerName);
}

// 获取排序层名称
public string GetSortingLayerName(int layerID)
{
return SortingLayer.IDToName(layerID);
}

// 创建排序层(如果不存在)
public void CreateSortingLayerIfNotExists(string layerName, int order)
{
if (!layerOrderMap.ContainsKey(layerName))
{
layerOrderMap[layerName] = order;
}
}

// 动态排序管理(基于距离)
public void DynamicSortByDistance(SpriteRenderer[] sprites, Transform referencePoint)
{
// 按距离排序
System.Array.Sort(sprites, (a, b) => {
float distA = Vector3.Distance(a.transform.position, referencePoint.position);
float distB = Vector3.Distance(b.transform.position, referencePoint.position);
return distA.CompareTo(distB); // 距离近的在前
});

// 应用排序
for (int i = 0; i < sprites.Length; i++)
{
sprites[i].sortingOrder = i;
}
}
}

9.10 Pixel Perfect相机控制器

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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
using UnityEngine;
using UnityEngine.Rendering.Universal;

[RequireComponent(typeof(PixelPerfectCamera))]
public class PixelPerfectCameraController : MonoBehaviour
{
[Header("Pixel Perfect Settings")]
public PixelPerfectCamera pixelPerfectCamera;

[Header("Resolution Settings")]
public int assetsPixelsPerUnit = 16;
public Vector2Int referenceResolution = new Vector2Int(1920, 1080);

[Header("Advanced Settings")]
public bool upscaleRenderTexture = false;
public bool cropFrameX = true;
public bool cropFrameY = true;

[Header("Dynamic Adjustment")]
public bool autoAdjustForScreenSize = true;
public float minScale = 0.5f;
public float maxScale = 4f;

private Camera mainCamera;
private Vector2Int currentResolution;

void Start()
{
if (pixelPerfectCamera == null)
pixelPerfectCamera = GetComponent<PixelPerfectCamera>();

if (pixelPerfectCamera == null)
pixelPerfectCamera = gameObject.AddComponent<PixelPerfectCamera>();

mainCamera = pixelPerfectCamera.GetComponent<Camera>();

ConfigurePixelPerfectCamera();

if (autoAdjustForScreenSize)
{
AdjustForCurrentScreenSize();
}
}

private void ConfigurePixelPerfectCamera()
{
pixelPerfectCamera.assetsPPU = assetsPixelsPerUnit;
pixelPerfectCamera.refResolutionX = referenceResolution.x;
pixelPerfectCamera.refResolutionY = referenceResolution.y;
pixelPerfectCamera.upscaleRT = upscaleRenderTexture;
pixelPerfectCamera.cropFrameX = cropFrameX;
pixelPerfectCamera.cropFrameY = cropFrameY;
}

private void AdjustForCurrentScreenSize()
{
currentResolution = new Vector2Int(Screen.width, Screen.height);

// 计算合适的缩放比例
float scaleX = (float)currentResolution.x / referenceResolution.x;
float scaleY = (float)currentResolution.y / referenceResolution.y;
float scale = Mathf.Min(scaleX, scaleY);

// 限制缩放范围
scale = Mathf.Clamp(scale, minScale, maxScale);

// 应用调整(通过修改相机参数)
float orthoSize = mainCamera.orthographicSize;
float adjustedOrthoSize = orthoSize / scale;

mainCamera.orthographicSize = adjustedOrthoSize;
}

// 动态调整像素每单位值
public void SetAssetsPixelsPerUnit(int ppu)
{
if (pixelPerfectCamera != null)
{
pixelPerfectCamera.assetsPPU = ppu;
assetsPixelsPerUnit = ppu;
}
}

// 动态调整参考分辨率
public void SetReferenceResolution(int x, int y)
{
if (pixelPerfectCamera != null)
{
pixelPerfectCamera.refResolutionX = x;
pixelPerfectCamera.refResolutionY = y;
referenceResolution = new Vector2Int(x, y);
}
}

// 获取当前像素完美状态
public bool IsPixelPerfect()
{
if (pixelPerfectCamera != null)
{
return pixelPerfectCamera.isBlendingPresent;
}
return false;
}

// 获取缩放信息
public float GetPixelScale()
{
if (pixelPerfectCamera != null)
{
return pixelPerfectCamera.pixelScale;
}
return 1f;
}

// 强制重新计算像素完美
public void RecalculatePixelPerfect()
{
if (pixelPerfectCamera != null)
{
pixelPerfectCamera.Reset();
ConfigurePixelPerfectCamera();
}
}

// 适配特定设备
public void AdaptToDevice(string deviceName)
{
switch (deviceName.ToLower())
{
case "mobile":
SetAssetsPixelsPerUnit(16); // 移动设备常用
SetReferenceResolution(1080, 1920); // 竖屏
break;
case "desktop":
SetAssetsPixelsPerUnit(32); // 桌面设备可用更高PPU
SetReferenceResolution(1920, 1080); // 横屏
break;
case "console":
SetAssetsPixelsPerUnit(16);
SetReferenceResolution(1920, 1080);
break;
default:
// 使用默认设置
break;
}
}

// 监听屏幕尺寸变化
private void OnRectTransformDimensionsChange()
{
if (autoAdjustForScreenSize)
{
AdjustForCurrentScreenSize();
}
}

// 监听屏幕分辨率变化
private void OnValidate()
{
if (pixelPerfectCamera != null)
{
ConfigurePixelPerfectCamera();
}
}
}

实践练习

9.11 练习1:创建2D光照场景

目标:创建一个包含多种2D光源的场景

步骤

  1. 创建一个2D场景,包含多个精灵对象
  2. 添加Point Light 2D,设置为暖色调
  3. 添加Freeform Light 2D,创建窗户光效
  4. 添加Parametric Light 2D,创建聚光灯效果
  5. 调整各光源的强度和颜色,观察混合效果

具体参数设置

  • Point Light:Color=#FFD700, Intensity=1.2, Outer Radius=5
  • Freeform Light:Color=#87CEEB, Intensity=0.8, Shape=Rectangle
  • Parametric Light:Color=#FFFFFF, Intensity=1.5, Inner Angle=30, Outer Angle=60

9.12 练习2:实现2D阴影系统

目标:创建2D阴影投射和接收效果

步骤

  1. 创建多个Sprite对象作为遮挡物
  2. 添加Light 2D并启用阴影
  3. 配置Sprite对象的阴影投射属性
  4. 观察阴影的形成和变化
  5. 调整阴影参数优化效果

技术要点

  • 使用Sprite Light Renderer组件
  • 配置Shadow Caster 2D组件
  • 调整Shadow Distance参数

9.13 练习3:法线贴图应用

目标:为2D精灵应用法线贴图,增强立体感

步骤

  1. 准备带法线贴图的精灵资源
  2. 创建支持法线贴图的材质
  3. 应用材质到Sprite Renderer
  4. 添加2D光源观察法线贴图效果
  5. 调整光照角度观察立体感变化

材质设置

  • Shader: Universal Render Pipeline/Lit 2D
  • Enable Normal Map keyword
  • Assign normal map texture

9.14 练习4:精灵遮罩效果

目标:使用Sprite Mask创建遮罩效果

步骤

  1. 创建Sprite Mask对象
  2. 设置遮罩形状和大小
  3. 创建多个Sprite对象
  4. 调整Sprite的Sorting Layer
  5. 观察遮罩效果

参数配置

  • Mask:Sorting Layer=UI, Order=10
  • Sprites:Sorting Layer=Default, Order=0

9.15 练习5:像素完美相机设置

目标:配置Pixel Perfect Camera实现像素艺术效果

步骤

  1. 添加Pixel Perfect Camera组件
  2. 设置Assets Pixels Per Unit=16
  3. 设置参考分辨率为1080p
  4. 测试不同分辨率下的显示效果
  5. 观察像素对齐效果

测试分辨率

  • 1920x1080
  • 1280x720
  • 960x540

9.16 练习6:动态光照系统

目标:实现可动态控制的2D光照系统

步骤

  1. 编写光照控制器脚本
  2. 实现光照强度动画
  3. 添加颜色渐变效果
  4. 创建UI控制面板
  5. 测试动态调整功能

代码实现要点

  • 使用协程实现平滑动画
  • 实现光照参数的实时调整
  • 添加光照效果预设

总结

第9章详细介绍了URP中2D渲染与Sprite光照的相关技术。URP为2D游戏开发提供了强大的光照系统,包括多种光源类型、法线贴图支持、精灵遮罩等功能。

关键要点总结:

  1. 2D Renderer:专门优化的2D渲染器,支持2D光照和遮罩
  2. 2D Lights:多种光源类型,支持动态调整和动画
  3. 法线贴图:增强2D精灵的立体感和细节
  4. 排序层管理:精确控制2D对象的渲染顺序
  5. 像素完美:确保像素艺术风格的精确显示
  6. 性能优化:2D渲染的性能考虑和优化策略

2D渲染在URP中的实现平衡了视觉效果和性能需求,为2D游戏开发者提供了强大的工具集。通过合理配置2D光照系统,可以创建出具有深度和氛围的2D场景。

下一章将探讨URP中的粒子系统与VFX Graph集成,了解如何在URP管线中实现高质量的粒子效果。