Newbie HLSL programmer here. I'm trying to create a very basic shader for directional light and i'm experiencing really bizarre behaviour trying to debug the shader code. I dont know if its my lack of understanding of how this all works or if something is really wrong.
This is the vertex shader
struct ShaderOutput
{
float4 Position : SV_POSITION;
float4 Normal: NORMAL;
float2 TexCoord: TEXCOORD;
float4 Colour: COLOR;
};
struct ShaderInput
{
float4 Position : POSITION;
float4 Normal: NORMAL;
float2 TexCoord: TEXCOORD;
};
ShaderOutput VShader(ShaderInput Input)
{
float NdotL;
ShaderOutput Output;
NdotL = max(0.0f, dot(Input.Normal, LightPosition));
Output.Colour = NdotL * LightColour;
Output.Position = mul(Input.Position, WorldMatrix);
Output.Position = mul(Output.Position, ViewMatrix);
Output.Position = mul(Output.Position, ProjectionMatrix);
Output.Normal = Input.Normal;
Output.TexCoord = Input.TexCoord;
return Output;
}
First issue, when I try to debug the shader code, the debugger starts on this line:
Output.Position = mul(Input.Position, WorldMatrix);
instead of this line:
NdotL = max(0.0f, dot(Input.Normal, LightPosition));
Which confused me, it only executes that first line after the vertex shader returns. Could someone explain why? Could this be the effect of parallel processing?
Second issue, following on from that, once this line is executed:
Output.Colour = NdotL * LightColour;
Output.Colour is blank (all values are NAN)
Third issue, these lines:
Output.Normal = Input.Normal;
Output.TexCoord = Input.TexCoord;
dont seem to be executed, it skips right over them and jumps to the function return, I dont know why. setting the position values seems to work fine though.
Any ideas?
Related
I am currently working on a multi-textured terrain and I have problems with the Sample function of Texture2DArray.
In my example, I use a Texture2DArray to store a set of different terrain texture, e.g. grass, sand, asphalt, etc. Each of my vertices stores a texture coordinate (UV coordinate) and an index of the texture I want to use. So, if my index is 0, I use the first texture. If the index is 1, I use the second texture, and so on. This works fine, as long as my index is a natural number (0, 1, ..). However, it fails, if the index is a real number (like 1.5f).
In order to look for the problem, I reduced my entire pixel shader to this:
Texture2DArray DiffuseTextures : register(t0);
Texture2DArray NormalTextures : register(t1);
Texture2DArray EmissiveTextures : register(t2);
Texture2DArray SpecularTextures : register(t3);
SamplerState Sampler : register(s0);
struct PS_IN
{
float4 pos : SV_POSITION;
float3 nor : NORMAL;
float3 tan : TANGENT;
float3 bin : BINORMAL;
float4 col : COLOR;
float4 TextureIndices : COLOR1;
float4 tra : COLOR2;
float2 TextureUV : TEXCOORD0;
};
float4 PS(PS_IN input) : SV_Target
{
float4 texCol = DiffuseTextures.Sample(Sampler, float3(input.TextureUV, input.TextureIndices.r));
return texCol;
}
The following image shows the result of a sample scene on the left side. As you can see, there is a hard border between the used textures. There is no form of interpolation.
In order to check my texture indices, I changed my pixel shader from above by returning the texture indices as a color:
return float4(input.TextureIndices.r, input.TextureIndices.r, input.TextureIndices.r, 1.0f);
The result can be seen on the right side of the image. The texture indices are correct, since they range in the interval [0, 1] and you can clearly see the interpolation at the border of the area. However, my sampled texture does not show any form of interpolation.
Since my pixel shader is pretty simple, I wonder what causes this behaviour? Is there any setting in DirextX responsible for this?
I use DirectX 11, pixel shader ps_5_0 (I also tested with ps_4_0) and I use DDS textures (BC3 compression).
Edit
This is the sampler I am using:
SharpDX.Direct3D11.SamplerStateDescription samplerStateDescription = new SharpDX.Direct3D11.SamplerStateDescription()
{
AddressU = SharpDX.Direct3D11.TextureAddressMode.Wrap,
AddressV = SharpDX.Direct3D11.TextureAddressMode.Wrap,
AddressW = SharpDX.Direct3D11.TextureAddressMode.Wrap,
Filter = SharpDX.Direct3D11.Filter.MinMagMipLinear
};
SharpDX.Direct3D11.SamplerState samplerState = new SharpDX.Direct3D11.SamplerState(_device, samplerStateDescription);
_deviceContext.PixelShader.SetSampler(0, samplerState);
Solution
I made a function using the code presented by catflier for getting a texture color:
float4 GetTextureColor(Texture2DArray textureArray, float2 textureUV, float textureIndex)
{
float tid = textureIndex;
int id = (int)tid;
float l = frac(tid);
float4 texCol1 = textureArray.Sample(Sampler, float3(textureUV, id));
float4 texCol2 = textureArray.Sample(Sampler, float3(textureUV, id + 1));
return lerp(texCol1, texCol2, l);
}
This way, I can get the desired texture color for all texture types (diffuse, specular, emissive, ...) with a simple function call:
float4 texCol = GetTextureColor(DiffuseTextures, input.TextureUV, input.TextureIndices.r);
float4 bumpMap = GetTextureColor(NormalTextures, input.TextureUV, input.TextureIndices.g);
float4 emiCol = GetTextureColor(EmissiveTextures, input.TextureUV, input.TextureIndices.b);
float4 speCol = GetTextureColor(SpecularTextures, input.TextureUV, input.TextureIndices.a);
The result is as smooth as I wanted it to be: :-)
Texture arrays do not sample across slices, so technically, this is expected result.
If you want to interpolate between slices (eg: 1.5f gives you "half" of second texture and "half" of third texture), you can use a Texture3d instead, which allows this (but will cost some more as it will perform trilinear filtering)
Otherwise, you can perform your sampling that way :
float4 PS(PS_IN input) : SV_Target
{
float tid = input.TextureIndices.r;
int id = (int)tid;
float l = frac(tid); //lerp amount
float4 texCol1 = DiffuseTextures.Sample(Sampler, float3(input.TextureUV,id));
float4 texCol2 = DiffuseTextures.Sample(Sampler, float3(input.TextureUV,id+1));
return lerp(texCol1,texCol2, l);
}
Please note that this technique is quite more flexible, since you can also provide non adjacent slices as input (so you can lerp between slice 2 and 23 for example), and eventually use a different blend mode by changing lerp by some other function.
really hoping that someone can help me here - I rarely can't resolve bugs in C# since I have a fair amount of experience in it but I don't have a lot to go on with HLSL.
The picture linked to below is of the same model (programmatically generated on run) twice, the first (white) time using BasicEffect and the second time using my custom shader, listed below. The fact that it works with BasicEffect makes me think that it's not an issue with generating the normals for the model or anything like that.
I've included different levels of subdividing to better illustrate the issue. It's worth mentioning that both effects are using the same lighting direction.
https://imagizer.imageshack.us/v2/801x721q90/673/qvXyBk.png
Here's my shader code (feel free to pick it apart, any tips are very welcome):
float4x4 WorldViewProj;
float4x4 NormalRotation = float4x4(1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1);
float4 ModelColor = float4(1, 1, 1, 1);
bool TextureEnabled = false;
Texture ModelTexture;
sampler ColoredTextureSampler = sampler_state
{
texture = <ModelTexture>;
magfilter = LINEAR; minfilter = LINEAR; mipfilter = LINEAR;
AddressU = mirror; AddressV = mirror;
};
float4 AmbientColor = float4(1, 1, 1, 1);
float AmbientIntensity = 0.1;
float3 DiffuseLightDirection = float3(1, 0, 0);
float4 DiffuseColor = float4(1, 1, 1, 1);
float DiffuseIntensity = 1.0;
struct VertexShaderInput
{
float4 Position : POSITION0;
float4 Normal : NORMAL0;
float2 TextureCoordinates : TEXCOORD0;
};
struct VertexShaderOutput
{
float4 Position : POSITION0;
float4 Color : COLOR0;
float2 TextureCoordinates : TEXCOORD0;
};
VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
{
VertexShaderOutput output = (VertexShaderOutput)0;
output.Position = mul(input.Position, WorldViewProj);
float4 normal = mul(input.Normal, NormalRotation);
float lightIntensity = dot(normal, DiffuseLightDirection);
output.Color = saturate(DiffuseColor * DiffuseIntensity * lightIntensity);
output.TextureCoordinates = input.TextureCoordinates;
return output;
}
float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
{
float4 pixBaseColor = ModelColor;
if (TextureEnabled == true)
{
pixBaseColor = tex2D(ColoredTextureSampler, input.TextureCoordinates);
}
float4 lighting = saturate((input.Color + AmbientColor * AmbientIntensity) * pixBaseColor);
return lighting;
}
technique BestCurrent
{
pass Pass1
{
VertexShader = compile vs_2_0 VertexShaderFunction();
PixelShader = compile ps_2_0 PixelShaderFunction();
}
}
In general, when implementing a lighting equation, there are a few things to ensure:
Normals, light directions, and other directional vectors should be normalized before using them in a dot product. In your case you could add something like:
normal = normalize(normal);
The same should be done for DiffuseLightDirection if it is already not normalized. It already is with your default value, but if your app changes it, it might not be normalized anymore. For that, it would be better to normalize in the application code since it only needs to be done once when it changes, and not per vertex.
Also remember that if you are multiplying the vector by a matrix that contains a scale, the vector will no longer be normalized, so it will need to be re-normalized.
The light direction and the normal must point in the same direction which is out from the surface. Your default light direction is (1,0,0). If you want light to point in the +x direction, then you must actually negate the vector before performing the dot product with the normal so that it is pointing out from the surface just like the normal. If you already take this into account, then it's not a problem.
Vectors can't be translated since they are just a direction not a position. So it is important to ensure when you transform them with a matrix that either the fourth component (w) of the vector is 0 or the matrix you are transforming it with has no translation. Setting w to 0 will zero out any translation from the matrix during the multiply. Since your matrix is called NormalRotation, I'm assuming it only contains a rotation, so this probably isn't an issue.
I have a simple enough shader that supports multiple point lights.
Lights are stored as an array of Light structs (up to a max size) and I pass in the number of active lights when it changes.
The problem is in the PixelShader function:
It's basic stuff, get the base color from the texture, loop through the lights array for 0 to numActiveLights and add the effect, and it works fine, but performance is terrible!
BUT if I replace the reference to the global var numActiveLights with a constant of the same value performance is fine.
I just can't fathom why referencing the variable makes a 30+ fps difference.
Can anyone please explain?
Full Shader code:
#define MAX_POINT_LIGHTS 16
struct PointLight
{
float3 Position;
float4 Color;
float Radius;
};
float4x4 World;
float4x4 View;
float4x4 Projection;
float3 CameraPosition;
float4 SpecularColor;
float SpecularPower;
float SpecularIntensity;
float4 AmbientColor;
float AmbientIntensity;
float DiffuseIntensity;
int activeLights;
PointLight lights[MAX_POINT_LIGHTS];
bool IsLightingEnabled;
bool IsAmbientLightingEnabled;
bool IsDiffuseLightingEnabled;
bool IsSpecularLightingEnabled;
Texture Texture;
sampler TextureSampler = sampler_state
{
Texture = <Texture>;
Magfilter = POINT;
Minfilter = POINT;
Mipfilter = POINT;
AddressU = WRAP;
AddressV = WRAP;
};
struct VS_INPUT
{
float4 Position : POSITION0;
float2 TexCoord : TEXCOORD0;
float3 Normal : NORMAL0;
};
struct VS_OUTPUT
{
float3 WorldPosition : TEXCOORD0;
float4 Position : POSITION0;
float3 Normal : TEXCOORD1;
float2 TexCoord : TEXCOORD2;
float3 ViewDir : TEXCOORD3;
};
VS_OUTPUT VS_PointLighting(VS_INPUT input)
{
VS_OUTPUT output;
float4 worldPosition = mul(input.Position, World);
output.WorldPosition = worldPosition;
float4 viewPosition = mul(worldPosition, View);
output.Position = mul(viewPosition, Projection);
output.Normal = normalize(mul(input.Normal, World));
output.TexCoord = input.TexCoord;
output.ViewDir = normalize(CameraPosition - worldPosition);
return output;
}
float4 PS_PointLighting(VS_OUTPUT IN) : COLOR
{
if(!IsLightingEnabled) return tex2D(TextureSampler,IN.TexCoord);
float4 color = float4(0.0f, 0.0f, 0.0f, 0.0f);
float3 n = normalize(IN.Normal);
float3 v = normalize(IN.ViewDir);
float3 l = float3(0.0f, 0.0f, 0.0f);
float3 h = float3(0.0f, 0.0f, 0.0f);
float atten = 0.0f;
float nDotL = 0.0f;
float power = 0.0f;
if(IsAmbientLightingEnabled) color += (AmbientColor*AmbientIntensity);
if(IsDiffuseLightingEnabled || IsSpecularLightingEnabled)
{
//for (int i = 0; i < activeLights; ++i)//works but perfoemnce is terrible
for (int i = 0; i < 7; ++i)//performance is fine but obviously isn't dynamic
{
l = (lights[i].Position - IN.WorldPosition) / lights[i].Radius;
atten = saturate(1.0f - dot(l, l));
l = normalize(l);
nDotL = saturate(dot(n, l));
if(IsDiffuseLightingEnabled) color += (lights[i].Color * nDotL * atten);
if(IsSpecularLightingEnabled) color += (SpecularColor * SpecularPower * atten);
}
}
return color * tex2D(TextureSampler, IN.TexCoord);
}
technique PerPixelPointLighting
{
pass
{
VertexShader = compile vs_3_0 VS_PointLighting();
PixelShader = compile ps_3_0 PS_PointLighting();
}
}
My guess is that changing the loop constraint to be a compile-time constant is allowing the HLSL compiler to unroll the loop. That is, instead of this:
for (int i = 0; i < 7; i++)
doLoopyStuff();
It's getting turned into this:
doLoopyStuff();
doLoopyStuff();
doLoopyStuff();
doLoopyStuff();
doLoopyStuff();
doLoopyStuff();
doLoopyStuff();
Loops and conditional branches can be a significant performance hit inside of shader code, and should be avoided wherever possible.
EDIT
This is just off the top of my head, but maybe you could try something like this?
for (int i = 0; i < MAX_LIGHTS; i++)
{
color += step(i, activeLights) * lightingFunction();
}
This way you calculate all possible lights, but always get a value of 0 for inactive lights. The benefit would depend on the complexity of the lighting function, of course; you would need to do more profiling.
Try using PIX to profile it. http://wtomandev.blogspot.com/2010/05/debugging-hlsl-shaders.html
Alternatively, read this rambling speculation:
Maybe because with a constant, the compiler can unravel and collapse your loop's instructions. When you replace it with a variable, the compiler becomes unable to make the same assumptions.
Though, somewhat unrelated to your actual question, I would push a lot of those conditions /calculations to the software level.
if(IsDiffuseLightingEnabled || IsSpecularLightingEnabled)
^- Like that.
Also, I think you could precompute a few thing before you call the shader program as well. Like l = (lights[i].Position - IN.WorldPosition) / lights[i].Radius; Pass a precomputed array of those rather than calculating each time over every pixel.
I might be misinformed of the optimizations that the HLSL compiler does, but I think each calculation you do like that on the pixel shader gets executed screen w*h times (though this is done insanely parallel), and I vaguely remember there being some limits to the number of instructions you could have in a shader (like 72?). (though I think that restriction was liberalized a lot in higher versions of HLSL). Maybe the fact that your shader generates so many instructions -- maybe it breaks your program up and turns it into a multi-pass pixel shader on compilation. If that's the case, that probably adds significant overhead.
Actually, here's another idea that might be stupid: Passing a variable to a shader has it transmit the data to the GPU. That transmission happens with limited bandwidth. Perhaps the compiler is smart enough such that when you're only staticly indexing the first 7 elements in an array, only transfer 7 elements. When the compiler doesn't make that optimization (because you aren't iterating with constants), it pushes the WHOLE array every frame, and you're flooding the bus. If that's the case, then my earlier suggestion of pushing calculations out, and passing more results in, would only make the problem worse, heh.
Hy everyone. I started re-coding my engine to convert it to directx 11. I'm now trying to get the basics working, but this error is really stoping me.
I created a basic shader, a simple dot product of the normal and the view. I got it to compile without errors, but it dosnt works.
It just totaly deforms the input mesh. I started debugging in vs2012, and found out that the pixel shader was getting as input all NaNs. I attached two screens and the shader code, if someone can provide any ideas, it would be really apriciated.
Vertex Shader
//----------------------------------------------------------------------------
// Constant Buffer Variables
//----------------------------------------------------------------------------
cbuffer ConstantBuffer : register( b0 )
{
matrix World;
matrix View;
matrix Projection;
//float3 CameraPos;
float Power;
}
//---------------------------------------------------------------------------
struct VS_INPUT
{
float4 Pos : POSITION;
float3 Normal : NORMAL;
};
struct VS_OUTPUT
{
float4 Pos : SV_POSITION;
float3 Normal : TEXCOORD0;
};
//--------------------------------------------------------------------------
// Vertex Shader
//-------------------------------------------------------------------------------
VS_OUTPUT VS( VS_INPUT input)
{
VS_OUTPUT output = (VS_OUTPUT)0;
output.Pos = mul( input.Pos, World );
output.Pos = mul( output.Pos, View );
output.Pos = mul( output.Pos, Projection );
output.Normal = mul( float4( input.Normal, 1 ), World ).xyz;
//output.wNormal = input.Normal;
return output;
}
And here the Pixel Shader
//------------------------------------------------------------------------------
// Constant Buffer Variables
//------------------------------------------------------------------------------
cbuffer ConstantBuffer : register( b0 )
{
matrix World;
matrix View;
matrix Projection;
float Power;
}
//------------------------------------------------------------------------------
struct VS_OUTPUT
{
float4 Pos : SV_POSITION;
float3 wNormal : TEXCOORD0;
};
//-----------------------------------------------------------------------------
// Pixel Shader
//-----------------------------------------------------------------------------
float4 PS( VS_OUTPUT input) : SV_Target
{
//return pow(dot(input.wNormal,float3(0,0,0)),Power); // fixed camera, just for now
return float4(0.1,0.6,0.1,1);
}
And at last, i created a xml file structure for my shaders, that i then parse, dont know if it relevant, but
<?xml version="1.0" encoding="utf-8"?>
<vs path = "D:\\Documentos\\Visual Studio 2012\\Projects\\Fusion Engine\\Tests\\ConstantLighting_VS.hlsl" name ="VS" target = "vs_4_0">
</vs>
<ps path = "D:\\Documentos\\Visual Studio 2012\\Projects\\Fusion Engine\\Tests\\ConstantLighting_PS.hlsl" name ="PS" target = "ps_4_0">
<val1 type = "scalar" value = "0.456645" name = "Power"/>
</ps>
You could just use #pragma pack_matrix(row_major) in your shader instead of tranposing matrices on the cpu side
Try simply concatenating the WorldViewProjection matrices into one, before calling the vertex function. Hopefully all of those compnenets were okay, not, say, all zero's? You might also want to explicitly set the matrix type in your HLSL code as "matrix <float, 4, 4>" or just declare the transform(s) as "float4x4".
Ok, i solved it. You MUST use DirectX::XMMatrixTranspose() when you pass any matrix to the shader.
Tricky DirectX ;)
Only in DirectX 11 you have to transpose the matrices berfore sending them to shader. They changed the way you keep and work with structures in shaders
I've been writing a program using directx11, and I have written a basic camera class which manipulates a view matrix. When I test the program, the result is that the scene does not move, but when I move the camera it has the effect of cutting off what is visible at an arbitrary location. I've attached some pictures to show what I mean.
I have left my pixel shader only outputting red pixels for now.
My vertex shader is based on the SDK example:
cbuffer cbChangeOnResize : register(b1)
{
matrix Projection;
};
cbuffer cbChangesEveryFrame : register(b2)
{
matrix View;
matrix World;
};
struct VS_INPUT
{
float4 Pos : POSITION;
float2 Tex : TEXCOORD0;
};
struct PS_INPUT
{
float4 Pos : SV_POSITION;
float2 Tex : TEXCOORD0;
};
PS_INPUT TEX_VS(VS_INPUT input)
{
PS_INPUT output = (PS_INPUT)0;
output.Pos = mul(input.Pos, World);
output.Pos = mul(output.Pos, View);
output.Pos = mul(output.Pos, Projection);
output.Tex = input.Tex;
return output;
}
I have been scratching my head for a couple of days about this problem, but I don't know what is causing this, or even which pieces of code are relevant. PIX shows that the world, view and projection matrices appear to exist and are being applied, although it is evident that something is not right.
Thank you.
You can use row_major modifier instead of transposing matrices before passing them inside shader
Mathematical fail, I had sent the view matrix instead of its transpose to the shader.