使用Unity簡單實現RayMarching
前言
最近在玩ShaderToy,發現各種酷炫的效果都是隻用數學公式計算出來的,心中膜拜之情溢於言表同時也深深自省:爲啥不好好學數學呢😂。
附上ShaderToy鏈接:https://www.shadertoy.com/
效果
簡單的使用RayMarching實現了一個蘭伯特模型,接下來應該會持續研究RayMarching的😀
RayMarching算法思想
從相機發射n條射線,射線有一個採樣的步長。當射線處在體紋理中時,每個步長採一次樣,獲取紋理值(實際上表示該點的密度值),計算光照,然後和該條射線當前累積的顏色值進行混合。
關於Ray tracing、Ray marching 、Ray casting的區別可以看看這個鏈接
https://www.zhihu.com/question/29863225
實現步驟
首先我們看一下RayMarch的過程,下面是pixel shader部分
float4 frag (v2f i ) : SV_TARGET
{
float3 worldPosition = i.worldPos;
float3 viewDirection = normalize(i.worldPos - _WorldSpaceCameraPos.xyz);
float3 lightDirection = normalize(UnityWorldSpaceLightDir(i.worldPos));
return raymarch (_WorldSpaceCameraPos.xyz,viewDirection,lightDirection);
}
可以看到RayMarch需要一個發射起始點rayOrigin和一個光線方向rayDirection,通過DistanceFunction方法繪製形狀
float4 raymarch (float3 rayOrigin, float3 rayDirection,float3 lightDir)
{
for (int i=0; i<256; i++)
{
float ray = DistanceFunction(rayOrigin);
if(_Limit != 0)
{
if (distance(rayOrigin,ray*rayDirection)>250)
break;
}
if (ray < _MinDistance)
return float4 (lighting(rayOrigin,lightDir,rayDirection),1.0);
else
rayOrigin+=ray*rayDirection;
}
return float4 (0.0,0.0,0.0,0.0);
}
這裏我就畫了個最簡單的球
float DistanceFunction (float3 p)
{
return length(p)-1;
}
當光線與模型撞擊時返回一個lighting()方法,否則就繼續前進
if (ray < _MinDistance)
return float4 (lighting(rayOrigin,lightDir,rayDirection),1.0);
else
rayOrigin+=ray*rayDirection;
lighting部分,簡單的蘭伯特光照模型
float3 lighting (float3 rayOrigin,float3 lightDir,float3 viewDir)
{
float3 AmbientLight = float3 (0.1,0.1,0.1);
float3 LightDirection = normalize(lightDir);
float3 NormalDirection = set_normal(rayOrigin);
float3 LightColor = _LightColor0.rgb;
return ( max(dot(LightDirection, NormalDirection) * 0.8 + 0.15,0.0) * LightColor + AmbientLight) * ambient_occlusion(rayOrigin,NormalDirection);
}
設置法線方向
float3 set_normal (float3 p)
{
float3 x = float3 (0.0001,0.00,0.00);
float3 y = float3 (0.00,0.0001,0.00);
float3 z = float3 (0.00,0.00,0.0001);
return normalize(float3(DistanceFunction(p+x)-DistanceFunction(p-x), DistanceFunction(p+y)-DistanceFunction(p-y), DistanceFunction(p+z)-DistanceFunction(p-z)));
}
AO部分
float ambient_occlusion( float3 pos, float3 nor )
{
float occ = 0.0;
float sca = 1.0;
for( int i=0; i<5; i++ )
{
float hr = 0.01 + 0.12*float(i)/4.0;
float3 aopos = nor * hr + pos;
float dd = DistanceFunction(aopos);
occ += -(dd-hr)*sca;
sca *= 0.95;
}
return clamp( 1.0 - 3.0*occ, 0.0, 1.0 );
}
參考
[1] https://www.jianshu.com/p/46e161b911dd
[2] https://www.zhihu.com/question/29863225
[3] https://www.shadertoy.com/view/lt33z7