I have developed a programmable node-based asset authoring tool ( NodeFlex ) and I’m in the process of also developing an advanced shader graph system for it and Unity.
ShaderPlay.com.
I’m looking to support per-node previews and need to render out the scene ( using the current camera ) multiple times to an image file or possibly render the scene to a smaller render target multiple times ( all without updating to the viewport ). Ideally this would all happen in the AssetPreprocessor:OnPostprocessAllAssets callback script function when a NodeFlex shader is changed. I would then parse a line in the shader file telling me how many previews to render and I would also need to set one of my material’s shader variables each time I render.
If this is possible, any direction would be greatly appreciated. I’m new to Unity, but really excited to get this part working.
Managed to figure it out and get it all working, but had to settle with the main camera instead of the viewport camera since it kept coming back null possibly since I’m calling this all in an AssetPostprocessor callback. Also couldn’t get the shader name from the shader’s asset pass which is all that OnPostprocessAllAssets gives you, so I just encoded it into the shader file along with my preview values which I parse. I use a directory change notification in NodeFlex to auto-load the preview snapshots as they change. I’m writting my own NodeFlex material inspector so i’ll be able to fire off previews when the user plays with materials settings. The only thing it could use is a direct connection to NodeFlex so I could stream over dynamic values for the node ports.
Here’s a snapshot and the code I used.
using UnityEngine;
using UnityEditor;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.IO;
//-------------------------------------------------------------------------------
[InitializeOnLoad]
class BackgroundRefresh : AssetPostprocessor
{
const float m_RefreshInterval = 0.1f; // in seconds
static double m_NextRefreshTime;
static RenderTexture m_TargetLarge;
static Texture2D m_TextureLarge;
static RenderTexture m_TargetSmall;
static Texture2D m_TextureSmall;
static bool m_TargetsInitialized = false;
//-------------------------------------------------------------------------------
static BackgroundRefresh()
{
EditorApplication.update += Update;
}
//-------------------------------------------------------------------------------
static void Update()
{
double time = EditorApplication.timeSinceStartup;
if ( time > m_NextRefreshTime )
{
AssetDatabase.Refresh();
m_NextRefreshTime = time + m_RefreshInterval;
}
}
//-------------------------------------------------------------------------------
static void AddDebugInfo( string Text )
{
Debug.Log( Text + "\n" ); // removes the "UnityEngine.Debug:Log(Object)" text in the debug console
}
//-------------------------------------------------------------------------------
static public void RenderPreview( string Path, string Name, bool Full )
{
if ( !m_TargetsInitialized )
{
m_TargetLarge = new RenderTexture( 1280, 1280, 16 );
m_TextureLarge = new Texture2D( 1280, 1280, TextureFormat.RGB24, false );
m_TargetSmall = new RenderTexture( 256, 256, 16 );
m_TextureSmall = new Texture2D( 256, 256, TextureFormat.RGB24, false );
m_TargetsInitialized = true;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - -
if ( m_TargetLarge == null || m_TextureLarge == null || m_TargetSmall == null || m_TextureSmall == null )
return;
// - - - - - - - - - - - - - - - - - - - - - - - - - -
Camera previewCamera;
if ( false ) // try to get the viewport camera
{
//EditorWindow.FocusWindowIfItsOpen<SceneView>();
// GameObject OVcamera = GameObject.FindGameObjectWithTag("OverviewCamera");
// yield return new WaitForEndOfFrame();
// Camera camOV = Camera.current;;//OVcamera.GetComponent<Camera>();
//if ( Camera.current != null )
// AddDebugInfo( "YEP" );
// else
// AddDebugInfo( "NOPE" );
// Camera camOV = Camera.current;//new Camera();
// camOV.CopyFrom( Camera.current );
//Camera camOV = Camera.current;// OVcamera.GetComponent<Camera>();
}
else
{
GameObject OVcamera = GameObject.FindGameObjectWithTag( "MainCamera" );
if ( OVcamera == null )
return;
previewCamera = OVcamera.GetComponent<Camera>();
if ( previewCamera == null )
return;
}
RenderTexture lastTarget = RenderTexture.active;
RenderTexture previewTarget = ( Full ) ? m_TargetLarge : m_TargetSmall;
Texture2D previewTexture = ( Full ) ? m_TextureLarge : m_TextureSmall;
previewCamera.targetTexture = previewTarget;
previewCamera.Render();
RenderTexture.active = previewTarget;
previewTexture.ReadPixels( new Rect( 0, 0, previewTarget.width, previewTarget.height ), 0, 0 );
previewTexture.Apply();
// - - - - - - - - - - - - - - - - - - - - - - - - - -
// Reset
previewCamera.targetTexture = null;
RenderTexture.active = lastTarget; // added to avoid errors
// - - - - - - - - - - - - - - - - - - - - - - - - - -
byte[] bytes = previewTexture.EncodeToPNG();
System.IO.File.WriteAllBytes( Path + Name, bytes );
}
//-------------------------------------------------------------------------------
static void RenderPreview( Shader UpdatedShader, string sNodeID, string sPassID )
{
//AddDebugInfo( "Preview: Node(" + sNodeID + ") - Pass (" + sPassID + ")" );
// - - - - - - - - - - - - - - - - - - - - - - - - - -
int NodeID = int.Parse( sNodeID );
int PassID = int.Parse( sPassID );
// - - - - - - - - - - - - - - - - - - - - - - - - - -
Material[] materialArray = (Material[])Resources.FindObjectsOfTypeAll(typeof(Material) );
// - - - - - - - - - - - - - - - - - - - - - - - - - -
foreach ( Material material in materialArray )
{
if ( material.shader == UpdatedShader )
{
material.SetInt( "g_PreviewNodeID", NodeID );
material.SetInt( "g_PreviewPassID", PassID );
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - -
RenderPreview( "c:/Snapshots/", "preview_n" + sNodeID + "_p" + sPassID + ".png", false );
// - - - - - - - - - - - - - - - - - - - - - - - - - -
// Reset the material preview IDs
foreach ( Material material in materialArray )
{
if ( material.shader == UpdatedShader )
{
material.SetInt( "g_PreviewNodeID", -1 );
material.SetInt( "g_PreviewPassID", -1 );
}
}
}
//-------------------------------------------------------------------------------
static void OnPostprocessAllAssets( string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths )
{
UnityEditorInternal.InternalEditorUtility.RepaintAllViews();
// - - - - - - - - - - - - - - - - - - - - - - - - - -
bool NeedsRender = false;
foreach ( var AssetPath in importedAssets )
{
if ( AssetPath.Contains( ".shader" ) )
{
StreamReader FileReader = new StreamReader( AssetPath, Encoding.Default );
string Line1 = FileReader.ReadLine();
string Line2 = FileReader.ReadLine();
FileReader.Close();
if ( Line1 != null && Line2 != null )
{
Line2 = Line2.Substring( 3 );
Line2.Trim();
Shader UpdatedShader = Shader.Find( Line2 );
if ( UpdatedShader != null )
{
string[] PreviewInfo = Line1.Split( ' ' );
if ( PreviewInfo.Length > 2 && PreviewInfo[1] == "NODEFLEX" )
{
for ( var n = 2; n < PreviewInfo.Length; n++ )
{
string[] PreviewIDs = PreviewInfo[n].Split( ':' );
if ( PreviewIDs.Length == 2 )
{
NeedsRender = true;
RenderPreview( UpdatedShader, PreviewIDs[0], PreviewIDs[1] );
}
}
}
}
}
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - -
if ( NeedsRender )
RenderPreview( "c:/Snapshots/", "preview.png", true );
}
}