Created
July 27, 2019 22:46
-
-
Save samloeschen/a510552e85882b330cfc9eb05f6244d4 to your computer and use it in GitHub Desktop.
ComputeRenderer for Unity. Rasterizes compute shader output to make masks and stuff
This file contains 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.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; | |
} | |
} | |
This file contains 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
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