|
[SUB] compute the conic shape for SPOTLIGHT dyn lights
#479190
02/27/20 20:01
02/27/20 20:01
|
Joined: Jun 2007
Posts: 1,337 Hiporope and its pain
txesmi
OP
Serious User
|
OP
Serious User
Joined: Jun 2007
Posts: 1,337
Hiporope and its pain
|
Hi! I compute spotlights with a formula I build based on the result of dot and cross products of vectors. I don't know if there is a faster way... Goemetrically, the dot product of two vectors returns the scalar of the orthogonal projection of the second vector over the first. This is the fastest way to compute the distance from a plane to a point. A plane can be described with a point and a normal which is precisely what we get as a description of a spotlight: a position and a direction (normal). The length of the resulting vector of the cross product of two vectors is the area of the parallelogram described by the two vectors. This is the fastest way to compute the distance from a line to a point.This is harder to explain, but it happens that when the first vector is a unit vector, the value of the area is the same as the minimum distance between the second vector and the line that contains the first vector. We start with: - world_pos -> the world position of the rendered pixel. - light_pos -> the world position of the light. - light_dir -> the direction of the light. The first thing we need to do is to transform the world position to a position referenced to the light position, so it describes the world position in the same coordinate system as the light direction.
float3 light_ray = world_pos - light_pos;
Since the light direction is a unit vector, the result of the dot product will return exactly the depth in the spotlight. In other words, the distance from the point to the plane described by the light data.
float depth = dot(light_dir, light_ray);
This depth can be easily transformed to a 0<->1 factor
float depth_factor = saturate(depth / light_range); // saturate clips the value between 0 and 1, any negative value will result in 0, and any value above 1 wiil result in 1.
Since the light direction is a unit vector, the length of the result of the cross product will be exactly the distance between the point and the light direction line.
float radius = length(cross(light_dir, light_ray));
This radius can be easily transformed to a 0<->1 factor when dividing it by the maximum radius: the light range. The conic shape is given by the multiply of the maximum radius by the depth factor so we get a larger maximum radius while the depth gets larger. Notice that the result of the division is linearly proportional to the radius, so we need to inverse it so the maximum lighting is in the light direction line. You can add a custom multiplo to the operation in order to get certain control over the cone proportions.
float spot_factor = 1.0 - saturate(radius / (light_range * depth_factor * custom_factor));
This spot factor has to be multiplied by the attenuation factor based on the length of light_ray, but that is another story. Salud!
|
|
|
Re: [SUB] compute the conic shape for SPOTLIGHT dyn lights
[Re: txesmi]
#479191
02/27/20 20:50
02/27/20 20:50
|
Joined: May 2009
Posts: 5,370 Caucasus
3run
Senior Expert
|
Senior Expert
Joined: May 2009
Posts: 5,370
Caucasus
|
Hey! If I understand correctly, light_range is stored in vecLightPos.w, so all above should sum up to something like this ?
float custom_factor = 1;
for(int i = 0; i < 8; i++)
{
if(vecLightDir[i].w > 0) // spotlight ?
{
float3 light_ray = Out.WorldPos - vecLightPos[i].xyz;
float depth = dot(vecLightDir[i], light_ray);
// saturate clips the value between 0 and 1
// any negative value will result in 0, and any value above 1 wiil result in 1.
float depth_factor = saturate(depth / vecLightPos[i].w);
float radius = length(cross(vecLightDir[i], light_ray));
float spot_factor = 1.0 - saturate(radius / (vecLightPos[i].w * depth_factor * custom_factor));
}
}
Greets
|
|
|
Re: [SUB] compute the conic shape for SPOTLIGHT dyn lights
[Re: txesmi]
#479192
02/27/20 21:34
02/27/20 21:34
|
Joined: Jun 2007
Posts: 1,337 Hiporope and its pain
txesmi
OP
Serious User
|
OP
Serious User
Joined: Jun 2007
Posts: 1,337
Hiporope and its pain
|
Yes, but I should heve used the radius variable in the last code line instead of the whole cross product. I fixed it. My last playground looks like this:
float light = 0;
for(; light < iLights; light += 1.0) {
// light ray
float3 ray = vtx.wpos - vecLightPos[light].xyz;
// spotlight factor
float depth = saturate(dot(vecLightDir[light].xyz, ray) / vecLightPos[light].w);
float spot = 1.0 - saturate(length(cross(vecLightDir[light].xyz, ray)) / (vecLightPos[light].w * depth));
// normalize the light ray
float dist = length(ray);
ray /= dist;
// attenuation
float att = 1.0 - saturate(dist / vecLightPos[light].w);
// final light factor
att *= vecLightDir[light].w ? spot : 1.0;
// diffuse term
diffuse += vecLightColor[light].rgb * saturate(-dot(ray, vtx.normal.xyz)) * att;
// specular term
refl = reflect(ray, vtx.normal.xyz);
specular += vecLightColor[light].rgb * pow(saturate(-dot(refl, vecViewDir.xyz)), fPower) * att;
}
Salud!
|
|
|
Re: [SUB] compute the conic shape for SPOTLIGHT dyn lights
[Re: txesmi]
#479193
02/27/20 21:45
02/27/20 21:45
|
Joined: May 2009
Posts: 5,370 Caucasus
3run
Senior Expert
|
Senior Expert
Joined: May 2009
Posts: 5,370
Caucasus
|
Yes, I see. I got this code from your latest standardshader on GitHub, but it doesn't work for me in VS. maybe there are some limitations that make this work only in PS?
Edit: I'll post a screenshot with more detailed explanation tomorrow, it's too late today. Thank you.
Last edited by 3run; 02/27/20 21:53.
|
|
|
Re: [SUB] compute the conic shape for SPOTLIGHT dyn lights
[Re: txesmi]
#479195
02/28/20 08:04
02/28/20 08:04
|
Joined: May 2009
Posts: 5,370 Caucasus
3run
Senior Expert
|
Senior Expert
Joined: May 2009
Posts: 5,370
Caucasus
|
Well, I guess this time I got them working correctly At least to my eye, they seem to work fine (the only thing I could mention is that on low poly stuff spotlight brightness seems to be too weak). Here is how my mtl_shaded.fx looks like:
#include <pos>
#include <normal>
#include <lights>
float4x4 matWorldViewProj;
float4 vecViewPos;
float4 vecLightDir[8];
float4 vecSunColor;
float4 vecFog;
float4 vecFogColor;
float4 vecSkill1;
Texture entSkin1;
Texture entSkin2;
sampler ColorSampler = sampler_state
{
Texture = <entSkin1>;
Mipfilter = None;
Minfilter = None;
Magfilter = None;
};
sampler ShadowSampler = sampler_state
{
Texture = <entSkin2>;
Mipfilter = Linear;
Minfilter = Linear;
Magfilter = Linear;
};
struct vertexOut
{
float4 Pos : POSITION;
float4 Color : COLOR0;
float3 Normal : NORMAL;
float4 Tex : TEXCOORD0;
float4 WorldPos : TEXCOORD1;
};
vertexOut VS (
in float4 inPos : POSITION,
in float3 inNormal : NORMAL,
in float2 inTex1 : TEXCOORD0,
in float2 inTex2 : TEXCOORD1
)
{
inPos.w = 1.0f;
vertexOut Out;
// vertex snapping
float4 snapToPixel = mul(inPos, matWorldViewProj);
float4 vertex = snapToPixel;
vertex.xyz = snapToPixel.xyz / snapToPixel.w;
vertex.x = floor((vecSkill1.y + 40) * vertex.x) / (vecSkill1.y + 40); // default 160
vertex.y = floor(vecSkill1.y * vertex.y) / vecSkill1.y; // default 120
vertex.xyz *= snapToPixel.w;
Out.Pos = vertex;
// affine texture mapping
Out.Pos *= inPos.w / length(mul(inPos, matWorldViewProj));
Out.Normal = normalize (mul(inNormal, (float3x3)matWorld));
Out.Tex.xy = inTex1.xy;
Out.Tex.zw = inTex2.xy;
Out.WorldPos = mul(inPos, matWorld);
Out.Color = 0;
float3 P = DoPos(inPos);
float3 N = DoNormal(inNormal);
// multiple dynamic lights
float light = 0;
float lastLight = iLights - vecSunColor.w;
for(; light < lastLight; light += 1.0)
{
// light ray
float3 ray = Out.WorldPos - vecLightPos[light].xyz;
// spotlight factor
float depth = saturate(dot(vecLightDir[light].xyz, ray) / vecLightPos[light].w);
float spot = 1.0 - saturate(length(cross(vecLightDir[light].xyz, ray)) / (vecLightPos[light].w * depth));
// normalize the light ray
float dist = length(ray);
ray /= dist;
// attenuation
float att = 1.0 - saturate(dist / vecLightPos[light].w);
// final light factor
float strength = vecLightDir[light].w ? spot * att : att;
// diffuse term
Out.Color.rgb += vecLightColor[light].rgb * saturate(-dot(ray, inNormal.xyz)) * strength;
}
// cut out polygons
float distance = length(mul(inPos, matWorldViewProj));
if(vecSkill1.z == 0){ vecSkill1.z = 512; }
if (distance > vecSkill1.z)
{
Out.Pos.w = 0;
}
return Out;
}
float4 PS(vertexOut In) : COLOR0
{
float4 textureColor = tex2D(ColorSampler, In.Tex.xy);
float4 color = tex2D(ShadowSampler, In.Tex.zw);
float fDepth = distance(vecViewPos.xyz, In.WorldPos.xyz);
color += In.Color; // add dynamic lights
color.rgb = saturate(color.rgb);
color.rgb = pow(color.rgb, vecSkill1.x);
color.rgb *= textureColor.rgb;
float Fog = saturate((fDepth - vecFog.x) * vecFog.z);
color.rgb = lerp(color.rgb, vecFogColor, Fog);
color.a = 1;
return color;
}
technique mtl_shaded
{
pass one
{
ZWriteEnable = True;
AlphaBlendEnable = False;
AlphaTestEnable = False;
VertexShader = compile vs_3_0 VS();
PixelShader = compile ps_3_0 PS();
}
} Also, the worldPos I use is float4 and the one used in your example is float3.. I tried to do float3 ray = Out.WorldPos.xyz - vecLightPos[light].xyz; instead of float3 ray = Out.WorldPos - vecLightPos[light].xyz; but it didn't work, so I left it as it is. Here you can watch video on youtube (clickable screenshot) Best regards Edit: level is dark, that was made intentionally (it can be adjusted to make it very bright if needed). Also, I noticed that the way you calculate shadows, make them more smooth almost like per pixel lightning, while the original one looked like this Maybe making them a bit smoother is better.. but just in case, is there an easy way to make them edgier? Edit2: adding custom strength factor seems to help a lot too! I'm pretty happy with results! Thank you one more time man!
Last edited by 3run; 02/28/20 08:31.
|
|
|
Re: [SUB] compute the conic shape for SPOTLIGHT dyn lights
[Re: txesmi]
#479210
03/01/20 19:44
03/01/20 19:44
|
Joined: May 2009
Posts: 5,370 Caucasus
3run
Senior Expert
|
Senior Expert
Joined: May 2009
Posts: 5,370
Caucasus
|
Hey! Sorry for off-topic, but I wanted to ask for a little advice. How would you pass light calculations from VS to PS ? Currently, I do all light calculations in VS, then in PS I read pixel color from texture and "add" it to the color which already has calculated lights. This isn't probably the best way to handle this situation, because I get 'illumination lag' when I rotate light source (let's say counterclockwise) and object receiving light (clockwise). This only happens in shader version 3.0 and with light calculations, you provided in this thread. This is what I currently do:
VS {
....
outcolor = (vecAmbient * vecLight) .... blahblah
for(i=0; i<8; i++)
{
outcolor.rgb += calculated light
}
...
}
PS {
...
float4 textureColor = tex2d
Incolor.rgb *= textureColor;
...
}
You can see how 'lag' looks like here: Youtube linkBest regards!
|
|
|
Re: [SUB] compute the conic shape for SPOTLIGHT dyn lights
[Re: txesmi]
#479211
03/01/20 20:15
03/01/20 20:15
|
Joined: Jun 2007
Posts: 1,337 Hiporope and its pain
txesmi
OP
Serious User
|
OP
Serious User
Joined: Jun 2007
Posts: 1,337
Hiporope and its pain
|
I have no idea what is happening there... I'm stunned, ashtonished edited______ As I understand, there is no difference in the static table of the effect because of the shader model version but its size. The rendering is an instant action, each draw call draws the render target inmediately, there is not relation between frames. I can't understand how can be possible that the lights have a different position relative to the shader model version. The only thing I can think on is that you are using other surface position but the world position, such the incoming vertex position in object space. But it should fail in both shader model versions...
Last edited by txesmi; 03/01/20 20:33.
|
|
|
Re: [SUB] compute the conic shape for SPOTLIGHT dyn lights
[Re: txesmi]
#479212
03/01/20 21:05
03/01/20 21:05
|
Joined: May 2009
Posts: 5,370 Caucasus
3run
Senior Expert
|
Senior Expert
Joined: May 2009
Posts: 5,370
Caucasus
|
I'm pretty sure this is all happening due to my crooked hands... Here you can see the whole .fx file (can compile):
#include <acknex.h>
#include <default.c>
#define PRAGMA_PATH "%EXE_DIR%\samples";
MATERIAL* mtl_test =
{
effect = "
float4x4 matWorldViewProj;
float4x4 matWorld;
float4 vecViewPos;
float iLights;
float4 vecLightPos[8];
float4 vecLightColor[8];
float4 vecLightDir[8];
float4 vecLight;
float4 vecColor;
float4 vecAmbient;
float4 vecEmissive;
float4 vecFog;
float4 vecFogColor;
texture entSkin1;
sampler ColorSampler = sampler_state
{
Texture = <entSkin1>;
Mipfilter = None;
Minfilter = None;
Magfilter = None;
};
struct vertexOut
{
float4 Pos : POSITION;
float4 Color : COLOR0;
float3 Normal : NORMAL;
float4 Tex : TEXCOORD0;
float4 WorldPos : TEXCOORD1;
};
vertexOut VS (
in float4 inPos : POSITION,
in float3 inNormal : NORMAL,
in float2 inTex1 : TEXCOORD0,
in float2 inTex2 : TEXCOORD1
)
{
inPos.w = 1.0f;
vertexOut Out;
Out.Pos = mul(inPos, matWorldViewProj);
Out.Normal = normalize (mul(inNormal, (float3x3)matWorld));
Out.Tex.xy = inTex1.xy;
Out.Tex.zw = inTex2.xy;
Out.WorldPos = mul(inPos, matWorld);
Out.Color = (vecAmbient * vecLight) + float4(vecEmissive.xyz * vecColor.xyz, vecLight.w);
// multiple dynamic lights
float light = 0;
for(light = 0; light < iLights; light += 1.0)
{
// light ray
float3 ray = Out.WorldPos - vecLightPos[light].xyz;
// spotlight factor
float depth = saturate(dot(vecLightDir[light].xyz, ray) / vecLightPos[light].w);
float spot = 1.0 - saturate(length(cross(vecLightDir[light].xyz, ray)) / (vecLightPos[light].w * depth));
// normalize the light ray
float dist = length(ray);
ray /= dist;
// attenuation
float att = 1.0 - saturate(dist / vecLightPos[light].w);
// final light factor
float strength = vecLightDir[light].w ? spot * att : att;
// diffuse term
Out.Color.rgb += vecLightColor[light].rgb * saturate(-dot(ray, inNormal.xyz)) * strength;
}
Out.Color.rgb = saturate(Out.Color.rgb);
return Out;
}
float4 PS(vertexOut In) : COLOR0
{
float4 textureColor = tex2D(ColorSampler, In.Tex.xy);
In.Color.rgb *= textureColor.rgb;
float fDepth = distance(vecViewPos.xyz, In.WorldPos.xyz);
float Fog = saturate((fDepth - vecFog.x) * vecFog.z);
In.Color.rgb = lerp(In.Color.rgb, vecFogColor, Fog);
In.Color.a = 1;
return In.Color;
}
technique model
{
pass one
{
VertexShader = compile vs_3_0 VS();
PixelShader = compile ps_3_0 PS();
}
}
";
flags = PASS_SOLID | ENABLE_VIEW;
}
action act_test()
{
my->material = mtl_test;
while(my)
{
my->pan += 5 * time_step;
wait(1);
}
}
action act_pointlight()
{
set(my, UNLIT | BRIGHT);
my->ambient = 100;
vec_fill(&my->scale_x, 0.25);
my->lightrange = 128;
vec_set(&my->blue, COLOR_WHITE);
vec_set(&my->skill1, &my->x);
while(my)
{
my->x = my->skill1 + fsin(total_ticks * 7, 64);
my->y = my->skill2 + fcos(total_ticks * 7, 64);
wait(1);
}
}
void main()
{
warn_level = 6;
fps_max = 60;
mouse_pointer = 0;
video_mode = 10;
level_load("");
wait(3);
camera->arc = 90;
camera->clip_far = 1024;
camera->clip_near = 0.1;
camera->fog_start = 512;
camera->fog_end = 1024;
fog_color = 4;
vec_set(&d3d_fogcolor4.blue, vector(128, 128, 128));
vec_set(&sky_color.blue, &d3d_fogcolor4.blue);
vec_set(&camera->x, vector(-56, 18, 38));
vec_set(&camera->pan, vector(338, -30, 0));
ent_create("blob.mdl", nullvector, act_test);
ent_create(CUBE_MDL, nullvector, act_pointlight);
}
Also I just noticed, that the sun doesn't illuminate object correctly.. illuminated (by sun) side of an object rotates with it as if the sun was rotating (pan) around it.. and if I copy the whole light calculations into PS everything works fine. Edit: also, I would like to ask, why do you use float instead of int for the 'for' loop where you cycle through all lights? Edit2: it seems to work with default.fx DoLight (but damn.. it doesn't have spotlight support)
// calculate the light attenuation factor
float DoLightFactor(float4 Light,float3 Pos)
{
float fac = 0.f;
if (Light.w > 0.f) {
float LD = length(Light.xyz-Pos)/Light.w;
if (LD < 1.f)
{
fac = saturate(1.f - LD);
}
}
return fac; // get the distance factor
}
float DoLightFactorN(float4 Light,float3 P,float3 N)
{
float3 D = Light.xyz-P; // ray pointing from the light to the surface
float NdotL = dot(N, normalize(D)); // angle between surface and light ray
if (NdotL >= 0.f)
{
return 2 * NdotL * DoLightFactor(Light,P);
}
else
{
return 0.f;
}
}
float4 DoPointLight(float3 P, float3 N, float4 Light, float4 LightColor)
{
return LightColor * DoLightFactorN(Light,P,N);
}
float4 DoLight(float3 P, float3 N, int i)
{
return DoPointLight(P, N, vecLightPos[i], float4(vecLightColor[i].x, vecLightColor[i].y, vecLightColor[i].z, 1));
}
Last edited by 3run; 03/01/20 21:35.
|
|
|
|