I couldn’t think of a good title for this.
I’m experimenting with doing dynamic lighting as an image effect in a very basic way. I can’t seem to get it to work at all.
All I want to do is specify a position in the world where there’s a light, specify its range and intensity. Then the shader should simply brighten every pixel within the range of that light.
So, here’s the shader:
Shader "Custom/Lighting"
{
Properties
{
_MainTex("Base (RGB)", 2D) = "white" {}
}
SubShader
{
Pass
{
Cull Off
ZWrite Off
ZTest Always
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
uniform sampler2D _MainTex;
uniform int _LightCount;
uniform float3 _Lights[100];
uniform float _Ranges[100];
uniform float _Intensities[100];
struct VertexIn
{
float4 pos : POSITION;
float2 uv : TEXCOORD0;
};
struct VertexOut
{
float4 pos : POSITION;
float2 uv : TEXCOORD0;
fixed3 worldPos : TEXCOORD1;
};
VertexOut vert(VertexIn v)
{
VertexOut o;
o.pos = mul(UNITY_MATRIX_MVP, v.pos);
o.worldPos = mul(unity_ObjectToWorld, v.pos).xyz;
o.uv = v.uv;
return o;
}
float4 frag(VertexOut o) : COLOR
{
half light = 0.0;
for (int i = 0; i < _LightCount; i++)
{
half dist = distance(o.worldPos, _Lights[i]);
half r = _Ranges[i];
half intensity = 1 - saturate(dist / r);
light = max(intensity * _Intensities[i], light);
}
light = saturate(light);
return tex2D(_MainTex, o.uv) + light;
}
ENDCG
}
}
}
-I try to compute the world position of the vertex using unity_ObjectToWorld, and pass that to the fragment shader.
-I loop through each light.
-I take the distance between the light (which is in world coordinates) and the world coordinates I computed in the vertex function.
-I get the range. I compute intensity. Using dist / r, if distance is larger than r this should be capped to 1 and produce a final intensity of 0 (light is out of range, no effect). If it’s smaller, it will be closer to 1 and have more effect.
-I multiply intensity by the intensity modifier passed to the shader (ranges from 0 to 1).
Then I store the best light I’ve found so far, because the strongest light is the one that should affect this pixel. Maybe I’ll have to get fancier, but trying to keep it very simple to start with to see if I can get this working.
At the end, I ensure the final value is between 0 and 1 and add the light to the result. This is very wrong and just produces a pure white image. But I just did that so I can see the area the light is affecting.
The problem? It doesn’t affect anything. There’s never any light anywhere, no matter what I make the range of my light or where I position it. I don’t know why. Clearly I don’t understand something. Here’s the C# code just in case it helps:
using UnityEngine;
using UnityEngine.Assertions;
public sealed class Lighting : MonoBehaviour
{
private const int MaxLights = 100;
private Material material;
private Vector4[] lights = new Vector4[MaxLights];
private float[] ranges = new float[MaxLights];
private float[] intensities = new float[MaxLights];
private int lightCount = 0;
private int lightCountID;
private int lightsID;
private int rangesID;
private int intensitiesID;
private void Awake()
{
material = new Material(Shader.Find("Custom/Lighting"));
lightCountID = Shader.PropertyToID("_LightCount");
lightsID = Shader.PropertyToID("_Lights");
rangesID = Shader.PropertyToID("_Ranges");
intensitiesID = Shader.PropertyToID("_Intensities");
SetLight(new Vector3(8.0f, 8.0f), 5.0f, 1.0f);
}
public void SetLight(Vector3 pos, float range, float intensity)
{
Assert.IsTrue(lightCount != MaxLights, "Exceeded the maximum light count.");
lights[lightCount] = new Vector4(pos.x, pos.y, pos.z);
ranges[lightCount] = range;
intensities[lightCount] = intensity;
lightCount++;
}
private void OnRenderImage(RenderTexture source, RenderTexture destination)
{
material.SetInt(lightCountID, lightCount);
material.SetVectorArray(lightsID, lights);
material.SetFloatArray(rangesID, ranges);
material.SetFloatArray(intensitiesID, intensities);
Graphics.Blit(source, destination, material);
}
}
Here I mainly just set the shader data. As you can see, my light is at 8, 8 in world coordinates with a range of 5 and max intensity. My camera is also there, and I see no light.
Thanks for your help!