DirectX11入门笔记---添加第三人称摄像机和diffuse光照

image-20201217210042177

第三人称摄像机

之前我们已经把baseObject抽象出来了,但是ViewMatrix和proJMatrix还是在Opda::InitResource中创建初始化的,对我们后续的操作非常不方便,所以我们要抽象出thirdCamera类。

class ThirdCamera
{

public:
    ThirdCamera();

    //围绕target旋转的方法
    void RotateAroundTarget(float RotateX,float RotateY);
    //获取摄像机向前的方向
    XMVECTOR GetForwardDir();
    //缩放FOV
    void ScrollFOV(float delta);


    //获取观察矩阵
    XMMATRIX GetViewMatrix();
    //获取投影矩阵
    XMMATRIX GetProjMatrix();

public:

    //近裁剪面距离
    float distanceNear;
    //远裁剪面距离
    float distanceFar;
    //FOV
    float cameraFOV;
    //FOV限制
    float FOV_Min, FOV_Max;

    ////摄像机位置
    XMFLOAT3 position;
    //旋转
    XMFLOAT3 rotation;
    //摄像机对准的方向
    XMFLOAT3 target;
    XMFLOAT3 up;


};

主要功能的实现

摄像机的主要功能:

  1. 获取ViewMatrix和ProjMatrix
  2. 缩放FOV
  3. 围绕Target进行旋转
ThirdCamera::ThirdCamera()
{

    //视锥体设置
    distanceNear = 1.0f;
    distanceFar = 1000.0f;

    //默认90FOV
    cameraFOV = 0.25f ;
    //最大180
    FOV_Min = (30.0f / 360.0f);
    //最小10
    FOV_Max = (150.0f / 360.0f);

    //初始化
    position = XMFLOAT3(0.0f, 0.0f, -5.0f);
    rotation = XMFLOAT3(0.0f, 0.0f, 0.0f);
    target = XMFLOAT3(0.0f, 0.0f, 0.0f);
    up = XMFLOAT3(0.0f, 1.0f, 0.0f);

}

void ThirdCamera::RotateAroundTarget(float RotateX, float RotateY)
{
    //首先围绕TargetY轴转动
    rotation.x = RotateX;
    rotation.y = -RotateY;

    //获取旋转之后的向前方向,位移一段距离
    XMVECTOR forwardDir = GetForwardDir();
    XMVECTOR tempPos = XMVectorMultiplyAdd(XMVectorReplicate(7.0f), XMVector3Normalize(forwardDir), XMLoadFloat3(&target));

    //计算完毕之后需要存储为XMFLOAT3类型
    XMStoreFloat3(&position, tempPos);


}

XMVECTOR ThirdCamera::GetForwardDir()
{
    XMMATRIX rotMatrix = XMMatrixRotationRollPitchYawFromVector(XMLoadFloat3(&rotation));
    //获取Z轴
    return rotMatrix.r[2];
}

void ThirdCamera::ScrollFOV(float delta)
{
    cameraFOV -= delta;
    //限制大小
    if (cameraFOV > FOV_Max)
    {
        cameraFOV = FOV_Max;
    }
    else if (cameraFOV < FOV_Min)
    {
        cameraFOV = FOV_Min;
    }

}

XMMATRIX ThirdCamera::GetViewMatrix()
{
    XMVECTOR eyePos = XMVectorSet(position.x, position.y, position.z, 0.0f);
    XMVECTOR targetPos = XMVectorSet(target.x, target.y, target.z, 0.0f);
    XMVECTOR upDir = XMVectorSet(up.x, up.y, up.z, 0.0f);
    XMMATRIX viewMatrix = XMMatrixTranspose(XMMatrixLookAtLH(eyePos, targetPos, upDir));
    return viewMatrix;
}

XMMATRIX ThirdCamera::GetProjMatrix()
{
    XMMATRIX proJMatrix = XMMatrixTranspose(XMMatrixPerspectiveFovLH(cameraFOV * XM_PI, 640.0f / 480.0f, distanceNear, distanceFar));
    return proJMatrix;
}

