Skip to content

Instantly share code, notes, and snippets.

@samloeschen
Created July 27, 2019 22:46
Show Gist options
  • Save samloeschen/a510552e85882b330cfc9eb05f6244d4 to your computer and use it in GitHub Desktop.
Save samloeschen/a510552e85882b330cfc9eb05f6244d4 to your computer and use it in GitHub Desktop.
ComputeRenderer for Unity. Rasterizes compute shader output to make masks and stuff
using System.IO;
using System;
using UnityEngine;
using UnityEditor;
using Debug = UnityEngine.Debug;
public class ComputeRenderer : EditorWindow {
public RenderTexture previewTexture;
public Vector2Int textureDimensions {
get { return _textureDimensions; }
set {
if (value != _textureDimensions) {
_textureDimensions = value;
_textureDirty = true;
if (autoRender) {
CreateTexture();
Render();
}
}
}
}
private Vector2Int _textureDimensions = new Vector2Int(256, 256);
public FilterMode filterMode {
get { return _filterMode; }
set {
if (value != _filterMode) {
_filterMode = value;
_textureDirty = true;
if (autoRender) {
CreateTexture();
Render();
}
}
}
}
private FilterMode _filterMode = FilterMode.Bilinear;
public ComputeShader computeShader {
get { return _computeShader; }
set {
if (value != _computeShader) {
_computeShader = value;
if (autoRender) Render();
}
}
}
private ComputeShader _computeShader;
public int kernelIndex {
get { return _kernelIndex; }
set {
if (_kernelIndex != value) {
_kernelIndex = value;
if (autoRender) Render();
}
}
}
private int _kernelIndex = 0;
public Texture2D overwriteTexture {
get { return _overwriteTexture; }
set {
if (_overwriteTexture != value) {
_overwriteTexture = value;
var path = AssetDatabase.GetAssetPath(_overwriteTexture);
if (path == null || path.Length == 0) {
targetPath = Application.dataPath + "/ComputeRendererOutput/NewOutput.png";
} else {
targetPath = path;
}
}
}
}
private Texture2D _overwriteTexture;
public string targetPath;
public bool autoRender = true;
private bool _textureDirty = false;
public static ComputeRenderer CurrentWindow;
[MenuItem("Window/ComputeRenderer")]
static void Init() {
CurrentWindow = (ComputeRenderer)EditorWindow.GetWindow(typeof(ComputeRenderer));
var windowWidth = 500;
var windowHeight = 600;
CurrentWindow.position = new Rect {
x = (Screen.width * 0.5f) - (windowWidth * 0.5f),
y = (Screen.height * 0.5f) - (windowHeight * 0.5f),
width = windowWidth,
height = windowHeight
};
CurrentWindow.targetPath = Application.dataPath + "/ComputeRendererOutput/NewOutput.png";
if (!CurrentWindow.previewTexture) {
CurrentWindow.CreateTexture();
}
CurrentWindow.Show();
}
void OnGUI () {
if (!CurrentWindow) return;
var windowRect = CurrentWindow.position;
var center = windowRect.size * 0.5f;
BeginLabelWidth(110);
overwriteTexture = (Texture2D)EditorGUI.ObjectField(
new Rect(center.x + 20f, 10f, 220f, 15f),
"Overwrite Texture",
overwriteTexture,
typeof(Texture2D),
allowSceneObjects: false
);
EndLabelWidth();
BeginLabelWidth(40);
targetPath = EditorGUI.TextField(
new Rect(center.x + 20f, 30f, 220f, 15f),
"Path",
targetPath
);
EndLabelWidth();
if (EditorGUI.actionKey) {
var curEvent = Event.current;
if(curEvent.type == EventType.KeyDown && curEvent.keyCode == KeyCode.S) {
WriteTexture();
}
}
GUILayout.BeginArea(new Rect(center.x + 20f, 65f, 220f, 30f));
if (GUILayout.Button("Save (cmd/ctrl + S)")) {
WriteTexture();
}
GUILayout.EndArea();
BeginLabelWidth(80);
computeShader = (ComputeShader)EditorGUI.ObjectField(
new Rect(center.x - 240f, 10f, 220f, 15f),
"Shader File",
computeShader,
typeof(ComputeShader),
allowSceneObjects: false
);
EndLabelWidth();
kernelIndex = EditorGUI.IntField(
new Rect(center.x - 240f, 30f, 220f, 15f),
"Kernel Index",
kernelIndex
);
textureDimensions = EditorGUI.Vector2IntField(
new Rect(center.x - 240f, 50f, 220f, 15f),
"Texture Dimensions",
textureDimensions
);
filterMode = (FilterMode)EditorGUI.EnumPopup(
new Rect(center.x - 240f, 90f, 220f, 15f),
"Preview Filter",
filterMode
);
autoRender = EditorGUI.Toggle(
new Rect(center.x - 240f, 110f, 220f, 15),
"Auto Render",
autoRender
);
if (EditorGUI.actionKey) {
var curEvent = Event.current;
if(curEvent.type == EventType.KeyDown && curEvent.keyCode == KeyCode.R) {
Render();
}
}
GUILayout.BeginArea(new Rect(center.x + 20f, 90f, 220f, 100f));
if (GUILayout.Button("RUN (cmd/ctrl + R)", GUILayout.Height(35f))) {
if (_textureDirty) CreateTexture();
Render();
}
GUILayout.EndArea();
var vCenter = (windowRect.height - 150f) * 0.5f;
var aspect = (float)textureDimensions.x / Mathf.Max(textureDimensions.y, 1f);
float width, height;
if (_textureDimensions.x <= _textureDimensions.y) {
width = Mathf.Min((windowRect.height - 170f) * aspect, windowRect.width - 20f);
height = width / aspect;
} else {
height = Mathf.Min(windowRect.height - 170f, (windowRect.width - 20f) / aspect);
width = height * aspect;
}
if (previewTexture) {
EditorGUI.DrawPreviewTexture(
new Rect(center.x - width * 0.5f, 150f + vCenter - height * 0.5f, width, height),
previewTexture
);
}
}
void CreateTexture () {
if (textureDimensions.x == 0 || textureDimensions.y == 0) return;
if (previewTexture && previewTexture.IsCreated()) {
previewTexture.Release();
}
previewTexture = new RenderTexture(textureDimensions.x, textureDimensions.y, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Linear);
previewTexture.enableRandomWrite = true;
previewTexture.format = RenderTextureFormat.ARGB32;
previewTexture.filterMode = filterMode;
previewTexture.Create();
}
void Render () {
if (!computeShader) return;
uint x, y, z;
try {
computeShader.GetKernelThreadGroupSizes(kernelIndex, out x, out y, out z);
computeShader.SetTexture(kernelIndex, "_Output", previewTexture);
computeShader.SetVector("_Resolution", new Vector4 {
x = previewTexture.width,
y = previewTexture.height
});
} catch {
Debug.LogError("Invalid kernel index!");
return;
}
var threads = new Vector3Int {
x = Mathf.Max(1, previewTexture.width / (int)x),
y = Mathf.Max(1, previewTexture.height / (int)y),
z = 1,
};
computeShader.Dispatch(kernelIndex, threads.x, threads.y, threads.z);
}
void WriteTexture () {
if (targetPath.Length == 0 || targetPath == null) {
Debug.LogError("You need to specify a target path to save the texture!");
return;
}
Debug.Log(targetPath);
var outputTexture = new Texture2D(previewTexture.width, previewTexture.height, TextureFormat.RGBA32, false);
RenderTexture.active = previewTexture;
outputTexture.ReadPixels(new Rect(0, 0, previewTexture.width, previewTexture.height), 0, 0);
outputTexture.Apply();
byte[] bytes = outputTexture.EncodeToPNG();
var directory = Path.GetDirectoryName(targetPath);
if (!Directory.Exists(directory)) {
Directory.CreateDirectory(directory);
}
try {
File.WriteAllBytes(targetPath, bytes);
}
catch (System.IO.FileNotFoundException) {
Debug.LogError("The file or directory cannot be found.");
}
catch (DirectoryNotFoundException) {
Debug.LogError("The file or directory cannot be found.");
}
catch (DriveNotFoundException) {
Debug.LogError("The drive specified in 'path' is invalid.");
}
catch (PathTooLongException) {
Debug.LogError("'path' exceeds the maxium supported path length.");
}
catch (UnauthorizedAccessException) {
Debug.LogError("You do not have permission to create this file.");
}
catch (IOException e) when ((e.HResult & 0x0000FFFF) == 32 ) {
Debug.LogError("There is a sharing violation.");
}
catch (IOException e) when ((e.HResult & 0x0000FFFF) == 80) {
Debug.LogError("The file already exists.");
}
catch (IOException e) {
Debug.LogError($"An exception occurred:\nError code: " +
$"{e.HResult & 0x0000FFFF}\nMessage: {e.Message}");
}
AssetDatabase.Refresh();
}
private float _defaultWidth = 60;
void BeginLabelWidth (int width) {
_defaultWidth = EditorGUIUtility.labelWidth;
EditorGUIUtility.labelWidth = width;
}
void EndLabelWidth () {
EditorGUIUtility.labelWidth = _defaultWidth;
}
}
RWTexture2D<float4> _Output;
uniform float4 _Resolution;
#pragma kernel Ring
[numthreads(4,4,1)]
void Ring (int2 id : SV_DISPATCHTHREADID)
{
float2 uv = id.xy / _Resolution.xy;
float len = length(uv - 0.5) * 2.0;
float circ = smoothstep(0.45, 0.5, len) - smoothstep(0.5, 0.55, len);
_Output[id.xy] = circ;
}
#pragma kernel UVGrad
[numthreads(4,4,1)]
void UVGrad (int2 id : SV_DISPATCHTHREADID)
{
float2 uv = id.xy / _Resolution.xy;
_Output[id.xy] = float4(uv.xy, 0, 1);
}
#pragma kernel ShitMandelbrot
[numthreads(4,4,1)]
void ShitMandelbrot (int2 id : SV_DISPATCHTHREADID)
{
float2 uv = id.xy / _Resolution.xy * 2.0 - 1.0;
uv *= 1.5;
uv.x -= 0.5;
float2 z = 0.0;
for (int i = 0; i < 128; i++) {
z = float2(z.x * z.x - z.y * z.y, 2.0 * z.x * z.y) + uv;
if (dot(z, z) > 4.0) {
float s = 0.125662 * float(i);
_Output[id.xy] = float4(s, s, s, 1.0);
}
}
}
#pragma kernel Simplex1D
#include "Noise.hlsl"
[numthreads(4,4,1)]
void Simplex1D (int2 id : SV_DISPATCHTHREADID)
{
float2 uv = id.xy / _Resolution.xy;
float n = snoise(float2(uv.x * 6.0, uv.y * 6.0)) * 0.5 + 0.5;
n = n * n * n ;
_Output[id.xy] = half4(n, n, n, n);
}
float cubicPulse( float c, float w, float x )
{
x = abs(x - c);
if( x>w ) return 0.0;
x /= w;
return 1.0 - x*x*(3.0-2.0*x);
}
#pragma kernel TriGrid
[numthreads(4,4,1)]
void TriGrid (int2 id : SV_DISPATCHTHREADID)
{
float2 uv = id.xy / _Resolution.xy;
float size = 3.0;
float grid1 = cubicPulse(0.5, 0.025, frac((uv.x + uv.y) * size));
float grid2 = cubicPulse(0.5, 0.025, frac((uv.x - uv.y) * size));
float grid3 = cubicPulse(1.0, 0.07, frac((uv.y - 0.006) * size * 2.0));
float grid = max(grid1, max(grid2, grid3));
_Output[id.xy] = half4(grid, 0, 0, 1);
}
float parabola( float x, float k )
{
return pow( 4.0*x*(1.0-x), k );
}
float pcurve( float x, float a, float b )
{
const float k = pow(a+b,a+b) / (pow(a,a)*pow(b,b));
return k * pow( x, a ) * pow( 1.0-x, b );
}
#pragma kernel DomeGradient
[numthreads(4,4,1)]
void DomeGradient (int2 id : SV_DISPATCHTHREADID)
{
float2 uv = id.xy / _Resolution.xy;
float x = 1.0 - parabola(uv.x, 6.0) - 0.6;
float a = pcurve(uv.y - (x * 0.7 * uv.y), 2.0, 7.0);
_Output[id.xy] = a;
}
float3 softmax(float3 a, float3 b, float r)
{
float3 e = max(r - abs(b - a), 0);
return max(a, b) + e*e*0.25/r;
}
float3 softmin(float3 a, float3 b, float r)
{
float3 e = max(r - abs(a - b), 0);
return min(a, b) - e*e*0.25/r;
}
#pragma kernel LinesGradient
[numthreads(4,4,1)]
void LinesGradient (int2 id : SV_DISPATCHTHREADID)
{
float2 uv = id.xy / _Resolution.xy;
float mask = pow(cubicPulse(0.5, 0.5, uv.y), 2.0) * 0.4;
mask = softmax(mask, pow(cubicPulse(0.5, 0.1, uv.y), 4.0), 0.3);
mask = pow(mask, 2.5);
mask = cubicPulse(0.5, 0.4, uv.y);
mask = saturate(smoothstep(0.95, 0.9, 1.0 - uv.x));
// mask = cubicPulse(0.9, 0.08, uv.y);
// mask = max(step(uv.y, 0.9) * uv.y * 0.05, mask);
// mask *= smoothstep(0.95, 0.9, 1.0 - uv.x);
_Output[id.xy] = mask;
}
#pragma kernel LinesGradient2
[numthreads(4,4,1)]
void LinesGradient2 (int2 id : SV_DISPATCHTHREADID)
{
float2 uv = id.xy / _Resolution.xy;
float mask = pow(cubicPulse(0.5, 0.5, uv.y), 2.0) * 0.4;
mask = softmax(mask, pow(cubicPulse(0.5, 0.1, uv.y), 4.0), 0.3);
mask = smoothstep(0, 0.1, uv.x) - smoothstep(0.9, 1.0, uv.x);
_Output[id.xy] = mask;
}
#pragma kernel ChunkNoise
[numthreads(4,4,1)]
void ChunkNoise (int2 id : SV_DISPATCHTHREADID)
{
float2 uv = id.xy / _Resolution.xy;
uv += 3.0;
float n = snoise(float2(uv.x * 1.5, uv.y * 1.5)) * 0.5 + 0.5;
n *= n * 1.5;
_Output[id.xy] = half4(n, n, n, n);
}
#pragma kernel DispNoise
[numthreads(4,4,1)]
void DispNoise (int2 id : SV_DISPATCHTHREADID)
{
float2 uv = id.xy / _Resolution.xy;
float n = snoise(float2(uv.x * 3, uv.y * 3)) * 0.5 + 0.5;
_Output[id.xy] = half4(n, n, n, n);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment