Skip to content

Instantly share code, notes, and snippets.

@jesterswilde
Created February 27, 2025 16:13
Show Gist options
  • Save jesterswilde/f63adc490cd85625c5958536a1c1aecb to your computer and use it in GitHub Desktop.
Save jesterswilde/f63adc490cd85625c5958536a1c1aecb to your computer and use it in GitHub Desktop.
Raymarcher
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);
}
}
}
}
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);
}
}
}
#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