仿塞尔达传说天空Unity shaderlab

{

目录
  • 人物移动+相机跟随脚本修改
  • Gpuinstance实现大面积草地
  • 风吹麦浪+人物影响
  • 后处理动态天空实现
  • 动态天空实现
  • 体积光实现的三种方法
Demo

链接: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));

通过这种方法,我们已经把天空的底色绘制出来了。
仿塞尔达传说天空Unity shaderlab_第1张图片
太阳的位置,我们先给定一个向量_Sunpos,用i.FCray与这个向量点乘,如果结果>0那就说明,夹角小于90°,根据这一点进行绘制即可。
仿塞尔达传说天空Unity shaderlab_第2张图片
云的绘制相对比较复杂,先根据云层给定不同的高度,再通过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
		}
	}
}

你可能感兴趣的:(Unity)