- Home /
Cel/Toon shading with multiple light sources on mobile
For the past couple of days I've been trying to implement a mechanism that would enable me to have a several light sources in my toon shaded scene. The game on which I'm currently working is designated for mobile phones, therefore it is important for me to keep it as simple as possible. Having said that, at this moment I don't know if it's even possible to have a cel shaded game with several light sources running on a mobile... just a thought...
Anyway, in the first approach I've successfully implemented a toon shader that utilized one directional light. The simplified body of that shader can be seen at the bottom of my previous question. Of course because one directional light wasn't enough for me I've struggle a bit to have my shader based on multiple light sources and after several trials I've end up with the code attached below. Now, the shader do a great job on my desktop machine, all the objects are properly lit up, but unfortunately on mobile what I get is probably simple Lambert (?) shading. Anyone help really appreciated.
Shader "Custom/Outlined Diffuse" {
Properties {
_MainTex ("Base (RGB)", 2D) = "white" { }
_Ramp ("Shading Ramp", 2D) = "gray" {}
}
SubShader {
Pass {
Name "OUTLINE"
Tags { "LightMode" = "Always" }
Cull Front
ZWrite Off
ZTest Always
//Offset 15,15
CGINCLUDE
#include "UnityCG.cginc"
struct appdata {
half4 vertex : POSITION;
half3 normal : NORMAL;
};
struct v2f {
half4 pos : POSITION;
};
v2f vert(appdata v) {
// just make a copy of incoming vertex data but scaled according to normal direction
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
half3 norm = mul ((half3x3)UNITY_MATRIX_IT_MV, v.normal);
half2 offset = TransformViewToProjection(norm.xy);
o.pos.xy += offset * o.pos.z * 0.002;
return o;
}
float4 frag(v2f i) : COLOR {
return float4(0.0, 0.0, 0.0, 1.0);
}
ENDCG
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
ENDCG
} // Name "OUTLINE"
// Cel shading based on one directional light with the use of a surface shader
// Tags {"LightMode" = "ForwardBase" }
//CGPROGRAM
// #pragma surface surf Ramp noambient noforwardadd approxview
//
// sampler2D_half _MainTex;
// sampler2D_half _Ramp;
//
// half4 LightingRamp (SurfaceOutput s, half3 lightDir, half atten) {
// half NdotL = dot (s.Normal, lightDir);
// half diff = NdotL * 0.5 + 0.5;
// half3 ramp = tex2D (_Ramp, half2(diff)).rgb;
// half4 c;
// c.rgb = s.Albedo * _LightColor0.rgb * ramp * (atten * 2);
// c.a = s.Alpha;
// return c;
// }
//
// struct Input {
// half2 uv_MainTex;
// };
//
// void surf (Input IN, inout SurfaceOutput o) {
// fixed4 c = tex2D(_MainTex, IN.uv_MainTex);
// o.Albedo = c.rgb;
// o.Alpha = c.a;
// }
//
//ENDCG
Pass {
Name "VERTEX_LIGHTING"
Tags { "LightMode" = "ForwardBase" }
Cull Back
CGPROGRAM
#pragma vertex vert_pass_2
#pragma fragment frag_pass_2
#include "UnityCG.cginc"
#include "Lighting.cginc"
sampler2D_half _MainTex;
sampler2D_half _Ramp;
struct vertexInput {
half4 vertex : POSITION;
half2 texcoord : TEXCOORD0;
half3 normal : NORMAL;
};
struct vertexOutput {
half4 pos : SV_POSITION;
half2 uv : TEXCOORD0;
half4 light : TEXCOORD1;
};
vertexOutput vert_pass_2(vertexInput v)
{
vertexOutput output;
output.pos = mul(UNITY_MATRIX_MVP, v.vertex);
output.uv = v.texcoord;
// 1st approach
// half3 worldPos = mul(_Object2World, v.vertex).xyz;
// half3 worldN = mul((half3x3)_Object2World, SCALED_NORMAL);
// half3 vertexL = Shade4PointLights(
// unity_4LightPosX0, unity_4LightPosY0, unity_4LightPosZ0,
// unity_LightColor0, unity_LightColor1, unity_LightColor2, unity_LightColor3,
// unity_4LightAtten0, worldPos, worldN);
// 2nd approach
half3 worldPos = mul(_Object2World, v.vertex).xyz;
half3 worldN = normalize(mul(half4(v.normal, 0.0), _World2Object).xyz);
half3 vertexL = half3(0.0, 0.0, 0.0);
for (int i = 0; i < 4; ++i)
{
half3 lightPos = half3(unity_4LightPosX0[i], unity_4LightPosY0[i], unity_4LightPosZ0[i]);
half3 vertexToLightSource = lightPos - worldPos;
half3 lightDirection = normalize(vertexToLightSource);
half squaredDistance = dot(vertexToLightSource, vertexToLightSource);
half attenuation = 1.0 / (1.0 + unity_4LightAtten0[i] * squaredDistance);
half3 diffuseReflection = attenuation * half3(unity_LightColor[i]) *
max(0.0, dot(worldN, lightDirection));
vertexL += diffuseReflection;
}
//
half3 dir = normalize(half3(_WorldSpaceLightPos0));
half3 directionL = _LightColor0.rgb * dot(worldN, dir);
half3 l = vertexL + directionL;
half intensity = (l.r + l.g + l.b) / 3.0;
output.light = half4(l, intensity);
return output;
}
half4 frag_pass_2(vertexOutput input) : COLOR
{
half4 ambientL = half4(half3(UNITY_LIGHTMODEL_AMBIENT), 1.0) * 2;
half4 c = tex2D(_MainTex, input.uv) * (ambientL + input.light * tex2D(_Ramp, half2(input.light.w, input.light.w)));
return c;
}
ENDCG
}
}
}
Answer by KEric · Mar 19, 2014 at 01:09 PM
Ok, I finally got it worked out. The problem was with the Ramping texture from which the color/light intensity was taken. The texture was handmade by me. Its size was quite small 4x4 (four intensity thresholds laid on squared png bitmap). What I think happened is that on a mobile device the texture's size was simply too small and some sort of approximation was implicitly made. Probably the whole texture was extended making its size larger (If someone can explain what really happened, please do so). In the result the Ramping intensities got washed up making ideal gradient which applied to the game objects resulted in soft shades.
Answer by CHPedersen · Mar 17, 2014 at 10:22 AM
The solution to this one is to define an additional pass which is tagged with "LightMode" = "ForwardAdd", and then do Blend One One to enable additive blending in this pass. Unity's lighting system will pick up that pass for the additional light sources and then add the contributions iteratively for each light.
Please see this post on anisotropic specular highlights. It has a complete shader which demonstrates how to plug into Unity's lighting system like that from a low-level vert/frag pair. Ignore all the math about the anisotropic BRDF and the implementation of that - it's an awesome shader, but I didn't link you to it for the sake of that information. ;) I just wanted to show you its example of different passes for ForwardBase + ForwardAdd.
Yes I think I've seen it already. There are two separate passes, that would mean I'll end up having 3 passes in my shader (+ one for outlines). Isn't that too much of a overhead work for a mobile?
Ah, that is possible. :-/ I don't do mobile program$$anonymous$$g, so I'm not equipped to tell you if its performance can handle this.
I'm assu$$anonymous$$g that your ideal shader should be able to calculate the lighting of multiple sources in one single pass, then? If that is your goal, then I recommend studying very closely the code in UnityCG.cginc and, particularly, UnityShaderVariables.cginc which can both be found at the path C:\Program Files (x86)\Unity\Editor\Data\CGIncludes (if you chose the default installation dir).
These files can give you a hint at the names and data structures Unity uses for storing the lighting information passed to the GPU at runtime. For example, at a glance, it appears that the positions of the standard 4 pixel lights might be stored in three float4 in UnityShaderVariables.cginc, with one float4 for each coordinate axis:
float4 unity_4LightPosX0;
float4 unity_4LightPosY0;
float4 unity_4LightPosZ0;
This is just me guessing, $$anonymous$$d you - I can't explain how this data structure might fit if the user chooses more than 4 pixel lights in Quality Settings. But anyway, take a look at those files and see if you can figure it out how it's stored by studying how it uses the data in UnityCG.cginc. Remember, in the very worst case, if you can't figure it out, you can always just explicitly pass the required lighting data to the shader yourself using the various $$anonymous$$aterial.Set methods ($$anonymous$$aterial.SetVector, SetColor, SetFloat, etc).
I'll take a look, still.. it's strange it works perfectly well on a desktop machine. $$anonymous$$aybe I'm using some functionality that is not supported on a mobile? It'd be great if anyone had a knowledge about the matter
Your answer
Follow this Question
Related Questions
Which specular shader to use on mobile? 0 Answers
How to fake light reflections on wet ground? 0 Answers
Cel Shading Possible In Unity Free Version? 2 Answers