Need some assistance with spot/point lighting for GPU based particles
Hello, I've been trying fruitlessly for some time now to get lighting working with my GPU powered particle system. Due to the use of a geometry shader I can't rely on simply using a surface shader to do my lighting for me, so I'm trying to use some older shader examples.
I've used the CG Programming reference from here to get lighting working to an extent, however both point light range and spotlights are not taking effect correctly. I don't have the need for a ForwardAdd pass as 4 lights is all I'll need at most. https://en.wikibooks.org/wiki/Cg_Programming/Unity/Multiple_Lights
I have also had some partial success with a surface shader still using SV_InstanceID/SV_VertexID from this Reddit thread: https://www.reddit.com/r/Unity3D/comments/33psb4/graphicsdrawprocedural_in_normal_render_queue/
However I wasn't able to get point or spotlights to work and was also limited with not being able to calculate normals correctly as I'd only be able to apply normals to a single set of 6 vertices that are instanced.
Here's my shader code, modified for my purpose using the base code from the first link:
Shader "Custom/WaterParticle"
{
// Bound with the inspector.
Properties
{
_MainTex("Base (RGB)", 2D) = "white" {}
_Color ("Main Color", Color) = (1, 1, 1, 1)
_Size ("Size", float) = 1.0
}
SubShader
{
Tags { "RenderType" = "Transparent" "Queue" = "Transparent"}
Pass
{
Tags { "LightMode" = "ForwardBase" }
Blend SrcAlpha OneMinusSrcAlpha
ZWrite Off
CGPROGRAM
#pragma target 5.0
#pragma vertex vert
#pragma geometry GS_Main
#pragma fragment frag
#pragma multi_compile_fwdbase
#include "UnityCG.cginc"
#include "AutoLight.cginc"
// User-specified properties
uniform float4 _Color;
uniform float4 _LightColor0;
uniform sampler2D _MainTex;
uniform float _Size;
// The same particle data structure used by both the compute shader and the shader.
struct Particle
{
float3 Position;
float3 Normal;
float3 StartPosition;
float3 Velocity;
float LifeTime;
float Life;
int Sprite;
};
struct GS_INPUT
{
float4 pos : SV_POSITION;
float4 col : COLOR;
float3 normal : TEXCOORD1;
float2 uv : TEXCOORD2;
float3 lightColour : TEXCOORD3;
};
struct FS_INPUT
{
float4 pos : SV_POSITION;
float4 worldPos : TEXCOORD0;
float3 normal : TEXCOORD1;
float2 uv : TEXCOORD2;
float3 lightColour : TEXCOORD3;
float4 col : TEXCOORD4;
};
// The buffer holding the particles shared with the compute shader.
StructuredBuffer<Particle> particleBuffer;
// DX11 vertex shader these 2 parameters come from the draw call: "1" and "particleCount",
// SV_VertexID: "1" is the number of vertex to draw peer particle, we could easily make quad or sphere particles with this.
// SV_InstanceID: "particleCount", number of particles...
GS_INPUT vert (uint id : SV_VertexID, uint inst : SV_InstanceID)
{
GS_INPUT output;
float3 up = float3(0, 1, 0);
output.normal = normalize(_WorldSpaceCameraPos - mul(_Object2World, float4(particleBuffer[inst].Position, 1)));
// color computation
output.col = _Color;
// Fade out
if (particleBuffer[inst].Life < 1.0f)
{
output.col.a *= particleBuffer[inst].Life;
}
else if (particleBuffer[inst].Life > 4.0f)
{
output.col.a *= 1.0f - (particleBuffer[inst].Life - 4.0f);
}
// position computation
output.pos = mul(_Object2World, float4(particleBuffer[inst].Position, 1));
// Use W component for sprite index
output.pos.w = particleBuffer[inst].Sprite;
output.uv = float2(0, 0);
// Diffuse reflection by four "vertex lights"
output.lightColour = 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 = lightPosition.xyz - output.posWorld.xyz;
float3 lightDirection = normalize(vertexToLightSource);
float squaredDistance = dot(vertexToLightSource, vertexToLightSource);
float attenuation = 1.0 / (1.0 + unity_4LightAtten0[index] * squaredDistance);
float3 diffuseReflection = attenuation * unity_LightColor[index].rgb * _Color.rgb
* max(0.0, dot(output.normal, lightDirection));
output.lightColour = output.lightColour + diffuseReflection;
}
#endif
return output;
}
// Geometry Shader -----------------------------------------------------
[maxvertexcount(8)]
void GS_Main(point GS_INPUT p[1], inout TriangleStream<FS_INPUT> triStream)
{
float3 up = float3(0, 1, 0);
float3 look = _WorldSpaceCameraPos - p[0].pos;
look = normalize(look);
float3 right = cross(up, look);
float halfS = 0.5f * _Size;
float4 v[4];
v[0] = float4(p[0].pos + halfS * right - halfS * up, 1.0f);
v[1] = float4(p[0].pos + halfS * right + halfS * up, 1.0f);
v[2] = float4(p[0].pos - halfS * right - halfS * up, 1.0f);
v[3] = float4(p[0].pos - halfS * right + halfS * up, 1.0f);
float4x4 vp = mul(UNITY_MATRIX_MVP, _World2Object);
FS_INPUT pIn;
pIn.normal = p[0].normal;
pIn.lightColour = p[0].lightColour;
pIn.col = p[0].col;
pIn.pos = mul(vp, v[0]);
pIn.worldPos = v[0];
pIn.uv = float2(1.0f, 0.0f + (0.1f * p[0].pos.w));
triStream.Append(pIn);
pIn.pos = mul(vp, v[1]);
pIn.worldPos = v[1];
pIn.uv = float2(1.0f, 0.1f + (0.1f * p[0].pos.w));
triStream.Append(pIn);
pIn.pos = mul(vp, v[2]);
pIn.worldPos = v[2];
pIn.uv = float2(0.0f, 0.0f + (0.1f * p[0].pos.w));
triStream.Append(pIn);
pIn.pos = mul(vp, v[3]);
pIn.worldPos = v[3];
pIn.uv = float2(0.0f, 0.1f + (0.1f * p[0].pos.w));
triStream.Append(pIn);
}
float4 frag (FS_INPUT input) : COLOR
{
float3 normalDirection = input.normal;
float3 lightDirection;
float attenuation;
if (0.0 == _WorldSpaceLightPos0.w) // directional light?
{
attenuation = 1.0; // no attenuation
lightDirection = normalize(_WorldSpaceLightPos0.xyz);
}
else // point or spot light
{
float3 vertexToLightSource =
_WorldSpaceLightPos0.xyz - input.worldPos.xyz;
float distance = length(vertexToLightSource);
attenuation = 1.0 / distance; // linear attenuation
lightDirection = normalize(vertexToLightSource);
}
float3 ambientLighting = UNITY_LIGHTMODEL_AMBIENT.rgb * _Color.rgb;
float3 diffuseReflection = attenuation * _LightColor0.rgb * _Color.rgb * max(0.0, dot(normalDirection, lightDirection));
float3 specularReflection;
if (dot(normalDirection, lightDirection) < 0.0) // light source on the wrong side?
{
specularReflection = float3(0.0, 0.0, 0.0); // no specular reflection
}
else // light source on the right side
{
specularReflection = attenuation * _LightColor0.rgb
* pow(max(0.0, dot(
reflect(-lightDirection, normalDirection),
normalDirection)), 10);
}
// Lighting output
float4 lightOutput = float4(input.lightColour + ambientLighting
+ diffuseReflection + specularReflection, 1.0);
return tex2D(_MainTex, input.uv) * input.col * lightOutput;
}
ENDCG
}
}
Fallback "Specular"
}
Your answer
Follow this Question
Related Questions
How to make particles react to scene lighting 3 Answers
How can I cast shadows onto sprites? 0 Answers
Half Lambert / Wrap Diffuse Shader 0 Answers
Lighting/Shaders !HELP! 0 Answers
Trees from SpeedTree looks different 0 Answers