为什么我的着色器产生不正确的镜面反射结果? [DX11]
Why does my shader produce incorrect specular results? [DX11]
我目前正在尝试使用 HLSL 和 DirectX 11 在带纹理的立方体上实施 Phong 着色和照明。我相信我的环境光和漫反射照明计算是正确的,并且在视觉上它们产生了预期的结果。然而,当我应用镜面照明时,我得到了奇怪的结果(见链接)
漫反射、环境光和镜面反射:https://i.gyazo.com/f7700d758e05227e27be91ab0cfdf64e.png
仅限高光:https://i.gyazo.com/27bbfa0efce5c60748f61f54365cc042.png
我的 .fx 文件:
//Texture Variables
Texture2D txDiffuse[2] : register(t0);
SamplerState anisoSampler : register(s0);
//--------------------------------------------------------------------------------------
// Constant Buffer Variables
//--------------------------------------------------------------------------------------
cbuffer ConstantBuffer : register(b0)
{
matrix World;
matrix View;
matrix Projection;
float4 DiffuseMtrl;
float4 DiffuseLight;
float3 LightPosition;
float4 AmbientMaterial;
float4 AmbientLight;
float4 specularMaterial;
float4 specularLight;
float specularPower;
float3 eyePosW;
}
//--------------------------------------------------------------------------------------
struct VS_INPUT
{
float4 Pos : POSITION;
float3 Normal : NORMAL;
float2 Tex : TEXCOORD0;
};
//--------------------------------------------------------------------------------------
struct VS_OUTPUT
{
float4 Pos : SV_POSITION;
float3 Norm : NORMAL;
float3 PosW : POSITION; //Eye Vector
float3 LPos : LIGHTPOS; //Position of light
float2 Tex : TEXCOORD0;
};
//--------------------------------------------------------------------------------------
// Vertex Shader
//--------------------------------------------------------------------------------------
VS_OUTPUT VS(VS_INPUT vIn)
{
VS_OUTPUT output = (VS_OUTPUT)0;
float4 worldPosition;
output.Tex = vIn.Tex;
vIn.Pos.w = 1.0f;
output.Pos = mul(vIn.Pos, World);
output.Pos = mul(output.Pos, View);
output.Pos = mul(output.Pos, Projection);
worldPosition = mul(vIn.Pos, World);
output.LPos = normalize(worldPosition - LightPosition);
output.PosW = normalize(eyePosW.xyz - worldPosition.xyz);
float3 normalW = mul(float4(vIn.Normal, 0.0f), World).xyz;
normalW = normalize(normalW);
output.Norm = normalW;
return output;
}
//--------------------------------------------------------------------------------------
// Pixel Shader
//--------------------------------------------------------------------------------------
float4 PS(VS_OUTPUT input) : SV_Target
{
float4 textureColor = txDiffuse[0].Sample(anisoSampler, input.Tex);
float4 bumpNormal = txDiffuse[1].Sample(anisoSampler, input.Tex);
float4 output;
float lightIntensity;
float specularAmount;
input.Norm = normalize(input.Norm);
bumpNormal = normalize(bumpNormal);
//Invert LDir for calculations
float3 LDir = -input.LPos;
lightIntensity = saturate(dot((input.Norm + bumpNormal.rgb), LDir));
float3 r = reflect(LDir, (input.Norm + bumpNormal.rgb));
specularAmount = pow(max(dot(r, input.PosW), 0.0f), specularPower);
// Compute Colour using Diffuse ambient and texture
float diffuseAmount = max(dot(LDir, (input.Norm + bumpNormal.rgb)), 0.0f);
float3 diffuse = (diffuseAmount * (DiffuseMtrl * DiffuseLight).rgb) * lightIntensity;
float3 ambient = AmbientLight * AmbientMaterial;
float3 specular = specularAmount * (specularMaterial * specularLight).rgb * lightIntensity;
output.rgb = ((ambient + diffuse) * textureColor) + specular;
output.a = textureColor.a;
return output;
}
请原谅任何乱七八糟的代码或变量名,我试图使用各种教程和教科书来实现它,每个教程和教科书都有自己的命名约定,所以我现在有点乱。
编辑:使用答案和其他一些信息来源,我重新设计了我的着色器并使其正常工作。我的一个问题是常量缓冲区中的填充不正确。我还添加了切线 space 计算并正确地将我的凹凸法线转换为 -1 到 +1 范围
//--------------------------------------------------------------------------------------
// Constant Buffer Variables
//--------------------------------------------------------------------------------------
cbuffer ConstantBuffer
{
matrix World;
matrix View;
matrix Projection;
}
struct PointLight
{
float4 ambient;
float4 diffuse;
float4 specular;
float3 pos;
float range;
float3 att;
float pad;
};
cbuffer CbPerFrame
{
PointLight light;
float3 eyePosW;
float pad;
float4 SpecularMaterial;
float SpecularPower;
float3 pad2;
};
Texture2D ObjTexture[2];
SamplerState ObjSamplerState;
//--------------------------------------------------------------------------------------
struct VS_OUTPUT
{
float4 Pos : SV_POSITION;
float4 worldPos : POSITION;
float2 TexCoord : TEXCOORD;
float3 normal : NORMAL;
float3 tangent : TANGENT;
float3 biTangent : BITANGENT;
};
void CalcTanBiTan(float3 norm, out float3 tan, out float3 biTan)
{
float3 c1 = cross(norm, float3(0.0f, 0.0f, 1.0f));
float3 c2 = cross(norm, float3(0.0f, 1.0f, 0.0f));
if (length(c1) > length(c2))
{
tan = c1;
}
else
{
tan = c2;
}
tan = normalize(tan);
biTan = cross(norm, tan);
biTan = normalize(biTan);
}
//--------------------------------------------------------------------------------------
// Vertex Shader
//--------------------------------------------------------------------------------------
VS_OUTPUT VS(float4 inPos : POSITION, float2 inTexCoord : TEXCOORD, float3 normal : NORMAL)
{
VS_OUTPUT output = (VS_OUTPUT)0;
output.Pos = mul(inPos, World);
output.worldPos = mul(inPos, World);
output.Pos = mul(output.Pos, View);
output.Pos = mul(output.Pos, Projection);
output.normal = mul(normal, World);
output.TexCoord = inTexCoord;
float3 tangent, biTangent;
CalcTanBiTan(normal, tangent, biTangent);
output.tangent = mul(tangent, (float3x3)World);
output.tangent = normalize(output.tangent);
output.biTangent = mul(biTangent, (float3x3)World);
output.biTangent = normalize(output.biTangent);
return output;
}
//--------------------------------------------------------------------------------------
// Pixel Shader
//--------------------------------------------------------------------------------------
float4 PS(VS_OUTPUT input) : SV_Target
{
input.normal = normalize(input.normal);
float4 diffuse = ObjTexture[0].Sample(ObjSamplerState, input.TexCoord);
float4 bumpMap = ObjTexture[1].Sample(ObjSamplerState, input.TexCoord);
bumpMap = (bumpMap * 2.0f) - 1.0f;
float3 bumpNormal = (bumpMap.x * input.tangent) + (bumpMap.y * input.biTangent) + (bumpMap.z * input.normal);
bumpNormal = normalize(bumpNormal);
float3 finalColor = float3(0.0f, 0.0f, 0.0f);
//Create vector between light and pixel
float3 lightToPixelVec = light.pos - input.worldPos;
//find distance between light pos and pixel pos
float d = length(lightToPixelVec);
float3 finalAmbient = diffuse * light.ambient;
if (d > light.range)
return float4(finalAmbient, diffuse.a);
//Turn lightToPixelVec into a unit vector describing pixel direction from the light position
lightToPixelVec /= d;
float howMuchLight = dot(lightToPixelVec, bumpNormal);
float3 toEye = normalize(eyePosW - input.worldPos);
float3 spec;
[flatten]
if (howMuchLight > 0.0f)
{
float3 v = reflect(-lightToPixelVec, bumpNormal);
float specFactor = pow(max(dot(v, toEye), 0.0f), SpecularPower);
spec = specFactor * SpecularMaterial * light.specular;
finalColor += howMuchLight * diffuse * light.diffuse;
finalColor /= light.att[0] + (light.att[1] * d) + light.att[2] * (d*d);
}
finalColor = saturate(finalColor + finalAmbient + spec);
return float4(finalColor, diffuse.a);
}
第一个:
float4 bumpNormal = txDiffuse[1].Sample(anisoSampler, input.Tex);
bumpNormal = normalize(bumpNormal);
纹理中的数据存储通常是 RGBA 格式,每个通道为 8 位。
除非您正在从浮动纹理中读取。
当纹理用 8 位存储每个通道时。您需要在着色器中将值形式 0~1 映射到 -1~1。
第二个:
input.Norm = normalize(input.Norm);
bumpNormal = normalize(bumpNormal);
(input.Norm + bumpNormal.rgb)
添加不正确,应将法线替换为凹凸法线。
第三名:
我不确定 space 你的法线存储在什么地方,通常法线会存储在切线 space 中,这意味着你还应该从顶点着色器传递切线或副法线。
如果你只想测试镜面反射,你可以使用原始法线。
我目前正在尝试使用 HLSL 和 DirectX 11 在带纹理的立方体上实施 Phong 着色和照明。我相信我的环境光和漫反射照明计算是正确的,并且在视觉上它们产生了预期的结果。然而,当我应用镜面照明时,我得到了奇怪的结果(见链接)
漫反射、环境光和镜面反射:https://i.gyazo.com/f7700d758e05227e27be91ab0cfdf64e.png
仅限高光:https://i.gyazo.com/27bbfa0efce5c60748f61f54365cc042.png
我的 .fx 文件:
//Texture Variables
Texture2D txDiffuse[2] : register(t0);
SamplerState anisoSampler : register(s0);
//--------------------------------------------------------------------------------------
// Constant Buffer Variables
//--------------------------------------------------------------------------------------
cbuffer ConstantBuffer : register(b0)
{
matrix World;
matrix View;
matrix Projection;
float4 DiffuseMtrl;
float4 DiffuseLight;
float3 LightPosition;
float4 AmbientMaterial;
float4 AmbientLight;
float4 specularMaterial;
float4 specularLight;
float specularPower;
float3 eyePosW;
}
//--------------------------------------------------------------------------------------
struct VS_INPUT
{
float4 Pos : POSITION;
float3 Normal : NORMAL;
float2 Tex : TEXCOORD0;
};
//--------------------------------------------------------------------------------------
struct VS_OUTPUT
{
float4 Pos : SV_POSITION;
float3 Norm : NORMAL;
float3 PosW : POSITION; //Eye Vector
float3 LPos : LIGHTPOS; //Position of light
float2 Tex : TEXCOORD0;
};
//--------------------------------------------------------------------------------------
// Vertex Shader
//--------------------------------------------------------------------------------------
VS_OUTPUT VS(VS_INPUT vIn)
{
VS_OUTPUT output = (VS_OUTPUT)0;
float4 worldPosition;
output.Tex = vIn.Tex;
vIn.Pos.w = 1.0f;
output.Pos = mul(vIn.Pos, World);
output.Pos = mul(output.Pos, View);
output.Pos = mul(output.Pos, Projection);
worldPosition = mul(vIn.Pos, World);
output.LPos = normalize(worldPosition - LightPosition);
output.PosW = normalize(eyePosW.xyz - worldPosition.xyz);
float3 normalW = mul(float4(vIn.Normal, 0.0f), World).xyz;
normalW = normalize(normalW);
output.Norm = normalW;
return output;
}
//--------------------------------------------------------------------------------------
// Pixel Shader
//--------------------------------------------------------------------------------------
float4 PS(VS_OUTPUT input) : SV_Target
{
float4 textureColor = txDiffuse[0].Sample(anisoSampler, input.Tex);
float4 bumpNormal = txDiffuse[1].Sample(anisoSampler, input.Tex);
float4 output;
float lightIntensity;
float specularAmount;
input.Norm = normalize(input.Norm);
bumpNormal = normalize(bumpNormal);
//Invert LDir for calculations
float3 LDir = -input.LPos;
lightIntensity = saturate(dot((input.Norm + bumpNormal.rgb), LDir));
float3 r = reflect(LDir, (input.Norm + bumpNormal.rgb));
specularAmount = pow(max(dot(r, input.PosW), 0.0f), specularPower);
// Compute Colour using Diffuse ambient and texture
float diffuseAmount = max(dot(LDir, (input.Norm + bumpNormal.rgb)), 0.0f);
float3 diffuse = (diffuseAmount * (DiffuseMtrl * DiffuseLight).rgb) * lightIntensity;
float3 ambient = AmbientLight * AmbientMaterial;
float3 specular = specularAmount * (specularMaterial * specularLight).rgb * lightIntensity;
output.rgb = ((ambient + diffuse) * textureColor) + specular;
output.a = textureColor.a;
return output;
}
请原谅任何乱七八糟的代码或变量名,我试图使用各种教程和教科书来实现它,每个教程和教科书都有自己的命名约定,所以我现在有点乱。
编辑:使用答案和其他一些信息来源,我重新设计了我的着色器并使其正常工作。我的一个问题是常量缓冲区中的填充不正确。我还添加了切线 space 计算并正确地将我的凹凸法线转换为 -1 到 +1 范围
//--------------------------------------------------------------------------------------
// Constant Buffer Variables
//--------------------------------------------------------------------------------------
cbuffer ConstantBuffer
{
matrix World;
matrix View;
matrix Projection;
}
struct PointLight
{
float4 ambient;
float4 diffuse;
float4 specular;
float3 pos;
float range;
float3 att;
float pad;
};
cbuffer CbPerFrame
{
PointLight light;
float3 eyePosW;
float pad;
float4 SpecularMaterial;
float SpecularPower;
float3 pad2;
};
Texture2D ObjTexture[2];
SamplerState ObjSamplerState;
//--------------------------------------------------------------------------------------
struct VS_OUTPUT
{
float4 Pos : SV_POSITION;
float4 worldPos : POSITION;
float2 TexCoord : TEXCOORD;
float3 normal : NORMAL;
float3 tangent : TANGENT;
float3 biTangent : BITANGENT;
};
void CalcTanBiTan(float3 norm, out float3 tan, out float3 biTan)
{
float3 c1 = cross(norm, float3(0.0f, 0.0f, 1.0f));
float3 c2 = cross(norm, float3(0.0f, 1.0f, 0.0f));
if (length(c1) > length(c2))
{
tan = c1;
}
else
{
tan = c2;
}
tan = normalize(tan);
biTan = cross(norm, tan);
biTan = normalize(biTan);
}
//--------------------------------------------------------------------------------------
// Vertex Shader
//--------------------------------------------------------------------------------------
VS_OUTPUT VS(float4 inPos : POSITION, float2 inTexCoord : TEXCOORD, float3 normal : NORMAL)
{
VS_OUTPUT output = (VS_OUTPUT)0;
output.Pos = mul(inPos, World);
output.worldPos = mul(inPos, World);
output.Pos = mul(output.Pos, View);
output.Pos = mul(output.Pos, Projection);
output.normal = mul(normal, World);
output.TexCoord = inTexCoord;
float3 tangent, biTangent;
CalcTanBiTan(normal, tangent, biTangent);
output.tangent = mul(tangent, (float3x3)World);
output.tangent = normalize(output.tangent);
output.biTangent = mul(biTangent, (float3x3)World);
output.biTangent = normalize(output.biTangent);
return output;
}
//--------------------------------------------------------------------------------------
// Pixel Shader
//--------------------------------------------------------------------------------------
float4 PS(VS_OUTPUT input) : SV_Target
{
input.normal = normalize(input.normal);
float4 diffuse = ObjTexture[0].Sample(ObjSamplerState, input.TexCoord);
float4 bumpMap = ObjTexture[1].Sample(ObjSamplerState, input.TexCoord);
bumpMap = (bumpMap * 2.0f) - 1.0f;
float3 bumpNormal = (bumpMap.x * input.tangent) + (bumpMap.y * input.biTangent) + (bumpMap.z * input.normal);
bumpNormal = normalize(bumpNormal);
float3 finalColor = float3(0.0f, 0.0f, 0.0f);
//Create vector between light and pixel
float3 lightToPixelVec = light.pos - input.worldPos;
//find distance between light pos and pixel pos
float d = length(lightToPixelVec);
float3 finalAmbient = diffuse * light.ambient;
if (d > light.range)
return float4(finalAmbient, diffuse.a);
//Turn lightToPixelVec into a unit vector describing pixel direction from the light position
lightToPixelVec /= d;
float howMuchLight = dot(lightToPixelVec, bumpNormal);
float3 toEye = normalize(eyePosW - input.worldPos);
float3 spec;
[flatten]
if (howMuchLight > 0.0f)
{
float3 v = reflect(-lightToPixelVec, bumpNormal);
float specFactor = pow(max(dot(v, toEye), 0.0f), SpecularPower);
spec = specFactor * SpecularMaterial * light.specular;
finalColor += howMuchLight * diffuse * light.diffuse;
finalColor /= light.att[0] + (light.att[1] * d) + light.att[2] * (d*d);
}
finalColor = saturate(finalColor + finalAmbient + spec);
return float4(finalColor, diffuse.a);
}
第一个:
float4 bumpNormal = txDiffuse[1].Sample(anisoSampler, input.Tex);
bumpNormal = normalize(bumpNormal);
纹理中的数据存储通常是 RGBA 格式,每个通道为 8 位。 除非您正在从浮动纹理中读取。 当纹理用 8 位存储每个通道时。您需要在着色器中将值形式 0~1 映射到 -1~1。
第二个:
input.Norm = normalize(input.Norm);
bumpNormal = normalize(bumpNormal);
(input.Norm + bumpNormal.rgb)
添加不正确,应将法线替换为凹凸法线。
第三名:
我不确定 space 你的法线存储在什么地方,通常法线会存储在切线 space 中,这意味着你还应该从顶点着色器传递切线或副法线。
如果你只想测试镜面反射,你可以使用原始法线。