You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

564 lines
20 KiB
HLSL

/******************************************************************************/
/*
Project - MudBun
Publisher - Long Bunny Labs
http://LongBunnyLabs.com
Author - Ming-Lun "Allen" Chou
http://AllenChou.net
*/
/******************************************************************************/
#ifndef MUDBUN_RAY_MARCHING
#define MUDBUN_RAY_MARCHING
#include "Render/ShaderCommon.cginc"
#define kLightTypeDirectional (1)
#define kLightTypePoint (2)
#if MUDBUN_VALID
#include "BrushFuncs.cginc"
int maxRayMarchSteps;
float rayMarchHitDistance;
float rayMarchMaxRayDistance;
int numLightMarchSteps;
float rayMarchStepSize;
float rayMarchVolumeDensity;
float4 rayMarchLightPositionType;
float4 rayMarchLightDirection;
float4 rayMarchAbsorption; // x: volume, y: light
float rayMarchDarknesThreshold;
float rayMarchTransmittanceCurve;
float rayMarchNoiseThreshold;
float rayMarchNoiseEdgeFade;
float4 rayMarchNoiseScrollSpeed;
float4 rayMarchNoiseBaseOctaveSize;
int rayMarchNoiseNumOctaves;
float rayMarchNoiseOctaveOffsetFactor;
struct RayMarchResults
{
bool hit;
float3 pos;
SdfBrushMaterial mat;
};
RayMarchResults init_ray_march_results()
{
RayMarchResults res;
res.hit = false;
res.pos = 0.0f;
res.mat = init_brush_material();
return res;
}
#define SDF_RAY_MARCH_MASKED_BRUSHES(res, p, brushMask, mat, outputMat) \
{ \
int iStack = -1; \
float3 pStack[kMaxBrushGroupDepth]; \
float resStack[kMaxBrushGroupDepth]; \
SdfBrushMaterial matStack[kMaxBrushGroupDepth]; \
\
res = kInfinity; \
mat = init_brush_material(); \
float3 groupP = p; \
float groupRes = kInfinity; \
SdfBrushMaterial groupMat = init_brush_material(); \
FOR_EACH_BRUSH_EXTERN_MASK(brushMask, /* TODO: skip brushes whose AABBs do not contain p */ \
switch (aBrush[iBrush].type) \
{ \
case kSdfBeginGroup: \
iStack = min(kMaxBrushGroupDepth - 1, iStack + 1); \
pStack[iStack] = p; \
resStack[iStack] = res; \
matStack[iStack] = mat; \
res = kInfinity; \
mat = init_brush_material(); \
break; \
case kSdfEndGroup: \
groupP = p; \
groupRes = res; \
groupMat = mat; \
p = pStack[iStack]; \
res = resStack[iStack]; \
mat = matStack[iStack]; \
break; \
} \
res = sdf_brush_apply(res, groupRes, groupMat, groupP, aBrush[iBrush], mat, outputMat); \
switch (aBrush[iBrush].type) \
{ \
case kSdfEndGroup: \
iStack = max(-1, iStack - 1); \
break; \
} \
); \
}
// macro that generates less inline code
#define SDF_RAY_MARCH_NORMAL(normal, p, brushMask, h) \
{ \
float3 aSign[4] = \
{ \
float3( 1.0f, -1.0f, -1.0f), \
float3(-1.0f, -1.0f, 1.0f), \
float3(-1.0f, 1.0f, -1.0f), \
float3( 1.0f, 1.0f, 1.0f), \
}; \
float3 aDelta[4] = \
{ \
float3( (h), -(h), -(h)), \
float3(-(h), -(h), (h)), \
float3(-(h), (h), -(h)), \
float3( (h * 1.0001f), (h * 1.0002f), (h * 1.0003f)), \
}; \
float3 ss = 0.0f; \
SdfBrushMaterial nmat; \
[loop] for (int iDelta = 0; iDelta < 4; ++iDelta) \
{ \
float rr = 0.0f; \
float3 pp = p + aDelta[iDelta]; \
SDF_RAY_MARCH_MASKED_BRUSHES(rr, pp, brushMask, nmat, false); \
ss += aSign[iDelta] * rr; \
} \
normal = normalize_safe(ss, float3(0.0f, 0.0f, 0.0f)); \
}
float3 ray_march_aabb_extents(Aabb aabb)
{
// TODO: use max blend
aabb.boundsMin -= 5.0f;
aabb.boundsMax += 5.0f;
return 0.5f * (aabb.boundsMax - aabb.boundsMin);
}
bool ray_march_aabb_intersects(Aabb a, Aabb b)
{
// TODO: use max blend
a.boundsMin -= 5.0f;
a.boundsMax += 5.0f;
return all(a.boundsMin <= b.boundsMax && a.boundsMax >= b.boundsMin);
}
float2 ray_march_aabb_ray_cast(Aabb aabb, float3 from, float3 to)
{
// TODO: use max blend
aabb.boundsMin -= 5.0f;
aabb.boundsMax += 5.0f;
float tMin = -kFltMax;
float tMax = +kFltMax;
float3 d = to - from;
float3 absD = abs(d);
bool3 isZero = absD < kEpsilon;
// parallel?
if (any(isZero && ((from < aabb.boundsMin) || (aabb.boundsMax < from))))
return -kFltMax;
float3 invD = sign(d) / max(kEpsilon, absD);
float3 t1 = (aabb.boundsMin - from) * invD;
float3 t2 = (aabb.boundsMax - from) * invD;
float3 minComps = isZero ? (-kFltMax) : min(t1, t2);
float3 maxComps = isZero ? (+kFltMax) : max(t1, t2);
tMin = max(minComps.x, max(minComps.y, minComps.z));
tMax = min(maxComps.x, min(maxComps.y, maxComps.z));
if (tMin > tMax)
return -kFltMax;
if (tMin > 1.0f)
return -kFltMax;
return float2(max(0.0f, tMin), min(1.0f, tMax));
}
// stmt = statements processing "iData" of hit leaf AABB nodes
// will gracefully handle maxed-out stacks
#define RAY_MARCH_AABB_TREE_RAY_CAST(tree, root, rayFrom, rayTo, stmt) \
{ \
float3 rayDir = normalize_safe(rayTo - rayFrom, kUnitZ); \
float3 rayDirOrtho = normalize_safe(find_ortho(rayDir), kUnitX); \
float3 rayDirOrthoAbs = abs(rayDirOrtho); \
\
Aabb rayBounds; \
rayBounds.boundsMin = min(rayFrom, rayTo); \
rayBounds.boundsMax = max(rayFrom, rayTo); \
\
int stackTop = 0; \
int stack[kAabbTreeNodeStackSize]; \
stack[stackTop] = root; \
\
int numIters = 0; \
while (stackTop >= 0 && numIters < 128 /* safeguard */) \
{ \
int index = stack[stackTop--]; \
if (index < 0) \
continue; \
\
if (!ray_march_aabb_intersects(tree[index].aabb, rayBounds)) \
continue; \
\
float3 aabbCenter = aabb_center(tree[index].aabb); \
float3 aabbHalfExtents = ray_march_aabb_extents(tree[index].aabb); \
float separation = \
abs(dot(rayDirOrtho, rayFrom - aabbCenter)) \
- dot(rayDirOrthoAbs, aabbHalfExtents); \
if (separation > 0.0f) \
continue; \
\
float2 t = ray_march_aabb_ray_cast(tree[index].aabb, rayFrom, rayTo); \
if (t.x < 0.0f) \
continue; \
\
if (tree[index].iChildA < 0) \
{ \
int iData = tree[index].iData; \
\
stmt \
} \
else \
{ \
stackTop = min(stackTop + 1, kAabbTreeNodeStackSize - 1); \
stack[stackTop] = tree[index].iChildA; \
stackTop = min(stackTop + 1, kAabbTreeNodeStackSize - 1); \
stack[stackTop] = tree[index].iChildB; \
} \
} \
}
float sample_noise(float3 p, float sdfSample, float detailWeight = 0.0f, float detailScale = 0.5f, float detailOffsetScale = 2.0f)
{
if (rayMarchNoiseThreshold < kEpsilon)
return 1.0f;
// base noise
float base =
mbn_cached_noise
(
p / rayMarchNoiseBaseOctaveSize.xyz
+ rayMarchNoiseScrollSpeed.xyz * _Time.y
);
float n = base;
if (detailWeight > kEpsilon)
{
float detail =
mbn_cached_noise
(
p / (rayMarchNoiseBaseOctaveSize.xyz * detailScale)
+ rayMarchNoiseScrollSpeed.xyz * _Time.y * detailOffsetScale
);
n = (base + detailWeight * detail) / (1.0f + max(0.0f, detailWeight));
}
n += 0.5f; // normalize to [0, 1]
// apply threshold
n = saturate(saturate(1.4f * n) - rayMarchNoiseThreshold);
//n = rayMarchNoiseThreshold - saturate(1.4f * n);
n = lerp(0.0f, n, saturate(10.0f * rayMarchNoiseThreshold));
return n;
}
float sample_density(float3 p, float sdfSample, float detailWeight = 0.0f, float detailScale = 0.5f, float detailOffsetScale = 2.0f)
{
if (rayMarchNoiseThreshold < kEpsilon)
return -sdfSample;
if (rayMarchNoiseEdgeFade < kEpsilon)
return -sdfSample;
float n = sample_noise(p, sdfSample, detailWeight, detailScale, detailOffsetScale);
float noiseWeight = 1.0f - saturate(-sdfSample / rayMarchNoiseEdgeFade);
float w = lerp(sdfSample, n, noiseWeight);
//sdfSample = sdf_int_cubic(sdfSample, w, 1.0f);
float density = n * (-sdfSample);
density = density * pow(density, max(kEpsilon, rayMarchTransmittanceCurve));
return density;
}
float light_transmittance(float3 rayFrom, float3 rayDirection)
{
BRUSH_MASK(brushMask);
BRUSH_MASK_CLEAR_ALL(brushMask);
float3 rayTo = rayFrom + rayMarchMaxRayDistance * rayDirection;
float tMin = kFltMax;
float tMax = -kFltMax;
RAY_MARCH_AABB_TREE_RAY_CAST(aabbTree, aabbRoot, rayFrom, rayTo,
BRUSH_MASK_SET(brushMask, iData);
tMin = min(tMin, t.x);
tMax = max(tMax, t.y);
);
float rayDist = (tMax - tMin) * rayMarchMaxRayDistance;
float transmittance = 1.0f;
float stepSize = rayDist / numLightMarchSteps;
float3 rayStep = rayDirection * stepSize;
float3 p = rayFrom;
[loop] for (int iStep = 0; iStep < numLightMarchSteps; ++iStep)
{
p += rayStep;
float sdfSample = 0.0f;
SdfBrushMaterial mat;
SDF_RAY_MARCH_MASKED_BRUSHES(sdfSample, p, brushMask, mat, true);
if (sdfSample < 0.0f)
{
float density = rayMarchVolumeDensity * sample_density(p, sdfSample);
transmittance *= exp(-density * stepSize * rayMarchAbsorption.y);
if (transmittance < 0.1f)
{
transmittance = 0.0f;
break;
}
}
}
return rayMarchDarknesThreshold + (1.0f - rayMarchDarknesThreshold) * transmittance;
}
// https://github.com/TheAllenChou/unity-ray-marching/blob/master/unity-ray-marching/Assets/Shader/Ray%20Marching/Resources/RayMarcherCs.compute
RayMarchResults ray_march_surface
(
float3 rayFrom,
float3 rayDirection,
out float3 normal
)
{
RayMarchResults results;
results.hit = false;
results.pos = rayFrom;
results.mat = init_brush_material();
normal = -rayDirection;
BRUSH_MASK(brushMask);
BRUSH_MASK_CLEAR_ALL(brushMask);
// gather shapes around ray by casting it against AABB tree
float3 rayTo = rayFrom + rayMarchMaxRayDistance * rayDirection;
float tMin = kFltMax;
float tMax = -kFltMax;
RAY_MARCH_AABB_TREE_RAY_CAST(aabbTree, aabbRoot, rayFrom, rayTo,
BRUSH_MASK_SET(brushMask, iData);
tMin = min(tMin, t.x);
tMax = max(tMax, t.y);
);
float rayDist = (tMax - tMin) * rayMarchMaxRayDistance;
// miss any AABB (tMin > 1.0f) ?
clip(1.0f - tMin);
// start at ray's earliest intersection with AABB tree
float3 p = lerp(rayFrom, rayTo, tMin);
// march ray
float dist = 0.0f;
[loop] for (int iStep = 0; iStep < maxRayMarchSteps; ++iStep)
{
float sdfSample = 0.0f;
SDF_RAY_MARCH_MASKED_BRUSHES(sdfSample, p, brushMask, results.mat, false);
if (sdfSample < rayMarchHitDistance)
{
SDF_RAY_MARCH_MASKED_BRUSHES(sdfSample, p, brushMask, results.mat, true);
#ifdef MUDBUN_RAY_MARCHING_COMPUTE_NORMAL
SDF_RAY_MARCH_NORMAL(normal, p, brushMask, 1e-3f);
#endif
results.hit = true;
results.pos = p;
break;
}
p += sdfSample * rayDirection;
dist += sdfSample;
if (dist > rayMarchMaxRayDistance || iStep == maxRayMarchSteps - 1)
discard;
}
if (!results.hit)
discard;
return results;
}
// https://github.com/TheAllenChou/unity-ray-marching/blob/master/unity-ray-marching/Assets/Shader/Ray%20Marching/Resources/RayMarcherCs.compute
// https://shaderbits.com/blog/creating-volumetric-ray-marcher
// https://github.com/SebLague/Clouds/blob/master/Assets/Scripts/Clouds/Shaders/Clouds.shader
RayMarchResults ray_march_volume
(
float3 rayFrom,
float3 rayDirection,
float3 backgroundColor,
sampler2D ditherTexture,
int ditherTextureSize,
float2 screenPos
)
{
RayMarchResults results;
results.hit = false;
results.pos = rayFrom;
results.mat = init_brush_material();
// TODO: this is temp
return results;
BRUSH_MASK(brushMask);
BRUSH_MASK_CLEAR_ALL(brushMask);
// gather shapes around ray by casting it against AABB tree
float3 rayTo = rayFrom + rayMarchMaxRayDistance * rayDirection;
float tMin = kFltMax;
float tMax = -kFltMax;
RAY_MARCH_AABB_TREE_RAY_CAST(aabbTree, aabbRoot, rayFrom, rayTo,
BRUSH_MASK_SET(brushMask, iData);
tMin = min(tMin, t.x);
tMax = max(tMax, t.y);
);
float rayDist = (tMax - tMin) * rayMarchMaxRayDistance;
// miss any AABB (tMin > 1.0f) ?
clip(1.0f - tMin);
// ray march step size is step size along camera direction
// actual ray step size is different for each ray
float3 camDir = mul(unity_CameraToWorld, float4(0.0f, 0.0f, 1.0f, 0.0f)).xyz;
float actualStepSize = rayMarchStepSize / dot(camDir, rayDirection);
float3 rayStep = rayDirection * actualStepSize;
// start at ray's earliest intersection with AABB tree
float3 p = lerp(rayFrom, rayTo, tMin);
// snap ray start position to view-aligned planes
//p -= fmod(dot(p - rayFrom, rayDirection), actualStepSize) * rayDirection;
// jitter start position
p -= tex2D(ditherTexture, screenPos / ditherTextureSize).r * rayStep;
// march ray
float transmittance = 1.0f;
float3 lightEnergy = 0.0f;
float3 toLightDir = -rayMarchLightDirection.xyz;
int numRayMarchSteps = min(maxRayMarchSteps, ceil(rayDist / actualStepSize));
[loop] for (int iStep = 0; iStep < numRayMarchSteps; ++iStep)
{
p += rayStep;
float sdfSample = 0.0f;
SdfBrushMaterial mat;
SDF_RAY_MARCH_MASKED_BRUSHES(sdfSample, p, brushMask, mat, false);
if (sdfSample < 0.0f)
{
float density = rayMarchVolumeDensity * sample_density(p, sdfSample, 0.1f, 0.3f, 2.0f);
switch ((int) rayMarchLightPositionType.w)
{
case kLightTypePoint:
toLightDir = normalize(rayMarchLightPositionType.xyz - p);
break;
}
float lightTransmittance = light_transmittance(p, toLightDir);
lightEnergy += density * actualStepSize * transmittance * lightTransmittance;
transmittance *= exp(-density * actualStepSize * rayMarchAbsorption.x);
if (transmittance < 1e-2f)
{
transmittance = 0.0f;
break;
}
}
}
float3 cloudColor = lightEnergy; // TODO: * lightColor
results.mat.color.rgb = backgroundColor * transmittance + cloudColor;
//results.mat.color.a = saturate(1.0f - transmittance * exp(rayMarchTransmittanceCurve));
results.mat.color.a = saturate(1.0f - transmittance);
return results;
}
#else
struct SdfBrushMaterialDummy
{
float4 color;
float4 emissionHash;
float4 metallicSmoothnessSizeTightness;
float4 textureWeight;
};
struct RayMarchResults
{
bool hit;
float3 pos;
SdfBrushMaterialDummy mat;
};
RayMarchResults ray_march_surface
(
float3 rayOrigin,
float3 rayDirection,
out float3 normal
)
{
RayMarchResults res;
res.hit = false;
res.pos = rayOrigin;
res.mat.color = float4(0.00001f * rayDirection, 1.0f);
res.mat.emissionHash = 0.0f;
res.mat.metallicSmoothnessSizeTightness = 0.0f;
res.mat.textureWeight = 0.0f;
normal = -rayDirection;
return res;
}
RayMarchResults ray_march_volume
(
float3 rayOrigin,
float3 rayDirection,
float3 screenColor,
sampler2D ditherTexture,
int ditherTextureSize,
float2 screenPos
)
{
RayMarchResults res;
res.hit = false;
res.pos = rayOrigin;
res.mat.color = float4(0.00001f * rayDirection, 1.0f);
return res;
}
#endif
#endif