Editor/ShaderGraphPlus/Nodes/TextureNodes.cs
using Editor;
namespace ShaderGraphPlus.Nodes;
public abstract class Texture2DSamplerBase : PreviewableNode, IErroringNode
{
[JsonIgnore, Hide, Browsable( false )]
public override Color NodeTitleColor => ShaderGraphPlusTheme.NodeHeaderColors.FunctionNode;
/// <summary>
/// Texture2D Object input
/// </summary>
[Title( "Texture2D" )]
[Input( typeof( Texture ), Order = 0 )]
[Hide]
public NodeInput Texture2DInput { get; set; }
[JsonIgnore, Hide] private Asset _asset;
[JsonIgnore, Hide] private string _texture;
[JsonIgnore, Hide] private string _internalImage;
[JsonIgnore, Hide] protected string TexturePath => _texture;
[JsonIgnore, Hide]
protected virtual TextureInput PreviewUI => new TextureInput
{
Type = TextureType.Tex2D,
ImageFormat = TextureFormat.DXT5,
SrgbRead = true,
DefaultColor = Color.White,
};
protected Texture2DSamplerBase() : base()
{
_internalImage = "materials/default/default.tga";
ExpandSize = new Vector2( 0, 8 + Inputs.Count() * 24 );
}
public override void OnPaint( Rect rect )
{
rect = rect.Align( 130, TextFlag.LeftBottom ).Shrink( 3 );
Paint.SetBrush( "/image/transparent-small.png" );
Paint.DrawRect( rect.Shrink( 2 ), 2 );
Paint.SetBrush( Theme.ControlBackground.WithAlpha( 0.7f ) );
Paint.DrawRect( rect, 2 );
if ( _asset != null )
{
Paint.Draw( rect.Shrink( 2 ), _asset.GetAssetThumb( true ) );
}
}
public override void EvaluatePreview( GraphCompiler compiler )
{
var texture2DResult = compiler.Result( Texture2DInput );
if ( texture2DResult.IsValid )
{
if ( compiler.TryGetPreviewImage( texture2DResult.Code, out var imagePath ) )
{
_internalImage = imagePath;
_asset = AssetSystem.FindByPath( _internalImage );
if ( _asset == null )
return;
}
else
{
throw new Exception( $"Cannot find PreviewImage for texture input : Texture2D" );
}
}
else
{
_internalImage = "materials/dev/white_color.tga";
}
var ui = PreviewUI with { DefaultTexture = _internalImage };
_texture = compiler.CompileTexture( ui );
}
protected NodeResult Component( string component, GraphCompiler compiler )
{
var result = compiler.Result( new NodeInput { Identifier = Identifier, Output = nameof( Result ) } );
return result.IsValid ? new( ResultType.Float, $"{result}.{component}", true ) : new( ResultType.Float, "0.0f", true );
}
protected bool GetTexture2DInputResult( GraphCompiler compiler, out NodeResult texture2DResult, out NodeResult errorResult )
{
errorResult = default;
texture2DResult = compiler.Result( Texture2DInput );
if ( texture2DResult.IsValid )
{
if ( texture2DResult.ResultType != ResultType.Texture2D )
{
errorResult = NodeResult.IncorrectInputType( "Texture2D", ResultType.Texture2D );
return false;
}
ClearError();
return true;
}
//InternalImage = "materials/dev/white_color.tga";
errorResult = NodeResult.MissingInput( "Texture2D" );
return false;
}
public List<string> GetErrors()
{
var errors = new List<string>();
var graph = Graph as ShaderGraphPlus;
return errors;
}
}
/// <summary>
/// Sample a 2D Texture
/// </summary>
[Title( "Sample Texture 2D" ), Category( "Textures" ), Icon( "colorize" )]
public sealed class SampleTexture2DNode : Texture2DSamplerBase
{
/// <summary>
/// Coordinates to sample this texture (Defaults to vertex coordinates)
/// </summary>
[Title( "Coordinates" )]
[Input( typeof( Vector2 ), Order = 1 )]
[Hide]
public NodeInput CoordsInput { get; set; }
/// <summary>
/// How the texture is filtered and wrapped when sampled
/// </summary>
[Title( "Sampler" )]
[Input( typeof( Sampler ), Order = 2 )]
[Hide]
public NodeInput SamplerInput { get; set; }
[InlineEditor( Label = false ), Group( "Default Sampler" ), Order( 2 )]
public Sampler SamplerState { get; set; } = new Sampler();
public SampleTexture2DNode() : base()
{
ExpandSize = new Vector2( 0, 8 + Inputs.Count() * 24 );
}
/// <summary>
/// RGBA color result
/// </summary>
[Hide]
[Output( typeof( Vector4 ) ), Title( "RGBA" )]
public NodeResult.Func Result => ( GraphCompiler compiler ) =>
{
if ( !GetTexture2DInputResult( compiler, out var texture2DResult, out var errorResult ) )
{
return errorResult;
}
var texture = string.IsNullOrWhiteSpace( TexturePath ) ? null : Texture.Load( TexturePath );
texture ??= Texture.White;
var textureGlobal = texture2DResult.Code;
var coords = compiler.Result( CoordsInput );
var samplerGlobal = compiler.ResultSamplerOrDefault( SamplerInput, SamplerState );
var attributeName = texture2DResult.Code.TrimStart( "g_t" ).ToString();
compiler.SetAttribute( attributeName, texture );
if ( compiler.Stage == GraphCompiler.ShaderStage.Vertex )
{
return new NodeResult( ResultType.Vector4, $"{textureGlobal}.SampleLevel(" +
$" {samplerGlobal}," +
$" {(coords.IsValid ? $"{coords.Cast( 2 )}" : "i.vTextureCoords.xy")}, 0 )" );
}
else
{
return new NodeResult( ResultType.Vector4, $"{textureGlobal}.Sample( {samplerGlobal}," +
$"{(coords.IsValid ? $"{coords.Cast( 2 )}" : "i.vTextureCoords.xy")} )" );
}
};
/// <summary>
/// Red component of result
/// </summary>
[Output( typeof( float ) ), Hide, Title( "R" )]
public NodeResult.Func R => ( GraphCompiler compiler ) => Component( "r", compiler );
/// <summary>
/// Green component of result
/// </summary>
[Output( typeof( float ) ), Hide, Title( "G" )]
public NodeResult.Func G => ( GraphCompiler compiler ) => Component( "g", compiler );
/// <summary>
/// Blue component of result
/// </summary>
[Output( typeof( float ) ), Hide, Title( "B" )]
public NodeResult.Func B => ( GraphCompiler compiler ) => Component( "b", compiler );
/// <summary>
/// Alpha (Opacity) component of result
/// </summary>
[Output( typeof( float ) ), Hide, Title( "A" )]
public NodeResult.Func A => ( GraphCompiler compiler ) => Component( "a", compiler );
}
/// <summary>
/// Sample a 2D Texture at a specific LOD level.
/// </summary>
[Title( "Sample Texture 2D LOD" ), Category( "Textures" ), Icon( "colorize" )]
public sealed class SampleTexture2DLodNode : Texture2DSamplerBase
{
/// <summary>
/// Coordinates to sample this texture (Defaults to vertex coordinates)
/// </summary>
[Title( "Coordinates" )]
[Input( typeof( Vector2 ), Order = 1 )]
[Hide]
public NodeInput CoordsInput { get; set; }
/// <summary>
/// How the texture is filtered and wrapped when sampled
/// </summary>
[Title( "Sampler" )]
[Input( typeof( Sampler ), Order = 2 )]
[Hide]
public NodeInput SamplerInput { get; set; }
[Title( "LOD" )]
[Input( typeof( int ), Order = 3 )]
[Hide]
public NodeInput LODInput { get; set; }
[InlineEditor( Label = false ), Group( "Default Sampler" ), Order( 2 )]
public Sampler SamplerState { get; set; } = new Sampler();
[InputDefault( nameof( LODInput ) ), Order( 3 )]
public int LODLevel { get; set; } = 0;
public SampleTexture2DLodNode() : base()
{
ExpandSize = new Vector2( 0, 8 + Inputs.Count() * 24 );
}
/// <summary>
/// RGBA color result
/// </summary>
[Hide]
[Output( typeof( Vector4 ) ), Title( "RGBA" )]
public NodeResult.Func Result => ( GraphCompiler compiler ) =>
{
if ( !GetTexture2DInputResult( compiler, out var texture2DResult, out var errorResult ) )
{
return errorResult;
}
var texture = string.IsNullOrWhiteSpace( TexturePath ) ? null : Texture.Load( TexturePath );
texture ??= Texture.White;
var textureGlobal = texture2DResult.Code;
var coords = compiler.Result( CoordsInput );
var samplerGlobal = compiler.ResultSamplerOrDefault( SamplerInput, SamplerState );
var lodLevel = compiler.ResultOrDefault( LODInput, LODLevel );
var attributeName = texture2DResult.Code.TrimStart( "g_t" ).ToString();
compiler.SetAttribute( attributeName, texture );
return new NodeResult( ResultType.Vector4, $"{textureGlobal}.SampleLevel(" +
$" {samplerGlobal}," +
$" {(coords.IsValid ? $"{coords.Cast( 2 )}" : "i.vTextureCoords.xy")}, {lodLevel} )" );
};
/// <summary>
/// Red component of result
/// </summary>
[Output( typeof( float ) ), Hide, Title( "R" )]
public NodeResult.Func R => ( GraphCompiler compiler ) => Component( "r", compiler );
/// <summary>
/// Green component of result
/// </summary>
[Output( typeof( float ) ), Hide, Title( "G" )]
public NodeResult.Func G => ( GraphCompiler compiler ) => Component( "g", compiler );
/// <summary>
/// Blue component of result
/// </summary>
[Output( typeof( float ) ), Hide, Title( "B" )]
public NodeResult.Func B => ( GraphCompiler compiler ) => Component( "b", compiler );
/// <summary>
/// Alpha (Opacity) component of result
/// </summary>
[Output( typeof( float ) ), Hide, Title( "A" )]
public NodeResult.Func A => ( GraphCompiler compiler ) => Component( "a", compiler );
}
/// <summary>
/// Sample a 2D Texture using a gradient
/// </summary>
[Title( "Sample Texture 2D Gradient" ), Category( "Textures" ), Icon( "colorize" )]
public sealed class SampleTexture2DGradientNode : Texture2DSamplerBase
{
/// <summary>
/// Coordinates to sample this texture (Defaults to vertex coordinates)
/// </summary>
[Title( "Coordinates" )]
[Input( typeof( Vector2 ), Order = 1 )]
[Hide]
public NodeInput CoordsInput { get; set; }
/// <summary>
/// How the texture is filtered and wrapped when sampled
/// </summary>
[Title( "Sampler" )]
[Input( typeof( Sampler ), Order = 2 )]
[Hide]
public NodeInput SamplerInput { get; set; }
[Title( "DDX" )]
[Input( typeof( Vector2 ), Order = 3 )]
[Hide]
public NodeInput DDXInput { get; set; }
[Title( "DDY" )]
[Input( typeof( Vector2 ), Order = 4 )]
[Hide]
public NodeInput DDYInput { get; set; }
[InlineEditor( Label = false ), Group( "Default Sampler" ), Order( 2 )]
public Sampler SamplerState { get; set; } = new Sampler();
[InputDefault( nameof( DDXInput ) ), Order( 3 )]
public Vector2 DDX { get; set; } = 0;
[InputDefault( nameof( DDYInput ) ), Order( 4 )]
public Vector2 DDY { get; set; } = 0;
public SampleTexture2DGradientNode() : base()
{
ExpandSize = new Vector2( 0, 8 + Inputs.Count() * 24 );
}
/// <summary>
/// RGBA color result
/// </summary>
[Hide]
[Output( typeof( Vector4 ) ), Title( "RGBA" )]
public NodeResult.Func Result => ( GraphCompiler compiler ) =>
{
if ( !GetTexture2DInputResult( compiler, out var texture2DResult, out var errorResult ) )
{
return errorResult;
}
var texture = string.IsNullOrWhiteSpace( TexturePath ) ? null : Texture.Load( TexturePath );
texture ??= Texture.White;
var textureGlobal = texture2DResult.Code;
var coords = compiler.Result( CoordsInput );
var samplerGlobal = compiler.ResultSamplerOrDefault( SamplerInput, SamplerState );
var ddx = compiler.ResultOrDefault( DDXInput, DDX );
var ddy = compiler.ResultOrDefault( DDYInput, DDY );
var attributeName = texture2DResult.Code.TrimStart( "g_t" ).ToString();
compiler.SetAttribute( attributeName, texture );
return new NodeResult( ResultType.Vector4, $"{textureGlobal}.SampleGrad(" +
$" {samplerGlobal}," +
$" {(coords.IsValid ? $"{coords.Cast( 2 )}" : "i.vTextureCoords.xy")}, ( {ddx} ).xy, ( {ddy} ).xy )" );
};
/// <summary>
/// Red component of result
/// </summary>
[Output( typeof( float ) ), Hide, Title( "R" )]
public NodeResult.Func R => ( GraphCompiler compiler ) => Component( "r", compiler );
/// <summary>
/// Green component of result
/// </summary>
[Output( typeof( float ) ), Hide, Title( "G" )]
public NodeResult.Func G => ( GraphCompiler compiler ) => Component( "g", compiler );
/// <summary>
/// Blue component of result
/// </summary>
[Output( typeof( float ) ), Hide, Title( "B" )]
public NodeResult.Func B => ( GraphCompiler compiler ) => Component( "b", compiler );
/// <summary>
/// Alpha (Opacity) component of result
/// </summary>
[Output( typeof( float ) ), Hide, Title( "A" )]
public NodeResult.Func A => ( GraphCompiler compiler ) => Component( "a", compiler );
}
/// <summary>
/// Sample a 2D texture from 3 directions, then blend based on a normal vector.
/// </summary>
[Title( "Sample Texture 2D Triplanar" ), Category( "Textures" ), Icon( "colorize" )]
public sealed class SampleTexture2DTriplanarNode : Texture2DSamplerBase
{
/// <summary>
/// Coordinates to sample this texture (Defaults to vertex coordinates)
/// </summary>
[Title( "Coordinates" )]
[Input( typeof( Vector2 ), Order = 1 )]
[Hide]
public NodeInput CoordsInput { get; set; }
/// <summary>
/// How the texture is filtered and wrapped when sampled
/// </summary>
[Title( "Sampler" )]
[Input( typeof( Sampler ), Order = 2 )]
[Hide]
public NodeInput SamplerInput { get; set; }
/// <summary>
/// Normal to use when blending between each sampled direction (Defaults to vertex normal)
/// </summary>
[Title( "Normal" )]
[Input( typeof( Vector3 ), Order = 3 )]
[Hide]
public NodeInput NormalInput { get; set; }
/// <summary>
/// Scalar tiling applied to the sampling position before projecting to each plane (Unity-style <c>Position * Tile</c>).
/// </summary>
[Title( "Tile" )]
[Input( typeof( float ), Order = 4 )]
[Hide]
public NodeInput TileInput { get; set; }
/// <summary>
/// Blend factor between different samples.
/// </summary>
[Title( "Blend Factor" )]
[Input( typeof( float ), Order = 5 )]
[Hide]
public NodeInput BlendFactorInput { get; set; }
[InlineEditor( Label = false ), Group( "Default Sampler" ), Order( 2 )]
public Sampler SamplerState { get; set; } = new Sampler();
[InputDefault( nameof( TileInput ) )]
public float DefaultTile { get; set; } = 1.0f;
[InputDefault( nameof( BlendFactorInput ) )]
public float DefaultBlendFactor { get; set; } = 4.0f;
public SampleTexture2DTriplanarNode() : base()
{
ExpandSize = new Vector2( 0, 8 + Inputs.Count() * 24 );
}
[Hide]
protected override TextureInput PreviewUI => new TextureInput
{
Type = TextureType.Tex2D,
ImageFormat = TextureFormat.DXT5,
SrgbRead = true,
DefaultColor = Color.White,
};
/// <summary>
/// RGBA color result
/// </summary>
[Hide]
[Output( typeof( Vector4 ) ), Title( "RGBA" )]
public NodeResult.Func Result => ( GraphCompiler compiler ) =>
{
if ( !GetTexture2DInputResult( compiler, out var texture2DResult, out var errorResult ) )
{
return errorResult;
}
var texture = string.IsNullOrWhiteSpace( TexturePath ) ? null : Texture.Load( TexturePath );
texture ??= Texture.White;
var textureGlobal = texture2DResult.Code;
var coords = compiler.Result( CoordsInput );
var samplerGlobal = compiler.ResultSamplerOrDefault( SamplerInput, SamplerState );
var normal = compiler.Result( NormalInput );
var tile = compiler.ResultOrDefault( TileInput, DefaultTile );
var blendfactor = compiler.ResultOrDefault( BlendFactorInput, DefaultBlendFactor );
var tileScalar = GraphCompiler.EmitScalarFloat( tile, DefaultTile );
var blendScalar = GraphCompiler.EmitScalarFloat( blendfactor, DefaultBlendFactor );
var attributeName = texture2DResult.Code.TrimStart( "g_t" ).ToString();
compiler.SetAttribute( attributeName, texture );
var result = compiler.ResultHLSLFunction( "TexTriplanar_Color",
textureGlobal,
samplerGlobal,
coords.IsValid ? coords.Cast( 3 ) : "(i.vPositionWithOffsetWs.xyz + g_vHighPrecisionLightingOffsetWs.xyz) / 39.3701",
tileScalar,
normal.IsValid ? normal.Cast( 3 ) : "normalize( i.vNormalWs.xyz )",
blendScalar
);
return new NodeResult( ResultType.Vector4, result );
};
/// <summary>
/// Red component of result
/// </summary>
[Output( typeof( float ) ), Hide, Title( "R" )]
public NodeResult.Func R => ( GraphCompiler compiler ) => Component( "r", compiler );
/// <summary>
/// Green component of result
/// </summary>
[Output( typeof( float ) ), Hide, Title( "G" )]
public NodeResult.Func G => ( GraphCompiler compiler ) => Component( "g", compiler );
/// <summary>
/// Blue component of result
/// </summary>
[Output( typeof( float ) ), Hide, Title( "B" )]
public NodeResult.Func B => ( GraphCompiler compiler ) => Component( "b", compiler );
/// <summary>
/// Alpha (Opacity) component of result
/// </summary>
[Output( typeof( float ) ), Hide, Title( "A" )]
public NodeResult.Func A => ( GraphCompiler compiler ) => Component( "a", compiler );
}
/// <summary>
/// Sample a normal map from 3 directions with Whiteout blending. Outputs <b>world-space</b> normals.
/// Use a TransformNormal node (World → Tangent, DecodeNormal OFF) to convert before the Material Normal input.
/// </summary>
[Title( "Sample Texture 2D Normal Map Triplanar" ), Category( "Textures" ), Icon( "colorize" )]
public sealed class SampleTexture2DNormalMapTriplanarNode : Texture2DSamplerBase
{
/// <summary>
/// Coordinates to sample this texture (Defaults to vertex coordinates)
/// </summary>
[Title( "Coordinates" )]
[Input( typeof( Vector2 ), Order = 1 )]
[Hide]
public NodeInput CoordsInput { get; set; }
/// <summary>
/// How the texture is filtered and wrapped when sampled
/// </summary>
[Title( "Sampler" )]
[Input( typeof( Sampler ), Order = 2 )]
[Hide]
public NodeInput SamplerInput { get; set; }
/// <summary>
/// Normal to use when blending between each sampled direction (Defaults to vertex normal)
/// </summary>
[Title( "Normal" )]
[Input( typeof( Vector3 ), Order = 3 )]
[Hide]
public NodeInput NormalInput { get; set; }
/// <summary>
/// Scalar tiling applied to the sampling position before projecting to each plane (Unity-style <c>Position * Tile</c>).
/// </summary>
[Title( "Tile" )]
[Input( typeof( float ), Order = 4 )]
[Hide]
public NodeInput TileInput { get; set; }
/// <summary>
/// Blend factor between different samples.
/// </summary>
[Title( "Blend Factor" )]
[Input( typeof( float ), Order = 5 )]
[Hide]
public NodeInput BlendFactorInput { get; set; }
[InlineEditor( Label = false ), Group( "Default Sampler" ), Order( 2 )]
public Sampler SamplerState { get; set; } = new Sampler();
[InputDefault( nameof( TileInput ) )]
public float DefaultTile { get; set; } = 1.0f;
[InputDefault( nameof( BlendFactorInput ) )]
public float DefaultBlendFactor { get; set; } = 4.0f;
protected override TextureInput PreviewUI => new TextureInput
{
Type = TextureType.Tex2D,
ImageFormat = TextureFormat.DXT5,
SrgbRead = false,
ColorSpace = TextureColorSpace.Linear,
Extension = TextureExtension.Normal,
Processor = TextureProcessor.NormalizeNormals,
DefaultColor = new Color( 0.5f, 0.5f, 1f, 1f )
};
public SampleTexture2DNormalMapTriplanarNode() : base()
{
ExpandSize = new Vector2( 56, 12 + Inputs.Count() * 24 );
}
/// <summary>
/// RGBA color result
/// </summary>
[Hide]
[Output( typeof( Vector4 ) ), Title( "RGBA" )]
public NodeResult.Func Result => ( GraphCompiler compiler ) =>
{
if ( !GetTexture2DInputResult( compiler, out var texture2DResult, out var errorResult ) )
{
return errorResult;
}
var texture = string.IsNullOrWhiteSpace( TexturePath ) ? null : Texture.Load( TexturePath );
texture ??= Texture.White;
var textureGlobal = texture2DResult.Code;
var coords = compiler.Result( CoordsInput );
var samplerGlobal = compiler.ResultSamplerOrDefault( SamplerInput, SamplerState );
var normal = compiler.Result( NormalInput );
var tile = compiler.ResultOrDefault( TileInput, DefaultTile );
var blendfactor = compiler.ResultOrDefault( BlendFactorInput, DefaultBlendFactor );
var tileScalar = GraphCompiler.EmitScalarFloat( tile, DefaultTile );
var blendScalar = GraphCompiler.EmitScalarFloat( blendfactor, DefaultBlendFactor );
var attributeName = texture2DResult.Code.TrimStart( "g_t" ).ToString();
compiler.SetAttribute( attributeName, texture );
var result = compiler.ResultHLSLFunction( "TexTriplanar_Normal",
textureGlobal,
samplerGlobal,
coords.IsValid ? coords.Cast( 3 ) : "(i.vPositionWithOffsetWs.xyz + g_vHighPrecisionLightingOffsetWs.xyz) / 39.3701",
tileScalar,
normal.IsValid ? normal.Cast( 3 ) : "normalize( i.vNormalWs.xyz )",
blendScalar
);
return new NodeResult( ResultType.Vector3, result );
};
/// <summary>
/// Red component of result
/// </summary>
[Output( typeof( float ) ), Hide, Title( "R" )]
public NodeResult.Func R => ( GraphCompiler compiler ) => Component( "r", compiler );
/// <summary>
/// Green component of result
/// </summary>
[Output( typeof( float ) ), Hide, Title( "G" )]
public NodeResult.Func G => ( GraphCompiler compiler ) => Component( "g", compiler );
/// <summary>
/// Blue component of result
/// </summary>
[Output( typeof( float ) ), Hide, Title( "B" )]
public NodeResult.Func B => ( GraphCompiler compiler ) => Component( "b", compiler );
/// <summary>
/// Alpha (Opacity) component of result
/// </summary>
[Output( typeof( float ) ), Hide, Title( "A" )]
public NodeResult.Func A => ( GraphCompiler compiler ) => Component( "a", compiler );
}
/// <summary>
/// Sample a Cube Texture
/// </summary>
[Title( "Sample Texture Cube" ), Category( "Textures" ), Icon( "colorize" )]
public sealed class SampleTextureCubeNode : ShaderNodePlus
{
[JsonIgnore, Hide, Browsable( false )]
public override Color NodeTitleColor => ShaderGraphPlusTheme.NodeHeaderColors.FunctionNode;
[JsonIgnore, Hide, Browsable( false )]
public bool IsSubgraph => (Graph is ShaderGraphPlus shaderGraph && shaderGraph.IsSubgraph);
/// <summary>
/// Optional TextureCube Object input when outside of subgraphs.
/// </summary>
[Title( "TextureCube" )]
[Input( typeof( Texture ) )]
[Hide]
public NodeInput TextureInput { get; set; }
/// <summary>
/// Coordinates to sample this cubemap
/// </summary>
[Title( "Coordinates" )]
[Input( typeof( Vector3 ) )]
[Hide]
public NodeInput CoordsInput { get; set; }
/// <summary>
/// How the texture is filtered and wrapped when sampled
/// </summary>
[Title( "Sampler" )]
[Input( typeof( Sampler ) )]
[Hide]
public NodeInput SamplerInput { get; set; }
/// <summary>
/// Texture to sample in preview
/// </summary>
[Hide, JsonIgnore]
public string Texture { get; set; }
[InlineEditor( Label = false ), Group( "Default Sampler" )]
[HideIf( nameof( IsSubgraph ), true )]
public Sampler SamplerState { get; set; } = new Sampler();
[Hide]
public TextureInput PreviewUI => new TextureInput
{
Type = TextureType.TexCube,
ImageFormat = TextureFormat.DXT5,
SrgbRead = true,
DefaultColor = Color.White,
};
public SampleTextureCubeNode() : base()
{
Texture = "materials/skybox/skybox_workshop.vtex";
ExpandSize = new Vector2( 0, 8 + Inputs.Count() * 24 );
}
public override void OnPaint( Rect rect )
{
rect = rect.Align( 130, TextFlag.LeftBottom ).Shrink( 3 );
Paint.SetBrush( "/image/transparent-small.png" );
Paint.DrawRect( rect.Shrink( 2 ), 2 );
Paint.SetBrush( Theme.ControlBackground.WithAlpha( 0.7f ) );
Paint.DrawRect( rect, 2 );
if ( !string.IsNullOrEmpty( Texture ) )
{
var tex = Sandbox.Texture.Find( Texture );
if ( tex is null ) return;
var pixmap = Pixmap.FromTexture( tex );
Paint.Draw( rect.Shrink( 2 ), pixmap );
}
}
/// <summary>
/// RGBA color result
/// </summary>
[Hide]
[Output( typeof( Vector4 ) ), Title( "RGBA" )]
public NodeResult.Func Result => ( GraphCompiler compiler ) =>
{
var input = PreviewUI;
input.Type = TextureType.TexCube;
var textureCubeResult = compiler.Result( TextureInput );
if ( textureCubeResult.IsValid )
{
if ( textureCubeResult.ResultType != ResultType.TextureCube )
{
return NodeResult.IncorrectInputType( "TextureCube", ResultType.TextureCube );
}
ClearError();
if ( compiler.TryGetPreviewImage( textureCubeResult.Code, out var imagePath ) )
{
Texture = imagePath;
}
else
{
throw new Exception( $"Cannot find PreviewImage for texture input : {textureCubeResult.Code}" );
}
}
else
{
Texture = "materials/skybox/skybox_workshop.vtex";
return NodeResult.MissingInput( "TextureCube" );
}
var texture = Sandbox.Texture.Load( Texture );
var textureGlobal = textureCubeResult.Code;
var coords = compiler.Result( CoordsInput );
var samplerGlobal = compiler.ResultSamplerOrDefault( SamplerInput, SamplerState );
var attributeName = textureCubeResult.Code.TrimStart( "g_t" ).ToString();
compiler.SetAttribute( attributeName, texture );
return new NodeResult( ResultType.Vector4, $"TexCubeS( {textureGlobal}," +
$"{samplerGlobal}," +
$" {(coords.IsValid ? $"{coords.Cast( 3 )}" : ViewDirection.Result.Invoke( compiler ))} )" );
};
private NodeResult Component( string component, GraphCompiler compiler )
{
var result = compiler.Result( new NodeInput { Identifier = Identifier, Output = nameof( Result ) } );
return result.IsValid ? new( ResultType.Float, $"{result}.{component}", true ) : new( ResultType.Float, "0.0f", true );
}
/// <summary>
/// Red component of result
/// </summary>
[Output( typeof( float ) ), Hide, Title( "R" )]
public NodeResult.Func R => ( GraphCompiler compiler ) => Component( "r", compiler );
/// <summary>
/// Green component of result
/// </summary>
[Output( typeof( float ) ), Hide, Title( "G" )]
public NodeResult.Func G => ( GraphCompiler compiler ) => Component( "g", compiler );
/// <summary>
/// Blue component of result
/// </summary>
[Output( typeof( float ) ), Hide, Title( "B" )]
public NodeResult.Func B => ( GraphCompiler compiler ) => Component( "b", compiler );
/// <summary>
/// Alpha (Opacity) component of result
/// </summary>
[Output( typeof( float ) ), Hide, Title( "A" )]
public NodeResult.Func A => ( GraphCompiler compiler ) => Component( "a", compiler );
}
/// <summary>
/// Texture Coordinate from vertex data.
/// </summary>
[Title( "Texture Coordinate" ), Category( "Variables" ), Icon( "texture" )]
public sealed class TextureCoord : ShaderNodePlus
{
[JsonIgnore, Hide, Browsable( false )]
public override Color NodeTitleColor => ShaderGraphPlusTheme.NodeHeaderColors.StageInputNode;
/// <summary>
/// Use the secondary vertex coordinate
/// </summary>
public bool UseSecondaryCoord { get; set; } = false;
/// <summary>
/// How many times this coordinate repeats itself to give a tiled effect
/// </summary>
public Vector2 Tiling { get; set; } = 1;
[Hide]
public override string Title => $"{DisplayInfo.For( this ).Name}{(UseSecondaryCoord ? " 2" : "")}";
/// <summary>
/// Coordinate result
/// </summary>
[Output( typeof( Vector2 ) )]
[Hide]
public NodeResult.Func Result => ( GraphCompiler compiler ) =>
{
if ( compiler.IsPreview )
{
var result = $"{compiler.ResultValue( UseSecondaryCoord )} ? i.vTextureCoords.zw : i.vTextureCoords.xy";
return new( ResultType.Vector2, $"{compiler.ResultValue( Tiling.IsNearZeroLength )} ? {result} : ({result}) * {compiler.ResultValue( Tiling )}" );
}
else
{
var result = UseSecondaryCoord ? "i.vTextureCoords.zw" : "i.vTextureCoords.xy";
return Tiling.IsNearZeroLength ? new( ResultType.Vector2, result ) : new( ResultType.Vector2, $"{result} * {compiler.ResultValue( Tiling )}" );
}
};
}