
using UnityEngine;using UnityEngine.Rendering;using UnityEngine.Rendering.Universal;
namespace BlobFighters.RayMarching{ public class RaymarchRenderPass : ScriptableRenderPass { private static RenderTexture _staticTemporaryRenderTexture; private RenderTargetIdentifier source; // a render target identifier points to a texture directly.
public void SetSource(RenderTargetIdentifier s) { source = s; }
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { if (!Application.isPlaying) return; if (_staticTemporaryRenderTexture == null) _staticTemporaryRenderTexture = new RenderTexture(renderingData.cameraData.cameraTargetDescriptor);
CommandBuffer cmd = CommandBufferPool.Get("Ray marching as render feature"); RenderTextureDescriptor cameraTextureDesc = renderingData.cameraData.cameraTargetDescriptor; cameraTextureDesc.depthBufferBits = 0;
Blit(cmd, source, _staticTemporaryRenderTexture); RenderTexture rmResult = RMMaster.Instance.ApplyRaymarching(_staticTemporaryRenderTexture); Blit(cmd, rmResult, source);
context.ExecuteCommandBuffer(cmd); CommandBufferPool.Release(cmd); } }}using UnityEngine;using UnityEngine.Rendering.Universal;
namespace BlobFighters.RayMarching{ public class RaymarchRenderFeature : ScriptableRendererFeature { [SerializeField] private RenderPassEvent ;
private RaymarchRenderPass renderPass;
public override void Create() { renderPass = new RaymarchRenderPass { renderPassEvent = }; }
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) { renderPass.SetSource(renderer.cameraColorTarget); renderer.EnqueuePass(renderPass); } }}using System.Collections.Generic;using UnityEngine;
// ReSharper disable NotAccessedField.Local// ReSharper disable InconsistentNaming
[DefaultExecutionOrder(-10)]public class RMMaster : MonoBehaviour{ private static RMMaster _singletonMember; public ComputeShader raymarching;
private List<ComputeBuffer> buffersToDispose; private Camera cam; private Light lightSource;
private RenderTexture target;
public static RMMaster Instance { get { if (_singletonMember != null) return _singletonMember;
_singletonMember = FindObjectOfType<RMMaster>(); return _singletonMember; } }
private void Awake() { DontDestroyOnLoad(this); }
private void Init() { cam = Camera.main; lightSource = FindObjectOfType<Light>(); }
public RenderTexture ApplyRaymarching(RenderTexture source) { Init(); buffersToDispose = new List<ComputeBuffer>();
InitRenderTexture(); CreateScene(); SetParameters();
raymarching.SetTexture(0, Source, source); raymarching.SetTexture(0, Destination, target);
int threadGroupsX = Mathf.CeilToInt(cam.pixelWidth / 8.0f); int threadGroupsY = Mathf.CeilToInt(cam.pixelHeight / 8.0f);
raymarching.Dispatch(0, threadGroupsX, threadGroupsY, 1);
foreach (ComputeBuffer buffer in buffersToDispose) buffer.Dispose();
return target; }
private void CreateScene() { List<RaymarchShape> allShapes = new List<RaymarchShape>(FindObjectsOfType<RaymarchShape>()); List<RaymarchShape> orderedShapes = new List<RaymarchShape>();
foreach (RaymarchShape shape in allShapes) { // if (shape.transform.parent != null) continue;
Transform parentShape = shape.transform; orderedShapes.Add(shape); shape.numChildren = parentShape.childCount; for (int j = 0; j < parentShape.childCount; j++) if (parentShape.GetChild(j).TryGetComponent(out RaymarchShape s)) { orderedShapes.Add(s); orderedShapes[^1].numChildren = 0; } }
ShapeData[] shapeData = new ShapeData[orderedShapes.Count]; for (int i = 0; i < orderedShapes.Count; i++) { RaymarchShape s = orderedShapes[i]; Vector3 col = new(s.colour.r, s.colour.g, s.colour.b); shapeData[i] = new ShapeData { position = s.Position, scale = s.Scale, colour = col, blendStrength = s.blendStrength * 3, numChildren = s.numChildren, shapeType = s.shapeType, angleZ = s.transform.eulerAngles.z }; }
ComputeBuffer shapeBuffer = new(shapeData.Length, ShapeData.GetSize()); shapeBuffer.SetData(shapeData); raymarching.SetBuffer(0, Shapes, shapeBuffer); raymarching.SetInt(NumShapes, shapeData.Length);
buffersToDispose.Add(shapeBuffer); }
private void SetParameters() { raymarching.SetFloat("_AspectRatio", (float)Screen.width / Screen.height); raymarching.SetVector("_WorldSpaceCameraPos", cam.transform.position); raymarching.SetMatrix(CameraToWorld, cam.cameraToWorldMatrix); raymarching.SetMatrix(CameraInverseProjection, cam.projectionMatrix.inverse); raymarching.SetVector(Light1, lightSource.transform.forward); }
private void InitRenderTexture() { if (target != null && target.width == cam.pixelWidth && target.height == cam.pixelHeight) return;
if (target != null) target.Release();
target = new RenderTexture(cam.pixelWidth, cam.pixelHeight, 0, RenderTextureFormat.ARGBFloat, RenderTextureReadWrite.Linear) { enableRandomWrite = true }; target.Create(); }
private struct ShapeData { public Vector3 position; public Vector3 scale; public Vector3 colour; public float blendStrength; public float angleZ; public int numChildren; public int shapeType;
public static int GetSize() { return sizeof(float) * 11 + sizeof(int) * 2; } }
#region ShaderPropertyIDs
private static readonly int Source = Shader.PropertyToID("Source"); private static readonly int Destination = Shader.PropertyToID("Destination"); private static readonly int Shapes = Shader.PropertyToID("shapes"); private static readonly int NumShapes = Shader.PropertyToID("numShapes"); private static readonly int CameraToWorld = Shader.PropertyToID("_CameraToWorld"); private static readonly int CameraInverseProjection = Shader.PropertyToID("_CameraInverseProjection"); private static readonly int Light1 = Shader.PropertyToID("_Light");
#endregion}
xxxxxxxxxx•#pragma kernel CSMain
Texture2D Source;RWTexture2D<float4> Destination;
half4x4 _CameraToWorld;half4x4 _CameraInverseProjection;
half3 _Light;bool positionLight;half _AspectRatio;
static const float maxDst = 20; // furkanstatic const float epsilon = 0.01f; // furkanstatic const float shadowBias = epsilon * 500; // furkan
struct Shape { half3 position; half3 size; half3 colour; float blendStrength; float angleZ; int numChildren; int shapeType; // 0 if sphere, 1 if box};
StructuredBuffer<Shape> shapes;int numShapes;
struct Ray{ half3 origin; half3 direction;};
// Rotation matrix around the Y axis.half3x3 rotateY(float theta) { float c = cos(theta); float s = sin(theta); return half3x3( half3(c, 0, s), half3(0, 1, 0), half3(-s, 0, c) );}
half3x3 rotateZ(float theta) { float c = cos(theta); float s = sin(theta); return half3x3( half3(c, -s, 0), half3(s, c, 0), half3(0, 0, 1) );}
// Rotation matrix around the X axis.half3x3 rotateX(float theta) { float c = cos(theta); float s = sin(theta); return half3x3( half3(1, 0, 0), half3(0, c, -s), half3(0, s, c) );}
float deg2rad(float rad){ return rad * 0.0174532925;}
half SphereDistance(half3 eye, half3 centre, const half radius){ return distance(eye, centre) - radius;}
half CubeDistance(half3 eye, half3 centre, half3 size, half angle){ eye = mul(rotateZ(deg2rad(-angle)), eye); centre = mul(rotateZ(deg2rad(-angle)), centre); half3 o = abs(eye-centre) - size/2; half ud = length(max(o,0)); half n = max(max(min(o.x,0),min(o.y,0)), min(o.z,0)); return ud+n;}
float sdBox( half3 p, half3 b ){ half3 d = abs(p) - b; return min(max(d.x,max(d.y,d.z)),0.0) + length(max(d,0.0));}
Ray create_ray(const half3 origin, const half3 direction){ Ray ray; ray.origin = origin; ray.direction = direction; return ray;}
float3 _WorldSpaceCameraPos;
Ray create_camera_ray(half2 uv){ const half3 origin = mul(_CameraToWorld, half4(0,0,0,1)).xyz; half3 direction = mul(_CameraInverseProjection, half4(uv,0,1)).xyz; direction = mul(_CameraToWorld, half4(direction,0)).xyz; direction = normalize(direction); return create_ray(origin,direction);}
Ray create_ortho_camera_ray(half2 uv){ uv.x = uv.x * _AspectRatio; float3 ro_ort = _WorldSpaceCameraPos + float3(uv * 5, 0); return create_ray(ro_ort, float3(0, 0, 1));}
half4 blend(const half a, const half b, const half3 colA, const half3 colB, const half k){ half h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0); half kH = k * h; const half oneMinusH = 1.0 - h;
half blendDst = lerp(b, a, h) - kH * oneMinusH; half3 blendCol = lerp(colB, colA, h);
return half4(blendCol, blendDst);}
half get_shape_distance(Shape shape, half3 eye){ if (shape.shapeType == 0) return SphereDistance(eye, shape.position, shape.size.x); // furkan else if (shape.shapeType == 1) return CubeDistance(eye, shape.position, shape.size, shape.angleZ); else if (shape.shapeType == 2) return sdBox(eye, shape.position);
return 300000;}
float4 get_scene_info(half3 eye){ half global_dst = maxDst; half3 global_colour = 1; for (int i = 0; i < numShapes; i ++) { const Shape shape = shapes[i];
const half localDst = get_shape_distance(shape,eye); const half3 localColour = shape.colour; half4 global_combined = blend(global_dst, localDst, global_colour, localColour, shape.blendStrength); global_colour = global_combined.xyz; global_dst = global_combined.w; } return half4(global_colour, global_dst);}
half3 estimate_normal(half3 p) { half x = get_scene_info(half3(p.x+epsilon,p.y,p.z)).w - get_scene_info(half3(p.x-epsilon,p.y,p.z)).w; half y = get_scene_info(half3(p.x,p.y+epsilon,p.z)).w - get_scene_info(half3(p.x,p.y-epsilon,p.z)).w; half z = get_scene_info(half3(p.x,p.y,p.z+epsilon)).w - get_scene_info(half3(p.x,p.y,p.z-epsilon)).w; return normalize(half3(x,y,z));}
[numthreads(8,8,1)]void CSMain (uint3 id : SV_DispatchThreadID){ uint width,height; Destination.GetDimensions(width, height);
Destination[id.xy] = Source[id.xy];
half2 uv = id.xy / half2(width,height) * 2 - 1; half rayDst = 0;
Ray ray = create_ortho_camera_ray(uv); int marchSteps = 0;
while (rayDst < maxDst) { marchSteps ++; float4 scene_info = get_scene_info(ray.origin); const float dst = scene_info.w; if (dst <= epsilon) { const half3 col = scene_info.xyz; const half3 pointOnSurface = ray.origin + ray.direction * dst; const half3 normal = estimate_normal(pointOnSurface - ray.direction * epsilon); const half lighting = saturate(dot(normal,-_Light));
Destination[id.xy] = half4(col * lighting ,1); break; }
ray.origin += ray.direction * dst; rayDst += dst; }}
How To Install
This package uses the [scoped registry] feature to resolve package dependencies. Open the Package Manager page in the Project Settings window and add the following entry to the Scoped Registries list:



Name: Enter any name here
URL: https://registry.npmjs.com
Scope: com.femiroglu


Now you can install the package from My Registries page in the Package Manager window.