DirectX12 3D game development with Chapter VIII of the actual content (under)

8.9, the material realization

The following is a part of the code structure of the material:

// 简单的结构体来表示我们所演示的材料
struct Material
{
    // 材质唯一对应的名称(便于查找)
    std::string Name;

    // 本材质的常量缓冲区索引
    int MatCBIndex = -1;

    // 漫反射在SRV堆中的索引(在第九章的纹理贴图中会使用)
    int DiffuseSrvHeapIndex = -1;

    
    int NormalSrvHeapIndex = -1;

    //已更新标志(dirty flag,也称为脏标志),用于表示本材质已经发生变动
    //因为每一个帧资源(Frame Resource)都有材质常量缓冲区,所以我们需要对每个
    //帧资源进行更新,因此当我们修改某一个材质的时候,我们需要对每一个帧资源进
    //行更新,即 NumFrameDirty = gNumFrameResource
    int NumFramesDirty = gNumFrameResources;

    // 用于着色的材质常量缓冲区数据
    DirectX::XMFLOAT4 DiffuseAlbedo = { 1.0f, 1.0f, 1.0f, 1.0f };
    DirectX::XMFLOAT3 FresnelR0 = { 0.01f, 0.01f, 0.01f };
    float Roughness = .25f;
    DirectX::XMFLOAT4X4 MatTransform = MathHelper::Identity4x4();
};

In order to simulate real-world materials, we need to set DiffuseAlbedo (diffuse reflectance albedo) and FresnelR0 (a property of the medium) pair and associated values fidelity, then add some details of the adjustment, in order to achieve better visual effect . In the structure of the material, we specify the roughness between the normalized floating-point values between the range [0, 1]. 0 represents the ideal smooth mirror, represents the most rough surface.

But now there is a question: What should be in accordance with the granularity to specify the data material? Because the surface material of the same data at different points may be different, such as a reflective car body, windows and headlights and the amount of light absorbed it is not the same.

One way of overcoming this problem is to specify each vertex as a reference material specific value, then the rasterization stage of material properties of these vertices are linearly interpolated, in order to obtain the value of each point of the triangle material. However, this method, like the last chapter of the demonstration program, they can not achieve fine results, the color of each vertex are very rough. In fact, the more common solution is to use texture mapping, but it again the next chapter introduced.

In this chapter, we allow frequent change of materials, so we define a unique attribute for each material, and they are listed in a table:

void LitWavesApp::BuildMaterials()
{
    auto grass = std::make_unique<Material>();
    grass->Name = "grass";
    grass->MatCBIndex = 0;
    grass->DiffuseAlbedo = XMFLOAT4(0.2f, 0.6f, 0.2f, 1.0f);
    grass->FresnelR0 = XMFLOAT3(0.01f, 0.01f, 0.01f);
    grass->Roughness = 0.125f;

    auto water = std::make_unique<Material>();
    water->Name = "water";
    water->MatCBIndex = 1;
    water->DiffuseAlbedo = XMFLOAT4(0.0f, 0.2f, 0.6f, 1.0f);
    water->FresnelR0 = XMFLOAT3(0.1f, 0.1f, 0.1f);
    water->Roughness = 0.0f;

    mMaterials["grass"] = std::move(grass);
    mMaterials["water"] = std::move(water);
}

By show off the table, we can store the data in system memory material, so that the GPU can access the data to these materials in the shader. At the same time we have to copy the relevant data to the constant buffer and there is constant for each material constants added to each frame buffer resources:

struct MaterialConstants
{
    DirectX::XMFLOAT4 DiffuseAlbedo = { 1.0f, 1.0f, 1.0f, 1.0f };
    DirectX::XMFLOAT3 FresnelR0 = { 0.01f, 0.01f, 0.01f };
    float Roughness = 0.25f;

    // 下一章的纹理贴图会使用
    DirectX::XMFLOAT4X4 MatTransform = MathHelper::Identity4x4();
};

In the update function, when the material data changes (that is, the presence of so-called dirty flag), we will copy it to the sub-region corresponding to the constant buffer, so the data is always in the GPU and the material constants buffer the latest data in system memory remains the same:

void LitWavesApp::UpdateMaterialCBs(const GameTimer& gt)
{
    auto currMaterialCB = mCurrFrameResource->MaterialCB.get();
    for(auto& e : mMaterials)
    {
        //当材质数据发生变化时,就对每一个帧资源进行更新
        Material* mat = e.second.get();
        if(mat->NumFramesDirty > 0)
        {
            XMMATRIX matTransform = XMLoadFloat4x4(&mat->MatTransform);

            MaterialConstants matConstants;
            matConstants.DiffuseAlbedo = mat->DiffuseAlbedo;
            matConstants.FresnelR0 = mat->FresnelR0;
            matConstants.Roughness = mat->Roughness;

            currMaterialCB->CopyData(mat->MatCBIndex, matConstants);

            // 也需要对下一个FrameResource进行更新
            mat->NumFramesDirty--;
        }
    }
}

Until now, rendering each item already have a pointer pointing Material structure, a plurality of items may be rendered the same reference point Material object, each object existence of an index Material, the material constants for buffer pointing to its own constant data. The following code demonstrates how to use the unused material to draw a rendering items:

void LitWavesApp::DrawRenderItems(ID3D12GraphicsCommandList* cmdList, const std::vector<RenderItem*>& ritems)
{
    UINT objCBByteSize = d3dUtil::CalcConstantBufferByteSize(sizeof(ObjectConstants));
    UINT matCBByteSize = d3dUtil::CalcConstantBufferByteSize(sizeof(MaterialConstants));

    auto objectCB = mCurrFrameResource->ObjectCB->Resource();
    auto matCB = mCurrFrameResource->MaterialCB->Resource();

    for(size_t i = 0; i < ritems.size(); ++i)
    {
        auto ri = ritems[i];

        cmdList->IASetVertexBuffers(0, 1, &ri->Geo->VertexBufferView());
        cmdList->IASetIndexBuffer(&ri->Geo->IndexBufferView());
        cmdList->IASetPrimitiveTopology(ri->PrimitiveType);

        D3D12_GPU_VIRTUAL_ADDRESS objCBAddress = objectCB->GetGPUVirtualAddress() + ri->ObjCBIndex*objCBByteSize;
        D3D12_GPU_VIRTUAL_ADDRESS matCBAddress = matCB->GetGPUVirtualAddress() + ri->Mat->MatCBIndex*matCBByteSize;

        cmdList->SetGraphicsRootConstantBufferView(0, objCBAddress);
        cmdList->SetGraphicsRootConstantBufferView(1, matCBAddress);

        cmdList->DrawIndexedInstanced(ri->IndexCount, 1, ri->StartIndexLocation, ri->BaseVertexLocation, 0);
    }
}

Key: We need to get the normal vector at each point on the triangle mesh surface, light exposure to the grid for determining the angle (a Lambertian cosine law), and each point to obtain a vector approximation, we need this layer is specified in the vertex normals. Then re-rasterization of the triangle, will be calculated using the interpolated vertex normals.

8.10, directional light

Parallel light (parallel light) is also known as the light source direction (directional light), a far object is a target from a light source. We can therefore light emitted by such a light source is regarded as parallel light rays to each other. (Calculation Method omitted)

8.11, a point light source

And a point light source (point light) reality example when the lamp is more appropriate, for the ball can emit light in all directions, in particular, for any point P, the light emitted by the point source at the position W, the total bundle of spread P points. The only point of difference is calculated between the light source and the light vector parallel light, the point source light vector with a different target point is changed, and for the parallel light, the light remains the same vector. (Calculation Method omitted)

8.12, spot lights

And a spot light source (in the Spotlight) realistic example is similar to the flashlight, In essence, the light spot is a circular cone in a direction d by the position irradiated range W (calculation omitted)

Illumination embodied 8.13

In this section, we will achieve the details of the three light sources discussed

8.13.1, Light structure

In d3dUtil file, we define the following sources described structure, this structure may represent the direction of light, the point light source and a spot light source. However, depending on the particular type of light source, we do not use all the data therein.

struct Light
{
    // 光源的颜色
    DirectX::XMFLOAT3 Strength = { 0.5f, 0.5f, 0.5f };
    // 仅供点光源和聚光灯光源使用
    float FalloffStart = 1.0f;                          
    // 仅供方向光源和聚光灯使用
    DirectX::XMFLOAT3 Direction = { 0.0f, -1.0f, 0.0f };
    // 仅供点光源和聚光灯使用
    float FalloffEnd = 10.0f;                           
    // 仅供点光源和聚光灯使用
    DirectX::XMFLOAT3 Position = { 0.0f, 0.0f, 0.0f };
    // 仅供聚光灯使用
    float SpotPower = 64.0f;                            
};

LightingUtil.hlsl while the file defines the structure corresponding to:

struct Light
{
    float3 Strength;
    float FalloffStart; 
    float3 Direction;   
    float FalloffEnd;   
    float3 Position;   
    float SpotPower;   
};

The order of the data structure members is not casually Light specified HLSL need to follow the rules of the package structure. This rule to the effect that the structure of the elements packaged into a 4D vector, and a single element can not be divided into two two 4D vectors. Light structure after it is packaged is such that:

    vertor4D 1: (Strength.x, Strength.y, strength.z, FalloffStart);
    vector4D 2: (Direction.x, Direction.y, Direction.z, FalloffEnd);
    vector4D 3: (Position.x, Position.y, Position.z, SpotPower);

If the order data members of the structure changes:

struct Light
{
    DirectX::XMFLOAT3 Strength = { 0.5f, 0.5f, 0.5f };                     
    DirectX::XMFLOAT3 Direction = { 0.0f, -1.0f, 0.0f };
    DirectX::XMFLOAT3 Position = { 0.0f, 0.0f, 0.0f };
    float FalloffStart = 1.0f;     
    float FalloffEnd = 10.0f;    
    float SpotPower = 64.0f;                      
};

//HLSL文件:
struct Light
{
    float3 Strength;
    float3 Direction;
    float3 Position;   
    float FalloffStart; 
    float FalloffEnd;   
    float SpotPower;   
};

It will be packaged as:

    vertor4D 1: (Strength.x, Strength.y, strength.z, empty);
    vector4D 2: (Direction.x, Direction.y, Direction.z, empty);
    vector4D 3: (Position.x, Position.y, Position.z, empty);
    vector4D 4: (FalloffStart, FalloffEnd, SpotPower, empth);

The second embodiment obviously take up more space, and because the structure and c ++ HLSL rules corresponding to the package is not the same, if not in accordance with the rules to achieve HLSL structure c ++ and an HLSL, memcpy function may result from the CPU by upload data to the GPU constant buffer will cause rendering errors

8.13.2, common helper functions

The following three functions are defined in LightingUtil.hlsl file, because these functions can handle multiple types of light, so we will define them as a secondary function

Function name effect
CalcAttenuation A calculation method to achieve linear attenuation factor may be applied to a point light source and the light spot
SchlickFresnel Instead of a Fresnel approximation equation Schlick, this function is based on an angle between the light L and the surface normal vector n is approximately calculated as a percentage of the normal line n of the light reflected by the surface
BlinnPhong Calculating the amount of light reflected to the eyes of the observer (the sum of the amount of reflected light is diffuse and specular light amount)
/*
**  summary:线性衰减因子的计算方法
**  Parameters:
**      d:距离光源的距离
**      falloffStart:开始衰减的距离(未到达这个距离前保持最大强度)
**      falloffEnd:大于这个距离将不会受到光照(即衰减因子为0)
**  Return:衰减因子
*/
float CalcAttenuation(float d, float falloffStart, float falloffEnd)
{
    // 线性衰减
    return saturate((falloffEnd-d) / (falloffEnd - falloffStart));
}
//R0:介质的一种属性,会影响反射光量
float3 SchlickFresnel(float3 R0, float3 normal, float3 lightVec)
{
    float cosIncidentAngle = saturate(dot(normal, lightVec));

    float f0 = 1.0f - cosIncidentAngle;
    float3 reflectPercent = R0 + (1.0f - R0)*(f0*f0*f0*f0*f0);

    return reflectPercent;
}
float3 BlinnPhong(float3 lightStrength, float3 lightVec, float3 normal, float3 toEye, Material mat)
{
    const float m = mat.Shininess * 256.0f;
    float3 halfVec = normalize(toEye + lightVec);

    float roughnessFactor = (m + 8.0f)*pow(max(dot(halfVec, normal), 0.0f), m) / 8.0f;
    float3 fresnelFactor = SchlickFresnel(mat.FresnelR0, halfVec, lightVec);

    float3 specAlbedo = fresnelFactor*roughnessFactor;

    specAlbedo = specAlbedo / (specAlbedo + 1.0f);

    return (mat.DiffuseAlbedo.rgb + specAlbedo) * lightStrength;
}

HLSL internal function used in the above code: DOT, POW and max respectively represent vector dot product function, a power function, and maximum function.

8.13.3 achieve directional light

Given the observation position E, the material properties can be seen that p and n is the normal to the surface, then the following HLSL function output from a light source direction, the direction of the surface at v = normalize (E - p) reflected into the observation by the amount of light in the eyes.

float3 ComputeDirectionalLight(Light L, Material mat, float3 normal, float3 toEye)
{
    // 光向量和光的传播方向刚好相反
    float3 lightVec = -L.Direction;

    // 通过朗伯余弦定律按比例降低光强
    float ndotl = max(dot(lightVec, normal), 0.0f);
    float3 lightStrength = L.Strength * ndotl;

    return BlinnPhong(lightStrength, lightVec, normal, toEye, mat);
}

8.13.4 achieve a point light source

Given observation point E, the material properties can be seen that p and n is the normal to the surface, the following function HLSL output light emitted from the point light source, via said surface to v = normalize (E - p) to the direction of the reflector the amount of light in the eyes of the viewer.

