Unity 卡通渲染 程序化天空盒
分享一下学习过程中的碎碎念,最终效果视频指路:程序化天空盒,实际上就是写个shader,把这个球从内部展现出来。其实也没什么可说的,搞明白贴图如何正确去映射到球面上就行了。
[*]日月绘制
[*]天空颜色
[*]星星、银河绘制
[*]大气散射
[*]云
日月绘制
可参考下面这篇:
太阳绘制根据方向光的方向计算距离就行,
float sunDist = distance(i.uv.xyz, _WorldSpaceLightPos0);
float sunArea = 1 - sunDist / _SunSize;
sunArea = smoothstep(_SunInnerBoundary,_SunOuterBoundary,sunArea);
但是月亮就不能只画区域了吧,我想将月亮贴图搞上去,但天空球这个uv不太好直接用,强行用uv.xz贴上去那么只在中心点的效果比较好,稍微动一下offset就发现没法看:
天空球的uv是按照一个在原点的球体映射的,在球体的面上,上下顶点的地方切面平行于xz平面,采样完看图像不会被明显拉伸,在靠近边缘的地方采样则不能很好对应。既然在原点映射比较好,那么就把月亮固定在原点,但我们要根据光线方向旋转它,那在方向光的空间搞就好了,把天空球的uv转换到方向光的空间,转换矩阵我用脚本从transform去拿:
Matrix4x4 LtoW = sun.localToWorldMatrix;
shader:
float3 sunUV = mul(i.uv.xyz,LToW);
float2 moonUV = sunUV.xy * _MoonTex_ST.xy + _MoonTex_ST.zw;
float4 moonTex = tex2D(_MoonTex, moonUV);
float3 finalMoonColor = (_MoonColor * moonTex.rgb * moonTex.a) * step(0,sunUV.z);到这日月就可以了
天空颜色
比较简单,搞几个颜色lerp就行
float sunNightStep = smoothstep(-0.3,0.25,_WorldSpaceLightPos0.y);
//DAY NIGHT
float3 gradientDay = lerp(_DayBottomColor, _DayMidColor, saturate(i.uv.y))*step(0,-i.uv.y)
+lerp(_DayMidColor, _DayTopColor, saturate(i.uv.y))*step(0,i.uv.y);
float3 gradientNight = lerp(_NightBottomColor, _NightTopColor, verticalPos);
float3 skyGradients = lerp(gradientNight, gradientDay,sunNightStep);
//HORIZONTAL
float horWidth = lerp(_NightHorWidth, _DayHorWidth,sunNightStep);
float horStrenth = lerp(_NightHorStrenth, _DayHorStrenth,sunNightStep);
float horLineMask = smoothstep(-horWidth,0,i.uv.y) * smoothstep(-horWidth,0,-i.uv.y);
float3 horLineGradients = lerp(_NightHorColor, _DayHorColor,sunNightStep);
星星、银河
星星贴图搞上去,这张图是我从资源商店搞的,但我现在还不知道怎么去生成或是它有什么专门的名字,如果有知道的请告诉我一下。
搞张密度低点的
随便搞张noise控制范围及闪烁,亮度不要拉太高,密度也不要拉太大,不然会有纹理抖动。
//STAR
float4 starTex = tex2D(_StarTex,i.uv.xz*_StarTex_ST.xy + _StarTex_ST.zw);
float4 starNoiseTex = tex3D(_StarNoise3D,i.uv.xyz*_StarNoise3D_ST.x + _Time.x*0.12);
float starPos = smoothstep(0.21,0.31,starTex.r);
float starBright = smoothstep(0.613,0.713,starNoiseTex.r);
float starColor = starPos*starBright;
starColor = starColor*galaxy.r*3 + starColor*(1-galaxy.r)*0.3;
float starMask = lerp((1 - smoothstep(-0.7,-0.2,-i.uv.y)),0,sunNightStep);starColor用了galaxy.r,一个银河mask里面的星星亮一点,周围暗一点。
银河系绘制首先我们要去....看原神的延时摄影!!(bushi)
不难看出有一个银河系的范围,加两次采样noise,那就很简单了,自己PS画一下范围得了(其实为了方便调用程序生成SDF改的):
r通道是整体,g通道划定中间的一个范围,r-g就能做出中间那一块缺的效果。
就这里
//GALAXY
float4 galaxyNoiseTex = tex2D(_GalaxyNoiseTex,(i.uv.xz )*_GalaxyNoiseTex_ST.xy + _GalaxyNoiseTex_ST.zw + float2(0,_Time.x*0.67));
float4 galaxy = tex2D(_GalaxyTex,(i.uv.xz + (galaxyNoiseTex-0.5)*0.3)*_GalaxyTex_ST.xy + _GalaxyTex_ST.zw);
float4 galaxyColor =(_GalaxyColor * (-galaxy.r+galaxy.g) + _GalaxyColor1*galaxy.r)*smoothstep(0,0.2,1-galaxy.g);
galaxyNoiseTex = tex2D(_GalaxyNoiseTex,(i.uv.xz )*_GalaxyNoiseTex_ST.xy + _GalaxyNoiseTex_ST.zw - float2(_Time.x*0.5,_Time.x*0.67));
galaxyColor +=(_GalaxyColor * (-galaxy.r+galaxy.g) + _GalaxyColor1*galaxy.r)*smoothstep(0,0.3,1-galaxy.g);然后我们就可以看到美丽的星空拉
大气散射(一些改动只是个人理解,请斟酌斟酌)
先跟着大哥做一遍大气散射,然后再讨论后面的:
只要跟着这个大哥做一遍,就不难发现,Rayleigh是解释你看到的太阳光的颜色,天为什么是蓝色的,夕阳为什么是红色的,Mie就解释了日出日落halo。
既然做卡通渲染,那就只保留Mie好了,我只想做个日出日落,但是要做天空盒还要按照大哥说的用raymarching计算D(CP) + D(PA)那太浪费了,对于基于物理的渲染有预计算的方法节省时间,视频
王希GAMES104
不过既然是做卡通渲染,就不需要物理正确性了,自然有很多做法。
SkyScattering部分代码:
void ComputeOutLocalDensity(float3 position, float3 lightDir, out float localDPA, out float DPC)
{
float3 planetCenter = float3(0,-_PlanetRadius,0);
float height = distance(position,planetCenter) - _PlanetRadius;
localDPA = exp(-(height/_DensityScaleHeight));
DPC = 0;
//DPC = ComputeDensityCP(position,lightDir);
/*
float cosAngle = dot(normalize(position - planetCenter), -lightDir.xyz);
DPC = tex2D(_TestTex,float2(cosAngle,height / _AtmosphereHeight)).r;
*/
}
float4 IntegrateInscattering(float3 rayStart,float3 rayDir,float rayLength, float3 lightDir,float sampleCount)
{
float3 stepVector = rayDir * (rayLength / sampleCount);
float stepSize = length(stepVector);
float scatterMie = 0;
float densityCP = 0;
float densityPA = 0;
float localDPA = 0;
float prevLocalDPA = 0;
float prevTransmittance = 0;
ComputeOutLocalDensity(rayStart,lightDir, localDPA, densityCP);
densityPA += localDPA*stepSize;
prevLocalDPA = localDPA;
float Transmittance = exp(-(densityCP + densityPA)*_ExtinctionM)*localDPA;
prevTransmittance = Transmittance;
for(float i = 1.0; i < sampleCount; i += 1.0)
{
float3 P = rayStart + stepVector * i;
ComputeOutLocalDensity(P,lightDir,localDPA,densityCP);
densityPA += (prevLocalDPA + localDPA) * stepSize/2;
Transmittance = exp(-(densityCP + densityPA)*_ExtinctionM)*localDPA;
scatterMie += (prevTransmittance + Transmittance) * stepSize/2;
prevTransmittance = Transmittance;
prevLocalDPA = localDPA;
}
scatterMie = scatterMie * MiePhaseFunction(dot(rayDir,-lightDir.xyz));
float3 lightInscatter = _ScatteringM*scatterMie;
return float4(lightInscatter,1);
}这里面我直接把D(CP)砍掉了,一是因为它计算太耗时了,二是因为对我最终的效果影响不大。
不过为什么会这样?我自己的理解是mie散射的相位函数其实是更倾向于直接沿着原方向进行散射,而rayleigh倾向于朝着不同方向,并且对不同波长影响也差别巨大。那么当我不计算D(CP)时,对mie的影响是比较小的,对于rayleigh散射影响较大,散射出的不同颜色也会变得相同。
用后处理基于物理大气散射看一下?
最后,在天空盒里计算,脚本控制下颜色
//Mie scattering
float3 scatteringColor = 0;
float3 rayStart = float3(0,10,0);
rayStart.y = saturate(rayStart.y);
float3 rayDir = normalize(i.uv.xyz);
float3 planetCenter = float3(0, -_PlanetRadius, 0);
float2 intersection = RaySphereIntersection(rayStart,rayDir,planetCenter,_PlanetRadius + _AtmosphereHeight);
float rayLength = intersection.y;
intersection = RaySphereIntersection(rayStart, rayDir, planetCenter, _PlanetRadius);
if (intersection.x > 0)
rayLength = min(rayLength, intersection.x*100);
float4 inscattering = IntegrateInscattering(rayStart, rayDir, rayLength, -_WorldSpaceLightPos0.xyz, 16);
scatteringColor = _MieColor*_MieStrength * ACESFilm(inscattering);
插一嘴:
拿到后处理效果能更好一些,我们用了raymarching,自然能想到体积雾体积光什么的对吧,其实之前计算density就是计算的大气雾,最后再乘以mie相位函数才有了最终效果,那么在计算的时候加上高度雾,就能做到地面雾效和mie散射结合的结果。如果仔细研究下PC上原神的日落日出可以发现地面雾效和散射的结合很好,如果飞得高一些会发现mie散射会有变化,大气散射和高度雾应该是结合在一起去做的。手机上的原神就少了地面雾的散射变化只有mie,可能是为了节约性能?
用后处理raymarching做下高度雾和大气散射:
云
这个好像没啥好说的吧,原神是在崩三经典云的基础上加了SDF做出消散的效果。
资源准备:
参考文章,请:
在最后这个兄弟实现的基础上,生成图片后再改成多张SDF混合lerp,(忘了哪篇文章了说的好像有问题,就这个混合地方被误导了挺久)
例如4张进行lerp:
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
int t1 = sdf1[(y * width + x) * 3];
int t2 = sdf2[(y * width + x) * 3];
int t3 = sdf3[(y * width + x) * 3];
int t4 = sdf4[(y * width + x) * 3];
int result = 0 ;
if (t1 < 128 && t2 < 128 && t3 < 128 && t4 < 128)
{
result = 0;
}
else if (t1 > 128 && t2 > 128 && t3 > 128 && t4 > 128)
{
result = 255;
}
else
{
for (int i = 0; i < 200; i++)
{
float weight = (float)i / 200;
if (weight < 0.33333)
{
float w1 = weight * 3;
result += ((1 - w1) * t1 + w1 * t2) < 128 ? 0 : 255;
}
else if (weight < 0.66666)
{
float w2 = (weight - 0.33333) * 3;
result += ((1 - w2) * t2 + w2 * t3) < 128 ? 0 : 255;
}
else
{
float w3 = (weight - 0.66666) * 3;
result += ((1 - w3) * t3 + w3 * t4) < 128 ? 0 : 255;
}
}
result /= 200;
}
if (result < 0)
{
result = 0;
}
else if (result > 255)
{
result = 255;
}
output =result;
}
}最终结果就是这张图:
阈值图
先PS画好几个阶段的效果,这块就是美术的东西了,不会美术,画得不好!!!!,最后全扔给Visual Studio生成SDF后混合去。混合后的图片应该塞到原图的alpha通道里去,不过我调不好效果就没搞。这几张图可以私我b站发给你。
到这里资源准备还没完,我要做的是一个天空盒,可是这个云怎么放上去啊,采样采上去也不是不行,但是我总不能一个一个云去采样把,那消耗太大了。那在unity里面一个面片一个面片摆上去?这么多面片对应的云形状不是一样的吧?而且消散的程度不能是一样的吧?那岂不是每片云我都得整个Material去控制,太麻烦了。还有一个问题,怎么让rim边缘光正确对应到太阳周围?
这么多问题,把所有的云整成一个多面片的模型就得了,调整uv0让每个面片的云对应图片中的不同片云,再整个uv1将云片平铺,将uv1对应到世界坐标x轴旋转角、y轴旋转角,消散信息放到顶点色。
Maya搞一下:
面片
uv1
uv0
代码准备:
云贴图应该做成的几个通道:r暗部1、g暗部2、b高光、aSDF。
云的亮部=1-暗部之和
边缘光部分要结合SDF,图没做好用了一堆lerp、smooth,美术苦手:
v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv.xy = v.texcoord.xy;
o.uv1 = v.uv1;
return o;
}
float computeCloudSize(float4 lerpTex, float4 cloudTex,half _LerpCtrl)
{
half cloudStep = 1- _LerpCtrl;
half cloudLerp = smoothstep(0.95,1,_LerpCtrl);
half a1 = smoothstep(saturate(cloudStep-0.1),cloudStep,lerpTex.r);
return lerp(a1,cloudTex.a ,cloudLerp);
}
fixed4 frag(v2f i):SV_Target
{
//local(cloud sets center) to World
float4x4 LToW = float4x4(_LtoW0,_LtoW1,_LtoW2,_LtoW3);
//距离太阳范围
#define PI 3.1415926
float3 objLightDir = mul(_WorldSpaceLightPos0.xyz,LToW);
float2 cossin0 = normalize(objLightDir.xz);
float theta = acos(cossin0.x)*step(0,cossin0.y) + (2*PI - acos(cossin0.x))*step(cossin0.y,0);
theta /= 2*PI;
float temp = abs(i.uv1.x - theta);
float xDis = temp * step(temp,0.5) + (1-temp)*step((1-temp),0.5);
float yDis = abs(i.uv1.y - (objLightDir.y*0.37+0.49));
//根据距离计算边缘光
_RimColor += (1-smoothstep(0,0.008*_RimSize,xDis*xDis + yDis*yDis))*_RimStrenth;
//根据距离太阳位置进行颜色的lerp
float colorLerp = (smoothstep(0,0.1*_ColorLerpSize,xDis*xDis + yDis*yDis))*_ColorLerpStrenth;
_BrightColor = lerp(_BrightColor,_BrightColor1,colorLerp);
_DarkColor = lerp(_DarkColor,_DarkColor1,colorLerp);
_SecDarkColor = lerp(_SecDarkColor,_SecDarkColor1,colorLerp);
_RimColor = lerp(_RimColor,_RimColor1,colorLerp);
fixed4 cloudEdgeNoise = tex2D(_CloudEdgeNoise,i.uv.xy*_CloudEdgeNoise_ST.xy + _CloudEdgeNoise_ST.zw*_Time.x);
float2 cloudNoiseUV = i.uv.xy + float2(cloudEdgeNoise.x-1,cloudEdgeNoise.y-0.25)*0.02;
cloudNoiseUV = cloudNoiseUV * _CloudTex_ST.xy + _CloudTex_ST.zw;
fixed4 cloudTex = tex2D(_CloudTex,cloudNoiseUV);
fixed4 cloudLerpTex = tex2D(_CloudLerpTex,cloudNoiseUV);
/*
half3 finalColor = _BrightColor * (1 - cloudTex.r - cloudTex.g)
+ _DarkColor * cloudTex.r
+ _SecDarkColor * cloudTex.g
+ _RimColor * cloudTex.b;
*/
half finalAlpha =computeCloudSize(cloudLerpTex,cloudTex,lerp((sin((_Time.x + _LerpTimeOffset)*_LerpSpeed)*0.78+0.78),1,_LerpCtrl));
half3 finalColor = _BrightColor * (1 - cloudTex.r - cloudTex.g)
+ _DarkColor * cloudTex.r
+ _SecDarkColor * cloudTex.g
+ (_RimColor-0.7) * (cloudTex.b*0.5 + 1 - smoothstep(0.5,1,finalAlpha));
return fixed4(finalColor ,finalAlpha);
}
云的效果
ProceduralSkybox,凑活看,比较乱:
fixed4 frag(v2f i):SV_Target
{
//light坐标系转换矩阵
float4x4 LToW = float4x4(_LtoW0,_LtoW1,_LtoW2,_LtoW3);
float verticalPos = i.uv.y*0.5 + 0.5;
float sunNightStep = smoothstep(-0.3,0.25,_WorldSpaceLightPos0.y);
float3 sunUV = mul(i.uv.xyz,LToW);
//SUN
//dist of skybox suface pixel and sun point
float sunDist = distance(i.uv.xyz, _WorldSpaceLightPos0);
float sunArea = 1 - sunDist / _SunSize;
sunArea = smoothstep(_SunInnerBound,_SunOuterBound,sunArea);
float3 fallSunColor = _SunColor.rgb*0.4;
float3 finalSunColor = lerp(fallSunColor,_SunColor.rgb,smoothstep(-0.03,0.03,_WorldSpaceLightPos0.y)) * sunArea;
//MOON
float2 moonUV = sunUV.xy * _MoonTex_ST.xy * (1/_MoonSize+0.001) + _MoonTex_ST.zw;
float4 moonTex = tex2D(_MoonTex, moonUV);
//float moonHaloArea = max(0,1 - length((moonUV - 0.5) * 1/_MoonHaloSize));
//float lerpmoonHaloArea = lerp(0,moonHaloArea,smoothstep(0.02,0.1,-_WorldSpaceLightPos0.y));
//step z 两个月亮问题
float3 finalMoonColor = (_MoonColor * moonTex.rgb * moonTex.a) * step(0,sunUV.z);
//sunInfluence
float sunMask2 = smoothstep(-0.4,0.4,-mul(i.uv.xyz,LToW).z) - 0.3;
float sunInfScaleMask = smoothstep(-0.01,0.1,_WorldSpaceLightPos0.y) * smoothstep(-0.4,-0.01,-_WorldSpaceLightPos0.y);
float3 finalSunInfColor = _SunInfColor * sunMask2 * _SunInfScale * sunInfScaleMask;
//Mie scattering
float3 scatteringColor = 0;
float3 rayStart = float3(0,10,0);
rayStart.y = saturate(rayStart.y);
float3 rayDir = normalize(i.uv.xyz);
float3 planetCenter = float3(0, -_PlanetRadius, 0);
float2 intersection = RaySphereIntersection(rayStart,rayDir,planetCenter,_PlanetRadius + _AtmosphereHeight);
float rayLength = intersection.y;
intersection = RaySphereIntersection(rayStart, rayDir, planetCenter, _PlanetRadius);
if (intersection.x > 0)
rayLength = min(rayLength, intersection.x*100);
float4 inscattering = IntegrateInscattering(rayStart, rayDir, rayLength, -_WorldSpaceLightPos0.xyz, 16);
scatteringColor = _MieColor*_MieStrength * ACESFilm(inscattering);
//DAY NIGHT
float3 gradientDay = lerp(_DayBottomColor, _DayMidColor, saturate(i.uv.y))*step(0,-i.uv.y)
+lerp(_DayMidColor, _DayTopColor, saturate(i.uv.y))*step(0,i.uv.y);
float3 gradientNight = lerp(_NightBottomColor, _NightTopColor, verticalPos);
float3 skyGradients = lerp(gradientNight, gradientDay,sunNightStep);
//HORIZONTAL
float horWidth = lerp(_NightHorWidth, _DayHorWidth,sunNightStep);
float horStrenth = lerp(_NightHorStrenth, _DayHorStrenth,sunNightStep);
float horLineMask = smoothstep(-horWidth,0,i.uv.y) * smoothstep(-horWidth,0,-i.uv.y);
float3 horLineGradients = lerp(_NightHorColor, _DayHorColor,sunNightStep);
//GALAXY
float4 galaxyNoiseTex = tex2D(_GalaxyNoiseTex,(i.uv.xz )*_GalaxyNoiseTex_ST.xy + _GalaxyNoiseTex_ST.zw + float2(0,_Time.x*0.67));
float4 galaxy = tex2D(_GalaxyTex,(i.uv.xz + (galaxyNoiseTex-0.5)*0.3)*_GalaxyTex_ST.xy + _GalaxyTex_ST.zw);
float4 galaxyColor =(_GalaxyColor * (-galaxy.r+galaxy.g) + _GalaxyColor1*galaxy.r)*smoothstep(0,0.2,1-galaxy.g);
galaxyNoiseTex = tex2D(_GalaxyNoiseTex,(i.uv.xz )*_GalaxyNoiseTex_ST.xy + _GalaxyNoiseTex_ST.zw - float2(_Time.x*0.5,_Time.x*0.67));
galaxy = tex2D(_GalaxyTex,(i.uv.xz + (galaxyNoiseTex-0.5)*0.3)*_GalaxyTex_ST.xy + _GalaxyTex_ST.zw);
galaxyColor +=(_GalaxyColor * (-galaxy.r+galaxy.g) + _GalaxyColor1*galaxy.r)*smoothstep(0,0.3,1-galaxy.g);
galaxyColor *= 0.5;
//STAR
float4 starTex = tex2D(_StarTex,i.uv.xz*_StarTex_ST.xy + _StarTex_ST.zw);
float4 starNoiseTex = tex3D(_StarNoise3D,i.uv.xyz*_StarNoise3D_ST.x + _Time.x*0.12);
float starPos = smoothstep(0.21,0.31,starTex.r);
float starBright = smoothstep(0.613,0.713,starNoiseTex.r);
float starColor = starPos*starBright;
starColor = starColor*galaxy.r*3 + starColor*(1-galaxy.r)*0.3;
float starMask = lerp((1 - smoothstep(-0.7,-0.2,-i.uv.y)),0,sunNightStep);
//FINAL
float3 finalSkyColor = skyGradients * (1 - horLineMask)
+ horLineGradients * horLineMask * horStrenth ;
float3 finalColor = finalSunColor + finalSunInfColor + finalMoonColor + finalSkyColor + (starColor + galaxyColor) * starMask + scatteringColor;
fixed cosRh = cos(_CloudRotate + _CloudHighSpeed * _Time.x);
fixed sinRh = sin(_CloudRotate + _CloudHighSpeed * _Time.x);
float4x4 rotateh = float4x4(fixed4(cosRh,0,sinRh,0),fixed4(0,1,0,0),fixed4(-sinRh,0,cosRh,0),fixed4(0,0,0,1));
float4 rotateUVh = mul(i.uv.xyz,rotateh);
float4 cloudHighTex = tex2D(_CloudHighTex, rotateUVh*_CloudHighTex_ST.xy + _CloudHighTex_ST.zw);
finalColor += cloudHighTex.r * 0.1;
return fixed4(finalColor,1.0);
}
页:
[1]