- Home /
CG Programming, Points Lights attenuation and light range
The image below is showing what is happening. I'm simply trying to write a shader that does diffuse and specular lighting. I'm following this tutorial.
However I noticed that for diffuse, it doesn't take range into account, and doesn't behave exactly like the default shader in unity. And we have the edges shown below. I don't know how to actually access the range information so i'm stuck. I've searched, but I have not found any good documentation that tells me how to avoid this. Basically what should I do to fix this.
Herese my messy shader code :
Shader "Custom/LightMap Blend Diffuse Specular Transparency" {
Properties {
_Color ("MainColor", Color) = (1, 1, 1, 1)
_SpecColor ("Specular Color", Color) = (1, 1, 1, 1)
_Shininess ("Shininess", Float) = 10
_MainTex ("Base (RGB)", 2D) = "white" {}
_LightMap01 ( "LightMap 1", 2D ) = "white" {}
_LightMap02 ( "LightMap 2", 2D ) = "white" {}
_Trans ("Transparency", Range(0, 1)) = 0
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200
Pass
{
Tags { "LightMode" = "ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
uniform float4 _LightColor0;
sampler2D _MainTex;
float4 _Color;
float4 _SpecColor;
float _Shininess;
struct vertexIn
{
float4 pos : POSITION;
float3 norm : normal;
};
struct vertexOut
{
float4 pos : SV_POSITION;
float4 col : COLOR;
float3 posWorld : TEXCOORD0;
float3 norm : TEXCOORD1;
};
vertexOut vert(vertexIn i)
{
vertexOut o;
float4x4 modelMatrix = _Object2World;
float4x4 modelMatrixInverse = _World2Object;
o.posWorld = mul(modelMatrix, i.pos);
o.norm = normalize( mul(float4(i.norm, 0), modelMatrixInverse).xyz );
o.col = float4 (0, 0, 0, 0);
o.pos = mul( UNITY_MATRIX_MVP, i.pos);
return o;
}
float4 frag(vertexOut o) : COLOR
{
float3 normalDirection = normalize(o.norm);
float3 viewDirection = normalize(_WorldSpaceCameraPos - o.posWorld.xyz);
float3 lightDirection = float3(0, 0, 0);
float attenuation;
lightDirection = normalize(_WorldSpaceLightPos0.xyz) * (1 - _WorldSpaceLightPos0.w);
float3 PointLightDirection = _WorldSpaceLightPos0.xyz - o.posWorld.xyz;
float PointLightLength = length(PointLightDirection);
lightDirection += normalize(PointLightDirection) * _WorldSpaceLightPos0.w;
attenuation = 1.0 / PointLightLength;
float3 diffuseColor = lerp(_LightColor0.rgb * _Color.rgb * max(0, dot(normalDirection, lightDirection)),
_LightColor0.rgb * _Color.rgb * max(0, dot(normalDirection, lightDirection) * attenuation), _WorldSpaceLightPos0.w);
float3 specularColor = (_LightColor0.rgb * _Color.rgb * max(0, dot(reflect(-lightDirection, normalDirection), viewDirection)) )
* max(0, dot (normalDirection, lightDirection));
return float4((UNITY_LIGHTMODEL_AMBIENT.rgb * _Color.rgb) + diffuseColor, 1);
}
ENDCG
}
Pass
{
Tags { "LightMode" = "ForwardAdd" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
uniform float4 _LightColor0;
sampler2D _MainTex;
float4 _Color;
float4 _SpecColor;
float _Shininess;
struct vertexIn
{
float4 pos : POSITION;
float3 norm : normal;
};
struct vertexOut
{
float4 pos : SV_POSITION;
float4 col : COLOR;
float3 posWorld : TEXCOORD0;
float3 norm : TEXCOORD1;
};
vertexOut vert(vertexIn i)
{
vertexOut o;
float4x4 modelMatrix = _Object2World;
float4x4 modelMatrixInverse = _World2Object;
o.posWorld = mul(modelMatrix, i.pos);
o.norm = normalize( mul(float4(i.norm, 0), modelMatrixInverse).xyz );
o.col = float4 (0, 0, 0, 0);
o.pos = mul( UNITY_MATRIX_MVP, i.pos);
return o;
}
float4 frag(vertexOut o) : COLOR
{
float3 normalDirection = normalize(o.norm);
float3 viewDirection = normalize(_WorldSpaceCameraPos - o.posWorld.xyz);
float3 lightDirection = float3(0, 0, 0);
float attenuation;
lightDirection = normalize(_WorldSpaceLightPos0.xyz) * (1 - _WorldSpaceLightPos0.w);
float3 PointLightDirection = _WorldSpaceLightPos0.xyz - o.posWorld.xyz;
float PointLightLength = length(PointLightDirection);
lightDirection += normalize(PointLightDirection) * _WorldSpaceLightPos0.w;
attenuation = 1.0 / PointLightLength;
float3 diffuseColor = lerp(_LightColor0.rgb * _Color.rgb * max(0, dot(normalDirection, lightDirection)),
_LightColor0.rgb * _Color.rgb * max(0, dot(normalDirection, lightDirection) * attenuation), _WorldSpaceLightPos0.w);
float3 specularColor = (_LightColor0.rgb * _Color.rgb * max(0, dot(reflect(-lightDirection, normalDirection), viewDirection)) )
* max(0, dot (normalDirection, lightDirection));
return float4( (UNITY_LIGHTMODEL_AMBIENT.rgb * _Color.rgb) + diffuseColor, 1);
}
ENDCG
}
}
FallBack "Diffuse"
}
by the way. I'm also calculating specular, but I'm not using the values in the output, because I wanted to isolate diffuse, which is why my code is a mess.
Answer by CHPedersen · Nov 03, 2014 at 08:46 AM
You've almost got your own question answered in the topic. :)
It's all about light range and implementing attenuation.
Attenuation is the highlight's size as a function of distance between light and pixel. So, to calculate it, you obviously need access to the light's position. There are three ways to go about that:
1:
You can implement your shaders as surface shaders instead of native vert/frag pairs. Surface shaders are compiled by Unity into native vert/frag pairs in ways that utilize Unity's built-in complex lighting engine, so you don't have to worry about it. This comes at the cost of some coding flexibility, though surface shaders still do allow quite a bit of customization, including your own vertex shader.
2:
You can stick to a native vert/frag pair as in the above, and then try to tap into the built-in variables Unity's internal engine passes to the shader system each frame, so your custom vert/frag pair accesses the same variables Unity's own vert/frag pairs would do after its compiler makes vert/frag pairs out of surface shaders. These variables are mostly defined in UnityCG.cginc, Lighting.cginc and UnityShaderVariables.cginc, which can be found at this path:
C:/"Program Files (x86)/Unity/Editor/Data/CGIncludes
For example in UnityShaderVariables.cginc, you'll find these declarations for what I believe to be the real-time positions of the 8 nearest light sources to each object:
float4 unity_LightColor[8];
float4 unity_LightPosition[8];
// x = -1
// y = 1
// z = quadratic attenuation
// w = range^2
float4 unity_LightAtten[8];
float4 unity_SpotDirection[8];
3:
You can ignore Unity's built-in lighting engine altogether, and transfer the needed variables yourself. This means you have to identify the light sources you want to calculate lighting for in C# yourself, and then transfer all of their data manually, including its position, light color, intensity, range, etc. You must declare your own variables to hold the data in your shader, and then you must set the value of these variables on the CPU side using Material.SetFloat, Material.SetColor and Material.SetVector respectively. (Or the static versions found in the Shader class, if you want them to be globally available to all shaders).
Having done that, the real world, physically correct equation for standard light attenuation is 1/d^2, where d is the distance to the light source. But that doesn't always suit 3D graphics well. There is a more elaborate formula for this in the Cg tutorial here, if you're interested (Chapter 5.5.1):
http://http.developer.nvidia.com/CgTutorial/cg_tutorial_chapter05.html
Thank your your replie. I going to use the second method. I know I'm going to unity_LightAtten. However I'm still confused on how to use it. I've tried using the the code in the tutorial on multiple point lights. However it still had the edges, and the light still didn't scale with range. The tutorial implies that I loop through the light values in the forward base pass, however that doesn't work, and its confusing because the lights are processed in the forward add pass. If you know what I should do next could you please post it. $$anonymous$$y code is below in pastebin.
Answer by FluFFey · Oct 26, 2017 at 05:43 AM
UnityShaderVariables.cginc has the built-in variable float4 _LightPositionRange
, where xyz = pos and w = (1/range). From this you get
float attenuation = 1-(distanceToLight / (1 / _LightPositionRange.w)) ;
Which gives a smooth transition and also works for ForwardAdd passes. You can find the distance by
_WorldSpaceLightPos0.xyz-posWorld.xyz;
You only need to include "AutoLight.cginc" for both _LightPositionRange and _WorldSpaceLightPos0. Found little info on this elsewhere so hope this helps someone.
Answer by Optimus6128 · Apr 02, 2015 at 12:42 PM
I have the same problem. Writing my own cg shaders for lighting. Want to get the light range so that I can calculate attenuation on my own. Or get attenuation. Any of two.
I debug in the shader by painting my pixels with some of the values. I realized this. unity_LightAtten does not update the range. The X = -1, Y = 1, Z = 0 and W = 1 No matter if I change the range to 0.5, 0.25 or 2 or 4 or whatever.
When I try to update other values like position or color, they don't seem to change when I move lightsource or change it's color. It seems like these are the original initialized values and nothing else is happening. Like Unity is not passing them unless I would write surface shaders which I don't want to. The third option would be to attach to my objects a C# code that simple reads the range and sends it as a uniform. I want to avoid this. Also what if I have different lights with different ranges?
I will continue to investigate this, but somehow some solutions to this are so hard to find (I searched for tutorials for hours).
Your answer
Follow this Question
Related Questions
Accessing light's range value within a fragment shader? 0 Answers
What is the proper way of calculating attentuation in Unity3D non-surface shaders? 0 Answers
How to force the compilation of a shader in Unity? 5 Answers
Shader: get nearest point light's color, direction and general custom lighting questions 0 Answers
UnityObjectToClipPos is inverted? 0 Answers