Directx11学习笔记【十六】 光照模型的实现

本文由zhangbaochong原创,转载请注明出处http://www.cnblogs.com/zhangbaochong/p/5579289.html

  在之前的场景绘制中我们都是给每个顶点指定了单一颜色 ,然后由系统插值计算各个部分颜色,最终显示出来。接下来我们将学习dx11中比较有意思的一部分——光照,通过光照和材质的相互作用来实现更真实的场景。

1. 光照

1.1 光照效果

  简单举个例子,看龙书中的一张图:

  

  a图没加光照看起来像一个2D图形,而加了光照的b图则看起来像一个3D图形。由此可见,光照在3D渲染方面是尤为重要的,通常借助光照可以让场景显得更加真实。

1.2 材质

  材质可以说是决定光如何作用在物体表面的一种属性。例如,光在物体表面反射和吸收的颜色,它的反射率、透明度、光滑程度等属性组成了一个物体表面的材质。在我们下面的示例一般只考虑光的反射和吸收颜色以及光滑程度用来计算全反射,暂不考虑其它因素。

2.法线

2.1面法线(face normal)

  什么是面法线呢?A face normal is a vector that describesthe direction a polygon is facing。

  

  计算面法线也很简单,在面内找到三点,取得两个向量作×积然后单位化即可。

  

2.2 顶点法线(vertex normal)

     在directx中我们需要知道顶点的法线,法线决定了光照射平面的角度。光线会被应用到每个顶点,并且根据面法线和光照方向的点积去调整光线颜色的强度。那么怎么计算顶点法线呢?下面的图介绍的很清楚了:

   

  

2.3 法线变换

     在一个顶点进行空间变换时,法线通常也需要进行变换。但是注意:顶点和法线的变换矩阵并不相同!

    

   对于一个顶点的变换矩阵A,其对应的法线变换时A的逆矩阵的转置。

2.4 朗伯余弦定律

   

     当一个面元的辐射亮度和光亮度在其表面上半球的所有方向相等时,并符合I(θ) = INcosθ时称为朗伯余弦定律。

     I(θ)----面元在θ角(与表面法线夹角)方向及其法线方向的辐射强度

     IN----面元在θ角方向及其法线方向的发光亮度

3. 光照计算处理的三部分

    光照计算处理共有三个部分:环境光(ambient)、漫反射光(diffuse)以及全反射光(又称高光,specular)。

3.1 环境光(ambient)

   在现实当中,光照是一个很复杂的物理现象。一个物体所接受的光,除了直接来自光源的部分外,还包括光源经过环境中其他各个物体的反射而来的部分。而在图形学中,我们默认的光照模型为局部光模型,即一个顶点的光照计算只跟该点信息与光源信息有关,而不考虑环境中其他物体的影响,比如阴影等。

3.2 漫反射光(diffuse)

   光照射在物体表面后,其反射光沿随机方向均匀的分布,即"漫反射”。反射光的强度与光照方向与表面法线的夹角theta相关,满足比例关系:I = Io * cos(theta)。由于反射光方向随机,因此该部分的计算与观察点无关,而只与光线方向与法线相关。

3.3 全反射光(specular)

   光线照射在光滑物体表面后,在特定方向上会有很强的反射,即发生全反射。全反射光主要集中在一个近似圆锥角的范围内。如下图所示:

   

n为法线,l为光线入射方向,r为全反射方向,E为观察点,因此v为视角方向。全反射光进入眼睛的强度与v和r的角度theta有关,随着该角度增大,全反射强度下降,其下降辐度与物体表面光滑程序相关。因此,对于该部分光的计算,除了需要光线方向、法线等信息外,还与观察点的位置有很大关系。具体计算公式在本文后面会详细给出。

4. 光源模型

4.1 平行光

平行光是最简单的一种光照模型,光照方向不变而且光照强度不随空间位置改变,可以用平行光来模拟日常生活中的太阳光。

   c++程序中平行光的定义: 

struct DirectionalLight
{
    DirectionalLight() { ZeroMemory(this, sizeof(this)); }

