Created
February 27, 2025 16:13
-
-
Save jesterswilde/f63adc490cd85625c5958536a1c1aecb to your computer and use it in GitHub Desktop.
Raymarcher
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Collections.Generic; | |
using UnityEditor; | |
using UnityEngine; | |
using Sirenix.OdinInspector; | |
namespace Instructions | |
{ | |
[ExecuteInEditMode] | |
public abstract class Instruction : MonoBehaviour { | |
[SerializeField, ReadOnly] | |
protected List<Instruction> children = new List<Instruction>(); | |
protected bool hierarchyIsDirty = true; | |
[Button] | |
protected void UpdateHierarchy(){ | |
children.Clear(); | |
foreach(Transform trans in transform){ | |
var child = trans.GetComponent<Instruction>(); | |
if(child != null){ | |
children.Add(child); | |
child.UpdateHierarchy(); | |
} | |
} | |
} | |
public virtual void SetData(List<int> instructions, List<float> data){ | |
foreach(var child in children){ | |
child.SetData(instructions, data); | |
} | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Collections.Generic; | |
using Sirenix.Serialization; | |
using UnityEngine.Scripting; | |
namespace Instructions | |
{ | |
public class OpMatrix : Instruction{ | |
public bool IsRoot = false; | |
void CheckIfRoot(){ | |
if(transform.parent == null || GetComponentInParent<OpMatrix>() == null){ | |
IsRoot = true; | |
} | |
} | |
void Start(){ | |
CheckIfRoot(); | |
} | |
public override void SetData(List<int> instructions, List<float> data) | |
{ | |
var mat = transform.localToWorldMatrix; | |
instructions.Add((int)StackInstruction.Matrix); | |
data.AddRange(new float[]{ | |
mat.m00, mat.m10, mat.m20, mat.m30, | |
mat.m01, mat.m11, mat.m21, mat.m31, | |
mat.m02, mat.m12, mat.m22, mat.m32, | |
}); | |
base.SetData(instructions, data); | |
if(IsRoot) | |
instructions.Add((int)StackInstruction.NullMatrix); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma kernel CSMain | |
Texture2D<float4> Source; | |
RWTexture2D<float4> Destination; | |
float4x4 _CameraToWorld; | |
float4x4 _CameraInverseProjection; | |
float3 _Light; | |
bool positionLight; | |
static const float maxDst = 80; | |
static const float epsilon = 0.001f; | |
static const float shadowBias = epsilon * 50; | |
struct Shape { | |
float3 position; | |
float3 size; | |
float3 colour; | |
int shapeType; | |
int operation; | |
float blendStrength; | |
int numChildren; | |
}; | |
StructuredBuffer<Shape> shapes; | |
Buffer<float> data; | |
Buffer<int> instructions; | |
RWBuffer<float> debug; | |
int numShapes; | |
struct Ray { | |
float3 origin; | |
float3 direction; | |
}; | |
Ray CreateRay(float3 origin, float3 direction) { | |
Ray ray; | |
ray.origin = origin; | |
ray.direction = direction; | |
return ray; | |
} | |
Ray CreateCameraRay(float2 uv) { | |
float3 origin = mul(_CameraToWorld, float4(0,0,0,1)).xyz; | |
float3 direction = mul(_CameraInverseProjection, float4(uv,0,1)).xyz; | |
direction = mul(_CameraToWorld, float4(direction,0)).xyz; | |
direction = normalize(direction); | |
return CreateRay(origin,direction); | |
} | |
float GetShapeDistance(Shape shape, float3 eye) { | |
if (shape.shapeType == 0) { | |
return SphereDistance(eye, shape.position, shape.size.x); | |
} | |
else if (shape.shapeType == 1) { | |
return CubeDistance(eye, shape.position, shape.size); | |
} | |
else if (shpe.shapeType == 2) { | |
return TorusDistance(eye, shape.position, shape.size.x, shape.size.y); | |
} | |
return maxDst; | |
} | |
float vmax(float2 v) { | |
return max(v.x, v.y); | |
} | |
float vmax(float3 v) { | |
return max(max(v.x, v.y), v.z); | |
} | |
float vmax(float4 v) { | |
return max(max(v.x, v.y), max(v.z, v.w)); | |
} | |
float4x4 makeMatrix(inout int i){ | |
float4x4 m; | |
m[0] = float4(data[i++],data[i++],data[i++],data[i++]); | |
m[1] = float4(data[i++],data[i++],data[i++],data[i++]); | |
m[2] = float4(data[i++],data[i++],data[i++],data[i++]); | |
m[3] = float4(0,0,0,1); | |
return m; | |
} | |
/* | |
Operations Reference: | |
Push - 0, | |
Push3 - 1, | |
Push_N - 2, | |
CopyPosition - 3, | |
Translate 4, | |
Matrix 5, | |
Union - 6, | |
Subtract - 7, | |
Intersect - 8, | |
Blend - 9, | |
Sphere - 10, | |
Box - 11, | |
Torus - 12, | |
InfiniteCylinder - 13 | |
Mandlebulb - 14 | |
*/ | |
const int OpLength[9] = {-1,-1,-1,-2,1,0,-3,-5,3}; | |
const int stackSize = 64; | |
int numInstructions; | |
float Evaluate(float3 pos, int id){ | |
int stackIndex = -1; | |
int dataIndex = 0; | |
float stack[32]; | |
bool hasMatrix = false; | |
float4x4 curMatrix; | |
for(int i = 0; i < 32; i++){ | |
stack[i] = 0; | |
} | |
for(int instI = 0; instI < numInstructions; instI++){ | |
int operation = instructions[instI]; | |
//ToPush Val | |
if (operation == 0) { //Push | |
stack[++stackIndex] = data[dataIndex++]; | |
} | |
else if(operation == 1){//Push3 | |
stack[++stackIndex] = data[dataIndex++]; | |
stack[++stackIndex] = data[dataIndex++]; | |
stack[++stackIndex] = data[dataIndex++]; | |
} | |
//N Val | |
else if (operation == 2) { //PushN | |
for(int i = 0; i < data[dataIndex++]; i++) { | |
stack[++stackIndex] = data[dataIndex++]; | |
} | |
} | |
else if (operation == 3){//CopyPosition | |
stack[++stackIndex] = pos.x; | |
stack[++stackIndex] = pos.y; | |
stack[++stackIndex] = pos.z; | |
} | |
else if(operation == 4){//Translate | |
stack[stackIndex-3] -= stack[stackIndex]; | |
stack[stackIndex-4] -= stack[stackIndex-1]; | |
stack[stackIndex-5] -= stack[stackIndex-2]; | |
stackIndex -= 3; | |
} | |
else if(operation == 5){//Matrix | |
hasMatrix = true; | |
curMatrix = makeMatrix(dataIndex); | |
} | |
//2 Val1 Val2 | |
else if (operation == 6) { //Union | |
stack[stackIndex-1] = min(stack[stackIndex], stack[stackIndex-1]); | |
stackIndex -= 1; | |
} | |
//2 Val1 Val2 | |
else if (operation == 7) { // Subtract | |
stack[stackIndex-1] = max(stack[stackIndex-1], -stack[stackIndex]); | |
stackIndex -= 1; | |
} | |
//2 Val1 Val2 | |
else if (operation == 8) { //Intersect | |
stack[stackIndex-1] = max(stack[stackIndex-1], stack[stackIndex]); | |
stackIndex -= 1; | |
} | |
//3 Val1 Val2 BlendStrength | |
else if (operation == 9) { //Blend | |
float a = stack[stackIndex - 2]; | |
float b = stack[stackIndex - 1]; | |
float k = stack[stackIndex]; | |
float h = clamp( 0.5+0.5*(b-a)/k, 0.0, 1.0 ); | |
stackIndex -= 2; | |
stack[stackIndex] = lerp( b, a, h ) - k*h*(1.0-h); | |
} | |
//4 PosX PosY PosZ Radius | |
else if (operation == 10) { //Sphere | |
float3 p = float3(stack[stackIndex-3], stack[stackIndex-2], stack[stackIndex-1]); | |
if(hasMatrix) | |
p = mul(curMatrix, float4(p,1)).xyz; | |
stack[stackIndex - 3] = length(p) - stack[stackIndex]; | |
stackIndex -= 3; | |
//stack[stackIndex] = 0.5f; | |
} | |
//[1,2,3,2,2,3] | |
// ^ | |
//6 PointX PointY PointZ BoundsX BoudnsY BoundsZ | |
else if (operation == 11) { //Box | |
float3 p = float3(stack[stackIndex-5], stack[stackIndex-4], stack[stackIndex-3]); | |
if(hasMatrix) | |
p = mul(curMatrix, float4(p,1)).xyz; | |
float3 b = float3(stack[stackIndex-2], stack[stackIndex-1], stack[stackIndex]); | |
float3 q = abs(p) - b; | |
stack[stackIndex - 5] = length(max(q, 0.0)) + min(max(q.x, max(q.y, q.z)), 0); | |
stackIndex -= 5; | |
} | |
//5 PosX PosY PosZ InnerRadius OuterRadius | |
else if (operation == 12) { //Torus | |
float3 p = float3(stack[stackIndex-4], stack[stackIndex-3], stack[stackIndex-2]); | |
if(hasMatrix) | |
p = mul(curMatrix, float4(p,1)).xyz; | |
stack[stackIndex-4] = length(float2(length(p.xz) - stack[stackIndex-1], p.y)) - stack[stackIndex]; | |
stackIndex -= 4; | |
} | |
else if (operation == 13) { //InfiniteCylinder | |
float3 p = float3(stack[stackIndex-5], stack[stackIndex-4], stack[stackIndex-3]); | |
if(hasMatrix) | |
p = mul(curMatrix, float4(p,1)).xyz; | |
float3 c = float3(stack[stackIndex-2], stack[stackIndex-1], stack[stackIndex]); | |
stack[stackIndex-5] = length(p.xz-c.xy)-c.z; | |
stackIndex -= 5; | |
} else if (operation == 14){ //SmoothSubtract | |
float a = stack[stackIndex - 2]; | |
float b = stack[stackIndex - 1]; | |
float k = stack[stackIndex]; | |
float h = clamp( 0.5-0.5*(b+a)/k, 0.0, 1.0 ); | |
stackIndex -= 2; | |
stack[stackIndex] = lerp(b, -a, h) + k*h*(1.0-h); | |
}else if (operation == 15){//SmoothIntersect | |
float a = stack[stackIndex - 2]; | |
float b = stack[stackIndex - 1]; | |
float k = stack[stackIndex]; | |
float h = clamp( 0.5-0.5*(b-a)/k, 0.0, 1.0 ); | |
stackIndex -= 2; | |
stack[stackIndex] = lerp(b, a, h) + k*h*(1.0-h); | |
} | |
else if(operation == 16){// NullMatrix | |
hasMatrix = false; | |
} | |
else if (operation == 17){ // Capsule | |
float3 p = float3(stack[stackIndex-4], stack[stackIndex-3], stack[stackIndex-2]); | |
if(hasMatrix) | |
p = mul(curMatrix, float4(p,1)).xyz; | |
p.y -= clamp(p.y, 0.0, stack[stackIndex-1]); | |
stack[stackIndex-4] = length(p) - stack[stackIndex]; | |
stackIndex -= 4; | |
} | |
} | |
debug[id*10] = stackIndex; | |
for(int i = 0; i < 9; i++){ | |
debug[id*10 + i + 1] = stack[i]; | |
} | |
return stack[stackIndex]; | |
} | |
float3 EstimateNormal(float3 p) { | |
float x = Evaluate(float3(p.x+epsilon,p.y,p.z),0) - Evaluate(float3(p.x-epsilon,p.y,p.z),0); | |
float y = Evaluate(float3(p.x,p.y+epsilon,p.z),0) - Evaluate(float3(p.x,p.y-epsilon,p.z),0); | |
float z = Evaluate(float3(p.x,p.y,p.z+epsilon),0) - Evaluate(float3(p.x,p.y,p.z-epsilon),0); | |
return normalize(float3(x,y,z)); | |
} | |
[numthreads(8,8,1)] | |
void CSMain (uint3 id : SV_DispatchThreadID) | |
{ | |
uint width,height; | |
Destination.GetDimensions(width, height); | |
int index = id.x + id.y * width; | |
Destination[id.xy] = Source[id.xy]; | |
float2 uv = id.xy / float2(width,height) * 2 - 1; | |
float rayDst = 0; | |
Ray ray = CreateCameraRay(uv); | |
int maxMarchSteps = 64; | |
int marchSteps = 0; | |
while (rayDst < maxDst && marchSteps < maxMarchSteps) { | |
marchSteps ++; | |
float dst = Evaluate(ray.origin,index); | |
if (dst <= epsilon) { | |
float3 pointOnSurface = ray.origin + ray.direction * dst; | |
float3 normal = EstimateNormal(pointOnSurface - ray.direction * epsilon); | |
float3 lightDir = (positionLight)?normalize(_Light-ray.origin):-_Light; | |
float val = dot(normal,lightDir); | |
Destination[id.xy] = float4(val,val,val,1); | |
break; | |
} | |
ray.origin += ray.direction * dst; | |
rayDst += dst; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment