DirectX11编程7 光照

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u012632851/article/details/83422932

环境:VS2017  语言:C++

 

总起:

这一章主要对应红龙书的第七章。

 

工程地址:https://github.com/anguangzhihen/Dx11。主要以Chapter 7_1 Lighting Demo作为讲解的基础。

 

首先说一下光照的重要性,在学习绘画的基础课程中有两个要点,一个是形,另一个就是光影的运用。好的光照不仅能提升场景的真实性,更加在氛围上做出烘托。3D游戏失去光照,就像建筑失去最主要的支撑柱最后只能轰塌。

 

接下说一下我现在接触到的一些光照的知识。

 

标准光照模型,最简单的光照模型,早期的游戏多使用该模型或该模型的改进版。物体表面的颜色由自发光(本章不会涉及)、高光、漫反射、环境光四部分组成。在标准光照模型的基础上,Blinn改进了高光算法,被称为Blinn-Phong光照模型。

 

我们使用的漫反射又被称为兰伯特(Lambert)光照模型,而因为背光区会失去明暗变化,所以Valve在半条命中使用一种叫做半兰伯特光照模型。

 

BRDF光照模型(PBR),基于物理渲染的光照模型,BRDF的含义是双向反射分布函数,较为真实,但性能消耗大。很多书上可能说多用于3A大作啥的,不过据我了解,现在很多大型的手游都开始使用该技术了。(关于这个详细的没太研究过,不过推荐三本书:RTR、PBRT、全局光照技术)

 

GI(Global Illumination)全局光照,GI一般来讲由两部分组成,一个是光源直接对物体照射获得的明暗,另一个是间接的光照,例如阴影、反射等。对应GI的LI(Local Illumination)局部光照只讨论直接的光照。

 

本章内容主要讨论的是最简单的光照模型。本次的素材是我们之前第四章实现的山地第五章实现的水波

 

首先来看一下效果:

 

准备数据:

事实上光照没有用到任何新的技术,只是在之前知识的基础上编写光照模型的函数。

 

我们来看一下需要从C++(CPU)传到Shader(GPU)的数据:

// C++代码
// 顶点Shader的传入参数
struct Vertex
{
	XMFLOAT3 Pos;	// 位置信息
	XMFLOAT3 Normal;	// 法线
};

// 顶点着色器的常量缓存
struct ConstantBuffer
{
	XMMATRIX gWorld;
	XMMATRIX gWorldInvTranspose;	// World逆转置矩阵,用于将法线从模型坐标系中转换到世界坐标系
	XMMATRIX gWorldViewProj;	 // wvp
};

// 片元着色器的常量缓存
struct FrameConstantBuffer
{
	DirectionalLight gDirLight;	// 直射光
	PointLight gPointLight;	// 点光源
	SpotLight gSpotLight;	// 聚光灯
	Material gMaterial;	// 物体材质
	XMFLOAT4 gEyePosW;	// 当前点到相机向量
};

// Shader代码
// 顶点着色器的常量缓存
cbuffer cbPerObject : register(b0)
{
	row_major matrix gWorld;	// 默认列主矩阵
	row_major matrix gWorldInvTranspose;	// World逆转置矩阵,用于将法线从模型坐标系中转换到世界坐标系
	row_major matrix gWorldViewProj;	// wvp
};

// 片元着色器的常量缓存
cbuffer cbPerFrame : register(b1)
{
	DirectionalLight gDirLight;	// 直射光
	PointLight gPointLight;	// 点光源
	SpotLight gSpotLight;	// 聚光灯
	Material gMaterial;	// 物体材质
	float3 gEyePosW;	// 当前点到相机向量
};

// 顶点Shader的传入参数
struct VertexIn
{
	float3 PosL : POSITION;
	float3 NormalL : NORMAL;
};

// 顶点Shader返回值,并传入片元Shader
struct VertexOut
{
	float4 PosH : SV_POSITION;
	float3 PosW : POSITION;
	float3 NormalW : NORMAL;
};

可以看到这边传入了光源的三种形式:直射光(太阳)、点光源、聚光灯。然后顶点数据中传入的法线用于光强度的计算。

 

这边提一下常量从C++代码中传到Shader一些注意事项(顶点数据因为一开始就在InputLayout中申明了数据类型所以一般很少会出问题)。

 

原则是所有数据结构都128bit(float4)对齐。

 

以下是一个容易出错的例子:

struct S

{

    float2 s

};



cbuffer A

{

    float a;

    S b;

    float2 c;

};

 

程序在处理时遵循一个简单的规则:遇到struct、数组换行;遇到类似float3、float4等数组,如果之前的还能放下则放入,否则换行。

 

所以以上的结果是:

a.x   empty empty empty

b.s.x b.s.y c.x   c.y

 

一开始可能感觉有点抽象,结论是写struct时不够128bit时使用空数据将其对齐,数组尽量使用float4数组或者已经对齐的struct数组。

 

光照的计算:

三种光照都遵循同一种思路:光照 = 漫反射 + 高光 + 环境光。(只是点光源和聚光灯有相应的衰减)

 

漫反射Lambert光照模型计算公式如下:

