最近在玩ShaderToy,发现各种酷炫的效果都是只用数学公式计算出来的,心中膜拜之情溢于言表同时也深深自省:为啥不好好学数学呢。
附上ShaderToy链接:https://www.shadertoy.com/
简单的使用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