HLSL中的镜面反射

Specular reflection in HLSL

本文关键字:反射 HLSL      更新时间:2023-10-16

我正在尝试理解HLSL和DirectX11 中的镜面反射

cbuffer ConstantBuffer : register( b0 )
{
    matrix World;           // Матрица мира
    matrix View;            // Матрица вида
    matrix Projection;      // Матрица проекции
    float4 vLightDir[3];    // Dir of light
    float4 vLightColor[3];  // color of light
    float4 vOutputColor;    // Active color
    //float3 Eye;
}
struct VS_INPUT                 // Входящие данные вершинного шейдера
{
    float4 Pos : POSITION;      // Позиция по X, Y, Z
    float3 Norm : NORMAL;       // Нормаль по X, Y, Z
};
struct PS_INPUT                 // Входящие данные пиксельного шейдера
{
    float4 Pos : SV_POSITION;   // Позиция пикселя в проекции (экранная)
    float3 Norm : TEXCOORD0;    // Относительная нормаль пикселя по tu, tv
};

PS_INPUT 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.Norm = mul( input.Norm, World );
    return output;
}

float4 Diffuse( PS_INPUT input) : SV_Target
{
    float4 diffuse = float4(1.0, 1.0, 1.0, 1.0);
    float4 finalColor = diffuse *0.1;
    // Adding all light colours
    for(int i=0; i<3; i++)
    {
        finalColor += saturate( dot( (float3)vLightDir[i], input.Norm) * vLightColor[i] );
    }
    finalColor.a = 1;
    return finalColor;
}
float4 Specular(PS_INPUT input) : SV_Target
{   
    float4 diffuse = float4(1.0, 0.0, 0.0, 1.0);
    float4 finalColor = diffuse *0.1;
    float3 Eye = float3(0.0f, 4.0f, -20.0f);
    float4 intensity = 0.1;
    float power = 4;
    float3 R = reflect(-normalize(Eye), input.Norm);
    for (int i = 0; i < 3; i++)
    {
        finalColor += saturate(intensity * vLightColor[i] * pow(dot(R, Eye), power));
    }   
    return finalColor;
}

float4 PSSolid( PS_INPUT input) : SV_Target
{
    return vOutputColor;
}

现在我只有漫反射,镜面反射只显示白色立方体(我在哪里可以得到关于镜面反射的例子或导师?

您试图实现的称为Phong反射模型,该模型通过计算法线向量和光方向向量之间的反射向量来生成镜面高光。然后使用点积来计算反射矢量和从表面到眼睛位置的矢量之间的角度的余弦。

Specular着色器存在一些问题。

首先,您的Eye矢量不正确。它需要是从表面到眼睛位置的矢量。目前,您只使用一个职位。若要完成此操作,您需要修改顶点着色器,以输出顶点位置世界空间位置。为此,添加:

float3 WorldPos : TEXCOORD1;

到您的PS_INPUT。现在计算顶点着色器中的世界空间顶点位置:

output.WorldPos = mul( input.Pos, World );

该值将被发送到像素着色器,并在像素之间进行插值,以便提供像素的世界位置。使用此选项,可以计算像素着色器中从像素到眼睛位置的视图向量V。要做到这一点,请使用:

float3 V = normalize( Eye - input.WorldPos );

现在,反射向量R是通过反射表面法线周围的入射光方向(而不是眼睛位置)来创建的,并且必须为循环中的每一盏灯计算它:

float3 R = reflect( normalize( vLightDir[i] ), normalize( input.Norm ) );

请注意,在这个等式中,光向量是从光指向表面的(所以不要像下面描述的那样否定它)。

现在,您可以使用RV之间的点积来计算每个光创建的镜面反射分量:

finalColor += intensity * vLightColor[i] * pow( saturate( dot( R, V ) ), power );

我看到的最后一个问题是,在Specular着色器中没有正确计算漫反射。如果希望不面向灯光的区域更暗,则需要像Diffuse着色器一样执行此操作。但是,您不需要直接使用vLightDir[i],而是需要将其取反,使其从表面指向光:

float3 L = -normalize( vLightDir[i].xyz );

一种比Phong反射模型更便宜的方法称为Blinn Phong反射模式。它不使用R,而是使用以下简单方程计算VL之间的半矢量:

float3 H = normalize( L + V );

现在,在计算镜面反射时不使用dot( R, V ),而是使用:

dot( N, H );