Unity Shader学习:RayMarching2D云和海洋
Unity Shader学习:RayMarching2D云和海洋
根据 JiepengTan的教程魔改了下,代码中有些可以合并的地方没有处理,其中一些数学的运算和光照原理还是比较有难度的,等以后再深入研究。
采样的噪声图:
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
}