    XMFLOAT4 Ambient;//环境光
    XMFLOAT4 Diffuse;//漫反射光
    XMFLOAT4 Specular;//高光
    XMFLOAT3 Direction;//光照方向
    float Pad; // Pad the last float so we can set an array of lights if we wanted.用于与HLSL中“4D向量”对齐规则匹配
};

4.2 点光源

   c++程序中点光源的定义:

struct PointLight
{
    PointLight() { ZeroMemory(this, sizeof(this)); }

    XMFLOAT4 Ambient;
    XMFLOAT4 Diffuse;
    XMFLOAT4 Specular;

    // Packed into 4D vector: (Position, Range)
    XMFLOAT3 Position;//光源位置
    float Range;      //光照范围

    // Packed into 4D vector: (A0, A1, A2, Pad)
    XMFLOAT3 Att;     //衰减系数
    float Pad; // Pad the last float so we can set an array of lights if we wanted.
};

4.3 聚光灯

c++程序中聚光灯定义:

struct SpotLight
{
    SpotLight() { ZeroMemory(this, sizeof(this)); }

    XMFLOAT4 Ambient;
    XMFLOAT4 Diffuse;
    XMFLOAT4 Specular;

    // Packed into 4D vector: (Position, Range)
    XMFLOAT3 Position;//光照位置
    float Range;      //光照范围

    // Packed into 4D vector: (Direction, Spot)
    XMFLOAT3 Direction;//光照方向
    float Spot;        //光照强度系数   

    // Packed into 4D vector: (Att, Pad)
    XMFLOAT3 Att;      //衰减系数
    float Pad; // Pad the last float so we can set an array of lights if we wanted.
};

4.4 HLSL中三种光源的定义

 1 struct DirectionalLight
 2 {
 3     float4 Ambient;
 4     float4 Diffuse;
 5     float4 Specular;
 6     float3 Direction;
 7     float pad;
 8 };
 9
10 struct PointLight
11 {
12     float4 Ambient;
13     float4 Diffuse;
14     float4 Specular;
15
16     float3 Position;
17     float Range;
18
19     float3 Att;
20     float pad;
21 };
22
23 struct SpotLight
24 {
25     float4 Ambient;
26     float4 Diffuse;
27     float4 Specular;
28
29     float3 Position;
30     float Range;
31
32     float3 Direction;
33     float Spot;
34
35     float3 Att;
36     float pad;
37 };

4.5 材质

    材质同样有环境光、漫反射光和高光三种成分,此外还有一个材质的镜面反射系数即表示光滑程度。

c++程序中定义:

struct Material
{
    Material() { ZeroMemory(this, sizeof(this)); }

    XMFLOAT4 Ambient;
    XMFLOAT4 Diffuse;
    XMFLOAT4 Specular;//w表示高光强度
    XMFLOAT4 Reflect;
};

HLSL定义:

struct Material
{
    float4 Ambient;
    float4 Diffuse;
    float4 Specular;
    float4 Reflect;
};

5. 光照计算

  光照计算无疑是最重要的部分,这一部分在HLSL中实现。

5.1 平行光

void ComputeDirectionalLight(Material mat,        //材质
                            DirectionalLight L,    //平行光
                            float3 normal,        //顶点法线
                            float3 toEye,        //顶点到眼睛的向量
                            out float4 ambient,    //计算结果:环境光
                            out float4 diffuse,    //计算结果:漫反射光
                            out float4 spec)    //计算结果:高光
{
    // 结果初始化为0
    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);

    // 光线方向
    float3 lightVec = -L.Direction;

    // 环境光直接计算
    ambient = mat.Ambient * L.Ambient;

    // 计算漫反射系数
    //光线、法线方向归一化

    float diffuseFactor = dot(lightVec, normal);

    // 顶点背向光源不再计算
    [flatten]
    if (diffuseFactor > 0.0f)
    {

        float3 v = reflect(-lightVec, normal);
        float specFactor = pow(max(dot(v, toEye), 0.0f), mat.Specular.w);
        //计算漫反射光
        diffuse = diffuseFactor * mat.Diffuse * L.Diffuse;
        //计算高光
        spec = specFactor * mat.Specular * L.Specular;
    }
}

5.2 点光源

 1 void ComputePointLight(Material mat,        //材质
 2                         PointLight L,        //点光源
 3                         float3 pos,            //顶点位置
 4                         float3 normal,        //顶点法线
 5                         float3 toEye,        //顶点到眼睛的向量
 6                         out float4 ambient, //计算结果:环境光
 7                         out float4 diffuse, //计算结果:漫反射光
 8                         out float4 spec)    //计算结果:高光
 9 {
10     ambient = float4(0.0f, 0.0f, 0.0f, 0.0f);
11     diffuse = float4(0.0f, 0.0f, 0.0f, 0.0f);
12     spec = float4(0.0f, 0.0f, 0.0f, 0.0f);
13
14     //光照方向:顶点到光源
15     float3 lightVec = L.Position - pos;
16
17     //顶点到光源距离
18     float d = length(lightVec);
19
20     //超过范围不再计算
21     if (d > L.Range)
22         return;
23
24     //归一化光照方向
25     lightVec /= d;
26
27     //计算环境光
28     ambient = mat.Ambient * L.Ambient;
29
30     //漫反射系数
31     float diffuseFactor = dot(lightVec, normal);
32
33     [flatten]
34     if (diffuseFactor > 0.0f)
35     {
36         float3 v = reflect(-lightVec, normal);
37         float specFactor = pow(max(dot(v, toEye), 0.0f), mat.Specular.w);
38         //计算漫反射光
39         diffuse = diffuseFactor * mat.Diffuse * L.Diffuse;
40         //计算高光
41         spec = specFactor * mat.Specular * L.Specular;
42     }
43
44     // 计算衰减
45     float att = 1.0f / dot(L.Att, float3(1.0f, d, d*d));
46
47     diffuse *= att;
48     spec *= att;
49 }

5.3 聚光灯

 1 void ComputeSpotLight(Material mat,            //材质
 2                         SpotLight L,        //聚光灯
 3                         float3 pos,            //顶点位置
 4                         float3 normal,        //顶点法线
 5                         float3 toEye,        //顶点到眼睛向量
 6                         out float4 ambient, //计算结果:环境光
 7                         out float4 diffuse, //计算结果:漫反射光
 8                         out float4 spec)    //计算结果:高光
 9 {
10     //初始化结果
11     ambient = float4(0.0f, 0.0f, 0.0f, 0.0f);
12     diffuse = float4(0.0f, 0.0f, 0.0f, 0.0f);
13     spec = float4(0.0f, 0.0f, 0.0f, 0.0f);
14
15     //光照方向:顶点到光源
16     float3 lightVec = L.Position - pos;
17
18     //顶点到光源距离
19     float d = length(lightVec);
20
21     //距离大于光照方向不再计算
22     if (d > L.Range)
23         return;
24
25     //归一化光照方向
26     lightVec /= d;
27
28     //计算环境光
29     ambient = mat.Ambient * L.Ambient;
30
31
32     //计算漫反射系数
33     float diffuseFactor = dot(lightVec, normal);
34
35     [flatten]
36     if (diffuseFactor > 0.0f)
37     {
38         float3 v = reflect(-lightVec, normal);
39         float specFactor = pow(max(dot(v, toEye), 0.0f), mat.Specular.w);
40         //漫反射光
41         diffuse = diffuseFactor * mat.Diffuse * L.Diffuse;
42         //高光
43         spec = specFactor * mat.Specular * L.Specular;
44     }
45
46     //聚光衰减系数
47     float spot = pow(max(dot(-lightVec, L.Direction), 0.0f), L.Spot);
48
49     //衰减系数
50     float att = spot / dot(L.Att, float3(1.0f, d, d*d));
51
52     ambient *= spot;
53     diffuse *= att;
54     spec *= att;
55 }

6.程序中使用的shader文件

#include "LightHelper.fx"

cbuffer cbPerFrame
{
    DirectionalLight gDirLight;
    PointLight gPointLight;
    SpotLight gSpotLight;
    float3 gEyePosW;            //观察点
};

cbuffer cbPerObject
{
    float4x4 gWorld;
    float4x4 gWorldInvTranspose;//世界矩阵的逆矩阵的转置
    float4x4 gWorldViewProj;
    Material gMaterial;
};

struct VertexIn
{
    float3 PosL    : POSITION;    //顶点坐标
    float3 NormalL : NORMAL;    //顶点法线
};

struct VertexOut
{
    float4 PosH    : SV_POSITION;    //投影后的坐标
    float3 PosW    : POSITION;        //世界变换后的坐标
    float3 NormalW : NORMAL;        //世界变换后的顶点法线
};

VertexOut VS(VertexIn vin)
{
    VertexOut vout;

    vout.PosW = mul(float4(vin.PosL, 1.0f), gWorld).xyz;
    vout.NormalW = mul(vin.NormalL, (float3x3)gWorldInvTranspose);

    vout.PosH = mul(float4(vin.PosL, 1.0f), gWorldViewProj);

    return vout;
}

float4 PS(VertexOut pin) : SV_Target
{
    //插值运算有可能使法线不再单位化,重新单位化法线
    pin.NormalW = normalize(pin.NormalW);

    //顶点到观察点向量,归一化
    float3 toEyeW = normalize(gEyePosW - pin.PosW);

    //初始化颜色值全部为0
    float4 ambient = float4(0.0f, 0.0f, 0.0f, 0.0f);
    float4 diffuse = float4(0.0f, 0.0f, 0.0f, 0.0f);
    float4 spec = float4(0.0f, 0.0f, 0.0f, 0.0f);

    //每个光源计算后得到的环境光、漫反射光、高光
    float4 A, D, S;

    //每个光源计算后将ADS更新到最终结果中
    ComputeDirectionalLight(gMaterial, gDirLight, pin.NormalW, toEyeW, A, D, S);
    ambient += A;
    diffuse += D;
    spec += S;

    ComputePointLight(gMaterial, gPointLight, pin.PosW, pin.NormalW, toEyeW, A, D, S);
    ambient += A;
    diffuse += D;
    spec += S;

    ComputeSpotLight(gMaterial, gSpotLight, pin.PosW, pin.NormalW, toEyeW, A, D, S);
    ambient += A;
    diffuse += D;
    spec += S;

    float4 litColor = ambient + diffuse + spec;

    //最终颜色透明度使用漫反射光的
    litColor.a = gMaterial.diffuse.a;

    return litColor;
}

technique11 LightTech
{
    pass P0
    {
        SetVertexShader(CompileShader(vs_5_0, VS()));
        SetGeometryShader(NULL);
        SetPixelShader(CompileShader(ps_5_0, PS()));
    }
}

7.程序运行结果

由于代码比较多,这里就不给出了,有兴趣的可以下载看看注释也比较详细

源码下载:http://files.cnblogs.com/files/zhangbaochong/LightDemo.zip

时间: 2024-10-12 20:27:40

Directx11学习笔记【十六】 光照模型的实现的相关文章

C++学习笔记十六-模板和泛型编程(二)