float3 ComputePointLight(Light L, Material mat, float3 pos, float3 normal, float3 toEye)
{
    // 自物体表面指向光源的光向量
    float3 lightVec = L.Position - pos;

    // 光源距离表面的距离
    float d = length(lightVec);

    // 范围检测(如果超出衰减距离,则该表面无法接收到光照)
    if(d > L.FalloffEnd)
        return 0.0f;

    // 对光向量进行规格化处理
    lightVec /= d;

    // 通过朗伯余弦定律按比例降低光强
    float ndotl = max(dot(lightVec, normal), 0.0f);
    float3 lightStrength = L.Strength * ndotl;

    // 根据距离计算衰减因子,然后计算出衰减后的光
    float att = CalcAttenuation(d, L.FalloffStart, L.FalloffEnd);
    lightStrength *= att;

    return BlinnPhong(lightStrength, lightVec, normal, toEye, mat);
}

8.13.5 achieve spot lights

Specified observation point E, and the material properties to point p n is the surface normal, the following function HLSL output light from the spotlight, after said surface in a direction v = normalize (E - p) the amount of light reflected to the eyes of the viewer .

float3 ComputeSpotLight(Light L, Material mat, float3 pos, float3 normal, float3 toEye)
{
    // 光向量等于自表面指向光源的向量
    float3 lightVec = L.Position - pos;

    // 计算物体表面距离光源的距离
    float d = length(lightVec);

    // 范围检测
    if(d > L.FalloffEnd)
        return 0.0f;

    // 规格化光向量
    lightVec /= d;

    // 根据朗伯余弦定律按比例降低光强
    float ndotl = max(dot(lightVec, normal), 0.0f);
    float3 lightStrength = L.Strength * ndotl;

    // 根据距离计算衰减因子
    float att = CalcAttenuation(d, L.FalloffStart, L.FalloffEnd);
    lightStrength *= att;

    // 根据聚光灯模型对光强进行缩放处理
    float spotFactor = pow(max(dot(-lightVec, L.Direction), 0.0f), L.SpotPower);
    lightStrength *= spotFactor;

    return BlinnPhong(lightStrength, lightVec, normal, toEye, mat);
}

8.13.6, more lighting superimposed

Light can be superimposed, so that the scene has a plurality of light, we need to traverse each light source, and then calculate their contributions we have to calculate the light dot or pixel values ​​are summed.

Examples of the supporting frame 16 up to the light source, i.e., the number of light sources can not exceed 16. In addition, the code is agreed direction light illumination must be stored at the beginning of the array, and a point light source, spot lights and finally stored . The following code is used to calculate a point lighting equations:

#define MaxLights 16

// 绘制过程中所使用的杂项常量数据
cbuffer cbPass : register(b2)
{
    ……
    //[0, NUM_DIR_LIGHTS]表示的是平行光源
    //[NUM_DIR_LISGHTS, NUM_DIR_LIGHTS + NUM_POINT_LIGHTS]表示的是点光源
    //[NUM_DIR_LIGHTS + NUM_POINT_LIGHTS, NUM_DIR_LIGHTS + NUM_POINT_LIGHTS + NUM_SPOT_LIGHTS]
    //表示的是聚光灯光源
    Light gLights[MaxLights];
};

float4 ComputeLighting(Light gLights[MaxLights], Material mat,
                       float3 pos, float3 normal, float3 toEye,
                       float3 shadowFactor)
{
    float3 result = 0.0f;

    int i = 0;

#if (NUM_DIR_LIGHTS > 0)
    for(i = 0; i < NUM_DIR_LIGHTS; ++i)
    {
        result += shadowFactor[i] * ComputeDirectionalLight(gLights[i], mat, normal, toEye);
    }
#endif

#if (NUM_POINT_LIGHTS > 0)
    for(i = NUM_DIR_LIGHTS; i < NUM_DIR_LIGHTS+NUM_POINT_LIGHTS; ++i)
    {
        result += ComputePointLight(gLights[i], mat, pos, normal, toEye);
    }
#endif

#if (NUM_SPOT_LIGHTS > 0)
    for(i = NUM_DIR_LIGHTS + NUM_POINT_LIGHTS; i < NUM_DIR_LIGHTS + NUM_POINT_LIGHTS + NUM_SPOT_LIGHTS; ++i)
    {
        result += ComputeSpotLight(gLights[i], mat, pos, normal, toEye);
    }
#endif 

    return float4(result, 0.0f);
}

8.13.7, HLSl master file

slightly

Guess you like

Origin www.cnblogs.com/yaya12138/p/11900385.html