mirror of
https://github.com/Facepunch/sbox-public.git
synced 2026-01-20 04:10:00 -05:00
This commit imports the C# engine code and game files, excluding C++ source code. [Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
506 lines
14 KiB
C#
506 lines
14 KiB
C#
|
|
namespace Editor.ShaderGraph.Nodes;
|
|
|
|
public abstract class TextureSamplerBase : ShaderNode, ITextureParameterNode, IErroringNode
|
|
{
|
|
/// <summary>
|
|
/// Texture to sample in preview
|
|
/// </summary>
|
|
[ImageAssetPath]
|
|
public string Image
|
|
{
|
|
get => _image;
|
|
set
|
|
{
|
|
_image = value;
|
|
_asset = AssetSystem.FindByPath( _image );
|
|
|
|
if ( _asset == null )
|
|
return;
|
|
|
|
CompileTexture();
|
|
}
|
|
}
|
|
|
|
private Asset _asset;
|
|
private string _texture;
|
|
private string _image;
|
|
private string _resourceText;
|
|
|
|
[JsonIgnore, Hide]
|
|
protected Asset Asset => _asset;
|
|
|
|
[JsonIgnore, Hide]
|
|
protected string TexturePath => _texture;
|
|
|
|
/// <summary>
|
|
/// How the texture is filtered and wrapped when sampled
|
|
/// </summary>
|
|
[InlineEditor( Label = false ), Group( "Sampler" )]
|
|
public Sampler Sampler { get; set; }
|
|
|
|
protected void CompileTexture()
|
|
{
|
|
if ( _asset == null )
|
|
return;
|
|
|
|
if ( string.IsNullOrWhiteSpace( _image ) )
|
|
return;
|
|
|
|
var ui = UI;
|
|
ui.DefaultTexture = _image;
|
|
UI = ui;
|
|
|
|
var resourceText = string.Format( ShaderTemplate.TextureDefinition,
|
|
_image,
|
|
UI.ColorSpace,
|
|
UI.ImageFormat,
|
|
UI.Processor );
|
|
|
|
if ( _resourceText == resourceText )
|
|
return;
|
|
|
|
_resourceText = resourceText;
|
|
|
|
var assetPath = $"shadergraph/{_image.Replace( ".", "_" )}_shadergraph.generated.vtex";
|
|
var resourcePath = FileSystem.Root.GetFullPath( "/.source2/temp" );
|
|
resourcePath = System.IO.Path.Combine( resourcePath, assetPath );
|
|
|
|
if ( AssetSystem.CompileResource( resourcePath, resourceText ) )
|
|
{
|
|
_texture = assetPath;
|
|
}
|
|
else
|
|
{
|
|
Log.Warning( $"Failed to compile {_image}" );
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Settings for how this texture shows up in material editor
|
|
/// </summary>
|
|
[InlineEditor( Label = false ), Group( "UI" )]
|
|
public TextureInput UI { get; set; } = new TextureInput
|
|
{
|
|
ImageFormat = TextureFormat.DXT5,
|
|
SrgbRead = true,
|
|
Default = Color.White,
|
|
};
|
|
|
|
[Hide]
|
|
public override string Title => string.IsNullOrWhiteSpace( UI.Name ) ? null : $"{DisplayInfo.For( this ).Name} {UI.Name}";
|
|
|
|
protected TextureSamplerBase() : base()
|
|
{
|
|
Image = "materials/dev/white_color.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 ) );
|
|
}
|
|
}
|
|
|
|
protected NodeResult Component( string component, GraphCompiler compiler )
|
|
{
|
|
var result = compiler.Result( new NodeInput { Identifier = Identifier, Output = nameof( Result ) } );
|
|
return result.IsValid ? new( 1, $"{result}.{component}", true ) : new( 1, "0.0f", true );
|
|
}
|
|
|
|
public List<string> GetErrors()
|
|
{
|
|
var errors = new List<string>();
|
|
|
|
if ( Graph is ShaderGraph sg && sg.IsSubgraph )
|
|
{
|
|
if ( string.IsNullOrWhiteSpace( UI.Name ) )
|
|
{
|
|
errors.Add( $"Texture parameter \"{DisplayInfo.For( this ).Name}\" is missing a name" );
|
|
}
|
|
|
|
foreach ( var node in sg.Nodes )
|
|
{
|
|
if ( node is ITextureParameterNode tpn && tpn != this && tpn.UI.Name == UI.Name )
|
|
{
|
|
errors.Add( $"Duplicate texture parameter name \"{UI.Name}\" on {DisplayInfo.For( this ).Name}" );
|
|
}
|
|
}
|
|
}
|
|
|
|
return errors;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sample a 2D Texture
|
|
/// </summary>
|
|
[Title( "Texture 2D" ), Category( "Textures" ), Icon( "image" )]
|
|
public sealed class TextureSampler : TextureSamplerBase
|
|
{
|
|
/// <summary>
|
|
/// Coordinates to sample this texture (Defaults to vertex coordinates)
|
|
/// </summary>
|
|
[Title( "Coordinates" )]
|
|
[Input( typeof( Vector2 ) )]
|
|
[Hide]
|
|
public NodeInput Coords { get; set; }
|
|
|
|
/// <summary>
|
|
/// RGBA color result
|
|
/// </summary>
|
|
[Hide]
|
|
[Output( typeof( Color ) ), Title( "RGBA" )]
|
|
public NodeResult.Func Result => ( GraphCompiler compiler ) =>
|
|
{
|
|
var input = UI;
|
|
input.Type = TextureType.Tex2D;
|
|
|
|
CompileTexture();
|
|
|
|
var texture = string.IsNullOrWhiteSpace( TexturePath ) ? null : Texture.Load( TexturePath );
|
|
texture ??= Texture.White;
|
|
|
|
var result = compiler.ResultTexture( Sampler, input, texture );
|
|
var coords = compiler.Result( Coords );
|
|
|
|
if ( compiler.Stage == GraphCompiler.ShaderStage.Vertex )
|
|
{
|
|
return new NodeResult( 4, $"{result.Item1}.SampleLevel(" +
|
|
$" g_sSampler{result.Item2}," +
|
|
$" {(coords.IsValid ? $"{coords.Cast( 2 )}" : "i.vTextureCoords.xy")}, 0 )" );
|
|
}
|
|
else
|
|
{
|
|
return new NodeResult( 4, $"Tex2DS( {result.Item1}," +
|
|
$" g_sSampler{result.Item2}," +
|
|
$" {(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 Cube Texture
|
|
/// </summary>
|
|
[Title( "Texture Cube" ), Category( "Textures" ), Icon( "view_in_ar" )]
|
|
public sealed class TextureCube : ShaderNode
|
|
{
|
|
/// <summary>
|
|
/// Coordinates to sample this cubemap
|
|
/// </summary>
|
|
[Title( "Coordinates" )]
|
|
[Input( typeof( Vector3 ) )]
|
|
[Hide]
|
|
public NodeInput Coords { get; set; }
|
|
|
|
/// <summary>
|
|
/// Texture to sample in preview
|
|
/// </summary>
|
|
[ImageAssetPath]
|
|
public string Texture { get; set; }
|
|
|
|
/// <summary>
|
|
/// How the texture is filtered and wrapped when sampled
|
|
/// </summary>
|
|
[InlineEditor( Label = false ), Group( "Sampler" )]
|
|
public Sampler Sampler { get; set; }
|
|
|
|
/// <summary>
|
|
/// Settings for how this texture shows up in material editor
|
|
/// </summary>
|
|
[InlineEditor( Label = false ), Group( "UI" )]
|
|
public TextureInput UI { get; set; } = new TextureInput
|
|
{
|
|
ImageFormat = TextureFormat.DXT5,
|
|
SrgbRead = true,
|
|
Default = Color.White,
|
|
};
|
|
|
|
public TextureCube() : 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( Color ) ), Title( "RGBA" )]
|
|
public NodeResult.Func Result => ( GraphCompiler compiler ) =>
|
|
{
|
|
var input = UI;
|
|
input.Type = TextureType.TexCube;
|
|
|
|
var result = compiler.ResultTexture( Sampler, input, Sandbox.Texture.Load( Texture ) );
|
|
var coords = compiler.Result( Coords );
|
|
|
|
return new NodeResult( 4, $"TexCubeS( {result.Item1}," +
|
|
$" g_sSampler{result.Item2}," +
|
|
$" {(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( 1, $"{result}.{component}", true ) : new( 1, "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>
|
|
/// Sample a 2D texture from 3 directions, then blend based on a normal vector.
|
|
/// </summary>
|
|
[Title( "Texture Triplanar" ), Category( "Textures" ), Icon( "photo_library" )]
|
|
public sealed class TextureTriplanar : TextureSamplerBase
|
|
{
|
|
/// <summary>
|
|
/// Coordinates to sample this texture (Defaults to vertex position)
|
|
/// </summary>
|
|
[Title( "Coordinates" )]
|
|
[Input( typeof( Vector3 ) )]
|
|
[Hide]
|
|
public NodeInput Coords { get; set; }
|
|
|
|
/// <summary>
|
|
/// Normal to use when blending between each sampled direction (Defaults to vertex normal)
|
|
/// </summary>
|
|
[Title( "Normal" )]
|
|
[Input( typeof( Vector3 ) )]
|
|
[Hide]
|
|
public NodeInput Normal { get; set; }
|
|
|
|
/// <summary>
|
|
/// RGBA color result
|
|
/// </summary>
|
|
[Hide]
|
|
[Output( typeof( Color ) ), Title( "RGBA" )]
|
|
public NodeResult.Func Result => ( GraphCompiler compiler ) =>
|
|
{
|
|
var input = UI;
|
|
input.Type = TextureType.Tex2D;
|
|
|
|
CompileTexture();
|
|
|
|
var texture = string.IsNullOrWhiteSpace( TexturePath ) ? null : Texture.Load( TexturePath );
|
|
texture ??= Texture.White;
|
|
|
|
var (tex, sampler) = compiler.ResultTexture( Sampler, input, texture );
|
|
var coords = compiler.Result( Coords );
|
|
var normal = compiler.Result( Normal );
|
|
|
|
var result = compiler.ResultFunction( "TexTriplanar_Color",
|
|
tex,
|
|
$"g_sSampler{sampler}",
|
|
coords.IsValid ? coords.Cast( 3 ) : "(i.vPositionWithOffsetWs.xyz + g_vHighPrecisionLightingOffsetWs.xyz) / 39.3701",
|
|
normal.IsValid ? normal.Cast( 3 ) : "normalize( i.vNormalWs.xyz )" );
|
|
|
|
return new NodeResult( 4, 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 2D texture from 3 directions, then blend based on a normal vector.
|
|
/// </summary>
|
|
[Title( "Normal Map Triplanar" ), Category( "Textures" ), Icon( "texture" )]
|
|
public sealed class NormapMapTriplanar : TextureSamplerBase
|
|
{
|
|
/// <summary>
|
|
/// Coordinates to sample this texture (Defaults to vertex position)
|
|
/// </summary>
|
|
[Title( "Coordinates" )]
|
|
[Input( typeof( Vector3 ) )]
|
|
[Hide]
|
|
public NodeInput Coords { get; set; }
|
|
|
|
/// <summary>
|
|
/// Normal to use when blending between each sampled direction (Defaults to vertex normal)
|
|
/// </summary>
|
|
[Title( "Normal" )]
|
|
[Input( typeof( Vector3 ) )]
|
|
[Hide]
|
|
public NodeInput Normal { get; set; }
|
|
|
|
public NormapMapTriplanar()
|
|
{
|
|
ExpandSize = new Vector2( 0f, 128f );
|
|
|
|
UI = new TextureInput
|
|
{
|
|
ImageFormat = TextureFormat.DXT5,
|
|
SrgbRead = false,
|
|
ColorSpace = TextureColorSpace.Linear,
|
|
Extension = TextureExtension.Normal,
|
|
Processor = TextureProcessor.NormalizeNormals,
|
|
Default = new Color( 0.5f, 0.5f, 1f, 1f )
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// RGBA color result
|
|
/// </summary>
|
|
[Hide]
|
|
[Output( typeof( Vector3 ) ), Title( "XYZ" )]
|
|
public NodeResult.Func Result => ( GraphCompiler compiler ) =>
|
|
{
|
|
var input = UI;
|
|
input.Type = TextureType.Tex2D;
|
|
|
|
CompileTexture();
|
|
|
|
var texture = string.IsNullOrWhiteSpace( TexturePath ) ? null : Texture.Load( TexturePath );
|
|
texture ??= Texture.White;
|
|
|
|
var (tex, sampler) = compiler.ResultTexture( Sampler, input, texture );
|
|
var coords = compiler.Result( Coords );
|
|
var normal = compiler.Result( Normal );
|
|
|
|
var result = compiler.ResultFunction( "TexTriplanar_Normal",
|
|
tex,
|
|
$"g_sSampler{sampler}",
|
|
coords.IsValid ? coords.Cast( 3 ) : "(i.vPositionWithOffsetWs.xyz + g_vHighPrecisionLightingOffsetWs.xyz) / 39.3701",
|
|
normal.IsValid ? normal.Cast( 3 ) : "normalize( i.vNormalWs.xyz )" );
|
|
|
|
return new NodeResult( 3, result );
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Texture Coordinate from vertex data
|
|
/// </summary>
|
|
[Title( "Texture Coordinate" ), Category( "Variables" ), Icon( "texture" )]
|
|
public sealed class TextureCoord : ShaderNode
|
|
{
|
|
/// <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( 2, $"{compiler.ResultValue( Tiling.IsNearZeroLength )} ? {result} : ({result}) * {compiler.ResultValue( Tiling )}" );
|
|
}
|
|
else
|
|
{
|
|
var result = UseSecondaryCoord ? "i.vTextureCoords.zw" : "i.vTextureCoords.xy";
|
|
return Tiling.IsNearZeroLength ? new( 2, result ) : new( 2, $"{result} * {compiler.ResultValue( Tiling )}" );
|
|
}
|
|
};
|
|
}
|