C++学习笔记十六-模板和泛型编程(二) 16.4 类模板成员 1.模板作用域中模板类型的引用: 通常,当使用类模板的名字的时候,必须指定模板形参.这一规则有个例外:在类本身的作用域内部,可以使用类模板的非限定名.例如,在默认构造函数和复制构造函数的声明中,名字 Queue 是 Queue<Type> 缩写表示.实质上,编译器推断,当我们引用类的名字时,引用的是同一版本.因此,复制构造函数定义其实等价于: Queue<Type>(const Queue<Type> &a

MYSQL进阶学习笔记十六:MySQL 监控!(视频序号:进阶_35)

知识点十六:MySQL监控(35) 一.为什么使用MySQL监控 随着软件后期的不断升级,myssql的服务器数量越来越多,软硬件故障的发生概率也越来越高.这个时候就需要一套监控系统,当主机发生异常时,此时通过监控系统发现和处理. 这个监控实际上是在我们的开发完成之后,这个时候软件就开始在运行,这个运行我们就需要去关注到mysql服务器是否正常,那么我们要观察它就需要给它提供一些监控,这监控就是当它发生故障之后, 那么我们这个监控就会告诉我们到底什么地方发生了一些异常或者一些错误,这个时候我们就

python学习笔记十六 django深入学习一

django 请求流程图 django 路由系统 在django中我们可以通过定义urls,让不同的url路由到不同的处理函数 from . import views urlpatterns = [ url(r'^articles/2003/$', views.special_case_2003), #精确匹配 url(r'^articles/([0-9]{4})/$', views.year_archive), #动态路由 url(r'^articles/([0-9]{4})/([0-9]{2

spring in action学习笔记十六:配置数据源的几种方式

第一种方式:JNDI的方式. 用xml配置的方式的代码如下: 1 <jee:jndi-lookup jndi-name="/jdbc/spittrDS" resource-ref="true" id="dataSource"/> 用注解方式的代码如下: 1 @Bean 2 public JndiObjectFactoryBean jndiObjectFactoryBean(){ 3 JndiObjectFactoryBean jndi

Directx11学习笔记【六】 基本的数学知识----矩阵篇

参考dx11龙书 Chapter2 matrix algebra(矩阵代数) 关于矩阵的一些基本概念定理(例如矩阵加减乘法,逆矩阵,伴随矩阵,转置矩阵等)可以参考维基百科 https://zh.wikipedia.org/wiki/ XNA MATRICES Matrix Types 在xna math中代表一个4*4的矩阵,我们使用XMMATRIX 当在类中存储数据时使用XMFLOAT4X4 用XMMatrixSet创建,原型为 XMMATRIX XMMatrixSet(FLOAT m00,F

yii2源码学习笔记(十六)

Module类的最后代码 1 /** 2 * Registers sub-modules in the current module. 3 * 注册子模块到当前模块 4 * Each sub-module should be specified as a name-value pair, where 5 * name refers to the ID of the module and value the module or a configuration 6 * array that can

[傅里叶变换及其应用学习笔记] 十六. 继续上次内容,晶体成像

x射线晶体照像术 1) x射线是1895年由伦琴(Roentgen)发现的,其波长为$10^{-8}$厘米左右,常用的测量可见光波长的方法会由于其波长太小而无法测量. 2) 晶体(Crystals),晶体的原子结构符合一定规律——原子有序地排列成晶格.劳厄(Laue)在1912年做了一系列著名实验,其目的是利用x射线进行衍射实验来研究晶体的本质. 劳尔假设: 1) x射线是波,因此可以进行衍射 2) 晶体可以充当合适的衍射光栅,即晶体具有晶格原子(lattice atomic)——周期性的原子结

JavaScript权威设计--CSS(简要学习笔记十六)

1.Document的一些特殊属性 document.lastModified document.URL document.title document.referrer document.domain document.write() document.writeIn() 2.查询选取的文本 使用鼠标mouseup事件 3.浏览器定义了多项文本编辑命令(富文本编辑器) 使用Document对象的execCommand()方法. document.queryCommandSupport()判断浏

Android学习笔记十六.Android数据存储与IO.SharedPreferences

SharedPreferences 对于应用程序的数据输入.输出,如果是应用程序只是少量数据需要保存,那么使用普通文件就可以了(SharedPrefereces);但如果应用程序有大量数据需要存储.访问,就需要借助数据库了.Android系统内置了SQLite数据库,SQLite数据库是一个真正轻量级的数据库,它没有后台进程,整个数据库就对应于一个文件. 1.SharedPreferences简介 (1)概念:SharedPreferences保存的数据主要是类似于配置信息格式的数据,因此它保存

PHP学习笔记十六【方法】

<?php //给一个函数传递基本数据类型 $a=90; $b=90.8; $c=true; $d="hello world"; function test1($a,$b,$c,$d) //test1(&$a,&$b,&$c,&$d)//如果是传地址那么值就会改变 { $a=78; $b=89.5; $c=false; $d="beijing"; } //调用函数 test1($a,$b,$c,$d); echo $a.&quo