参考来源:
天空盒教程第 1 部分 |开尔文·范·霍恩 (kelvinvanhoorn.com)
【程序化天空盒】过程记录02:云扰动 边缘光 消散效果_九九345的博客-CSDN博客
一、太阳
目标:改变光的方向,使天空球旋转(日夜交替);光的正方向:太阳,光的反方向:月亮;
步骤一:计算天空球上的uv坐标与光的方向的距离,越与光的方向重合,越接近0,贴图越黑。(天空盒uv为xyz三个方向)
步骤二:除以半径创建圆形,然后反相。
smoothstep:可以用来生成0到1的平滑过渡值,它也叫平滑阶梯函数。
代码:
Shader "URP_Skybox"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_SunRadius ("太阳半径", float) = 1.0
}
SubShader
{
Tags { "RenderType"="Background" "RenderPipeline"="UniversalPipeline"}
Pass
{
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
CBUFFER_START(UnityPerMaterial)
float _SunRadius;
CBUFFER_END
TEXTURE2D(_MainTex);
SAMPLER(sampler_MainTex);
struct appdata
{
float4 vertex : POSITION;
float3 uv : TEXCOORD0;
};
struct v2f
{
float3 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
float4 posWS : TEXCOORD1;
};
v2f vert (appdata v)
{
v2f o;
o.vertex = TransformObjectToHClip(v.vertex);
o.uv = v.uv;
o.posWS = mul(unity_ObjectToWorld, v.vertex);
return o;
}
float4 frag (v2f i) : SV_Target
{
Light light = GetMainLight(TransformWorldToShadowCoord(i.posWS));
half sunDist = distance(i.uv.xyz, light.direction);
//smoothstep:大于等于0.7 为0,小于等于1 为1
half sunArea = 1-smoothstep(0.7, 1, sunDist * _SunRadius);
return float4(sunArea,sunArea,sunArea,1);
}
ENDHLSL
}
}
}
二、月亮
目标:月亮定位、贴图采样等 月亮使用cubemap,可以旋转。
步骤一:光线追踪球体信息并从中创建模板(使用sphIntersect),如果没有相交就创建遮罩。
步骤二:计算照明(NdotL),需要法线(视线方向-月亮顶点方向),光线方向为太阳光方向的反向。
步骤三:添加曝光度控制月亮的亮度,采样月亮cubemap纹理,并使用矩阵在月亮变换的时候自旋转。
Shader "URP_Skybox"
{
Properties
{
[Header(Moon)]
[NoScaleOffset] _MoonCubeMap ("月亮贴图", Cube) = "_Skybox" {}
_MoonRadius ("月亮半径", Range(0, 1)) = 0.05
_MoonMaskRadius("月亮遮罩半径", range(1, 25)) = 10
_MoonSmooth("月亮边缘平滑度", Range(0, 1)) = 0.7
_MoonExposure("月亮曝光度", range(0, 1)) = 1
[Header(Sun)]
_SunRadius ("太阳半径", float) = 1.0
}
SubShader
{
Tags { "RenderType"="Background" "RenderPipeline"="UniversalPipeline"}
Pass
{
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
CBUFFER_START(UnityPerMaterial)
float _SunRadius;
float _MoonRadius;
float _MoonMaskRadius;
float _MoonSmooth;
float _MoonExposure;
CBUFFER_END
TEXTURECUBE(_MoonCubeMap);
SAMPLER(sampler_MoonCubeMap);
//参数:射线方向(视角方向) ,球体的位置(月球方向),半径(月球半径)。
//从光线原点(我们的摄像机)到球面交点的距离,如果没有交点,它返回 -1。然后检查交集是否大于 -1来创建一个mask。
float sphIntersect(float3 rayDir, float3 spherePos, float radius)
{
float3 oc = -spherePos;
float b = dot(oc, rayDir);
float c = dot(oc, oc) - radius * radius;
float h = b * b - c;
if(h < 0.0) return -1.0;
h = sqrt(h);
return -b-h;
}
struct appdata
{
float4 vertex : POSITION;
float3 uv : TEXCOORD0;
};
struct v2f
{
float3 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
float4 posWS : TEXCOORD1;
};
v2f vert (appdata v)
{
v2f o;
o.vertex = TransformObjectToHClip(v.vertex);
o.uv = v.uv;
o.posWS = mul(unity_ObjectToWorld, v.vertex);
return o;
}
float4 frag (v2f i) : SV_Target
{
float3 posWS = normalize(i.posWS);
Light light = GetMainLight(TransformWorldToShadowCoord(posWS));
//月亮
float3 lDir = normalize(-light.direction);
half moonIntersect = sphIntersect(posWS, lDir, _MoonRadius);
half moonDist = distance(i.uv.xyz, lDir);
half moonMask = 1-smoothstep(_MoonSmooth, 1, moonDist * _MoonMaskRadius);
//照明 需要NdotL计算法线
half3 moonNormal = normalize(lDir - posWS * moonIntersect);
half3x3 correctionMatrix = float3x3(0, -0.2588190451, -0.9659258263,
0.08715574275, 0.9622501869, -0.2578341605,
0.9961946981, -0.08418598283, 0.02255756611);
moonNormal = mul(correctionMatrix, moonNormal);
float moonNdotL = saturate(dot(moonNormal, lDir))*0.5+0.5;
half3 moonTex = SAMPLE_TEXTURECUBE(_MoonCubeMap, sampler_MoonCubeMap, moonNormal).rgb;
half3 moonColor = moonMask * moonNdotL * exp2(_MoonExposure) * moonTex;
//太阳
half sunDist = distance(i.uv.xyz, light.direction);//天空盒uv三个方向
half sunArea = 1-smoothstep(0.7, 1, sunDist * _SunRadius);
half3 sunColor = float3(sunArea,sunArea,sunArea);
return float4(sunColor+moonColor,1);
}
ENDHLSL
}
}
}
三、天空渐变 + 地平线渐变
目标:白天+夜晚 夜晚→白天:日出,白天→夜晚:日落
实现代码用了一堆lerp,地平线和天空背景尝试了不同的颜色混合方法,叠加的话会很亮并且颜色不纯;不叠加的话(lerp),又会使天空割裂的感觉……算了,先叠加以后又更好的方法再改进。
根据y和z的数值进行lerp。关于lerp、smoothstep和step的区别参考这篇:Shader实验室: smoothstep函数 - 知乎 (zhihu.com)
Unity Shader 极简实践3——step,lerp 和 smoothstep 应用 - 简书 (jianshu.com)
加入地平线:
//天空渐变
float4 dayColor = (0,0,0,1);
float4 nightColor = (0,0,0,1);
float3 lDirRamp = (light.direction + 1) * 0.5;
float4 horizonMask = smoothstep(_HorizonHeight,0,i.uv.y) * smoothstep(_HorizonHeight,0,-i.uv.y) * _HorizonIntensity;
float4 dayHorizonColor = (0,0,0,0);
float4 nightHorizonColor = (0,0,0,0);
float4 daymixColor = (0,0,0,0);
float4 nightmixColor = (0,0,0,0);
if(lDirRamp.y > 0.5 && lDirRamp.y < 1)
{
dayColor = lerp(lerp(_MorningColor, _NoonColor, smoothstep(0,_MorningDuration,lDirRamp.z)),
_NightfallColor, smoothstep(_NightfallDuration, 1, lDirRamp.z)) * step(0.5, lDirRamp.y); //y值大于0.5返回color,否则返回黑色
dayHorizonColor = lerp(lerp(_MorningHorizonColor,_NoonHorizonColor,smoothstep(0,_MorningDuration,lDirRamp.z)),
_NightfallHorizonColor,smoothstep(_NightfallDuration, 1, lDirRamp.z))* step(0.5, lDirRamp.y)
* pow(saturate(horizonMask), _HorizonPow);
//daymixColor = lerp(dayHorizonColor, dayColor, posWS.y);
daymixColor = dayHorizonColor + dayColor;
}
else
{
nightColor = lerp(_MorningColor,lerp(_NightColor, _NightfallColor, smoothstep(_NightfallDuration,1,lDirRamp.z)),
smoothstep(0, _MorningDuration, lDirRamp.z)) * step(-0.5, -lDirRamp.y);
nightHorizonColor = lerp(_MorningHorizonColor,lerp(_NightHorizonColor,_NightfallHorizonColor,smoothstep(_NightfallDuration,1,lDirRamp.z)),
smoothstep(0, _MorningDuration, lDirRamp.z)) * step(-0.5, -lDirRamp.y)
* pow(saturate(horizonMask), _HorizonPow);
//nightmixColor = lerp(nightHorizonColor, nightColor, posWS.y);
nightmixColor = nightHorizonColor + nightColor;
}
float4 skyColor = daymixColor + nightmixColor;
四、太阳月亮Bloom
光辉颜色和背景颜色的混合方法研究了好久,也是和天空及地平线的混合问题一样会越叠越亮,但是用lerp又会有很强的割裂感(太菜了,改了好久没改出来,以后再改进吧
效果图看上面第三部分。
//太阳月亮Bloom
half moonBloomMask = (1 - smoothstep(0, 1, moonDist *3 )) * smoothstep(0.4, 1, lDir.y);
moonColor *= lerp(0.1, 0.5, smoothstep(0.1, 1, posWS.y));
half4 moonBloomColor = moonBloomMask * skyColor * 2* _MoonBloomColor;
float Mask = saturate(dot(light.direction, posWS));
//float Mask1 = lerp(_SumBloomMask1,_SumBloomMask,smoothstep(0, _MorningDuration, lDirRamp.z));
float sunBloomMask = pow(Mask, _SumBloomMask);
float sunBloomMask1 = pow(Mask, _SumBloomMask1);
float sunBloomMask2 = pow(Mask, _SumBloomMask2);
float sunBloomMask3 = pow(Mask, _SumBloomMask3);
half4 sunBloomColor = lerp(lerp(_SunBloomColor1 * sunBloomMask1 + _SunBloomColor3 * (sunBloomMask-sunBloomMask1), _SunBloomColor* sunBloomMask, smoothstep(0, _MorningDuration, lDirRamp.z)),
_SunBloomColor2* sunBloomMask2 + _SunBloomColor4 * (sunBloomMask3 - sunBloomMask2), smoothstep(_NightfallDuration, 1, lDirRamp.z));
五、高天云
基本操作加uv动画
//高天云
float2 highCloudUV = TRANSFORM_TEX(i.uv.xy, _HighCloudTex);
highCloudUV.x = i.uv.z < 0 ? 1-highCloudUV.x : highCloudUV.x;
highCloudUV.x += _Time *0.1*_HighCloudSpeed;
highCloudUV.y = smoothstep(0,1,highCloudUV.y);
float highCloudTex = SAMPLE_TEXTURE2D(_HighCloudTex, sampler_HighCloudTex, highCloudUV).a;
highCloudTex = smoothstep(0,1,highCloudTex) * abs(i.uv.z) * 10;
输出至今的背景色:
float4 SumColor = float4(sunColor + moonColor,1) + skyColor * (1-sunBloomMask*0.5) + sunBloomColor + moonBloomColor;
六、银河
使用两个通道作为遮罩,用noise扰乱采样UV,再用lerp设置颜色;看了大佬们的是采样两次贴图,进行两次uv扰动,我觉得有点费,但是没有更好的改法,又菜又爱玩,以后再改。
//银河
float2 galaxyNoiseUV = TRANSFORM_TEX(i.uv.xz, _GalaxyNoise);
galaxyNoiseUV.x += _Time*0.1;
float4 galaxyNoise = SAMPLE_TEXTURE2D(_GalaxyNoise, sampler_GalaxyNoise, galaxyNoiseUV);
float2 galaxyUV = TRANSFORM_TEX(i.uv.xz, _Galaxy);
galaxyUV += (galaxyNoise-0.1) *0.8* galaxyUV.xy;
float4 galaxy = SAMPLE_TEXTURE2D(_Galaxy, sampler_Galaxy, galaxyUV)* smoothstep(0,1,i.uv.y-0.3);
float4 galaxyColor = lerp(lerp(_GalaxyColor,_GalaxyColor2,smoothstep(0.2,_ColorPos,galaxyUV.x)), _GalaxyColor,smoothstep(_ColorPos1,0.7,galaxyUV.x))
* (galaxy.g - galaxy.r*2) + _GalaxyColor1* galaxy.r ;
galaxyColor *= 0.4;
七、星星
//星星
float2 starTexUV = TRANSFORM_TEX(i.uv.xz,_StarTex);
float starTex = smoothstep(0.1,0.2,SAMPLE_TEXTURE2D(_StarTex, sampler_StarTex, starTexUV).r) * smoothstep(0,1,i.uv.y-0.3);
float2 starNoiseTexUV = TRANSFORM_TEX(i.uv.xz, _StarNoiseTex);
starNoiseTexUV.x += _Time*0.1;
float starNoiseTex = smoothstep(0.4,0.45,SAMPLE_TEXTURE2D(_StarNoiseTex, sampler_StarNoiseTex, starNoiseTexUV).r);
float starColor = starTex*starNoiseTex;
//银河遮罩内的星星更亮
starColor = starColor *galaxy.g*5 + starColor*(1-galaxy.g)*0.5;
float4 starAndGalaxy = (galaxyColor+float4(starColor, starColor, starColor, 1)) * smoothstep(0,1,lDir.y);
return SumColor + starAndGalaxy + float4(highCloudTex,highCloudTex,highCloudTex,1) * skyColor;
八、云
看了大佬教程是使用sdf做云,本来没有找到合适的贴图,自己画但是没有带手绘板……尬住,最后拿了原的云朵贴图,真好看很满意,美术yyds。
首先在建模软件里摆好面片云的位置。
比较菜,为了设置不同的消散进度,分成了好几组云。
float3 posWS = normalize(i.posWS);
Light light = GetMainLight(TransformWorldToShadowCoord(posWS));
float3 lDirRamp = (light.direction + 1) * 0.5;
//采样扰动贴图
float2 cloudNoiseUV = TRANSFORM_TEX(i.uv.xy, _CloudNoiseTex);
cloudNoiseUV.x += _Time * _CloudSpeed;
float4 cloudNoiseTex = SAMPLE_TEXTURE2D(_CloudNoiseTex, sampler_CloudNoiseTex, cloudNoiseUV);
//采样云朵贴图
float2 cloudUV = TRANSFORM_TEX(i.uv.xy, _CloudTex);
cloudUV += float2(cloudNoiseTex.x-1, cloudNoiseTex.y-0.25)*0.02;
float4 cloudTex = SAMPLE_TEXTURE2D(_CloudTex, sampler_CloudTex, cloudUV);
漫反射颜色的实现和天空颜色实现方法一样,用了一堆lerp。
//漫反射颜色
float4 brightColor = (0,0,0,0);
float4 shadowColor = (0,0,0,0);
float4 dayMixColor = (0,0,0,0);
float4 nightMixColor = (0,0,0,0);
if(lDirRamp.y > 0.5 && lDirRamp.y < 1)
{
brightColor = lerp(lerp(_BrightMorningColor, _BrightNoonColor, smoothstep(0,_MorningDuration,lDirRamp.z)),
_BrightNightfallColor, smoothstep(_NightfallDuration, 1, lDirRamp.z)) * step(0.5,lDirRamp.y);
shadowColor = lerp(lerp(_ShadowMorningColor, _ShadowNoonColor, smoothstep(0,_MorningDuration,lDirRamp.z)),
_ShadowNightfallColor, smoothstep(_NightfallDuration, 1, lDirRamp.z)) *step(0.5,lDirRamp.y);
dayMixColor = lerp(shadowColor, brightColor, cloudTex.r);
}
else
{
brightColor = lerp(_BrightMorningColor,lerp(_BrightNightColor, _BrightNightfallColor, smoothstep(_NightfallDuration,1,lDirRamp.z)),
smoothstep(0, _MorningDuration, lDirRamp.z))
*step(-0.5,-lDirRamp.y);
shadowColor = lerp(_ShadowMorningColor,lerp(_ShadowNightColor, _ShadowNightfallColor, smoothstep(_NightfallDuration,1,lDirRamp.z)),
smoothstep(0, _MorningDuration, lDirRamp.z))
*step(-0.5,-lDirRamp.y);
nightMixColor = lerp(shadowColor, brightColor, cloudTex.r);
}
float4 beseCol = dayMixColor + nightMixColor;
边缘光:
边缘遮罩在贴;:图通道g中,边缘光使用NdotL。
//边缘光
float3 worldNormal = normalize(i.normalWS);
float NdotL = saturate(dot(-light.direction, worldNormal));
float rimIntensity = lerp(0, _RimColorIntensity, smoothstep(0.3,1,lDirRamp.y));
float4 _rimColor = light.direction.z < 0.5 ? _RimMoringColor : _RimNightfallColor;
float4 rimColor = _rimColor * NdotL * cloudTex.g * rimIntensity;
最重要的sdf云消散:
lerpCtrl为阈值,并且用sin函数设置云的消散和复原。看了大佬教程,消散的透明效果由smoothstep实现,cloud.b为云的sdf通道。
但是在实现的过程中,发现在复原到原来的图像时,会闪,最后乘以了a通道后就没问题了。
//SDF云 透明度
float lerpCtrl = lerp((sin((_Time.x + _LerpTimeOffset)*_LerpSpeed)*0.78+0.78),2,_LerpCtrl);
float cloudStep = 1-lerpCtrl;
//cloudStep 云层的分层层数
float alpha_SDF = smoothstep(saturate(cloudStep-0.3), cloudStep, cloudTex.b*cloudTex.a);
float alpha = lerp(alpha_SDF, cloudTex.a, smoothstep(0.99,1,lerpCtrl));
float3 finalColor = (beseCol+ rimColor)*alpha;
return float4(finalColor,alpha);