For a very long time I have been bothered by the fact that you cannot make a decent glass material in Unity using the standard shaders because they all link the material alpha to the specular intensity somehow. I recently started writing my own shaders to get some other features I wanted and when I moved on to trying to create a transparent shader that provided independent specularity, I was disappointed to find that the link between the two properties was not at the subshader level but somewhere deeper that I don’t know how to deal with.
I know that the folks who made the HardSurface shader package have worked around this problem somehow, but the directory for their shader code is hidden so I can’t see how they did it (and no I don’t mean hidden in windows, I mean hidden some other way I do not know how to access… and no I am not trying to re-write/steal HardSurface I simply trying to solve the singular problem of not being able to decouple alpha from specular).
Here is a demonstration of what Unity provides in its standard shaders, what I am trying to accomplish (using a render from Blender), and a snippet of my shader code that results in the same problem present in default shaders.
//This is just a simple snippet showing that alpha does not have to be tied to specularity in the subshader in order for the undesired
//effect to occur.
void surf (Input IN, inout SurfaceOutput o) {
half4 diffInfo = tex2D (_MainTex, IN.uv_MainTex);
o.Albedo = diffInfo.rgb * _Color.rgb;
o.Normal = UnpackNormal (tex2D (_NormalTex, IN.uv_NormalTex));
o.Gloss = diffInfo.a * _SpecColor.a;
o.Specular = _Shininess;
o.Alpha = _Color.a;
}
So – how do I achieve a shader in which it is possible for the material to be fully transparent, but the specular highlight still shows in its full intensity??
(Sorry about the attached images I had no idea what size my original image was and then it seems you can’t delete them the system does it for you in an hour.)
It’s not so much that alpha and specular are linked, but alpha and everything. Turning down the opacity in a surface shader reduces the overall contribution of the calculated colour, right down to zero when it is completely transparent.
Note that this is a limitation of surface shaders, and so it’s quite possible to write a shader that separates specular reflection from the equation. Due to the complexity of the pipeline (and the work involved in updating everything manually when it changes), I’ve never bothered to write a proper glass shader. The closest I got (because it was easy) are these additive shaders which handle only specular and reflection.
Hmm. Additive may be a viable solution for now. There has to be a way to handle this correctly, however. I am able to make such shaders in UDK using their material editor with ease. I may have to go deeper than the subshader system in shaderlab but there has to be a process…
Anybody know where documentation on creating an additive shader with shaderlab is? I found this: Unity - Manual: ShaderLab command: Blend
But it uses the more traditional concept of passes and combines instead of the Input struct and void surf calls as described here: Unity - Manual: Surface Shader examples
There are three types of shaders in Unity: pure fixed function (SetTexture/Combine), vertex/fragment (programmable pipeline, using Cg or GLSL) shader, and surface shader. Surface shaders are actually just a template-based approach to programmable pipeline shaders. Passes are used by both fixed and programmable pipelines, but surface shaders generate multiple passes automatically.
The Blend command can be used in fixed function and programmable programs, but the surface shader system doesn’t give you full control over it. If you want to make a really nice glass shader, you should probably start with a surface shader that does something similar to what you want, and view its constructed output using #pragma debug. You can use the resulting output (viewed by pushing “Open Compiled Shader” in the inspector after you’ve added the #pragma) as the basis for your programmable pipeline shader.
Oh wow, that’s excellent. I taught myself Cg some years ago but find I am very rusty now. And the surface shader system is very lovely and I find a reticence to retrain myself because of it. I am excited to try your suggestion to debug out the generated shader, thanks!
The process for turning the debug output into an editable shader is:
- Remove all added lines that start with the block comment tokens /* and */
- Remove all Program blocks, (including all their Subprogram contents)
You can achieve the effect you want by doing multiple surface shader passes. A blended pass to do diffuse lighting and an addative pass to do specular highlights:
Shader "Custom/Glass"
{
Properties
{
_Color ("Main Color", Color) = (1,1,1,1)
_SpecColor ("Specular Color", Color) = (0.5, 0.5, 0.5, 0)
_Gloss ("Gloss", Range (0.01, 1)) = 0.5
_Shininess ("Shininess", Range (0.01, 1)) = 0.078125
_MainTex ("Base (RGB) Trans (A)", 2D) = "white" {}
_BumpMap ("Normalmap", 2D) = "bump" {}
}
SubShader
{
Tags { "Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent" }
LOD 400
CGPROGRAM
#pragma surface surf Lambert alpha
sampler2D _MainTex;
sampler2D _BumpMap;
fixed4 _Color;
struct Input
{
float2 uv_MainTex;
float2 uv_BumpMap;
};
void surf(Input IN, inout SurfaceOutput o)
{
fixed4 tex = tex2D(_MainTex, IN.uv_MainTex);
o.Albedo = tex.rgb * _Color.rgb;
o.Alpha = tex.a * _Color.a;
o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
}
ENDCG
CGPROGRAM
#pragma surface surf BlinnPhong finalcolor:final decal:add
sampler2D _BumpMap;
half _Gloss;
half _Shininess;
struct Input
{
float2 uv_BumpMap;
};
void surf(Input IN, inout SurfaceOutput o)
{
o.Gloss = _Gloss;
o.Specular = _Shininess;
o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
}
void final(Input IN, SurfaceOutput o, inout fixed4 color)
{
// Need to set alpha here rather than in surf to make this work with all lights
color.a = 1;
}
ENDCG
}
FallBack "Transparent/VertexLit"
}
In theory you can mix blended and addative effects in a single pass by doing pre-multiplied alpha. Although I can’t see a way of getting this to work with surface shaders. You would have to write a custom vertex and pixel shader.
http://blogs.msdn.com/b/shawnhar/archive/2009/11/06/premultiplied-alpha.aspx
Wow thanks. I’m going to try this. I did some silly things where I stored the reflective and specular information and added it back to the alpha amount so that those regions would not fade even if alpha was 0 but I suspect this is mathematically bleh. It is also not entirely accurate the way I dealt with specularity.
Here’s what I got though:
