In order to properly compute lighting attenuation in my ForwardAdd pass, I need to access the light’s range. It looks like the range is stored in _LightPositionRange.w, but it doesn’t seem to work; I think that only works in ForwardBase mode. How can I get the light’s range?
You might not be able to get it directly. As far as I remember Unity often implements light attenuation with lookup tables in light coordinates; thus, Unity doesn’t need those specific values and might not provide them to shader programs. See Cg Programming/Unity/Light Attenuation - Wikibooks, open books for an open world .
Check out the AutoLight.cginc files, you’ll want to copy/paste the attenuation defines for each type of light in there but delete the * SHADOW_ATTENUATION(a) parts.
You’ll need to keep the #ifdef bits around each of them, as it’s it won’t compile otherwise.
That won’t give you the range, but it will give you the attenuation value (as opposed to the attenuation and shadow value).
Hmm… the points on _LightTexture0 seems to always return 1.
Can you post up your code? I was playing with this myself not too long ago, I got it to sort of work in the end but it was fiddly.
I just direct copy-pasted the code from here: Cg Programming/Unity/Light Attenuation - Wikibooks, open books for an open world
Changing the range on the lights doesn’t seem to do anything to the attenuation.
I’m having trouble getting the the version with the copied macros from autoLight working with multiple lights; give me a sec with that and I’ll post the code for that version.
What exactly should I be copying over? I’m only using point lights, so here’s the part from AutoLight:
#ifdef POINT
#define LIGHTING_COORDS(idx1,idx2) float3 _LightCoord : TEXCOORD##idx1; SHADOW_COORDS(idx2)
uniform sampler2D _LightTexture0;
uniform float4x4 _LightMatrix0;
#define TRANSFER_VERTEX_TO_FRAGMENT(a) a._LightCoord = mul(_LightMatrix0, mul(_Object2World, v.vertex)).xyz; TRANSFER_SHADOW(a)
#define LIGHT_ATTENUATION(a) (tex2D(_LightTexture0, dot(a._LightCoord,a._LightCoord).rr).UNITY_ATTEN_CHANNEL * SHADOW_ATTENUATION(a))
#endif
All I want is that LIGHT_ATTENUATION equation. Should I just copy that directly into my code, and replace a._LightCoord with _WorldSpaceLightPos0? And also replace UNITY_ATTEN_CHANNEL with a?
Here’s what I have so far. I’m not getting any attenuation this way.
Shader "Custom/NewTileShader"
{
Properties {
_MainTex ("Texture", 2D) = "white" {}
}
SubShader {
Pass {
Tags { "LightMode" = "ForwardBase" } // pass for
// 4 vertex lights, ambient light first pixel light
CGPROGRAM
#pragma multi_compile_fwdbase
#pragma vertex vert
#pragma fragment frag
uniform sampler2D _MainTex;
///////
uniform float4x4 _Object2World;
uniform float4 _WorldSpaceLightPos0;
uniform float4 _LightPositionRange;
uniform float4 _LightColor0;
// Built-in uniforms for "vertex lights"
uniform float4 unity_LightColor[4];
uniform float4 unity_4LightPosX0;
uniform float4 unity_4LightPosY0;
uniform float4 unity_4LightPosZ0;
uniform float4 unity_4LightAtten0;
struct vertexInput {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};
struct vertexOutput {
float4 pos : SV_POSITION;
float4 uv : TEXCOORD0;
float4 posWorld : TEXCOORD1;
float3 vertexLighting : TEXCOORD2;
};
vertexOutput vert(vertexInput input)
{
vertexOutput output;
float4x4 modelMatrix = _Object2World;
float4 tilePosition = float4(input.normal, input.vertex.a);
output.posWorld = mul(modelMatrix, tilePosition);
output.pos = mul(UNITY_MATRIX_MVP, input.vertex);
output.uv = input.texcoord;
// Diffuse reflection by four "vertex lights"
output.vertexLighting = float3(0.0, 0.0, 0.0);
#ifdef VERTEXLIGHT_ON
for (int index = 0; index < 4; index++)
{
float4 lightPosition = float4(unity_4LightPosX0[index],
unity_4LightPosY0[index],
unity_4LightPosZ0[index], 1.0);
float3 vertexToLightSource =
float3(lightPosition - output.posWorld);
float squaredDistance =
dot(vertexToLightSource, vertexToLightSource);
float attenuation = 1.0 / (1.0 +
unity_4LightAtten0[index] * squaredDistance);
float3 diffuse =
attenuation * float3(unity_LightColor[index]);
output.vertexLighting += diffuse;
}
#endif
return output;
}
float4 frag(vertexOutput input) : COLOR
{
float attenuation;
if (0.0 == _WorldSpaceLightPos0.w) // directional light?
{
attenuation = 1.0; // no attenuation
}
else // point or spot light
{
float3 vertexToLightSource =
float3(_WorldSpaceLightPos0 - input.posWorld);
float distance = length(vertexToLightSource);
attenuation = 1.0 / distance;
}
float3 diffuse = attenuation * float3(_LightColor0);
float4 c = tex2D(_MainTex, float2(input.uv));
c.rgb *= input.vertexLighting + diffuse;
return c;
}
ENDCG
}
// pass for additional light sources
Pass {
Tags { "LightMode" = "ForwardAdd" }
Blend One One // additive blending
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
uniform sampler2D _MainTex;
///////
uniform float4x4 _Object2World;
uniform float4 _WorldSpaceLightPos0;
uniform float4 _LightPositionRange;
uniform float4 _LightColor0;
uniform float4x4 _LightMatrix0;
uniform sampler2D _LightTexture0;
struct vertexInput {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};
struct vertexOutput {
float4 pos : SV_POSITION;
float4 uv : TEXCOORD0;
float4 posWorld : TEXCOORD1;
};
vertexOutput vert(vertexInput input)
{
vertexOutput output;
float4x4 modelMatrix = _Object2World;
float4 tilePosition = float4(input.normal, input.vertex.a);
output.posWorld = mul(modelMatrix, tilePosition);
output.pos = mul(UNITY_MATRIX_MVP, input.vertex);
output.uv = input.texcoord;
return output;
}
float4 frag(vertexOutput input) : COLOR
{
float attenuation;
if (0.0 == _WorldSpaceLightPos0.w) // directional light?
{
attenuation = 1.0; // no attenuation
}
else // point or spot light
{
//// It does linear attenuation if I have this code instead:
//float3 vertexToLightSource =
// float3(_WorldSpaceLightPos0 - input.posWorld);
//float distance = length(vertexToLightSource);
//attenuation = 1.0 / distance;
attenuation = tex2D(_LightTexture0, dot(_WorldSpaceLightPos0,_WorldSpaceLightPos0).rr).a;
}
float3 diffuse = attenuation * float3(_LightColor0);
float4 c = tex2D(_MainTex, float2(input.uv));
c.rgb *= diffuse;
return c;
}
ENDCG
}
}
Fallback "Specular"
}
If you just want point lights, you might be better off with… (bear with me, writing this on a phone)…
In vertex shader output struct…
float3 _LightCoord : TEXCOORDX; // Replace X with next available texcoord in your struct.
Before vertex shader…
#ifdef POINT
uniform sampler2D _LightTexture0;
uniform float4x4 _LightMatrix0;
#endif
In the vertex shader…
#ifdef POINT
X._LightCoord = mul(_LightMatrix0, mul(_Object2World, v.vertex)).xyz; // Replace X with name of your struct... traditionally o it seems.
#endif
In your fragment shader…
fixed atten = 0.0;
#ifdef POINT
atten = tex2D(_LightTexture0, dot(X._LightCoord,X._LightCoord).rr).UNITY_ATTEN_CHANNEL; // Replace X with name of incoming vertex data struct, traditionally i, it seems.
#endif
That should do it, I think…
Waitaminute… what exactly is a point light? Isn’t it just a light of Type “Point”? Because it doesn’t seem like my code in the #ifdef POINT blocks are getting called. I tried your code and it’s ignored all of that.
Thanks for your help so far, by the way. I’ve been stuck on this for weeks!
Holy crap It’s working! Thanks so much. I still to mess with it a bit, and to get shadows working, but it looks like the hardest part is over.
(btw I had to remove the #ifdef POINT blocks to make it work):
As far as I remember there is a difference between point lights with and without cookies; I think the wikibook covers only the case of point lights with cookies (and spot lights) and only mentions the difference for point lights without cookies in the main text.
Oh, if you want shadows working too, then it’s way easier than all this stuff. Just do #include "AutoLight.cginc"
as that’s what it’s there for.
Then use the defines specified in there in your shader code - putting them in both forward base and add pass will work fine, e.g.;
Vertex-to-fragment struct;
LIGHTING_COORDS(X,Y) // swap X and Y with the next two available texcoord numbers. LIGHTING_COORDS(3,4) for example. Note there is no ; at the end of this line.
Vertex shader;
TRANSFER_VERTEX_TO_FRAGMENT(X); // Swap X with the name of your vertex output struct, usually o.
Fragment shader;
fixed atten = LIGHT_ATTENUATION(X); // Swap X with the name of your vertex input struct, usually i.
I’m hesitating to use AutoLight.cginc because it is apparently legacy code that is no longer used by the built-in shaders; thus, Unity might abandon it and/or break the code at some point. (Of course, the same is true for all the undocumented uniforms. )
Farfarer: Do you think this is a problem?
Hmm, the built-in surface shaders still use it. In 3.x at least, you can see it referenced in in the compiled shader if you use #pragma debug;
Here’s a simple lambert (i.e. default Create > Shader) with pragma debug…
Not sure about 4.0, though… it’s the same in 4.0b7, although I guess they could always remove it from release version.
What’s it being replaced with, do we know?
Thanks, I was just about to ask about shadows. I tried following your code but it didn’t work. I suspect it’s because I’m using point lights, and that code seems to be for forward rendering only (non-point lights)? How do I get point-light shadows (in deferred mode) working in this kind of shader? Here’s what I have now:
Shader "Custom/TileShader"
{
Properties {
_MainTex ("Texture", 2D) = "white" {}
}
SubShader {
Pass {
Tags { "LightMode" = "ForwardBase" } // pass for
// 4 vertex lights, ambient light first pixel light
CGPROGRAM
#pragma multi_compile_fwdbase
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "AutoLight.cginc"
uniform sampler2D _MainTex;
///////
uniform float4 _LightColor0;
struct vertexInput {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};
struct vertexOutput {
float4 pos : SV_POSITION;
float4 uv : TEXCOORD0;
float4 posWorld : TEXCOORD1;
float3 vertexLighting : TEXCOORD2;
LIGHTING_COORDS(3,4)
};
vertexOutput vert(vertexInput v)
{
vertexOutput o;
float4x4 modelMatrix = _Object2World;
float4 tilePosition = float4(v.normal, v.vertex.a);
o.posWorld = mul(modelMatrix, tilePosition);
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv = v.texcoord;
// Diffuse reflection by four "vertex lights"
o.vertexLighting = float3(0.0, 0.0, 0.0);
#ifdef VERTEXLIGHT_ON
for (int index = 0; index < 4; index++)
{
float4 lightPosition = float4(unity_4LightPosX0[index],
unity_4LightPosY0[index],
unity_4LightPosZ0[index], 1.0);
float3 vertexToLightSource =
float3(lightPosition - o.posWorld);
float squaredDistance =
dot(vertexToLightSource, vertexToLightSource);
float attenuation = 1.0 / (1.0 +
unity_4LightAtten0[index] * squaredDistance);
float3 diffuse = attenuation * float3(unity_LightColor[index]);
o.vertexLighting += diffuse;
}
#endif
TRANSFER_VERTEX_TO_FRAGMENT(o);
return o;
}
float4 frag(vertexOutput i) : COLOR
{
float attenuation;
if (0.0 == _WorldSpaceLightPos0.w) // directional light?
{
attenuation = 1.0; // no attenuation
}
else // point or spot light
{
//float3 vertexToLightSource =
// float3(_WorldSpaceLightPos0 - i.posWorld);
//float distance = length(vertexToLightSource);
//attenuation = 1.0 / distance;
attenuation = LIGHT_ATTENUATION(i);
}
float3 diffuse = attenuation * float3(_LightColor0);
float4 c = tex2D(_MainTex, float2(i.uv));
c.rgb *= i.vertexLighting + diffuse;
return c;
}
ENDCG
}
// pass for additional light sources
Pass {
Tags { "LightMode" = "ForwardAdd" }
Blend One One // additive blending
CGPROGRAM
#pragma multi_compile_fwdadd
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "AutoLight.cginc"
uniform sampler2D _MainTex;
///////
uniform float4 _LightColor0;
struct vertexInput {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};
struct vertexOutput {
float4 pos : SV_POSITION;
float4 uv : TEXCOORD0;
float4 posWorld : TEXCOORD1;
LIGHTING_COORDS(3,4)
};
vertexOutput vert(vertexInput v)
{
vertexOutput o;
float4x4 modelMatrix = _Object2World;
float4 tilePosition = float4(v.normal, v.vertex.a);
o.posWorld = mul(modelMatrix, tilePosition);
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv = v.texcoord;
float4 tempVertex = v.vertex;
v.vertex = tilePosition;
TRANSFER_VERTEX_TO_FRAGMENT(o);
v.vertex = tempVertex;
return o;
}
float4 frag(vertexOutput i) : COLOR
{
float attenuation;
if (0.0 == _WorldSpaceLightPos0.w) // directional light?
{
attenuation = 1.0;
}
else // point or spot light
{
attenuation = LIGHT_ATTENUATION(i);
}
float3 diffuse = attenuation * float3(_LightColor0);
float4 c = tex2D(_MainTex, float2(i.uv));
c.rgb *= diffuse;
return c;
}
ENDCG
}
}
// The definition of a fallback shader should be commented out
// during development:
// Fallback "Specular"
}
I was thinking I might have to do it in a seperate pass, and blend them together. But I couldn’t get them to blend properly - is there a better way to do it?
You won’t get shadows in forward rendering without using a surface shader with the fullforwardshadows directive. It’s probably possible to do it in vert/frag but I’ve not found out how
For deferred stuff, you’d be better off writing a surface shader I think. It’s just easier.
Can you write a surface shader with a custom attenuation function? Mine isn’t that complicated, I’m just using a different float3 as the vertex position.
If not, can I just write a simple surface shader with shadows, before or after the forward passes, and expect them to blend nicely? I’m trying it and the blending is NOT matching up with what it should look like (it looks like it’s barely blending at all).