Colored Skins Mod/Library
McGlaspie
www.team156.com Join Date: 2010-07-26 Member: 73044Members, Super Administrators, Forum Admins, NS2 Developer, NS2 Playtester, Squad Five Blue, Squad Five Silver, Squad Five Gold, Reinforced - Onos, WC 2013 - Gold, Subnautica Playtester
Greetings fellow Modders!
During the development of my Marine vs Marine mod, I needed to devise a means to colorize all of the Marine related models. My initial "hack" to get this to work was modifying the shaders to switch which texture is loaded based on a Team Number input. This did the job just fine; however, it bloated the size of MvM to just over 169MB uncompressed. This was not satisfactory nor was it flexible in any way. So, during the Post-Reinforced update of MvM, I decided to come up with a more robust, lighter-weight, and modular way to colorize the models. As a result, my Colored Skins mod was born. I've released the first beta version of it on the Workshop in the hope it will provide some new options for other modders out there.
Here's a poorly made (and soundless) demo video of the system in action:
Steam Workshop: http://steamcommunity.com/sharedfiles/filedetails/?id=177027441
It's not complete just yet, but I'll be adding in support for additional shaders soon. When this is done, it will allow for more models to be colorized. I'm also working on a How-To type guide for using that I'll post here once it's finished.
During the development of my Marine vs Marine mod, I needed to devise a means to colorize all of the Marine related models. My initial "hack" to get this to work was modifying the shaders to switch which texture is loaded based on a Team Number input. This did the job just fine; however, it bloated the size of MvM to just over 169MB uncompressed. This was not satisfactory nor was it flexible in any way. So, during the Post-Reinforced update of MvM, I decided to come up with a more robust, lighter-weight, and modular way to colorize the models. As a result, my Colored Skins mod was born. I've released the first beta version of it on the Workshop in the hope it will provide some new options for other modders out there.
Here's a poorly made (and soundless) demo video of the system in action:
Steam Workshop: http://steamcommunity.com/sharedfiles/filedetails/?id=177027441
It's not complete just yet, but I'll be adding in support for additional shaders soon. When this is done, it will allow for more models to be colorized. I'm also working on a How-To type guide for using that I'll post here once it's finished.
Comments
Overview:
This guide will not cover how to make textures, UV mapping, or techniques (in a graphics program) to use in order to create color map textures compatible with this library. It will cover the prerequisites of a color map texture and provide some details for what is needed, in addition to some general tips. This guide also makes the assumption that you have an understanding of how to create mods for NS2 and possess and understanding of the Lua scripting language.
Now that we have that out of the way, we can move on to what the Colored Skins system is and how it works. Onwards!
Components:
The ColoredSkinsMixin is how you link your mod's code to this system. This Mixin Lua class provides your game code with the prerequisites to control the data passed to the ColoredSkins_XYZ pixel shader program(s). It is very important to understand this Mixin is not designed to function in a networked (Client, Predict, and Server VMs) context. It is intended solely to be executed in the Client Lua VM. The ColoredSkinsMixin forces the implementing classes to define several new methods. It will also add some new class properties (members) to your objects. These changes are as follows:
Required Methods:
Additional Properties:
In addition to the above, it also defines its own OnRenderUpdate() method. This is the function used to feed the color data from an entity to the pixel shaders. In its default state, it can handle almost all conditions meet in the NS2 game. It will attempt to feed the material parameters (skin color values) to the shader each time this method is called. This is done for every frame that the implementing entities are on-screen, and it is called on a per-entity basis. This allows for each implementor of this Mixin to contain its own color values driven by whatever conditons or state-data you see fit.
This library contains several surface shaders. These are the meat-n-potato of this mod. There are a number of different surface shader files. These should be set as the shader used in the material file for the models you wish to be colorized. All of the shaders can be found in the "shaders" folder of the mod and the operations they perform will be convered in the next section.
Each model that will be colorized by this system must have a new (or modified vanilla) material file. The material file is used to link together the selected shader, the model, and the color map texture. In addition to this, it also defines the properties of a single color map texture. By specifying the properties of the Color Map texture, in the material file, the option to have multiple maps per texture file is made available. The details of what exactly goes into a material file will be covered in the Color Processing and Blending Control section.
This is an RGBA DXT5 format file. It defined the behavior of the pixel shader in the context of how it should blend the colors together in addition to defining how much and where specific color layers should be applied to a model's original (default) texture. Much more will be convered on this in the Color Map Texture and Color Processing and Blending Control sections.
Material Files:
With any materials in this system, the appropriate Surface Shader needs to be chosen. The shader parameter should be set to: shader = "shaders/ColoredSkins[XYZ].surface_shader". The [XYZ] will vary depending on what the model requires to match it's desired look. Look in the 'shaders' directory of the ColoredSkins mod to see the available and compatible shaders.
ColoredSkins Materials Parameters:
- modelColorMap - DXT5 format texture file that specifies how much and where to apply the three layer colors. It is possible for this to be a Texture Atlas. An atlas allows for multiple color map textures to be condensed into a single texture file. Each section within the file is denoted by a set of X,Y coordinates to specify where the Top-Left corner of a new color map image begins.
- colorizeEnabled - Float boolean to toggle skin colorization via the surface shader(s)
- colorMapIndex - (0 to X) Integer. This specifies which texture to read as the color map from the modelColorMap Texture Atlas file. The actual X,Y coordinates within the texture itself are determined by the shader program at run-time.
- numColumns - (1 to X) Integer. This describes the Texture Atlas. If this parameter is not set in a material its value will default to: 1. This is used in conjunction with the colorMapIndex and numRows to calculate the X,Y texture coordinates that should be sampled from the modelColorMap texture at run-time.
- numRows - (1 to X) Integer. Identical to the numColums paramter, this describes the dimensions of the modelColorMap Texture Atlas. This will default to 1 if it is not specified in the material file.
- modelBaseColorR, modelBaseColorG, modelBaseColorB - Float,Float,Float
- modelAccentColorR, modelAccentColorG, modelAccentColorB - Float,Float,Float
- modelTrimColorR, modelAccentColorG, modelAccentColorB - Float,Float,Float
All of the model color values (modeXYZColorR/G/B parameters) must be a 0 - 1 value. To determine what the float value is for the equivalent 0-255, use the below forumula:Texture Atlas
As mentioned in the description of the modelColorMap material parameter; this system supports usage of a Texture Atlas. This is multiple textures contained in a single texture file. Below is an example texture atlas file:
This example atlas has four color map textures in it. The numColumns and numRows material parameters for this example would be: The number of rows and columns in a altas color map is dependent upon the entire texture's dimensions in constrast with a model's albedo texture dismensions. So, imagine taking an albedo/diffuse texture that is 512x512 pixels. If you wanted two color maps as an atlas texture with a single coumn, you would have the color map's dimension be 512x1024.
The system support multiple columns and rows, up to a maximum of 4x4; however, it is recommend that a absolute minimum number of rows and columns be used because this will increase the memory usage, and mod filesize.
The Color Map:
So let's go over just WTH is going on in this texture? As it shows, there are distinctly colored areas of Red, Green, and Blue. These colors denote to the shader program: which color to apply, how much of that color to apply, and where to apply it. The system as whole supports the application of three colors via the color map. These color channels denote where the Base, Accent, and Trim colors get processed and applied to a models original texture. The Color Map texture's Alpha channel is used to specify the intensity of a specific channel's (RGB) color. For example, if a pixel's Alpha channel value is 255, then the R,G, or B color (depending on which color application pass is being performed in the shader program) will be wholely applied to the model's original albedo/diffuse texture. If a pixel's Alpha channel value is 0, then no coloration will occur.
Assuming ColoredSkinsMixin and associated shaders have not changed:
ColoredSkinMixin.skinBaseColor is applied via the Color Map texture's RED channel
ColoredSkinMixin.skinAccentColor is applied via the Color Map texture's GREEN channel
ColoredSkinMixin.skinTrimColor is applied via the Color Map texture's BLUE channel
By segementing out the usage of the R,G, and B channels in the Color Map texture we can have up to three Colors applied to the models vanilla texture. This does not mean the per-pixel RGB values of the Color Map texture must explicity be Red, Green, or Blue. They can be mixed and matched as a Texture Artist sees fit. When using more than one channel (RGB) in a Color Map, it is important to remember the Green channel is considered to be the accented or highlights pass. In its default conditions, the ColoredSkinsMixin will consider the Accent Color (Green channel in Color Map texture) to be the parts of the texture that should glow (Have Emissive qualities). This should be taken into account when creating a Color Map for a model. However, this doesn't not always the case, as some of the Surface Shaders in the Colored Skins mod do not have an emissive layer.
Color Processing and Blending Control
Go grab a serving of your preferred beverage or snack (both?), this section will take some time to get through. This will go over how the entire system goes start to finish in the coloration process, and it will explain how you can control the system to suite your mod's needs.
TODO Explain shader load sequence
TODO Explain albedo process
TODO Explain color channel segregation and blending
TODO Explain color processing
TODO Explain blending control via color map pixels
TODO Explain Emissive layer handling and how it is tied to albedo layer processing
TODO Explain Glow / Dissolve customization sequence
TODO Explain misc variation(s) of all above
TODO Make picture sequence illustrating entire process start to finish
The Game Code:
This example will be updating an Extractor, so it is colorized differently based on the which team it is associated with.
Script File: lua/mymod/Extractor.lua
This is identical to (mostly) how Mixins are initialized. Since the ColoredSkinsMixin does not (by default) associate or require any network fields, we do not have to externally (to the modified class) add any network fields. The Mixin should only be added to Clients, and never Server, or Predict VMs. It is also required to initialize the Mixin in an entity's OnCreate method in order for the ColoredSkinsMixin specific properties to be available for the initialization routine(s).
The above excerpt isn't anything special; however, there are a number of important things going that need an explanation. First off, we're calling "InitializeSkin()". This function is required by the Mixin, and you will get script errors if it is not defined. This method effectively primes an entity to be ready to be colorized. The default Base, Accent, and Trim colors defined in the ColoredSkinsMixin itself are always solid black (I.e. Color(0, 0, 0, 1) - RGBA). The purpose of the "InitializeSkin()" function is to allow the colorized entity to prime itself for it's default state.
All of the shown methods, excluding the OnCreate and OnInitialized, are required by any classes implementing the ColoredSkinsMixin. In this example, the specific color values will never change for the life-cycle of an Extractor (extractors cannot change teams). This does not prevent and Entity from changing the color values during its lifetime. If this is needed, the self.XYZColor property can be updated from the Entity's OnUpdate method. The ColoredSkinsMixin uses the implementing Entity's self.skinBaseColor, self.skinAccentColor, and self.skinTrimColor properties to update the Colored Skins shaders, via the material parameters. So, any changes based on other game factors and/or data should be done with these aforementioned properties.
Note: The color values should NEVER be updated in the Entity's OnUpdateRender() method. Doing so can introduce framerate drops that will increase the more colorized entities on-screen.
Last, but not least, reload the Extractor class:
All done. It is very important to understand the ColoredSkinMixin uses the self.skinXYZ properties to supply the pixel shaders with parameter values. This mean these properties must be updated in an Entity's appropriate XYZUpdate method if the coloration needs to be more dynamic. Otherwise, the implementing entity will only get its coloration value when it is initialized (I.e. when self:InitializeSkin() runs).
Limitations and Known Issues
If an entity should change it's color value based on some game rules. Then those values need to be driven by class properties that are sent across the network, and they should be accessible to all clients. If not, then odd synchronization type problems (not networks related, visually related) issues will come up.
During developing this library, steps were taken to try and improve compatibility with both DX9/11 and OpenGL; however, this mod has not been testing on multiple graphics cards and/or operating systems. So really weird undocumented bugs may occur.
Thanks for lighting the NS2 modding torch.
And with the multiple maps per entity, I plan to make my kill streak implementation pretty unique
Thanks a lot for this McGlaspie!!
Update: Added section about Materials to the usage-guide-post-thing.
I'll be making an update to the mod in a week or so. The update will fix a few things and add support for more types of shaders.
Here is my use case...
I have created a male_body_colorMap.dds file with 1 row, a straight forward whole texture change:
Here is a jpeg of the .dds file:
Here is the code from my male_body.materials file:
I want to add this skin change just to team 2, I want team 1 to remain as a default marine skin. So I have added the ColorSkinsMixin to the file, and copyed the initializeSkin() function etc, to the file for the specific Avatar I want to edit:
My question is now, what do I need to enter as the color values to get the skin to be show up as it has been created?. Do I need to enter the color values of the image, or do i specify 1,1,1, so the image is unchanged?
Also, what is the atlas reference for? I have only 1 colorMap, for team two. Does this affect the Atlasindex in anyway?
I really appreciate any help
In other words, by having only a single color map in the texture file, there is not reason to ever set the skinAtlasIndex to anything but 0.
Second problem I see. You're blending a lot of the Red, Green, and Blue color channels together. You're seeing the Color Map texture as what defines the actual colors to get applied.
The code controls what colors to apply. The Color Map texture control where and how to apply them.
i have implemented the code, but I seem to have some sort of shader issue:
It looks the same on both teams.. and I'm sure it is not supposed to be doing that...
You can also specify some new Color objects as locals to whatever Player.lua file you have. The ColoredSkinsMixin.lua file has a function ColorAsParam in it, you can use that to Print the values of a color object easily. I'd use it to test and verify the values of the colors are what you need them to be.
From what I see, it is either a call order issue, or the color values are never customized, therefore are black.
GreenAvatar.lua (This is the player class that's called at team level)
//
// lua\GreenAvatar.lua
//
// Created by: Andy 'Soul Rider' Wilson for Proving Grounds
//
// ===================================================================
Script.Load("lua/AvatarVariantMixin.lua")
Script.Load("lua/Avatar.lua")
if Client then
Script.Load("lua/ColoredSkinsMixin.lua")
Script.Load("lua/TeamMessageMixin.lua")
end
class 'GreenAvatar' (Avatar)
GreenAvatar.kMapName = "greenavatar"
local networkVars =
{
}
AddMixinNetworkVars(AvatarVariantMixin, networkVars)
function GreenAvatar:OnCreate()
InitMixin(self, AvatarVariantMixin)
Avatar.OnCreate(self)
if Client then
InitMixin(self, TeamMessageMixin, { kGUIScriptName = "GUIMarineTeamMessage" })
InitMixin(self, ColoredSkinsMixin) //Client only
end
end
if Client then
function GreenAvatar:InitializeSkin()
self.skinBaseColor = Color(0.078, 0.878, 0.984, 1)
self.skinAccentColor = Color(0.756, 0.982, 1, 1)
self.skinTrimColor = Color(0.725, 0.921, 0.949, 1)
self.skinAtlasIndex = self:GetTeamNumber() - 1 //ColorMap atlas texture is 0 indexed
end
end
function GreenAvatar:OnInitialized()
// SetModel must be called before Player.OnInitialized is called so the attach points in
// the Marine are valid to attach weapons to. This is far too subtle...
// Player.OnInitialised is called in Avatar.OnInitialized - AW Proving Grounds
Avatar.OnInitialized(self)
if Client then
self.gColorOverridesEnabled = true
end
end
Shared.LinkClassToMap("GreenAvatar", GreenAvatar.kMapName)
The other teams base player is:
PurpleAvatar.lua
//
// lua\PurpleAvatar.lua
//
// Created by: Andy 'Soul Rider' Wilson for Proving Grounds
//
// ============================================
Script.Load("lua/Avatar.lua")
Script.Load("lua/AvatarVariantMixin.lua")
if Client then
Script.Load("lua/ColoredSkinsMixin.lua")
Script.Load("lua/TeamMessageMixin.lua")
end
class 'PurpleAvatar' (Avatar)
PurpleAvatar.kMapName = "purpleavatar"
local networkVars =
{
}
AddMixinNetworkVars(AvatarVariantMixin, networkVars)
function PurpleAvatar:OnCreate()
Avatar.OnCreate(self)
if Client then
InitMixin(self, TeamMessageMixin, { kGUIScriptName = "GUIMarineTeamMessage" })
InitMixin(self, ColoredSkinsMixin) //Client only
end
InitMixin(self, AvatarVariantMixin)
end
if Client then
function PurpleAvatar:InitializeSkin()
self.skinBaseColor = Color(0.61, 0.43, 0.16, 1)
self.skinAccentColor = Color(1.0, 0.0, 0.0, 1)
self.skinTrimColor = Color(0.576, 0.194, 0.011, 1)
self.skinAtlasIndex = self:GetTeamNumber() - 1 //ColorMap atlas texture is 0 indexed
end
end
function PurpleAvatar:OnInitialized()
// SetModel must be called before Player.OnInitialized is called so the attach points in
// the Marine are valid to attach weapons to. This is far too subtle...
// Player.OnInitialised is called in Avatar.OnInitialized - AW Proving Grounds
Avatar.OnInitialized(self)
if Client then
self.gColorOverridesEnabled = true
end
end
Shared.LinkClassToMap("PurpleAvatar", PurpleAvatar.kMapName)
I know it is a simple implementation, but it should work as far as I can tell. I still get exactly the same issue with the shader. I have only tested under DX11 so far, i will check with the other renderer's and see what happens.
Edit- - -
It's the same problem with all renderer's.
offtopic - OpenGL doesn't have a third person model for Marine
You won't be able to call it from the parent class either (Avatar in this case), because the InitializeSkin function must be defined in the implementing class (GreenAvatar & PurpleAvatar).
Change each of those player classes to:
Also, you need to remove the
There is no "gColorOverridesEnabled" class property defined by the ColoredSkinsMixin. The Global "gColorOverridesEnabled" is used solely for the console command to determine what state the system is in, and if each entiy's color values should be overriden.
Also, you are not defining the GetBaseSkinColor(), GetAccentSkinColor(), and GetTrimSkinColor() functions in either of those player classes. Those functions are not optional. I'm assuming you are loading Mixins the same way NS2 does. You should be getting a console error.
One final note, if you only have a single Color Map in the ColorMap texture file, then you should not be setting the skinAtlasIndex propert via the team numbers. It should be statically set to Zero. Otherwise, the shaders will think the ColorMap texture is larger that it actually is, and you'll get weird texture wrapping visual bugs.
I am using your mixin and it work like a charm, thanks !
Also, i think you should tell where the "Class_Reload( "Extractor", {} )" should be written.
An other things, do we need to add the 'self' in parameter ? (just wondering)
Edit ---
Although I implemented this a long time ago, I never posted in here what I did with the code...
This system does not work, at all as intended, without a ColorMap texture. You can make the texture whatever size you want. The ColorMap will be scaled according to the original Albedo Map (diffuse texture) and Emissive Map sizes. However, if you create a small ColorMap texture, the shader will lose accuracy in applied the colorization where you want it. The result is what basically looks like a very poorly applied water down, water color painting. It really looks bad.
Next week, If i can get ride of an issue I have when creating a new weapon (Flame gaz grenade) I will use your mixin to colorize the gaz and use your MvM mod as an example. i think the ColorMap texture should be light for that one.
I have full HD .dds file if it turns out to look legit haha, here is a little bit grittyer version if it turns out to look to pee-like