MessageProc中对鼠标滚轮的处理

缩放FOV涉及到鼠标滚轮的信息处理,在OpdaGraphics::RealMessageProc中添加对鼠标滚轮信息的处理

case WM_MOUSEWHEEL:
        //处理鼠标滚轮事件
        MouseScrollWheel((short)HIWORD(wParam));
        break;

这里我们只需要接受(short)HIWORD(wParam),其他的用不到

 afx_msg   BOOL   OnMouseWheel(  
 UINT   nFlags,  //是否按下了虚拟键
 short   zDelta,  //鼠标滚轮转动的距离,返回120的倍数
 CPoint   pt   );   //鼠标光标的位置

这里由于我们重新设置了FOV,所以要重新更新ViewMatrix的常量缓冲区

void OpdaGraphics::MouseScrollWheel(short WheelDelte)
{
    thirdCamera->ScrollFOV((WheelDelte / 120.0f) * 0.01f);

    //创建Proj变换矩阵
    cbProj cbP;
    cbP.ProjMatrix = thirdCamera->GetProjMatrix();
    //更新缓冲区
    D3D11_MAPPED_SUBRESOURCE mappedData;
    DirectxDeviceContext->Map(constantBuffer[2].Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedData);
    //安全内存复制,将cbstruct复制到pData
    memcpy_s(mappedData.pData, sizeof(cbProj), &cbP, sizeof(cbProj));
    //关闭常量缓冲区的访问权限
    DirectxDeviceContext->Unmap(constantBuffer[2].Get(), 0);
}

记住对摄像机的属性进行了设置要重新更新相对应的常量缓冲区

thirdCamera->RotateAroundTarget(RotateX, RotateY);

    //创建view变换矩阵
    cbView cbV;
    cbV.ViewMatrix = thirdCamera->GetViewMatrix();
    //更新常量缓冲区
    D3D11_MAPPED_SUBRESOURCE mappedData;
    //用于存储map函数获取到缓冲区的内存
    DirectxDeviceContext->Map(constantBuffer[1].Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedData);
    //安全内存复制,将cbstruct复制到pData
    memcpy_s(mappedData.pData, sizeof(cbView), &cbV, sizeof(cbView));
    //关闭常量缓冲区的访问权限
    DirectxDeviceContext->Unmap(constantBuffer[1].Get(), 0);

添加鼠标旋转功能

同样需要在OpdaGraphics::RealMessageProc中处理一下信息

    case WM_LBUTTONDOWN:
    case WM_MBUTTONDOWN:
    case WM_RBUTTONDOWN:
        //处理鼠标点击事件
        MouseDown(wParam, (int)LOWORD(lParam), (int)HIWORD(lParam));
        break;
    case WM_LBUTTONUP:
    case WM_MBUTTONUP:
    case WM_RBUTTONUP:
        //处理鼠标松开事件
        MouseUp(wParam, (int)LOWORD(lParam), (int)HIWORD(lParam));
        break;
    case WM_MOUSEMOVE:
        MouseMove(wParam, (int)LOWORD(lParam), (int)HIWORD(lParam));
        break;

记录鼠标左键按下的LastPos位置,在MouseMove计算鼠标位置的delta距离,这样就能得到大概旋转的值,最后更新LastPos的位置

void OpdaGraphics::MouseDown(WPARAM btnState, int x, int y)
{
    mouseLastPosX = x;
    mouseLastPosY = y;
    //捕捉鼠标
    SetCapture(hwnd);
}

void OpdaGraphics::MouseUp(WPARAM btnState, int x, int y)
{
    ReleaseCapture();
}

void OpdaGraphics::MouseMove(WPARAM btnState, int x, int y)
{
    if (btnState == MK_LBUTTON)
    {
        float dx = 0.01f * static_cast<float> (x - mouseLastPosX);
        float dy = 0.01f * static_cast<float> (y - mouseLastPosY);

        RotateX -= dy;
        RotateY -= dx;
        mouseLastPosX = x;
        mouseLastPosY = y;

    }
}

最后在UpdateScene中调用摄像机的RotateAroundTarget即可。

添加diffuse光照

我们创建最基本的平行光diffuse光照,首先要定义好材质和灯光的数据结构

struct Material
{
    XMFLOAT4 diffuse;        //漫反射颜色
    XMFLOAT4 specular;        //高光颜色
    XMFLOAT4 ambient;        //环境光
};

//平行光
struct Light
{
    XMFLOAT4 lightColor;        //光的颜色
    XMFLOAT3 lightDirection;    //光的方向
    float temp;        //对齐用的
};

HLSL内存对齐

注意到平行光的结构体有一个Temp的数据,这个涉及到HLSL的内存对齐,不对齐会报错,程序无法运行

相关文章:https://blog.csdn.net/BonChoix/article/details/8445218

创建材质灯光的各自缓冲区

同样先在OpdaGraphics::InitResource()中进行初始化创建,后续再进行优化

    //创建材质
    Material normalMat;
    Light DirectionLight;
    DirectionLight.lightColor = XMFLOAT4(0.8f, 0.8f, 0.8f, 1.0f);
    DirectionLight.lightDirection = XMFLOAT3(1.0f, -5.0f, 1.0f);
    DirectionLight.temp = 0;
    normalMat.ambient = XMFLOAT4(0.2f, 0.2f, 0.2f, 1.0f);
    normalMat.diffuse = XMFLOAT4(0.7f, 1.0f, 1.0f, 1.0f);
    normalMat.specular = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f);

接下来的任务就是绑定到渲染管线,注意这里我是把材质灯光的常量缓冲区绑定到了像素着色器,因为我们的光照计算是在像素着色器中进行。

    //将常量缓冲区绑定到像素着色器
    DirectxDeviceContext->PSSetConstantBuffers(3, 1, constantBuffer[3].GetAddressOf());
    DirectxDeviceContext->PSSetConstantBuffers(4, 1, constantBuffer[4].GetAddressOf());

HLSL光照计算

普通的漫反射计算,跟UnityShader入门精要差不多,由于我们现在的物体是没有动

//常量缓冲区
cbuffer cbWorld : register(b0)
{
    matrix WorldMatrix;
}

cbuffer cbView : register(b1)
{
    matrix ViewMatrix;
}

cbuffer cbProj : register(b2)
{
    matrix ProjMatrix;
}

cbuffer material : register(b3)
{
    float4 diffuse; //漫反射颜色
    float4 specular; //高光颜色
    float4 ambient; //环境光
}

cbuffer light : register(b4)
{
    float4 lightColor; //光的颜色
    float3 lightDirection; //光的方向
    float temp;
}

struct VertexIn
{
    float4 pos : POSITION;
    float3 normal : NORMAL;
    float4 color : COLOR;
};

struct VertexOut
{
    float4 pos : SV_POSITION;
    float3 worldNormal : COLOR0;
    float4 color : COLOR1;
};

//顶点着色器
VertexOut VS(VertexIn vIn)
{
    VertexOut vOut;
    //变换到裁剪空间,MVP矩阵
    vOut.pos = mul(mul(mul(vIn.pos, WorldMatrix), ViewMatrix),ProjMatrix);
    vOut.worldNormal = normalize(mul(vIn.normal, (float3x3) WorldMatrix));
    vOut.color = vIn.color;
    return vOut;
}

//像素着色器
float4 PS(VertexOut pIn):SV_Target
{
    //计算漫反射
    float3 lightDir = -normalize(lightDirection);
    float3 diffuseColor = lightColor.rgb * diffuse.rgb * (dot(pIn.worldNormal,lightDir)*0.5+0.5);

    float3 finalColor = saturate(diffuseColor + ambient.rgb);
    return float4(finalColor, 1.0f);
}
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2015-2021 Opda
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信