0%

使用HLSL实现一个Mc末地传送门Shader

要实现一个末地传送门Shader,首先先要分析传送门实现效果,大致可以总结一下:跟随视角移动的星空,随机移动的星星,有深层效果
同时考虑避免使用贴图,使用程序化生成星空。
参考链接:https://www.youtube.com/watch?v=EmFUAupZ0pI&t=603s

主渲染流程

1
2
3
4
5
6
7
8
9
10
baseUV <- screenPosUV
for each layer
layerSpeed <- random
layerTime <- baseTime * animationSpeedJitter + timeJitter
ratatedValue <- RotateUV(baseUV, randomRotation)
outUV <- PanUV(rotatedValues * randomParam, panVector)
color <- SampleStarfield(outUV, layerSeed)
layerOut <- color * layerOpacity * GetBaseCyanColor(layerIdx)
finalOut += layerOut
end

核心参数

1
2
3
4
_LayerCount ("Layer Count", Range(1, 8)) = 8  // 星空层数
_RotationSpeed ("Rotation Speed", Range(0.0, 2.0)) = 1.72 // 旋转速度
_StarDensity ("Star Density", Range(0.1, 20.0)) = 2.0 // 星星密度
_StarBrightness ("Star Brightness", Range(0.1, 20.0)) = 10.0 // 亮度

核心算法

星星创建

使用距离场计算星星的辐射效果
实现闪烁效果,模拟贴图的不同亮度星星动态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
float CreateStar(float2 uv, float2 pos, float size, float brightness, float time)
{
float2 delta = uv - pos;
float dist = length(delta);
float star = 1.0 / (1.0 + dist * 50.0 / size);
star = pow(star, 3.0) * brightness;

float starSeed = dot(pos, float2(127.1, 311.7));
float twinkleSpeed = 0.3 + 0.7 * Hash(float2(starSeed, 0.0));
float twinklePhase = Hash(float2(starSeed, 1.0)) * 6.28318;

float twinkle = sin(time * _AnimationSpeed * twinkleSpeed + twinklePhase) * 0.25 + 0.75;
twinkle = smoothstep(0.5, 1.0, twinkle);
// star *= twinkle;

float intensityVar = 0.8 + 0.4 * Hash(float2(starSeed, 2.0));
star *= intensityVar;

star = min(star, 0.9);

return star;
}

单层星星控制

在给定的UV坐标空间内,生成一层星星,并返回该层在当前像素位置的亮度值。
使用三重网格系统,生成不同大小、密度和亮度的星星

grid 和 layer

层是整个星空的独立层级, 类似于图层,每个层有自己独立的参数,比如颜色,密度,速度等
而网格是层内部的空间分割方式,用来控制生成大小不同亮度不同和密度不同的星星,这里增加网格系统是为了进一步增强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
float StarfieldLayer(float2 uv, float layerSeed, float time)
{
float starValue = 0.0;

float layerDensity = _StarDensity * LayerVariation(layerSeed, 1.0, 0.5);
float layerBrightness = _StarBrightness * LayerVariation(layerSeed + 1.0, 1.0, 0.6);
float layerSize = _StarSize * LayerVariation(layerSeed + 2.0, 1.0, 0.4);

float2 gridOffset = float2(
SmoothRandom(layerSeed + 50.0),
SmoothRandom(layerSeed + 51.0)
) * 0.5;

float2 grid1 = (uv + gridOffset) * layerDensity * LayerVariation(layerSeed + 20.0, 15.0, 8.0);
float2 id1 = floor(grid1);
float2 gv1 = frac(grid1) - 0.5;

float threshold1 = 0.85 + 0.1 * SmoothRandom(layerSeed + 30.0);
float rand1 = SmoothHash(id1 + layerSeed);
if (rand1 > threshold1)
{
float2 offset1 = (SmoothHash(id1 + layerSeed + 1.0) - 0.5) * LayerVariation(layerSeed + 40.0, 0.6, 0.3);
float starBrightness1 = SmoothHash(id1 + layerSeed + 2.0) * layerBrightness;
starValue += CreateStar(gv1, offset1, layerSize * 1.2, starBrightness1, time + layerSeed);
}

float gridScale2 = LayerVariation(layerSeed + 60.0, 30.0, 15.0);
float2 grid2 = (uv + gridOffset * 0.7) * layerDensity * gridScale2;
float2 id2 = floor(grid2);
float2 gv2 = frac(grid2) - 0.5;

float threshold2 = 0.8 + 0.15 * SmoothRandom(layerSeed + 70.0);
float rand2 = SmoothHash(id2 + layerSeed + 10.0);
if (rand2 > threshold2)
{
float2 offset2 = (SmoothHash(id2 + layerSeed + 11.0) - 0.5) * LayerVariation(layerSeed + 80.0, 0.6, 0.4);
float starBrightness2 = SmoothHash(id2 + layerSeed + 12.0) * layerBrightness * 0.8;
starValue += CreateStar(gv2, offset2, layerSize * 1.0, starBrightness2, time + layerSeed * 0.7);
}

if (SmoothRandom(layerSeed + 90.0) > 0.3)
{
float gridScale3 = LayerVariation(layerSeed + 100.0, 50.0, 25.0);
float2 grid3 = (uv + gridOffset * 1.3) * layerDensity * gridScale3;
float2 id3 = floor(grid3);
float2 gv3 = frac(grid3) - 0.5;

float threshold3 = 0.88 + 0.1 * SmoothRandom(layerSeed + 110.0);
float rand3 = SmoothHash(id3 + layerSeed + 20.0);
if (rand3 > threshold3)
{
float2 offset3 = (SmoothHash(id3 + layerSeed + 21.0) - 0.5) * 0.4;
float starBrightness3 = SmoothHash(id3 + layerSeed + 22.0) * layerBrightness * 0.5;
starValue += CreateStar(gv3, offset3, layerSize * 0.7, starBrightness3, time + layerSeed * 1.3);
}
}

return min(starValue, _MaxBrightness);
}

随机控制

1
2
3
float Hash(float2 p)  // 基础随机数生成
float SmoothHash(float2 p) // 平滑插值的噪声
float SmoothRandom(float seed) // 基于种子的平滑随机数

动画

旋转动画

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
float2 RotateUV(float2 uv, float angle)
{
float2 center = float2(0.5, 0.5);
float2 delta = uv - center;

float smoothAngle = angle * 0.5;
float cosAngle = cos(smoothAngle);
float sinAngle = sin(smoothAngle);

float2 rotated;
rotated.x = delta.x * cosAngle - delta.y * sinAngle;
rotated.y = delta.x * sinAngle + delta.y * cosAngle;

return rotated + center;
}

平移动画:

1
2
3
4
float2 PanUV(float2 uv, float2 speed)
{
return uv + speed * _Time.y;
}