Skip to content

Instantly share code, notes, and snippets.

@cnlohr
Created October 28, 2021 04:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cnlohr/3d76264458095fdf53442b8a518c4232 to your computer and use it in GitHub Desktop.
Save cnlohr/3d76264458095fdf53442b8a518c4232 to your computer and use it in GitHub Desktop.
Ray Casting Logic For No Euclid Avatar in VRChat
// This was the version from Dec 12, 2020
/*
NO EUCLID 2: This time written from the ground up for MODERN (2020)
GPUs! No more janky math stuff to support the NVIDIA 460M or some
awful ancient ATI card.
The three textures we have are:
Read only when a collision may be possible.
GeoTex:
< BlockID, Meta, Density, 0 >
Read every step.
MovTex:
< DensityX, DensityY, DensityZ, DensityW >
Read every step.
AddTex:
< DoesBlockExist, 0, 0, 0>
Note: 0 is "reserved" for other functionality.
DISCUSSION: Representing a blob at "0,0,0"
GeoTex defines the blocks as a function of <0.0, 0.0, 0.0> to
<1.0, 1.0, 1.0> I.e. one off from origin with the center offset being,
<0.5, 0.5, 0.5> And AddTex is offset by <-0.5, -0.5, -0.5> from that.
This means if you have a solit block centered at <0.5, 0.5, 0.5> then
you should set <0,0,0>.R to be 1.0f density in GeoTex. And, in AddTex,
<0,0,0><0,0,1><0,1,0><1,0,0><1,1,0><0,1,1><1,0,1><1,1,1> you should
set the R term in AddTex to be 1.0.
TODO:
* Figure out how to do fmix but values are always positive. this is
important for our function where we can't handle negative logic, so we
shift the world over.
*/
#include "tanoise.cginc"
#include "UnityLightingCommon.cginc"
#define glsl_mod(x,y) abs(((x)-(y)*floor((x)/(y))))
//abs(((x)-(y)*floor((x)/(y))))
fixed4 GetTexture3( Texture2D<fixed4> samp, int3 xyz )
{
xyz.z = (ARRAYSIZE*40 - xyz.z - 1);
xyz = glsl_mod( xyz, (uint)ARRAYSIZE );
return samp.Load( int3( xyz.x + xyz.y*ARRAYSIZE, PageOffset + xyz.z + ARRAYSIZE, 0 ) );
}
fixed4 GetTexture2( Texture2D<fixed4> samp, int2 xy )
{
xy = glsl_mod( xy, uint2(256,256) );
return samp.Load( int3( xy.x, xy.y, 0 ) );
}
//This is the shape mixing function for subtrace.
float3 mixfn( float3 ins )
{
//Wonky function...
// return lerp( cos( ins*3.14159 + 3.14159 )/2.0+0.5, ins, ShapeMixVal );
//Sharp edges...
// return ins;
//Nice, smooth function we decided to use.
float3 coss = -cos( ins*3.141592653589f );
float3 sins = sign( coss );
coss = abs( coss );
coss = pow( coss, float3( ShapeMixVal, ShapeMixVal, ShapeMixVal ) );
coss *= sins;
return coss / 2.0 + 0.5;
}
float maintrace( float surfacedistance )
{
Color = 1.;
//Start at RayPos -> RayDir
PerceivedDistanceTraversed = 0;
TotalDistanceTraversed = 0;
int iteration = 0;
fixed4 LastMovTex;
fixed3 LastCalcMov;
float3 CurrentRayDir;
//TODO: If we're above or below the map, scoot our start pointer to the edge of the map.
float adv = 0;
//TODO: Quicktrace to surface of square.
#if 1
if( ParentTo )
{
float lin = max( 0, (length( surfacedistance )-1.) );
adv = max( adv, lin );
}
else
{
float lin = max( 0, (length( surfacedistance )-1.) );
adv = max( adv, lin );
}
#endif
//Trace to Y.
{
float yadv = 0;
if( RayPos.y >= ARRAYSIZE )
{
float distY = RayPos.y-(ARRAYSIZE-1);
yadv = -distY / RayDir.y;
}
else if( RayPos.y <= 0 )
{
float distY = -RayPos.y;
yadv = distY / RayDir.y;
}
adv = max( adv, yadv );
}
//Trace to XZ
if( !ParentTo )
{
float xadv = 0, zadv = 0;
if( RayPos.x >= (ARRAYSIZE) )
{
float distX = RayPos.x-(ARRAYSIZE-1);
xadv = -distX / RayDir.x;
}
else if( RayPos.x <= 0 )
{
float distX = -RayPos.x;
xadv = distX / RayDir.x;
}
if( RayPos.z >= (ARRAYSIZE))
{
float distZ = RayPos.z-(ARRAYSIZE);
zadv = -distZ / RayDir.z;
}
else if( RayPos.z <= 0 )
{
float distZ = -RayPos.z;
zadv = distZ / RayDir.z;
}
adv = max( adv, xadv );
adv = max( adv, zadv );
}
{
adv += 0.01;
PerceivedDistanceTraversed += adv;
TotalDistanceTraversed += adv;
RayPos += RayDir * adv;
}
//Prevent any negative indexing into texture.
RayPos += ARRAYSIZE * FORCEADVANCE;
fixed3 Normal;
int3 CellD;
int3 CellP = int3( RayPos );
fixed4 VecZP = 0.;
fixed4 VecZM = 0.;
float3 PartialRayPos = frac( RayPos );
{
//Fist step is to step into the the cell, colliding with the grid
//defined by AddTex.
//Used for binary search subtracer.
half TracedMinM = 0.;
half MinDist;
half MixTot = 0;
int3 LowestAxis = 0.0;
half3 DirComps;
half3 DirAbs;
UNITY_LOOP
do
{
iteration++;
if( CellP.y >= ARRAYSIZE*(FORCEADVANCE+1) ) discard;
if( CellP.y < ARRAYSIZE*FORCEADVANCE ) discard;
if( !ParentTo )
{
if( CellP.x < ARRAYSIZE*FORCEADVANCE ||
CellP.z < ARRAYSIZE*FORCEADVANCE ||
CellP.x >= ARRAYSIZE*(FORCEADVANCE+1) ||
CellP.z >= ARRAYSIZE*(FORCEADVANCE+1) )
discard;
}
LastMovTex = GetTexture3( MovTex, CellP );
LastCalcMov = LastMovTex.xyz;
CurrentRayDir = normalize( RayDir * LastCalcMov );
DirComps = -sign( CurrentRayDir ); //+1 if pos, -1 if neg
DirAbs = abs( CurrentRayDir );
CellD = int3( sign( CurrentRayDir ) );
//We are tracing into a cell. Need to figure out how far we move
//to get into the next cell.
float3 NextSteps = frac( PartialRayPos * DirComps );
//Anywhere we have already stepped, force it to be one full step forward.
NextSteps = NextSteps * ( 1 - LowestAxis ) + LowestAxis;
//Find out how many units the intersection point between us and
//the next intersection is in ray space. This is effectively
float3 Dists = NextSteps / DirAbs;
//XXX TODO: This should be optimized!
LowestAxis = (Dists.x < Dists.y) ?
( ( Dists.x < Dists.z ) ? int3( 1, 0, 0 ) : int3( 0, 0, 1 ) ) :
( ( Dists.y < Dists.z ) ? int3( 0, 1, 0 ) : int3( 0, 0, 1 ) );
//Find the closest axis. We do this so we don't overshoot a hit.
MinDist = min( min( Dists.x, Dists.y ), Dists.z );
//We will use mindist in the initial subtrace.
[branch]
if( LastMovTex.a > 0.001 )
{
{
fixed4 Geo000 = GetTexture3( GeoTex, CellP - int3( 0, 0, 0 ) );
fixed4 Geo100 = GetTexture3( GeoTex, CellP - int3( 1, 0, 0 ) );
fixed4 Geo010 = GetTexture3( GeoTex, CellP - int3( 0, 1, 0 ) );
fixed4 Geo110 = GetTexture3( GeoTex, CellP - int3( 1, 1, 0 ) );
fixed4 Geo001 = GetTexture3( GeoTex, CellP - int3( 0, 0, 1 ) );
fixed4 Geo101 = GetTexture3( GeoTex, CellP - int3( 1, 0, 1 ) );
fixed4 Geo011 = GetTexture3( GeoTex, CellP - int3( 0, 1, 1 ) );
fixed4 Geo111 = GetTexture3( GeoTex, CellP - int3( 1, 1, 1 ) );
VecZM = fixed4( Geo000.b, Geo100.b, Geo010.b, Geo110.b );
VecZP = fixed4( Geo001.b, Geo101.b, Geo011.b, Geo111.b );
//It goes: (rgba) = (-x,-y;+x,-y;+y,-x;+x,+y)
}
int i;
#define STEPSET 6
float step = MinDist * 1./STEPSET;
float fmarch = step/2.0;
float3 checkpos = frac( PartialRayPos + CurrentRayDir * fmarch );
for( i = 0; i < STEPSET; i++ )
{
checkpos += CurrentRayDir * step;
fmarch += step;
float3 tnpos = mixfn( checkpos );
MixTot = lerp( lerp( lerp(VecZP.a, VecZP.b, tnpos.x ),
lerp( VecZP.g, VecZP.r, tnpos.x ), tnpos.y ),
lerp( lerp(VecZM.a, VecZM.b, tnpos.x ),
lerp( VecZM.g, VecZM.r, tnpos.x ), tnpos.y ),
tnpos.z );
if( MixTot > DensityLimit ) { TracedMinM = fmarch; break; }
}
//If we have a hit, pull the ripcord. We can worry about what else
//We want to do later, but we should not put the code to figure
//that out here.
if( i != STEPSET )
{
break;
}
}
//Continue the path motion.
//We now know which direction we wish to step.
CellP += CellD * LowestAxis;
float3 Motion = MinDist * CurrentRayDir;
PartialRayPos = frac( PartialRayPos + Motion );
PerceivedDistanceTraversed += length( Motion / LastCalcMov );
TotalDistanceTraversed += length( Motion );
} while( iteration < MaxIterations );
if( iteration >= MaxIterations )
{
discard;
}
//##############################################################
//We hit geometry, and we know we hit it, so we have to drop out to
//the subsurface.
//##############################################################
float fmarch;
float step = MinDist * 1./STEPSET;
fmarch = TracedMinM;
float3 checkpos;
PartialRayPos = frac( PartialRayPos + CurrentRayDir * .001 );
for( int i = 0; i < 15; i++ )
{
//Run twice, for each possible step direction.
checkpos = ( PartialRayPos + CurrentRayDir * fmarch );
//You may notice here - we don't actually shoot a ray through.
float3 tnpos = mixfn( checkpos );
MixTot = lerp( lerp( lerp(VecZP.a, VecZP.b, tnpos.x ),
lerp( VecZP.g, VecZP.r, tnpos.x ), tnpos.y ),
lerp( lerp(VecZM.a, VecZM.b, tnpos.x ),
lerp( VecZM.g, VecZM.r, tnpos.x ), tnpos.y ),
tnpos.z );
float guessprox = abs( MixTot - DensityLimit );
//If we overstep, we know we've gone too far.
if( MixTot > DensityLimit )
fmarch -= step;
else
fmarch += step;
step *= 0.65;
}
//Color = fixed4( fmarch*.6+.1, 0.0, 0.0, 1.0 );
PerceivedDistanceTraversed += length( fmarch * CurrentRayDir / LastCalcMov );
TotalDistanceTraversed += length( fmarch * CurrentRayDir );
PartialRayPos = checkpos;
//XXX: TODO: How compute normal?
//Find normal...
//We do this by inching a very small amount in each direction
//to compute the gradiant of the density of the metasurface.
//The normal is actually the normalized gradient.
float3 npx = checkpos + float3( 0.01, 0., 0. );
npx = mixfn( npx );
half mixtotx = lerp( lerp( lerp(VecZP.a, VecZP.b, npx.x ), lerp( VecZP.g, VecZP.r, npx.x ), npx.y ),
lerp( lerp(VecZM.a, VecZM.b, npx.x ), lerp( VecZM.g, VecZM.r, npx.x ), npx.y ), npx.z );
float3 npy = checkpos + float3( 0.0, 0.01, 0. );
npy = mixfn( npy );
half mixtoty = lerp( lerp( lerp(VecZP.a, VecZP.b, npy.x ), lerp( VecZP.g, VecZP.r, npy.x ), npy.y ),
lerp( lerp(VecZM.a, VecZM.b, npy.x ), lerp( VecZM.g, VecZM.r, npy.x ), npy.y ), npy.z );
float3 npz = checkpos + float3( 0., 0., 0.01 );
npz = mixfn( npz );
half mixtotz = lerp( lerp( lerp(VecZP.a, VecZP.b, npz.x ), lerp( VecZP.g, VecZP.r, npz.x ), npz.y ),
lerp( lerp(VecZM.a, VecZM.b, npz.x ), lerp( VecZM.g, VecZM.r, npz.x ), npz.y ), npz.z );
Normal = -normalize( float3( mixtotx, mixtoty, mixtotz ) - MixTot );
}
//We have to update our global positon and cell, because we may have hopped
//cells since our trace.
float3 GlobalPos = CellP + PartialRayPos;
PartialRayPos = GlobalPos - (int3)GlobalPos;
CellP = (int3)(GlobalPos );
//Re-integrate to global position.
//Here, we have:
// Normal
// TotalDistanceTraversed
// PerceivedDistanceTraversed
// PartialRayPos
// CellP
fixed3 CellPoint;
{
//Next step: shade it.
//Select an actuall source cell.
//If we're at the cusp or edge between cell types
//Mix between them.
//TODO: Figure out how to not do this extra operation. We "should"
//be able to re-use the previous VecZM/VecZP
fixed4 Geo000 = GetTexture3( GeoTex, CellP - int3( 0, 0, 0 ) );
fixed4 Geo100 = GetTexture3( GeoTex, CellP - int3( 1, 0, 0 ) );
fixed4 Geo010 = GetTexture3( GeoTex, CellP - int3( 0, 1, 0 ) );
fixed4 Geo110 = GetTexture3( GeoTex, CellP - int3( 1, 1, 0 ) );
fixed4 Geo001 = GetTexture3( GeoTex, CellP - int3( 0, 0, 1 ) );
fixed4 Geo101 = GetTexture3( GeoTex, CellP - int3( 1, 0, 1 ) );
fixed4 Geo011 = GetTexture3( GeoTex, CellP - int3( 0, 1, 1 ) );
fixed4 Geo111 = GetTexture3( GeoTex, CellP - int3( 1, 1, 1 ) );
VecZM = fixed4( Geo000.b, Geo100.b, Geo010.b, Geo110.b );
VecZP = fixed4( Geo001.b, Geo101.b, Geo011.b, Geo111.b );
VecZP = step( -VecZP, -0.001 );
VecZM = step( -VecZM, -0.001 );
//Provide some noise to make interface between blocks look natural
{
//float sn = snoise( GlobalPos.xyz * 8. );
//We don't need good random noise, we just need some random noise.
//So, we can extrapolate this.
float4 PN1 = tanoise3( GlobalPos.xyz * 8. )*3.; //cos( sn * 3.14159 + float4( 0., 1.5707, 3.14159, 4.71238 ) );
float4 PN2 = (1.-PN1);
VecZP *= (PN1*.14+1.);
VecZM *= (PN2*.14+1.);
}
{
//Mix betwen the edges.
float3 ppmod = 1.-frac(GlobalPos+fixed3(0.0,0.0,0.0));
float4 pmx = float4( 1.-ppmod.x,ppmod.x,1.-ppmod.x,ppmod.x );
VecZP *= pmx *
(ppmod.z) * float4( (1.-ppmod.yy ), ppmod.yy );
VecZM *= pmx *
(1.-ppmod.z) * float4( ( 1.-ppmod.yy ), ppmod.yy );
}
float4 minsA = max( VecZP, VecZM );
Color = minsA.gaga;
//return 0;
float2 minsB = max ( minsA.xy, minsA.zw );
float amin = max( minsB.x, minsB.y );
if( VecZM.x == amin ) CellPoint = float3( 0., 0., 0. );
else if( VecZM.y == amin ) CellPoint = float3( 1., 0., 0. );
else if( VecZM.z == amin ) CellPoint = float3( 0., 1., 0. );
else if( VecZM.w == amin ) CellPoint = float3( 1., 1., 0. );
else if( VecZP.x == amin ) CellPoint = float3( 0., 0., 1. );
else if( VecZP.y == amin ) CellPoint = float3( 1., 0., 1. );
else if( VecZP.z == amin ) CellPoint = float3( 0., 1., 1. );
else CellPoint = float3( 1., 1., 1. );
//Color = fixed4( CellPoint * fixed3( 1, 1, 1 ), 1 );
//return 0;
}
float3 OutColor;
{
//Uncomment this to disable the effect from the material selectionabove.
float4 ExtraData = GetTexture3( GeoTex, CellP - CellPoint );
float3 ppmod = frac(GlobalPos-0.5);// - 0.5;
//Color = fixed4( CellP%2, 1.0 );
//return 0;
//Discard 0xFF. It's a "Done" flag.
if( ExtraData.a >= 1.0 )
{
discard;
}
//Ugh, Y is stored in reverse order. XXX TODO FIXME
int ID = 255 - (ExtraData.a*255.);
int META = 0;
float4 BaseColor = GetTexture2( TileAttribTex, int2 ( 0+META, ID ) );
float4 NoiseColor = GetTexture2( TileAttribTex, int2 ( 1+META, ID ) );
float4 NoiseSet = GetTexture2( TileAttribTex, int2 ( 2+META, ID ) );
float4 NoiseMux = GetTexture2( TileAttribTex, int2 ( 3+META, ID ) );
float4 CoreData = GetTexture2( TileAttribTex, int2 ( 4+META, ID ) );
float4 TimeSettings = GetTexture2( TileAttribTex, int2 ( 5+META, ID ) );
float4 Speckles = GetTexture2( TileAttribTex, int2 ( 6+META, ID ) );
float4 ShaderEndingTerms = GetTexture2( TileAttribTex, int2 ( 7+META, ID ) );
float3 noiseplace = (NoiseSet.xyz * float3( GlobalPos ) + TimeSettings.xyz * _Time.a*4.) * 8.;
float noise = tanoise3_1d( noiseplace ) * NoiseMux.r
+ tanoise3_1d( noiseplace * 2. ) * NoiseMux.g +
tanoise3_1d( noiseplace * 4. ) * NoiseMux.b + tanoise3_1d( noiseplace * 8. ) * NoiseMux.a + NoiseColor.a;
float core = length( (ppmod.xyz-.5) * float3( 1., CoreData.y, 1.0 ) ) * CoreData.x*10.;
core = glsl_mod( core+noise, CoreData.z ) * CoreData.a;
noise = noise+core;
float3 NoiseOut = noise * ((noise<Speckles.a*1.2)?Speckles.rgb:NoiseColor.rgb);
noise = abs( noise );
OutColor = BaseColor.rgb + NoiseOut;
//Directional lights only
float amt = dot( Normal, LightInRaySpace );
amt = amt * 0.5 + 0.2;
Color = pow( fixed4( OutColor*amt, 1. ), 1.8 );
}
// Color = -_WorldSpaceLightPos0;
// Color = fixed4( OutColor, 1. );
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment