Refactor material parsing and loading

This should hopefully pave the way for usable module switching using B9PS
This commit is contained in:
Andrew Cassidy 2020-06-02 00:53:17 -07:00
parent 114fc745f0
commit 7e520f97ca
9 changed files with 133 additions and 151 deletions

View File

@ -47,10 +47,9 @@ PART
decalFront = Decal-Front decalFront = Decal-Front
decalBack = Decal-Back decalBack = Decal-Back
decalShader = ConformalDecals/Feature/Bumped
MATERIAL useBaseNormal = false
{
shader = ConformalDecals/Feature/Bumped
TEXTURE TEXTURE
{ {
@ -66,5 +65,4 @@ PART
isNormalMap = true isNormalMap = true
} }
} }
}
} }

View File

@ -49,11 +49,11 @@
</Reference> </Reference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="MaterialModifiers\ColorMaterialProperty.cs" /> <Compile Include="MaterialModifiers\MaterialColorProperty.cs" />
<Compile Include="MaterialModifiers\FloatMaterialProperty.cs" /> <Compile Include="MaterialModifiers\MaterialFloatProperty.cs" />
<Compile Include="MaterialModifiers\MaterialProperty.cs" /> <Compile Include="MaterialModifiers\MaterialProperty.cs" />
<Compile Include="MaterialModifiers\MaterialPropertyCollection.cs" /> <Compile Include="MaterialModifiers\MaterialPropertyCollection.cs" />
<Compile Include="MaterialModifiers\TextureMaterialProperty.cs" /> <Compile Include="MaterialModifiers\MaterialTextureProperty.cs" />
<Compile Include="ProjectionTarget.cs" /> <Compile Include="ProjectionTarget.cs" />
<Compile Include="ModuleConformalDecal.cs" /> <Compile Include="ModuleConformalDecal.cs" />
<Compile Include="Logging.cs" /> <Compile Include="Logging.cs" />

View File

@ -2,10 +2,10 @@ using System;
using UnityEngine; using UnityEngine;
namespace ConformalDecals.MaterialModifiers { namespace ConformalDecals.MaterialModifiers {
public class ColorMaterialProperty : MaterialProperty { public class MaterialColorProperty : MaterialProperty {
private readonly Color _color; private readonly Color _color;
public ColorMaterialProperty(ConfigNode node) : base(node) { public MaterialColorProperty(ConfigNode node) : base(node) {
_color = ParsePropertyColor(node, "color", false); _color = ParsePropertyColor(node, "color", false);
} }

View File

@ -2,10 +2,10 @@ using System;
using UnityEngine; using UnityEngine;
namespace ConformalDecals.MaterialModifiers { namespace ConformalDecals.MaterialModifiers {
public class FloatMaterialProperty : MaterialProperty { public class MaterialFloatProperty : MaterialProperty {
private readonly float _value; private readonly float _value;
public FloatMaterialProperty(ConfigNode node) : base(node) { public MaterialFloatProperty(ConfigNode node) : base(node) {
_value = ParsePropertyFloat(node, "value", false); _value = ParsePropertyFloat(node, "value", false);
} }

View File

@ -1,22 +1,16 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Smooth.Delegates;
using UnityEngine; using UnityEngine;
namespace ConformalDecals.MaterialModifiers { namespace ConformalDecals.MaterialModifiers {
public class MaterialPropertyCollection : ScriptableObject { public class MaterialPropertyCollection : ScriptableObject {
private static int _opacityID = Shader.PropertyToID("_Opacity"); private static readonly int OpacityId = Shader.PropertyToID("_Opacity");
private static int _cutoffID = Shader.PropertyToID("_Cutoff"); private static readonly int CutoffId = Shader.PropertyToID("_Cutoff");
public TextureMaterialProperty MainTextureProperty { get; set; } public MaterialTextureProperty MainMaterialTextureProperty { get; set; }
public bool UseBaseNormal { get; private set; }
private List<MaterialProperty> _materialProperties; private List<MaterialProperty> _materialProperties;
private List<TextureMaterialProperty> _textureMaterialProperties; private List<MaterialTextureProperty> _textureMaterialProperties;
public String BaseNormalSrc { get; private set; }
public String BaseNormalDest { get; private set; }
public Material DecalMaterial { public Material DecalMaterial {
get { get {
@ -31,90 +25,43 @@ namespace ConformalDecals.MaterialModifiers {
private Shader _decalShader; private Shader _decalShader;
private Material _protoDecalMaterial; private Material _protoDecalMaterial;
private const string _normalTextureName = "_BumpMap"; public void Initialize() {
public void Initialize(ConfigNode node, PartModule module) {
// Initialize fields
_materialProperties = new List<MaterialProperty>(); _materialProperties = new List<MaterialProperty>();
_textureMaterialProperties = new List<TextureMaterialProperty>(); _textureMaterialProperties = new List<MaterialTextureProperty>();
// Get shader
var shaderString = node.GetValue("shader") ?? throw new FormatException("Missing shader name in material");
_decalShader = Shabby.Shabby.FindShader(shaderString);
// note to self: null coalescing does not work on UnityEngine classes
if (_decalShader == null) {
throw new FormatException($"Shader not found: '{shaderString}'");
} }
// Get useBaseNormal value public void AddProperty(MaterialProperty property) {
var useBaseNormalString = node.GetValue("useBaseNormal"); foreach (var p in _materialProperties) {
if (p.PropertyName == property.PropertyName) {
if (useBaseNormalString != null) { _materialProperties.Remove(property);
if (bool.TryParse(useBaseNormalString, out var useBaseNormalRef)) {
UseBaseNormal = useBaseNormalRef;
} }
else {
throw new FormatException($"Improperly formatted bool value for 'useBaseNormal' : {useBaseNormalString}");
}
}
else {
UseBaseNormal = false;
}
// Get basenormal source and destination property names
BaseNormalSrc = node.GetValue("baseNormalSource") ?? _normalTextureName;
BaseNormalDest = node.GetValue("baseNormalDestination") ?? _normalTextureName;
// Parse all materialProperties
foreach (ConfigNode propertyNode in node.nodes) {
try {
MaterialProperty property;
switch (propertyNode.name) {
case "FLOAT":
property = new FloatMaterialProperty(propertyNode);
break;
case "COLOR":
property = new ColorMaterialProperty(propertyNode);
break;
case "TEXTURE":
property = new TextureMaterialProperty(propertyNode);
var textureModifier = (TextureMaterialProperty) property;
if (textureModifier.IsMain) {
if (MainTextureProperty == null) {
MainTextureProperty = textureModifier;
}
else {
// multiple textures have been marked as main!
// non-fatal issue, ignore this one and keep using current main texture
module.LogWarning(
$"Material texture property {textureModifier.TextureUrl} is marked as main, but material already has a main texture! \n" +
$"Defaulting to {MainTextureProperty.TextureUrl}");
}
}
_textureMaterialProperties.Add(textureModifier);
break;
default:
throw new FormatException($"Invalid property type '{propertyNode.name}' in material");
} }
_materialProperties.Add(property); _materialProperties.Add(property);
}
catch (Exception e) { if (property is MaterialTextureProperty textureProperty) {
// Catch exception from parsing current material property foreach (var p in _textureMaterialProperties) {
// And print it to the log as an Error if (p.PropertyName == textureProperty.PropertyName) {
module.LogException("Exception while parsing material node", e); _textureMaterialProperties.Remove(textureProperty);
} }
} }
module.Log($"Parsed {_materialProperties.Count} properties"); _textureMaterialProperties.Add(textureProperty);
if (textureProperty.IsMain) MainMaterialTextureProperty ??= textureProperty;
}
}
public void SetShader(string shaderName) {
if (_decalShader == null && string.IsNullOrEmpty(shaderName)) {
throw new FormatException("Shader name not provided");
}
var shader = Shabby.Shabby.FindShader(shaderName);
if (shader == null) throw new FormatException($"Unable to find specified shader '{shaderName}'");
_decalShader = shader;
} }
public void SetScale(Material material, Vector2 scale) { public void SetScale(Material material, Vector2 scale) {
@ -124,11 +71,11 @@ namespace ConformalDecals.MaterialModifiers {
} }
public void SetOpacity(Material material, float opacity) { public void SetOpacity(Material material, float opacity) {
material.SetFloat(_opacityID, opacity); material.SetFloat(OpacityId, opacity);
} }
public void SetCutoff(Material material, float cutoff) { public void SetCutoff(Material material, float cutoff) {
material.SetFloat(_cutoffID, cutoff); material.SetFloat(CutoffId, cutoff);
} }
private Material MakeMaterial(Shader shader) { private Material MakeMaterial(Shader shader) {

View File

@ -2,7 +2,7 @@ using System;
using UnityEngine; using UnityEngine;
namespace ConformalDecals.MaterialModifiers { namespace ConformalDecals.MaterialModifiers {
public class TextureMaterialProperty : MaterialProperty { public class MaterialTextureProperty : MaterialProperty {
public string TextureUrl { get; } public string TextureUrl { get; }
public Texture2D TextureRef { get; } public Texture2D TextureRef { get; }
@ -17,7 +17,7 @@ namespace ConformalDecals.MaterialModifiers {
private readonly Vector2 _textureOffset; private readonly Vector2 _textureOffset;
private readonly Vector2 _textureScale; private readonly Vector2 _textureScale;
public TextureMaterialProperty(ConfigNode node) : base(node) { public MaterialTextureProperty(ConfigNode node) : base(node) {
TextureUrl = node.GetValue("textureURL"); TextureUrl = node.GetValue("textureURL");
var textureInfo = GameDatabase.Instance.GetTextureInfo(TextureUrl); var textureInfo = GameDatabase.Instance.GetTextureInfo(TextureUrl);

View File

@ -28,6 +28,7 @@ namespace ConformalDecals {
[KSPField] public string decalBack = string.Empty; [KSPField] public string decalBack = string.Empty;
[KSPField] public string decalModel = string.Empty; [KSPField] public string decalModel = string.Empty;
[KSPField] public string decalProjector = string.Empty; [KSPField] public string decalProjector = string.Empty;
[KSPField] public string decalShader = string.Empty;
[KSPField] public Transform decalFrontTransform; [KSPField] public Transform decalFrontTransform;
[KSPField] public Transform decalBackTransform; [KSPField] public Transform decalBackTransform;
@ -46,9 +47,12 @@ namespace ConformalDecals {
[KSPField] public Vector2 decalQueueRange = new Vector2(2100, 2400); [KSPField] public Vector2 decalQueueRange = new Vector2(2100, 2400);
[KSPField] public bool updateBackScale = true; [KSPField] public bool updateBackScale = true;
[KSPField] public bool useBaseNormal = true;
[KSPField] public MaterialPropertyCollection materialProperties; [KSPField] public MaterialPropertyCollection materialProperties;
[KSPField] public Material decalMaterial; [KSPField] public Material decalMaterial;
[KSPField] public Material backMaterial;
private static int _decalQueueCounter = -1; private static int _decalQueueCounter = -1;
@ -59,7 +63,11 @@ namespace ConformalDecals {
private Bounds _decalBounds; private Bounds _decalBounds;
private Vector2 _backTextureBaseScale; private Vector2 _backTextureBaseScale;
private Material _backMaterial; public ModuleConformalDecal() {
decalBackTransform = null;
decalModelTransform = null;
decalProjectorTransform = null;
}
private int DecalQueue { private int DecalQueue {
get { get {
@ -76,16 +84,40 @@ namespace ConformalDecals {
public override void OnLoad(ConfigNode node) { public override void OnLoad(ConfigNode node) {
this.Log("Loading module"); this.Log("Loading module");
try { try {
// parse MATERIAL node
var materialNode = node.GetNode("MATERIAL") ?? throw new FormatException("Missing MATERIAL node in module"); if (materialProperties == null) {
// materialProperties is null, so make a new one
materialProperties = ScriptableObject.CreateInstance<MaterialPropertyCollection>(); materialProperties = ScriptableObject.CreateInstance<MaterialPropertyCollection>();
materialProperties.Initialize(materialNode, this); materialProperties.Initialize();
}
else {
// materialProperties already exists, so make a copy
materialProperties = ScriptableObject.Instantiate(materialProperties);
}
// add texture nodes
foreach (var textureNode in node.GetNodes("TEXTURE")) {
materialProperties.AddProperty(new MaterialTextureProperty(textureNode));
}
// add float nodes
foreach (var floatNode in node.GetNodes("FLOAT")) {
materialProperties.AddProperty(new MaterialFloatProperty(floatNode));
}
// add color nodes
foreach (var colorNode in node.GetNodes("COLOR")) {
materialProperties.AddProperty(new MaterialColorProperty(colorNode));
}
// set shader
materialProperties.SetShader(decalShader);
// get decal material // get decal material
decalMaterial = materialProperties.DecalMaterial; decalMaterial = materialProperties.DecalMaterial;
// get aspect ratio from main texture, if it exists // get aspect ratio from main texture, if it exists
var mainTexture = materialProperties.MainTextureProperty; var mainTexture = materialProperties.MainMaterialTextureProperty;
if (mainTexture != null) { if (mainTexture != null) {
aspectRatio = mainTexture.AspectRatio; aspectRatio = mainTexture.AspectRatio;
} }
@ -127,6 +159,12 @@ namespace ConformalDecals {
decalProjectorTransform = part.FindModelTransform(decalProjector); decalProjectorTransform = part.FindModelTransform(decalProjector);
if (decalProjectorTransform == null) throw new FormatException($"Could not find decalProjector transform: '{decalProjector}'."); if (decalProjectorTransform == null) throw new FormatException($"Could not find decalProjector transform: '{decalProjector}'.");
} }
// update EVERYTHING if currently attached
if (_isAttached) {
OnDetach();
OnAttach();
}
} }
catch (Exception e) { catch (Exception e) {
this.LogException("Exception parsing partmodule", e); this.LogException("Exception parsing partmodule", e);
@ -207,12 +245,12 @@ namespace ConformalDecals {
this.LogError($"Specified decalBack transform {decalBack} has no renderer attached! Setting updateBackScale to false."); this.LogError($"Specified decalBack transform {decalBack} has no renderer attached! Setting updateBackScale to false.");
updateBackScale = false; updateBackScale = false;
} }
else if ((_backMaterial = backRenderer.material) == null) { else if ((backMaterial = backRenderer.material) == null) {
this.LogError($"Specified decalBack transform {decalBack} has a renderer but no material! Setting updateBackScale to false."); this.LogError($"Specified decalBack transform {decalBack} has a renderer but no material! Setting updateBackScale to false.");
updateBackScale = false; updateBackScale = false;
} }
else { else {
_backTextureBaseScale = _backMaterial.GetTextureScale(PropertyIDs._MainTex); _backTextureBaseScale = backMaterial.GetTextureScale(PropertyIDs._MainTex);
} }
} }
@ -300,7 +338,7 @@ namespace ConformalDecals {
this.Log($"Adding target for object {meshFilter.gameObject.name} with the mesh {mesh.name}"); this.Log($"Adding target for object {meshFilter.gameObject.name} with the mesh {mesh.name}");
// create new ProjectionTarget to represent the renderer // create new ProjectionTarget to represent the renderer
var target = new ProjectionTarget(renderer, mesh, materialProperties); var target = new ProjectionTarget(renderer, mesh, useBaseNormal);
this.Log("done."); this.Log("done.");
@ -349,7 +387,7 @@ namespace ConformalDecals {
// update back material scale // update back material scale
if (updateBackScale) { if (updateBackScale) {
_backMaterial.SetTextureScale(PropertyIDs._MainTex, new Vector2(size.x * _backTextureBaseScale.x, size.y * _backTextureBaseScale.y)); backMaterial.SetTextureScale(PropertyIDs._MainTex, new Vector2(size.x * _backTextureBaseScale.x, size.y * _backTextureBaseScale.y));
} }
// update material scale // update material scale

View File

@ -17,30 +17,29 @@ namespace ConformalDecals {
private bool _projectionEnabled; private bool _projectionEnabled;
// property block // property block
public readonly MaterialPropertyBlock decalMPB; private readonly MaterialPropertyBlock _decalMPB;
public ProjectionTarget(MeshRenderer targetRenderer, Mesh targetMesh, MaterialPropertyCollection properties) { public ProjectionTarget(MeshRenderer targetRenderer, Mesh targetMesh, bool useBaseNormal) {
target = targetRenderer.transform; target = targetRenderer.transform;
_targetRenderer = targetRenderer; _targetRenderer = targetRenderer;
_targetMesh = targetMesh; _targetMesh = targetMesh;
var targetMaterial = targetRenderer.sharedMaterial; var targetMaterial = targetRenderer.sharedMaterial;
decalMPB = new MaterialPropertyBlock(); _decalMPB = new MaterialPropertyBlock();
if (properties.UseBaseNormal) { if (useBaseNormal) {
var normalSrcID = Shader.PropertyToID(properties.BaseNormalSrc); var normalID = Shader.PropertyToID("_BumpMap");
var normalDestID = Shader.PropertyToID(properties.BaseNormalDest); var normalIDST = Shader.PropertyToID("_BumpMap_ST");
var normalDestIDST = Shader.PropertyToID(properties.BaseNormalDest + "_ST");
var normal = targetMaterial.GetTexture(normalSrcID); var normal = targetMaterial.GetTexture(normalID);
if (normal != null) { if (normal != null) {
decalMPB.SetTexture(normalDestID, targetMaterial.GetTexture(normalSrcID)); _decalMPB.SetTexture(normalID, targetMaterial.GetTexture(normalID));
var normalScale = targetMaterial.GetTextureScale(normalSrcID); var normalScale = targetMaterial.GetTextureScale(normalID);
var normalOffset = targetMaterial.GetTextureOffset(normalSrcID); var normalOffset = targetMaterial.GetTextureOffset(normalID);
decalMPB.SetVector(normalDestIDST, new Vector4(normalScale.x, normalScale.y, normalOffset.x, normalOffset.y)); _decalMPB.SetVector(normalIDST, new Vector4(normalScale.x, normalScale.y, normalOffset.x, normalOffset.y));
} }
} }
} }
@ -55,9 +54,9 @@ namespace ConformalDecals {
var decalNormal = projectorToTargetMatrix.MultiplyVector(Vector3.back).normalized; var decalNormal = projectorToTargetMatrix.MultiplyVector(Vector3.back).normalized;
var decalTangent = projectorToTargetMatrix.MultiplyVector(Vector3.right).normalized; var decalTangent = projectorToTargetMatrix.MultiplyVector(Vector3.right).normalized;
decalMPB.SetMatrix(_projectionMatrixID, projectionMatrix); _decalMPB.SetMatrix(_projectionMatrixID, projectionMatrix);
decalMPB.SetVector(_decalNormalID, decalNormal); _decalMPB.SetVector(_decalNormalID, decalNormal);
decalMPB.SetVector(_decalTangentID, decalTangent); _decalMPB.SetVector(_decalTangentID, decalTangent);
Debug.Log($"Projection enabled for {target.gameObject}"); Debug.Log($"Projection enabled for {target.gameObject}");
} }
else { else {
@ -68,10 +67,10 @@ namespace ConformalDecals {
public bool Render(Material decalMaterial, MaterialPropertyBlock partMPB, Camera camera) { public bool Render(Material decalMaterial, MaterialPropertyBlock partMPB, Camera camera) {
if (_projectionEnabled) { if (_projectionEnabled) {
decalMPB.SetFloat(PropertyIDs._RimFalloff, partMPB.GetFloat(PropertyIDs._RimFalloff)); _decalMPB.SetFloat(PropertyIDs._RimFalloff, partMPB.GetFloat(PropertyIDs._RimFalloff));
decalMPB.SetColor(PropertyIDs._RimColor, partMPB.GetColor(PropertyIDs._RimColor)); _decalMPB.SetColor(PropertyIDs._RimColor, partMPB.GetColor(PropertyIDs._RimColor));
Graphics.DrawMesh(_targetMesh, target.localToWorldMatrix, decalMaterial, 0, camera, 0, decalMPB, ShadowCastingMode.Off, true); Graphics.DrawMesh(_targetMesh, target.localToWorldMatrix, decalMaterial, 0, camera, 0, _decalMPB, ShadowCastingMode.Off, true);
return true; return true;
} }