Unity Shader学习:RayMarching2D云和海洋

Unity Shader学习:RayMarching2D云和海洋

根据 JiepengTan的教程魔改了下,代码中有些可以合并的地方没有处理,其中一些数学的运算和光照原理还是比较有难度的,等以后再深入研究。

Unity Shader学习:RayMarching2D云和海洋
采样的噪声图:
Unity Shader学习:RayMarching2D云和海洋

c#部分:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[ExecuteInEditMode]
public class RayMarchingCamera : MonoBehaviour {
    private Matrix4x4 frustumCorners = Matrix4x4.identity;
    public Material material;
    public Camera myCamera;
    public Transform cameraTransform;
    // Use this for initialization
    private void Start()
    {
        myCamera.depthTextureMode = DepthTextureMode.Depth;
    }

    private void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        //field of view
        float fov = myCamera.fieldOfView;
        //近裁面距离
        float near = myCamera.nearClipPlane;
        //横纵比
        float aspect = myCamera.aspect;
        //近裁面一半的高度
        float halfHeight = near * Mathf.Tan(fov * 0.5f * Mathf.Deg2Rad);
        //向上和向右的向量
        Vector3 toRight = myCamera.transform.right * halfHeight * aspect;
        Vector3 toTop = myCamera.transform.up * halfHeight;

        //分别得到相机到近裁面四个角的向量
        //depth/dist=near/|topLeft|
        //dist=depth*(|TL|/near)
        //scale=|TL|/near
        Vector3 topLeft = cameraTransform.forward * near + toTop - toRight;
        float scale = topLeft.magnitude / near;

        topLeft.Normalize();
        topLeft *= scale;

        Vector3 topRight = cameraTransform.forward * near + toTop + toRight;
        topRight.Normalize();
        topRight *= scale;

        Vector3 bottomLeft = cameraTransform.forward * near - toTop - toRight;
        bottomLeft.Normalize();
        bottomLeft *= scale;

        Vector3 bottomRight = cameraTransform.forward * near - toTop + toRight;
        bottomRight.Normalize();
        bottomRight *= scale;

        //给矩阵赋值
        frustumCorners.SetRow(0, bottomLeft);
        frustumCorners.SetRow(1, bottomRight);
        frustumCorners.SetRow(2, topRight);
        frustumCorners.SetRow(3, topLeft);
        //将向量传给定点着色器,将屏幕画面传个shader
        material.SetMatrix("_FrustumCornorsRay", frustumCorners);
        material.SetTexture("_MainTex", source);
        Graphics.Blit(source, destination,material,0);
    }
}

shader部分:

Shader "Unlit/SeaCloudTest"
{
	Properties{
		_MainTex("Texture", 2D) = "white" {}
		//噪声贴图
		_NoiseTex("NoiseTex",2D)="white"{}
		//海浪幅度
		_SeaWaveHeight("SeaWaveHeight",float)=5.0
		_SeaBaseColor("SeaBaseColor",Color)=(1,1,1,1)
		_SeaWaterColor("SeaWaterColor",Color)=(1,1,1,1)
		//海平面高度
		_SeaLevel("SeaLevel",float)=0.0
	}
		SubShader{
		Tags{ "RenderType" = "Opaque" }
		LOD 100
		Pass{
		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 interpolatedRay:TEXCOORD1;
	};

	sampler2D _MainTex;
	float4 _MainTex_ST;
	//深度图
	sampler2D _CameraDepthTexture;
	//传入近裁面四个点的向量
	float4x4 _FrustumCornorsRay;
	sampler2D _NoiseTex;
	float _SeaWaveHeight;
	float4 _SeaBaseColor;
	float4 _SeaWaterColor;
	float _SeaLevel;

#define lightDir (normalize(float3(5.,3.0,-1.0)))

	v2f vert(appdata v) {
		v2f o;
		o.vertex = UnityObjectToClipPos(v.vertex);
		o.uv = v.uv;
		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;
		}
		o.interpolatedRay = _FrustumCornorsRay[index];
		return o;
	}

	//定义三个宏
	//计算法线
#define _MACRO_CALC_NORMAL(pos,rz, MAP_FUNC)\
    float2 e = float2(1.0,-1.0)*0.5773*0.002*rz;\
    return normalize( e.xyy*MAP_FUNC( pos + e.xyy ).x + \
                        e.yyx*MAP_FUNC( pos + e.yyx ).x + \
                        e.yxy*MAP_FUNC( pos + e.yxy ).x + \
                        e.xxx*MAP_FUNC( pos + e.xxx ).x );

	//计算shadow
#define _MACRO_SOFT_SHADOW(ro, rd, maxH,MAP_FUNC) \
    float res = 1.0;\
    float t = 0.001;\
    for( int i=0; i<80; i++ ){\
        float3  p = ro + t*rd;\
        float h = MAP_FUNC( p).x;\
        res = min( res, 16.0*h/t );\
        t += h;\
        if( res<0.001 ||p.y> maxH ) break;\
    }\
    return clamp( res, 0.0, 1.0 );

	//raycast
#define _MRCRO_RAY_CAST( ro, rd ,tmax,MAP_FUNC)\
    float t = .1;\
    float m = -1.0;\
    for( int i=0; i<256; i++ ) {\
        float precis = 0.0005*t;\
        float2 res = MAP_FUNC( ro+rd*t );\
        if( res.x<precis || t>tmax ) break;\
        t += 0.8*res.x;\
        m = res.y;\
    } \
    if( t>tmax ) m=-2.0;\
    return float2( t, m );

	//使用多个发射点(圆形)发射不同频率、振幅的波来合成波浪效果
#define Waves(pos,NUM)\
    float2 uv = pos.xz;\
    float w = 0.0,sw = 0.0;\
    float iter = 0.0, ww = 1.0;\
    uv += _Time.y * 0.5;\
	for(int i = 0;i<NUM;i++){\
			w += ww * Wave(uv * 0.06, float2(sin(iter), cos(iter)) * 10.0, 2.0 + iter * 0.08, 2.0 + iter * 3.0);\
			sw += ww;\
			ww = lerp(ww, 0.0115, 0.4);\
			iter += 2.39996;\
	}\
		return float2(pos.y-_SeaLevel - w / sw * _SeaWaveHeight, 1.);\

	//单个波扩散
	float Wave(float2 uv, float2 emitter, float speed, float phase) {
		//圆形扩散 极坐标
		float dst = distance(uv, emitter);
		return pow((0.5 + 0.5 * sin(dst * phase - _Time.y * speed)), 5.0);
	}

	//三种精度的map
	float2 TerrainL(float3 pos) {
		Waves(pos, 5);
	}

	float2 TerrainM(float3 pos) {
		Waves(pos, 9);
	}

	float2 TerrainH(float3 pos) {
		Waves(pos, 24);
	}

	//raymarching迭代多,精度低
	float2 RaycastTerrain(float3 ro, float3 rd) {
		_MRCRO_RAY_CAST(ro, rd, 1999, TerrainL);
	}

	//法线只计算一次,精度高
	float3 NormalTerrian(in float3 pos, float rz) {
		_MACRO_CALC_NORMAL(pos, rz, TerrainH);
	}

	//阴影计算精度中等
	float SoftShadow(in float3 ro, in float3 rd, float tmax) {
		_MACRO_SOFT_SHADOW(ro, rd, tmax, TerrainM);
	}

	//时间分形布朗运动函数
	float TimeFBM(float2 p, float t) {
		float f = 0.0;
		float s = 0.5;
		float sum = 0;
		for (int i = 0; i < 5; i++)
		{
			//每层初始偏移位置不同
			p += t;
			//每一层时间偏移不同,得到不同层不同移速的效果
			t *= 1.5;
			//采样噪声贴图
			f += s * tex2D(_NoiseTex, p / 256).x;
			//逆时针旋转37度并控制云的聚合程度
			p = mul(float2x2(0.8, -0.6, 0.6, 0.8), p)*2.02;
			sum += s;
			s *= 0.6;
		}
		return f / sum;
	}

	//2D云层
	float3 Cloud(float3 bgCol, float3 ro, float3 rd, float3 cloudCol, float spd, float layer) {
		float3 col = bgCol;
		float time = _Time.y*0.05*spd;
		//对不同的大的分层添加不同的高度
		for (int i = 0; i < layer; i++)
		{
			//(i+3)*40000为高度,-ro.y保持在世界空间位置不变
			//rd.xz/rd.y把rd.xz按照rd.y=1时候缩放,后半部分表示rd.y=(高度)时rd.xz的值
			//sc表示视线射线在(i+3)*40000高度时xz的世界坐标
			float2 sc = ro.xz + rd.xz*((i + 3)*40000.0 - ro.y) / rd.y;
			//背景色与云颜色混合叠加
			col = lerp(col, cloudCol, 0.5*smoothstep(0.5, 0.8, TimeFBM(0.00002*sc, time*(i + 3))));
		}
		return col;
	}

	//渲染天空
	float3 Sky(float3 ro, float3 rd, float3 lDir) {
		fixed3 col = fixed3(0.0, 0.0, 0.0);
		float sundot = clamp(dot(rd, lDir), 0.0, 1.0);

		// sky      
		col = float3(0.2, 0.5, 0.85)*1.1 - rd.y*rd.y*0.5;
		col = lerp(col, 0.85*float3(0.7, 0.75, 0.85), pow(1.0 - max(rd.y, 0.0), 4.0));
		// sun
		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.4*float3(1.0, 0.8, 0.6)*pow(sundot, 512.0);
		// clouds
		col = Cloud(col, ro, rd, float3(1.0, 0.95, 1.0), 1, 1);
		// 过滤地平线以下部分的云
		col = lerp(col, 0.68*float3(0.4, 0.65, 1.0), pow(1.0 - max(rd.y, 0.0), 16.0));
		return col;
	}

	// 海基本渲染
	float3 RenderSea(float3 pos, float3 rd, float rz, float3 nor, float3 lDir) {
		//菲涅尔
		float fresnel = clamp(1.0 - dot(nor, -rd), 0.0, 1.0);
		fresnel = pow(fresnel, 3.0) * 0.65;
		//反射
		float3 reflected = Sky(pos, reflect(rd, nor), lDir);
		//漫反射
		float3 diff = pow(dot(nor, lDir) * 0.4 + 0.6, 3.);
		//折射
		float3 refracted = _SeaBaseColor + diff * _SeaWaterColor * 0.12;
		float3 col = lerp(refracted, reflected, fresnel);
		//高光
		float spec = pow(max(dot(reflect(rd, nor), lDir), 0.0), 60.) * 3.;
		col += float3(spec, spec, spec);

		return col;
	}

	float2 Map(float3 pos) {
		//不要被天空盒遮挡就行
		return float2(1999,-1);
	}

#define MARCH_NUM 256 //最多光线检测次数
	float2 RayCast(float3 ro,float3 rd) {
		//步进距离的范围
		float tmin = 0.1;
		float tmax = 20.0;
		float t = tmin;
		float2 res = float2(0.,-1.0);
		for (int i = 0; i<MARCH_NUM; i++)
		{
			//精度
			float precis = 0.0005;
			//步进一次的位置
			float3 pos = ro + rd * t;
			res = Map(pos);
			//如果返回的距离小于精度表明已经接触到物体表面
			//如果返回的距离大于最大值表明后方已经没有物体
			if (res.x<precis || t > tmax) break;
			//加速检测速度 这里可以有不同的策略
			t += 0.5*res.x;
		}
		//当步进一次的距离超出范围,返回
		if (t>tmax) return float2(t,-1.0);
		//返回步进距离以及材质信息
		return float2(t, res.y);
	}

	//混合云和Unity场景
	float4 ProcessRayMarch2DCloudSkyOriginalScene(float2 uv, float3 ro, float3 rd, float sceneDep, float4 sceneCol) {
		float3 col = sceneCol.xyz;
		float rz = RayCast(ro, rd).x;		
		//云
		col = Cloud(col, ro, rd, float3(1.0, 0.95, 1.0), 1, 1);
		//过滤掉地平线以下部分
		col = lerp(col, sceneCol.xyz, pow(1.0 - max(rd.y, 0.0), 16.0));
		//判断unity场景深度与rayMarching场景深度,然后返回深度小的颜色
		if (sceneDep<rz)
		{
			col = sceneCol.xyz;
			rz = sceneDep;
		}
		return float4(col, 1.0);
	}

	//绘画云和天空
	float4 ProcessRayMarch2DCloudSky(float2 uv, float3 ro, float3 rd, float sceneDep, float4 sceneCol) {
		float3 col = float3(0.0, 0.0, 0.0);	
		//光线方向
		float3 light1 = normalize(float3(-0.8, 0.4, -0.3));
		//太阳光强度为视线与光线方向夹角
		float sundot = clamp(dot(rd, light1), 0.0, 1.0);
		//天空颜色过度,视线角度越低颜色越暗
		col = float3(0.2, 0.5, 0.85)*1.1 - rd.y*rd.y*0.5;
		col = lerp(col, 0.85*float3(0.7, 0.75, 0.85), pow(1.0 - max(rd.y, 0.0), 4.0));
		//太阳绘制
		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);
		//云
		col = Cloud(col, ro, rd, float3(1.0, 0.95, 1.0), 1, 1);
		//过滤掉地平线以下部分
		col = lerp(col, 0.68*float3(0.4, 0.65, 1.0), pow(1.0 - max(rd.y, 0.0), 16.0));
		return float4(col, 1.0);
	}

	//渲染天空+海洋+Unity场景
	float4 ProcessRayMarch2DCloudSkySea(float2 uv, float3 ro, float3 rd, float sceneDep, float4 sceneCol) {
		float3 col = sceneCol.xyz;	
		//海渲染
		float rz1 = RaycastTerrain(ro, rd).x;		
		float3 pos = ro + rd * rz1;
		float3 nor = NormalTerrian(pos, rz1);		
		float3 sea=RenderSea(pos, rd, rz1, nor, lightDir);
		//若超出raymarching步进最大距离,返回黑色
		if (RaycastTerrain(ro, rd).y == -2)
		{
			sea = float3(0, 0, 0);
		}
		//天空渲染
		float rz2 = RayCast(ro, rd).x;
		float3 cloud = Cloud(sceneCol.xyz, ro, rd, float3(1.0, 0.95, 1.0), 1, 1);
		//过滤地平线以下
		cloud = lerp(cloud, float3(0,0,0), pow(1.0 - max(rd.y+0.05, 0.0), 16.0));
		//混合海+天空
		col = sea * (1 - cloud.r) + cloud;
		//深度取两次raymarching最小值
		float rz = min(rz1, rz2);
	    if (sceneDep<rz)
		{
			col = sceneCol.xyz;
			rz = sceneDep;
		}
		return float4(col,1.0);
	}

	fixed4 frag(v2f i) : SV_Target
	{
		//unity场景深度图
		float depth =LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,i.uv));
		//对于相机深度
		depth *=length(i.interpolatedRay.xyz);
		//获取Unity渲染结果
		float4 sceneCol = tex2D(_MainTex, i.uv);
		//uv为屏幕
		float2 uv = i.uv*float2(_ScreenParams.x / _ScreenParams.y, 1.0);
		//rayOriginPos原点为照相机世界空间位置
		float3 ro = _WorldSpaceCameraPos;
		//rayDirection为每个像素世界空间z方向
		float3 rd = normalize(i.interpolatedRay.xyz);
		//天空
		//return ProcessRayMarch2DCloudSky(uv, ro, rd, depth, sceneCol);
		//天空+Unity场景
		//return ProcessRayMarch2DCloudSkyOriginalScene(uv,ro,rd,depth,sceneCol);
		//海洋+天空+Unity场景
		return ProcessRayMarch2DCloudSkySea(uv, ro, rd, depth, sceneCol);
	}
		ENDCG
		}//end pass
	}//end SubShader
		FallBack Off
}