{
链接:https://github.com/Claymoreno1/Grass-Wind-VolumeLight-Sky-Chan
}
题外话{
一个月没有搞Unity,因为考试的原因,之后又因为某些原因搞了一个星期的前端,感觉uniapp真是个好东西,提高开发效率神器,同时也感觉前端有点无聊(接触unity的原因? )
}
在shadertoy上看到了一个天空https://www.shadertoy.com/view/Msdfz8,感觉和塞尔达传说的很像,于是就抄了过来。
之前做的天空,用的Simplex 噪声,是用一个球形的Mesh来把摄像机包裹起来,云的形态非常好,但是看起来并不真实,因为天空需要给人无边无际的感觉,这次直接使用屏幕后处理,通过深度图重建世界坐标,分别绘制天空颜色,太阳,云效果比较好。
关于如何把GLSL转换成shaderlab,在动态天空实现篇已经讲过,相关文章也很多
算法上核心要素就是:用坐标点相对于摄像机的位移向量来做各种计算。
比如,这里i.FCray为该像素点的位置相对于摄像机的位移向量,越高或越低,y^2值也就越大,我们通过这个来控制颜色渐变,然后,如果i.FCray.y小于0,那么我们就让该像素颜色趋向于_Skycolor2
float3 col = _Skycolor*1.1-i.FCray.y*i.FCray.y*0.5;
col=lerp(col,_Skycolor2,pow(1.0-max(i.FCray.y,0.0),3.0));
通过这种方法,我们已经把天空的底色绘制出来了。
太阳的位置,我们先给定一个向量_Sunpos,用i.FCray与这个向量点乘,如果结果>0那就说明,夹角小于90°,根据这一点进行绘制即可。
云的绘制相对比较复杂,先根据云层给定不同的高度,再通过FBM噪声对颜色进行插值,这次并没有实时计算噪声,直接对噪声纹理进行采样,但是有次听大佬说纹理比复杂计算还要费,这个纹理可以根据需要用伪随机数替代。这里的FBM我没有照搬shadertoy上,进行了一些简化,噪声这块完全可以参照女神的谈谈噪声
以下是C#与shaderlab的全部代码
C#部分就是重建世界坐标模板代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[ExecuteInEditMode]
public class Sky : MonoBehaviour
{
public Material skymat;
private Camera thiscamera;
private Transform thiscameratrans;
private void OnEnable()
{
thiscamera = this.GetComponent<Camera>();
thiscameratrans = this.transform;
thiscamera.depthTextureMode |= DepthTextureMode.Depth;
}
[ImageEffectOpaque]
private void OnRenderImage(RenderTexture source, RenderTexture destination)
{
SetRay();
Graphics.Blit(source, destination,skymat);
}
private void SetRay()
{
Matrix4x4 frustumCorners = Matrix4x4.identity;//返回单位矩阵
float fov = thiscamera.fieldOfView;
float near = thiscamera.nearClipPlane;
float aspect = thiscamera.aspect;
float halfHeight = near * Mathf.Tan(fov * 0.5f * Mathf.Deg2Rad);
Vector3 toRight =thiscameratrans.right * halfHeight * aspect;
Vector3 toTop = thiscameratrans.up * halfHeight;
Vector3 topLeft = thiscameratrans.forward * near + toTop - toRight;
float scale = topLeft.magnitude / near;
topLeft.Normalize();
topLeft *= scale;
Vector3 topRight = thiscameratrans.forward * near + toRight + toTop;
topRight.Normalize();
topRight *= scale;
Vector3 bottomLeft = thiscameratrans.forward * near - toTop - toRight;
bottomLeft.Normalize();
bottomLeft *= scale;
Vector3 bottomRight = thiscameratrans.forward * near + toRight - toTop;
bottomRight.Normalize();
bottomRight *= scale;
frustumCorners.SetRow(0, bottomLeft);
frustumCorners.SetRow(1, bottomRight);
frustumCorners.SetRow(2, topRight);
frustumCorners.SetRow(3, topLeft);
skymat.SetMatrix("_FrustumCornersRay", frustumCorners);
skymat.SetMatrix("_UnityMatVP", frustumCorners);
}
}
shaderlab部分,主要也是混合噪声,已经注释了很多,主要难点就在云的把控
Shader "myshaders/postsky"
{
Properties
{
_NoiseTex("Texture", 2D) = "white" {}
_Skycolor("_Skycolor",color)=(1,1,1,1)
_Skycolor2("_Skycolor2",color)=(1,1,1,1)
_Sunpos("_Sunpos",vector)=(-0.8,0.4,-0.3,0)
_Cloudcolor("_Cloudcolor",color)=(1.0,0.95,1.0)
_Cloudspd("_Cloudspd",float)=1
_Layer("_Layer",float)=3
}
SubShader
{
Pass
{
ZTest Always Cull Off ZWrite Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
float4 FCray:TEXCOORD1;
};
sampler2D _NoiseTex;
float4 _NoiseTex_TexelSize;
float4x4 _FrustumCornersRay;
float4x4 _UnityMatVP;
//float4 _Windway;
sampler2D _CameraDepthTexture;
float4 _Skycolor;
float4 _Skycolor2;
float4 _Sunpos;
float4 _Cloudcolor;
float _Cloudspd;
float _Layer;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
#if UNITY_UV_STARTS_AT_TOP
if(_NoiseTex_TexelSize.y<0)
o.uv.y=1-o.uv.y;
#endif
int index =0;
if(v.uv.x<0.5&&v.uv.y<0.5){
index=0;
}else if(v.uv.x>0.5&&v.uv.y<0.5){
index=1;
}else if(v.uv.x>0.5&&v.uv.y>0.5){
index=2;
}else{
index=3;
}
#if UNITY_UV_STARTS_AT_TOP
if(_NoiseTex_TexelSize.y<0)
index=3-index;
#endif
o.FCray=_FrustumCornersRay[index];
return o;
}
float FBM(float2 p,float t){
float2 f=0.0;
float s=0.5;
for(int i=0;i<5;i++){
p+=t;//时间偏移
t*=1.5;//速度不同
f+=s*tex2D(_NoiseTex,p/512).x;
p*=2.0;
s*=0.5;
}
return f;
}
fixed4 frag (v2f i) : SV_Target
{
//float linearDepth=LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,i.uv));
//float3 worldPos=_WorldSpaceCameraPos+linearDepth*i.FCray.xyz;
i.FCray=normalize(i.FCray);
//-----天空
//距离越远颜色越深,*1.1是为了让颜色不要太深
float3 col = _Skycolor*1.1-i.FCray.y*i.FCray.y*0.5;
//为了把下面的颜色剔除,给一个制定的颜色
col=lerp(col,_Skycolor2,pow(1.0-max(i.FCray.y,0.0),3.0));
//-----/天空
//-----太阳
//通过向量点乘,确定太阳位置A.*b>0说明夹角小于90°
float sundot=clamp(dot(i.FCray.xyz,normalize(_Sunpos.xyz)),0.0,1.0);
//感性计算,位置越正,rgb越趋近于1
col+=0.25*float3(1.0,0.7,0.4)*pow(sundot,5.0);
col+=0.25*float3(1.0,0.8,0.6)*pow(sundot,64.0);
col+=0.2*float3(1.0,0.8,0.6)*pow(sundot,512.0);
//-----/太阳
//-----云
float3 campos=_WorldSpaceCameraPos;
float time=_Time.y*0.05*_Cloudspd;
float3 c=i.FCray.xyz;
for(int i=0;i<_Layer;i++){
//给不同的层不同的高度
float2 sc=campos.xz+c.xz*((i+3)*40000.0-campos.y)/c.y;
//噪声插值混色
col=lerp(col,_Cloudcolor,0.5*smoothstep(0.425,0.89,FBM(0.00006*sc,time*(i+3))));
}
//把下面的云剔除
col=lerp(col,_Skycolor2,pow(1.0-max(c.y,0.0),16.0));
//-----/云
return float4(col,1);
}
ENDCG
}
}
}