I need my shader to support multiple lights. What I want to happen is for the secondary light change the vertex color to be bright like the first light. So it will increase the lit area. 1 light 2 lights Code (CSharp): Shader "Custom/Cartoon" { Properties { [HideInInspector][PerRenderer][HDR]_Color ("Color", Color) = (1,1,1,1) _MainTex ("Albedo (RGB)", 2D) = "white" {} _LitArea("Lit Area", Range(0,1)) = 0 } SubShader { Tags { "RenderType"="Opaque"} AlphaToMask On LOD 200 CGPROGRAM //Custom light #pragma surface surf Cartoon // Use shader model 3.0 target, to get nicer looking lighting #pragma target 3.0 sampler2D _MainTex; struct Input { float2 uv_MainTex; }; fixed4 _Color; half _CellAmount; half _LitArea; void surf (Input IN, inout SurfaceOutput o) { // Albedo comes from a texture tinted by color o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgba; } half4 LightingCartoon (SurfaceOutput s, half3 lightDir, half3 viewDir, half atten) { //Direction of the light half NdotL = (dot(s.Normal, normalize(lightDir)) + 1)/2; //Store Color half4 c; //Determine if it is in a lit area then smoothly transition to dark half Cell = smoothstep(_LitArea-.01, _LitArea, NdotL); //Calculate the light on the tip of the object to the middle half rim = 1- saturate(dot(normalize(viewDir), s.Normal)); //Add lighting to the rim of lit areas half3 _rimLighting = pow(rim, 5) * smoothstep(_LitArea,1,NdotL)*_LightColor0.rgb; //Calculate the color of the vert half3 _shadingOfObject = s.Albedo * _Color * (Cell+.3) -.3; //Put them together c.rgb = _shadingOfObject + _rimLighting; c.a = s.Alpha; return c; } ENDCG } FallBack "Diffuse" }
Not possible with a surface shader. Unity's lighting system is a multi pass additive system. Each light draws the entire object again and adds the results on top of the previous pass. What you want is to have knowledge of all lights in one pass so you can take the min light value of all lights affecting the mesh rather then adding them together. But this just isn't how Unity's lighting system works, and Surface Shaders are confined to this system. The "easiest" way to do this might be too take the generated shader code from a surface shader and change the blend mode of the ForwardAdd from the default: Blend One One to: BlendOp Min BlendOp Max The other way I'd go about it is to write a custom vertex fragment shader with only the ForwardBase pass and uses the data intended to be used for vertex lights to do all lighting. That limits you to one directional light and four point lights per object.
So there is no way to get the previous color before hand even through vertex fragment shaders? Then subtract it from the new one then take the gray scale to determine if it is white or gray/black? Do you think adding textures would completely break this? Is it possible to just add textures on one final pass? Thanks for the reply I'm pretty new to shaders.
There's Unity's GrabPass stuff, but that's really slow, and there's no way to have Unity do this before every light's ForwardAdd pass. You're really just limited to whatever Blend operations are available on the GPU. https://docs.unity3d.com/Manual/SL-Blend.html Well, technically if you're only targeting Apple devices you can read the current framebuffer directly in the shader, but it really literally is only iOS devices that have this. You should need to do any subtraction. Pretty much any elementary arithmetic you do isn't going to get you what you want. Shouldn't. Sure, you could have a final pass that multiplies the final lighting results using Blend DstColor Zero, but this shouldn't be necessary.
If Im correct I can have a blend mode for each pass if so would it be possible for me to save the passes into a texture this way I have something that tells me what is lit and what isn't then use that to tell me what is lit and what isn't (0 being the dark areas and 1 being the lit areas )
Yes! Yes and no. That requires a fairly significant change to how rendering systems work that's more than just modifying a shader file. You'd need to set the current render target to be said texture, render out your object's lighting too that texture, then change the render target again and sample the previous texture to get the lighting data. It's similar in concept to a light pre-pass rendering pipeline. That requires a good deal of c# scripting and basically not using any of Unity's existing rendering paths. It is also completely unnecessary since the math you need can already be handled by the per-pass blend modes.
Except that I want rim lighting to know the light direction. For the rim lighting to be white. What I was thinking I would do is basically make the white area 1 and the dark 0. This way when I multiply it with forward add that it would only have the lit area then I would need to multiply it by the color/texture. I want after that I need to add in rim lighting so the rim lighting is in it. which is ok but in order to give it the look I want I need to add to that pass so that the dark part isn't just black which would go before color and rim lighting. Not to mention I wouldn't get the gradient effect through NdotL. Basically if I could somehow interact with the previous passes it probably would look a lot better. Also if I wanted to in the future I couldn't have the ability to (if I wanted) light it with a gradient of the color of the light to white
I found graphics.blit but I don't understand how I'm going to get my render texture to reciere the calculated ndotl could you point me in the direction where I can figure out how to make a ndotl render texture
Simple proof of concept using nothing but two shader passes: Code (CSharp): Shader "Toon BlendOp Max" { Properties { _Color ("Color", Color) = (1,1,1,1) [NoScaleOffset] _MainTex ("Texture", 2D) = "white" {} } SubShader { Pass { Tags { "LightMode" = "ForwardBase" } CGPROGRAM #include "UnityCG.cginc" #pragma multi_compile_fwdbase #pragma vertex vert #pragma fragment frag float4 _Color; sampler2D _MainTex; float4 _LightColor0; struct v2f { float4 pos : SV_POSITION; float2 uv : TEXCOORD0; float3 normal : TEXCOORD1; }; void vert(appdata_full v, out v2f o) { o.uv = v.texcoord; o.normal = UnityObjectToWorldNormal(v.normal); o.pos = UnityObjectToClipPos(v.vertex); } float4 frag (v2f i) : SV_Target { float4 tex = tex2D(_MainTex, i.uv); float ndotl = dot(i.normal, _WorldSpaceLightPos0.xyz); float toon = saturate(ndotl / fwidth(ndotl) + 0.5); float ambient = ShadeSH9(float4(i.normal, 1)); float3 lighting = lerp(ambient, _LightColor0.rgb, toon); return float4(tex.rgb * _Color.rgb * lighting, 1.0); } ENDCG } Pass { Tags { "LightMode" = "ForwardAdd" } BlendOp Max CGPROGRAM #include "UnityCG.cginc" #pragma multi_compile_fwdadd #pragma vertex vert #pragma fragment frag float4 _Color; sampler2D _MainTex; float4 _LightColor0; struct v2f { float4 pos : SV_POSITION; float2 uv : TEXCOORD0; float3 normal : TEXCOORD1; float3 worldPos : TEXCOORD2; }; void vert(appdata_full v, out v2f o) { o.uv = v.texcoord; o.normal = UnityObjectToWorldNormal(v.normal); o.worldPos = mul(unity_ObjectToWorld, float4(v.vertex.xyz, 1.0)).xyz; o.pos = UnityObjectToClipPos(v.vertex); } float4 frag (v2f i) : SV_Target { float4 tex = tex2D(_MainTex, i.uv); float3 lightDir = _WorldSpaceLightPos0.w == 0 ? _WorldSpaceLightPos0.xyz : normalize(_WorldSpaceLightPos0.xyz - i.worldPos); float ndotl = dot(i.normal, lightDir); float toon = saturate(ndotl / fwidth(ndotl) + 0.5); float3 lighting = toon * _LightColor0.rgb; return float4(tex.rgb * _Color.rgb * lighting, 1.0); } ENDCG } } }
I understand that would work with the most intense lighting that is available. Although I want it to handle multiple lights if I could just make it a texture. Not only could I get the light color I could do everything I need to do. Im sorry for wasting your time have a nice day.
The main issue is that you can’t read and write to the same texture at the same time. So you have to get around that by making multiple textures and switching between them. Bgolus gave a few ways around it. The other would be you could gather information about nearby lights manually and add them to shader variables. Then use them in your surface shader. But doing grab passes and other multi texture stuff is going to hurt your performance. It could be worth it for a hero type entity but doing a whole scene would probably not be the best idea when other options exists.