result = max(n·l, 0) * Ld * Md(其中,n为法线;l光方向的矢量;Ld直射光颜色;Md材质颜色)

 

Phong高光的计算公式如下:

result = (max(r·eye, 0))^p * Lc * Mc(其中,r为光线反射方向 r = 2(n·l)n – l;eye为视角方向;p为高光衰减参数;Lc高光颜色;Md材质高光颜色)

 

环境只是单纯对物体颜色的叠加,不多说。

 

以下是计算函数:

// 计算直射光

void ComputeDirectionalLight(Material mat, DirectionalLight L,

    float3 normal, float3 toEye,

    out float4 ambient, out float4 diffuse, out float4 spec)

{

    // 初始化输出

    ambient = float4(0.0f, 0.0f, 0.0f, 0.0f);

    diffuse = float4(0.0f, 0.0f, 0.0f, 0.0f);

    spec = float4(0.0f, 0.0f, 0.0f, 0.0f);



    // 环境光的计算

    ambient = mat.Ambient * L.Ambient;



    float3 lightVec = -L.Direction;    // 获取light的向量

    float diffuseFactor = dot(lightVec, normal); // 漫反射的强度



    [flatten]

    if (diffuseFactor > 0.0f)

    {

        // 漫反射光的计算

        diffuse = diffuseFactor * mat.Diffuse * L.Diffuse;



        // 高光的计算

        float3 v = reflect(-lightVec, normal); // 计算反射角

        float specFactor = pow(max(dot(v, toEye), 0.0f), mat.Specular.w); // 高光的衰减程度

        spec = specFactor * mat.Specular * L.Specular;



        // Blinn的方式

        //float3 h = normalize(toEye + lightVec);

        //specFactor = pow(max(dot(h, normal), 0.0f), mat.Specular.w);

        //spec = specFactor * mat.Specular * L.Specular;

    }

}



// 计算点光源

void ComputePointLight(Material mat, PointLight L,

    float3 pos, float3 normal, float3 toEye,

    out float4 ambient, out float4 diffuse, out float4 spec)

{

    // 初始化输出

    ambient = float4(0.0f, 0.0f, 0.0f, 0.0f);

    diffuse = float4(0.0f, 0.0f, 0.0f, 0.0f);

    spec = float4(0.0f, 0.0f, 0.0f, 0.0f);



    // 获取light的向量

    float3 lightVec = L.Position - pos;

    float d = length(lightVec);

    lightVec /= d;



    // 超过范围直接跳出

    if (d > L.Range)

        return;



    // 环境光的计算

    ambient = mat.Ambient * L.Ambient;



    float diffuseFactor = dot(lightVec, normal); // 漫反射的强度



    [flatten]

    if (diffuseFactor > 0.0f)

    {

        // 漫反射光的计算

        diffuse = diffuseFactor * mat.Diffuse * L.Diffuse;



        // 高光的计算

        float3 v = reflect(-lightVec, normal); // 计算反射角

        float specFactor = pow(max(dot(v, toEye), 0.0f), mat.Specular.w); // 高光的衰减程度

        spec = specFactor * mat.Specular * L.Specular;



        // 衰减

        float att = 1.0f / dot(L.Att, float3(1.0f, d, d*d));



        diffuse *= att;

        spec *= att;

    }

}



// 计算聚光灯

void ComputeSpotLight(Material mat, SpotLight L,

    float3 pos, float3 normal, float3 toEye,

    out float4 ambient, out float4 diffuse, out float4 spec)

{

    // 初始化输出

    ambient = float4(0.0f, 0.0f, 0.0f, 0.0f);

    diffuse = float4(0.0f, 0.0f, 0.0f, 0.0f);

    spec = float4(0.0f, 0.0f, 0.0f, 0.0f);



    // 获取light的向量

    float3 lightVec = L.Position - pos;

    float d = length(lightVec);

    lightVec /= d;



    if (d > L.Range)

        return;



     // 环境光的计算

     ambient = mat.Ambient * L.Ambient;

     float spot = pow(max(dot(-lightVec, L.Direction), 0.0f), L.Spot);

     ambient *= spot;

     

     float diffuseFactor = dot(lightVec, normal);   // 漫反射的强度



     [flatten]

     if (diffuseFactor > 0.0f)

     {

        // 漫反射光的计算

        diffuse = diffuseFactor * mat.Diffuse * L.Diffuse;



        // 高光的计算

        float3 v = reflect(-lightVec, normal); // 计算反射角

        float specFactor = pow(max(dot(v, toEye), 0.0f), mat.Specular.w); // 高光的衰减程度

        spec = specFactor * mat.Specular * L.Specular;



        // 衰减

        float att = spot / dot(L.Att, float3(1.0f, d, d * d));

        diffuse *= att;

        spec *= att;

     }

}

 

这边提醒一点,高光类似于一个椎体,相机在中线处没有衰减,越往外衰减越厉害,脱离椎体后就看不到高光了。实际上聚光灯的原理和高光是类似的。

猜你喜欢

转载自blog.csdn.net/u012632851/article/details/83422932
今日推荐