环境光
添加LightManagerLightManager类,此类的主要功能为管理光照的相关参数。首先给出此类的声明,具体代码如下:
#ifndef VULKANEXBASE_LIGHTMANAGER_H
#define VULKANEXBASE_LIGHTMANAGER_H //防止重复引用
class LightManager
{
public:
static float lightAmbientR, lightAmbientG, lightAmbientB, lightAmbientA;//环境光强度RGBA分量
static void setlightAmbient(float lightAmbientRIn, float lightAmbientGIn, float lightAmbientBIn, float lightAmbientAIn);//设置环境光强度的方法
};
#endif
LightManager.cpp
#include "LightManager.h"
#include <math.h>
//,首先初始化了用于存储环境光强度RGBA分量的成员变量值,然后实现了设置环境光强度的setlightAmbient方法。在setlightAmbient方法中接收环境光强度RGBA分量的值,并记录到对应的成员变量中;
float LightManager::lightAmbientR = 0;//初始化环境光强度R分量
float LightManager::lightAmbientG = 0;//初始化环境光强度G分量
float LightManager::lightAmbientB = 0;//初始化环境光强度B分量
float LightManager::lightAmbientA = 0;//初始化环境光强度A分量
void LightManager::setlightAmbient(float lightAmbientRIn, float lightAmbientGIn, float lightAmbientBIn, float lightAmbientAIn) {//设置环境光强度的方法
lightAmbientR = lightAmbientRIn;//设置环境光强度R分量
lightAmbientG = lightAmbientGIn;//设置环境光强度G分量
lightAmbientB = lightAmbientBIn;//设置环境光强度B分量
lightAmbientA = lightAmbientAIn;//设置环境光强度A分量
}
接下来介绍的是MyVulkanManager类的initMatrixAndLight方法,主要变化是在其中添加了调用设置环境光强度方法的代码,具体内容如下。收环境光强度RGBA分量的值,并记录到对应的成员变量中
在void MyVulkanManager::initMatrixAndLight()中添加
void MyVulkanManager::initMatrixAndLight()
{
MatrixState3D::setCamera(0, 0, 30.0f, 0, 0, 0, 0, 1, 0);
MatrixState3D::setInitStack();//初始化基本变换矩阵
float ratio = (float)screenWidth / (float)screenHeight;//求屏幕长宽比
MatrixState3D::setProjectFrustum(-ratio, ratio, -1, 1, 20.0f, 1000);
//添加
LightManager::setlightAmbient(0.2f, 0.2f, 0.2f, 0.2f);//设置环境光强度
}
行调用了LightManager类的setlightAmbient方法设置环境光强度RGBA的4个分量的值都为0.2。由于每个分量的取值范围都是0~1,0表示最暗、1表示最亮,故0.2是一个表示比较暗光照强度的取值。
接下来在void MyVulkanManager::flushUniformBuffer()中
void MyVulkanManager::flushUniformBuffer()//将当前帧相关数据送入一致变量缓冲
{
//此方法的主要工作是首先将环境光强度RGBA的4个分量值存放进一个内存数组中
float vertexUniformData[4] =
{
LightManager::lightAmbientR,LightManager::lightAmbientG,
LightManager::lightAmbientB,LightManager::lightAmbientA,//环境光强度RGBA分量值
};
//然后将一致变量缓冲对应的设备内存映射为CPU可访问
uint8_t *pData;//CPU访问时的辅助指针
//接着将环境光强度RGBA的4个分量数据送入了设备内存,最后解除了内存映射。
VkResult result = vkMapMemory(device, sqsCL->memUniformBuf, 0, sqsCL->bufferByteCount, 0, (void **)&pData);//将设备内存映射为CPU可访问
assert(result == VK_SUCCESS);
memcpy(pData, vertexUniformData, sqsCL->bufferByteCount);//将最终矩阵数据拷贝进显存
vkUnmapMemory(device, sqsCL->memUniformBuf); //解除内存映射
//这样在绘制时,着色器就可以访问经一致变量缓冲送入管线的环境光强度RGBA的4个分量值
}
了解了如何将环境光强度RGBA分量值送入一致变量缓冲后,下面给出的是ShaderQueue Suit_Common类的create_uniform_buffer的方法,其中设置了一致变量缓冲的总字节数,具体代码如下。
//创建Unifirm Buffer
void ShaderQueueSuit_Common::create_uniform_buffer(VkDevice& device, VkPhysicalDeviceMemoryProperties& memoryroperties)
{
//此处设置的一致变量缓冲总字节数应当与步骤(4)中vertexUniformData数组的数据总字节数一致,。Vulkan中一个变化涉及的代码位置较多.
bufferByteCount = sizeof(float) * 4;
VkBufferCreateInfo buf_info = {};//构建一致变量缓冲创建信息结构体实例
buf_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; //结构体的类型
buf_info.pNext = NULL;//自定义数据的指针
.......
}
void MyVulkanManager::drawObject()
//的MatrixState3D类的相关方法执行了一系列坐标变换(主要包括保护现场、平移、恢复现场等),绘制了场景中所需的左右两个球体。
void MyVulkanManager::drawObject()
{
FPSUtil::init();//初始化FPS计算
while (MyVulkanManager::loopDrawFlag)//每循环一次绘制一帧画面
{
vkCmdBeginRenderPass(cmdBuffer, &rp_begin, VK_SUBPASS_CONTENTS_INLINE);//启动渲染通道
MatrixState3D::pushMatrix();
MatrixState3D::translate(-1.5f, 0, -15);//执行平移
ballForDraw->drawSelf(cmdBuffer, sqsCL->pipelineLayout, sqsCL->pipeline, &(sqsCL->descSet[0])); /
MatrixState3D::popMatrix();
MatrixState3D::pushMatrix();//保护现场
//画两个球
MatrixState3D::translate(1.5f, 0, -15);//执行平移
ballForDraw->drawSelf(cmdBuffer, sqsCL->pipelineLayout, sqsCL->pipeline, &(sqsCL->descSet[0]));//绘制右侧的球
MatrixState3D::popMatrix();//恢复现场
}
}
着色器
commonTexLight.vert
#version 400
#extension GL_ARB_separate_shader_objects : enable//开启separate_shader_objects
#extension GL_ARB_shading_language_420pack : enable //开启shading_language_420pack
//增加了计算环境光强度和将环境光强度传递给片元着色器的代码
layout (std140,set = 0, binding = 0) uniform bufferVals
{//一致块
vec4 lightAmbient; //环境光强度
} myBufferVals;
layout (push_constant) uniform constantVals {//一致块
mat4 mvp;//最终变换矩阵
mat4 mm;//基本变换矩阵
} myConstantVals;
layout (location = 0) in vec3 pos;//传入的顶点位置
layout (location = 0) out vec4 outLightQD;//输出的光照强度
layout (location = 1) out vec3 vposition;//输出的顶点位置
out gl_PerVertex
{//输出接口块
vec4 gl_Position;//顶点最终位置
};
void main()
{//主函数
gl_Position = myConstantVals.mvp * vec4(pos,1.0);//计算顶点最终位置
//由于环境光强度实际不需要进行计算,故直接进行了赋值
outLightQD=myBufferVals.lightAmbient;//将顶点最终光照强度传递给片元着色器
vposition=pos;//把顶点位置传给片元着色器
}
介绍片元着色器䆀䋪
commonTexLight.frag
#version 400
#extension GL_ARB_separate_shader_objects : enable//开启separate_shader_objects
#extension GL_ARB_shading_language_420pack : enable//开启shading_language_420pack
layout (location = 0) in vec4 inLightQD;//顶点着色器传入的光照强度
layout (location = 1) in vec3 vposition;//顶点着色器传入的顶点位置
layout (location = 0) out vec4 outColor;//输出到渲染管线的片元颜色值
vec4 genBoardColor(vec3 position)
{
const float R=1.0;//球的半径
vec4 color;//结果颜色
float n = 8.0;//球体外接立方体每个坐标轴方向切分的份数
float span = 2.0*R/n;//每一份的尺寸
int i = int((position.x + 1.0)/span);//当前片元位置小方块的行数
int j = int((position.y + 1.0)/span);//当前片元位置小方块的层数
int k = int((position.z + 1.0)/span);//当前片元位置小方块的列数
int whichColor = int(mod(float(i+j+k),2.0));//计算行数、层数、列数的和并对2取模
if(whichColor == 1)
{ //奇数时为颜色A
color = vec4(0.678,0.231,0.129,1.0);//红色
}
else
{//偶数时为白色
color = vec4(1.0,1.0,1.0,1.0);//白色
}
return color;//返回结果颜色
}
void main()
{//主方法
//主要是增加了接收光照强度以及使用光照强度与片元本身颜色值加权计算以产生最终片元颜色值的相关代码
outColor=inLightQD*genBoardColor(vposition);//计算最终片元颜色
}
散射光
仅仅使用环境光的案例,视觉效果应该不是很好,没有层次感和立体感。本节将介绍另外一种真实感好很多的光照通道——散射光(Diffuse),其指的是从特定角度入射经物体表面向四周(全方位360º)均匀反射的光
散射光代表的是现实世界中粗糙的物体表面被光照射时,反射光在各个方向基本均匀
虽然反射后的散射光在各个方向上是均匀的,但散射光反射的强度与入射光的强度以及入射的角度密切相关。因此当光源的位置发生变化时,散射光的效果会发生明显变化。主要体现为当光垂直地照射到物体表面时比斜照时要亮,其具体计算公式如下。
散射光照射结果=材质的反射系数×散射光强度×max(cos(入射角),0)
实际开发中往往分两步进行计算,此时上述公式被拆解为如下情况。
散射光最终强度=散射光强度×max(cos(入射角),0)
散射光照射结果=材质的反射系数×散射光最终强度
LightManager.h
#ifndef VULKANEXBASE_LIGHTMANAGER_H
#define VULKANEXBASE_LIGHTMANAGER_H //防止重复引用
class LightManager {
public:
static float lx, ly, lz; //光源位置
static float lightDiffuseR, lightDiffuseG, lightDiffuseB, lightDiffuseA;//散射光强度RGBA分量
static void setLightPosition(float lxIn, float lyIn, float lzIn);//设置光源位置的方法
static void setlightDiffuse(float lightDiffuseRIn, float lightDiffuseGIn, float lightDiffuseBIn, float lightDiffuseAIn);//设置散射光强度RGBA分量
};
#endif
LightManager.cpp
#include "LightManager.h"
#include <math.h>
float LightManager::lx = 0;//初始化光源位置X
float LightManager::ly = 0;//初始化光源位置Y
float LightManager::lz = 0;//初始化光源位置Z
float LightManager::lightDiffuseR = 0;//初始化散射光强度R分量
float LightManager::lightDiffuseG = 0;//初始化散射光强度G分量
float LightManager::lightDiffuseB = 0;//初始化散射光强度B分量
float LightManager::lightDiffuseA = 0;//初始化散射光强度A分量
void LightManager::setLightPosition(float lxIn, float lyIn, float lzIn)
{ //设置光源位置的方法
lx = lxIn;//设置光源位置X坐标
ly = lyIn;//设置光源位置Y坐标
lz = lzIn;//设置光源位置Z坐标
}
void LightManager::setlightDiffuse(float lightDiffuseRIn, float lightDiffuseGIn, float lightDiffuseBIn, float lightDiffuseAIn)
{//设置散射光强度的方法
lightDiffuseR = lightDiffuseRIn;//设置散射光强度R分量
lightDiffuseG = lightDiffuseGIn;//设置散射光强度G分量
lightDiffuseB = lightDiffuseBIn;//设置散射光强度B分量
lightDiffuseA = lightDiffuseAIn;//设置散射光强度A分量
}
主要是将设置环境光强度的方法替换为设置散射光强度的方法,同时增加了设置光源位置的方法。
commonTextLight.vert
根据前面介绍的公式计算散射光最终强度的pointLight方法。最需要注意的一点是:由于本案例中的散射光光照计算基于世界坐标系进行,故在进行散射光光照计算前要将物体坐标系中的顶点法向量变换为世界坐标系中的顶点法向量
#version 400
#extension GL_ARB_separate_shader_objects : enable//开启separate_shader_objects
#extension GL_ARB_shading_language_420pack : enable //开启shading_language_420pack
layout (std140,set = 0, binding = 0) uniform bufferVals
{//一致块
//增加了光源位置一致变量lightPosition
vec4 lightPosition; //光源位置
//散射光强度一致变量lightDiffuse
vec4 lightDiffuse; //散射光强度
} myBufferVals;
layout (push_constant) uniform constantVals
{//一致块
mat4 mvp;//最终变换矩阵
mat4 mm;//基本变换矩阵
} myConstantVals;
layout (location = 0) in vec3 pos;//传入的顶点位置
//增加了传入的顶点法向量inNormal
layout (location = 1) in vec3 inNormal; //传入的顶点法向量
layout (location = 0) out vec4 outLightQD;//输出的光照强度
layout (location = 1) out vec3 vposition;//输出的顶点位置
out gl_PerVertex {//输出接口块
vec4 gl_Position;//顶点最终位置
};
//是增加了调用pointLight方法计算散射光最终强度的相关代码
vec4 pointLight( //定位光光照计算的方法
in mat4 uMMatrix, //基本变换矩阵
in vec3 lightLocation, //光源位置
in vec4 lightDiffuse, //散射光强度
in vec3 normal, //法向量
in vec3 aPosition
)//顶点位置
{
vec4 diffuse; //散射光最终强度
vec3 normalTarget=aPosition+normal; //计算世界坐标系中的法向量
vec3 newNormal=(uMMatrix*vec4(normalTarget,1)).xyz-(uMMatrix*vec4(aPosition,1)).xyz;
newNormal=normalize(newNormal); //对法向量规格化
vec3 vp= normalize(lightLocation-(uMMatrix*vec4(aPosition,1)).xyz);//计算表面点到光源位置的向量vp
vp=normalize(vp); //格式化vp 向量
float nDotViewPosition=max(0.0,dot(newNormal,vp)); //求法向量与vp 的点积与0 的最大值
diffuse=lightDiffuse*nDotViewPosition; //计算散射光的最终强度
return diffuse; //返回散射光最终强度
}
void main(){//主函数
outLightQD=pointLight( //将散射光最终强度传递给片元着色器
myConstantVals.mm, //基本变换矩阵
myBufferVals.lightPosition.xyz, //光源位置
myBufferVals.lightDiffuse, //散射光强度
inNormal, //法向量
pos //顶点位置
);
gl_Position = myConstantVals.mvp * vec4(pos,1.0);//计算顶点最终位置
vposition=pos;//把顶点位置传给片元着色器
}
commonTextLight.frag
#version 400
#extension GL_ARB_separate_shader_objects : enable//开启separate_shader_objects
#extension GL_ARB_shading_language_420pack : enable//开启shading_language_420pack
layout (location = 0) in vec4 inLightQD;//顶点着色器传入的光照强度
layout (location = 1) in vec3 vposition;//顶点着色器传入的顶点位置
layout (location = 0) out vec4 outColor;//输出到渲染管线的片元颜色值
vec4 genBoardColor(vec3 position)
{
const float R=1.0;//球的半径
vec4 color;//结果颜色
float n = 8.0;//球体外接立方体每个坐标轴方向切分的份数
float span = 2.0*R/n;//每一份的尺寸
int i = int((position.x + 1.0)/span);//当前片元位置小方块的行数
int j = int((position.y + 1.0)/span);//当前片元位置小方块的层数
int k = int((position.z + 1.0)/span);//当前片元位置小方块的列数
int whichColor = int(mod(float(i+j+k),2.0));//计算行数、层数、列数的和并对2取模
if(whichColor == 1){ //奇数时为颜色A
color = vec4(0.678,0.231,0.129,1.0);//红色
}
else {//偶数时为白色
color = vec4(1.0,1.0,1.0,1.0);//白色
}
return color;//返回结果颜色
}
void main(){//主方法
outColor=inLightQD*genBoardColor(vposition);//计算最终片元颜色
}
镜面光
当光滑表面被照射时会有方向很集中的反射光。这就是镜面光(Specular)
镜面光照射结果=材质的反射系数×镜面光强度×max(0,(cos(半向量与法向量的夹角))^粗糙度)
镜面光最终强度=镜面光强度×max(0,(cos(半向量与法向量的夹角)) 粗糙度)
镜面光照射结果=材质的反射系数×镜面光最终强度
首先需要对工具类LightManager进行进一步升级,将原来散射光相关的成员变量和设置方法修改为存储镜面光强度的成员变量以及设置镜面光强度的方法
LightManager.h
#ifndef VULKANEXBASE_LIGHTMANAGER_H
#define VULKANEXBASE_LIGHTMANAGER_H //防止重复引用
class LightManager {
public:
static float lx, ly, lz; //光源位置
static float lightSpecularR, lightSpecularG, lightSpecularB, lightSpecularA; //镜面光强度RGBA 分量
static void setLightPosition(float lxIn, float lyIn, float lzIn);//设置光源位置的方法
static void setlightSpecular(float lightSpecularRIn, float //设置镜面光强度的方法
lightSpecularGIn, float lightSpecularBIn, float lightSpecularAIn);
};
#endif
上述代码很简单,只是将原来散射光的相关内容替换为镜面光的相关内容
LightManager.cpp
#include "LightManager.h"
#include <math.h>
float LightManager::lx = 0;//初始化光源位置X
float LightManager::ly = 0;//初始化光源位置Y
float LightManager::lz = 0;//初始化光源位置Z
float LightManager::lightSpecularR = 0; //初始化镜面光强度R 分量
float LightManager::lightSpecularG = 0; //初始化镜面光强度G 分量
float LightManager::lightSpecularB = 0; //初始化镜面光强度B 分量
float LightManager::lightSpecularA = 0; //初始化镜面光强度A 分量
//设置光源的位置
void LightManager::setLightPosition(float lxIn, float lyIn, float lzIn)
{
lx = lxIn;//设置光源位置X坐标
ly = lyIn;//设置光源位置Y坐标
lz = lzIn;//设置光源位置Z坐标
}
void LightManager::setlightSpecular(float lightSpecularRIn, float lightSpecularGIn, float lightSpecularBIn, float lightSpecularAIn) { //设置镜面光强度的方法
lightSpecularR = lightSpecularRIn; //设置镜面光强度R 分量
lightSpecularG = lightSpecularGIn; //设置镜面光强度G 分量
lightSpecularB = lightSpecularBIn; //设置镜面光强度B 分量
lightSpecularA = lightSpecularAIn; //设置镜面光强度A 分量
}
码相比于散射光案例主要是将设置散射光强度的方法更改为设置镜面光强度的方法,同时更改了方法中设置的成员变量
//的initMatrixAndLight方法中主要是将调用设置散射光强度方法的代码更改为调用设置镜面光强度方法的代码
void MyVulkanManager::initMatrixAndLight()
{
MatrixState3D::setCamera(0, 0, 30.0f, 0, 0, 0, 0, 1, 0);
MatrixState3D::setInitStack();//初始化基本变换矩阵
float ratio = (float)screenWidth / (float)screenHeight;//求屏幕长宽比
MatrixState3D::setProjectFrustum(-ratio, ratio, -1, 1, 20.0f, 1000);
LightManager::setLightPosition(0, 0, -13);//设置光源位置
LightManager::setlightSpecular(0.7f, 0.7f, 0.7f, 0.7f); //设置镜面光强度
}
//的flushUniformBuffer方法中主要是修改了一致变量缓冲数据数组的长度,并将摄像机位置坐标xyz分量、光源位置坐标xyz分量、镜面光强度RGBA分量依次存入了一致变量缓冲数据数组。每次绘制一帧新的画面前,程序都会调用flushUniformBuffer方法刷新相关数据。因此当用户滑动手指改变光源位置时,绘制的画面就会随光源位置的变化而变化了
void MyVulkanManager::flushUniformBuffer()//将当前帧相关数据送入一致变量缓冲
{
float vertexUniformData[12] =
{
MatrixState3D::cx,MatrixState3D::cy,MatrixState3D::cz,1.0,//摄像机位置XYZ 分量值
LightManager::lx,LightManager::ly,LightManager::lz,1.0,//光源位置XYZ
LightManager::lightSpecularR,LightManager::lightSpecularG,LightManager::lightSpecularB,LightManager::lightSpecularA //镜面光强度RGBA 分量值
};
uint8_t *pData;//CPU访问时的辅助指针
VkResult result = vkMapMemory(device, sqsCL->memUniformBuf, 0, sqsCL->bufferByteCount, 0, (void **)&pData);//将设备内存映射为CPU可访问
assert(result == VK_SUCCESS);
memcpy(pData, vertexUniformData, sqsCL->bufferByteCount);//将最终矩阵数据拷贝进显存
vkUnmapMemory(device, sqsCL->memUniformBuf); //解除内存映射
}
摄像机位置坐标xyz分量和光源位置坐标xyz分量数据后面都补了一个1.0。原因与前面散射光案例中的相同,都是为了使总的数据量是16字节的整数倍。若总的数据量不是16字节的整数倍,可能程序不能正常运行。新的画面前,程序都会调用flushUniformBuffer方法刷新相关数据。因此当用户滑动手指改变光源位置时,绘制的画面就会随光源位置的变化而变化了
//创建Unifirm Buffer
void ShaderQueueSuit_Common::create_uniform_buffer(VkDevice& device, VkPhysicalDeviceMemoryProperties& memoryroperties)
{
bufferByteCount = sizeof(float) * 12; //改为12
VkBufferCreateInfo buf_info = {};//构建一致变量缓冲创建信息结构体实例
buf_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; //结构体的类型
buf_info.pNext = NULL;//自定义数据的指针
buf_info.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT;//缓冲的用途
buf_info.size = bufferByteCount;//缓冲总字节数
buf_info.queueFamilyIndexCount = 0; //队列家族数量
buf_info.pQueueFamilyIndices = NULL;//队列家族索引列表
buf_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;//共享模式
buf_info.flags = 0;//标志
.......
}
commonTexLight.vert
//的主要变化是改动了一致块中的内容,增加了摄像机位置,将前面案例中的散射光强度替换为镜面光强度
#version 400
#extension GL_ARB_separate_shader_objects : enable//开启separate_shader_objects
#extension GL_ARB_shading_language_420pack : enable //开启shading_language_420pack
layout (std140,set = 0, binding = 0) uniform bufferVals {//一致块
vec4 uCamera; //摄像机位置
vec4 lightPosition; //光源位置
vec4 lightSpecular; //镜面光强度
} myBufferVals;
layout (push_constant) uniform constantVals {//一致块
mat4 mvp;//最终变换矩阵
mat4 mm;//基本变换矩阵
} myConstantVals;
layout (location = 0) in vec3 pos;//传入的顶点位置
layout (location = 1) in vec3 inNormal; //传入的顶点法向量
layout (location = 0) out vec4 outLightQD;//输出的光照强度
layout (location = 1) out vec3 vposition;//输出的顶点位置
out gl_PerVertex {//输出接口块
vec4 gl_Position;//顶点最终位置
};
//为根据前面介绍的公式计算镜面光最终强度的pointLight方法。最需要注意的一点是:由于本案例中的镜面光光照计算基于世界坐标系进行,故在进行镜面光光照计算前要将物体坐标系中的顶点法向量变换为世界坐标系中的顶点法向量。
vec4 pointLight( //定位光光照计算的方法
in mat4 uMMatrix, //基本变换矩阵
in vec3 uCamera, //摄像机位置
in vec3 lightLocation, //光源位置
in vec4 lightSpecular, //镜面光强度
in vec3 normal, //法向量
in vec3 aPosition
){
vec4 specular; //镜面光最终强度
vec3 normalTarget=aPosition+normal; //计算世界坐标系中的法向量
vec3 newNormal=(uMMatrix*vec4(normalTarget,1)).xyz-(uMMatrix*vec4(aPosition,1)).xyz;
newNormal=normalize(newNormal); //对法向量规格化
vec3 eye= normalize(uCamera-(uMMatrix*vec4(aPosition,1)).xyz);//计算表面点到摄像机的向量
vec3 vp= normalize(lightLocation-(uMMatrix*vec4(aPosition,1)).xyz);//计算表面点到光源位置的向量vp
vp=normalize(vp); //格式化vp 向量
vec3 halfVector=normalize(vp+eye); //求视线与VP 向量的半向量
float shininess=50.0; //粗糙度,越小越光滑
float nDotViewHalfVector=dot(newNormal,halfVector); //法线与半向量的点积
float powerFactor=max(0.0,pow(nDotViewHalfVector,shininess)); //镜面反射光强度因子
specular=lightSpecular*powerFactor; //计算镜面光的最终强度
return specular; //返回镜面光最终强度
}
//主要是将原来调用散射光计算的相关代码修改为调用镜面光计算的相关代码。
void main(){//主函数
outLightQD=pointLight( //将散射光最终强度传递给片元着色器
myConstantVals.mm, //基本变换矩阵
myBufferVals.uCamera.xyz, //摄像机位置
myBufferVals.lightPosition.xyz, //光源位置
myBufferVals.lightSpecular, //镜面光强度
inNormal, //法向量
pos //顶点位置
);
gl_Position = myConstantVals.mvp * vec4(pos,1.0);//计算顶点最终位置
vposition=pos;//把顶点位置传给片元着色器
}
3种光照通道的合成
而现实世界中3种通道是同时作用的。因此本节将通过一个案例Sample5_5将前面3节案例中不同通道(环境光、散射光、镜面光)的光照效果综合起来,其运行效果如图5-15所示。
commonTexLight.vert
#version 400
#extension GL_ARB_separate_shader_objects : enable//开启separate_shader_objects
#extension GL_ARB_shading_language_420pack : enable //开启shading_language_420pack
layout (std140,set = 0, binding = 0) uniform bufferVals {//一致块
vec4 uCamera; //摄像机位置
vec4 lightPosition; //光源位置
//添加环境光强度散射光强度镜面光强度
vec4 lightAmbient; //环境光强度
vec4 lightDiffuse; //散射光强度
vec4 lightSpecular; //镜面光强度
} myBufferVals;
layout (push_constant) uniform constantVals {//一致块
mat4 mvp;//最终变换矩阵
mat4 mm;//基本变换矩阵
} myConstantVals;
layout (location = 0) in vec3 pos;//传入的顶点位置
layout (location = 1) in vec3 inNormal; //传入的顶点法向量
layout (location = 0) out vec4 outLightQD;//输出的光照强度
layout (location = 1) out vec3 vposition;//输出的顶点位置
out gl_PerVertex {//输出接口块
vec4 gl_Position;//顶点最终位置
};
//上述代码只是将环境光、散射光、镜面光3种通道光照的计算都综合到了pointLight方法中,并将最终的总光照强度传递给了片元着色器。另外要注意的是,上述代码中第36~38行采用的基于物体坐标系中法向量计算世界坐标中法向量的策略并不是效率最高的,只是为了便于初学者的理解。这3行代码还可以替换为“vec3 newNormal=normalize((uMMatrix*vec4(normal,0)).xyz);”,这样得到的结果也是相同的,但效率更高,实际开发中读者也可以选用。
vec4 pointLight( //定位光光照计算的方法
in mat4 uMMatrix, //基本变换矩阵
in vec3 uCamera, //摄像机位置
in vec3 lightLocation, //光源位置
in vec4 lightAmbient, //环境光强度
in vec4 lightDiffuse, //散射光强度
in vec4 lightSpecular, //镜面光强度
in vec3 normal, //法向量
in vec3 aPosition
){
vec4 ambient; //环境光最终强度
vec4 diffuse; //散射光最终强度
vec4 specular; //镜面光最终强度
ambient=lightAmbient; //直接得出环境光的最终强度
vec3 normalTarget=aPosition+normal; //计算世界坐标系中的法向量
vec3 newNormal=(uMMatrix*vec4(normalTarget,1)).xyz-(uMMatrix*vec4(aPosition,1)).xyz;
newNormal=normalize(newNormal); //对法向量规格化
vec3 eye= normalize(uCamera-(uMMatrix*vec4(aPosition,1)).xyz);//计算表面点到摄像机的向量
vec3 vp= normalize(lightLocation-(uMMatrix*vec4(aPosition,1)).xyz);//计算表面点到光源位置的向量vp
vp=normalize(vp); //格式化vp 向量
vec3 halfVector=normalize(vp+eye); //求视线与VP 向量的半向量
float shininess=50.0; //粗糙度,越小越光滑
float nDotViewPosition=max(0.0,dot(newNormal,vp)); //求法向量与vp 向量的点积与0 的最大值
diffuse=lightDiffuse*nDotViewPosition; //计算散射光的最终强度
float nDotViewHalfVector=dot(newNormal,halfVector); //法线与半向量的点积
float powerFactor=max(0.0,pow(nDotViewHalfVector,shininess)); //镜面反射光强度因子
specular=lightSpecular*powerFactor; //计算镜面光的最终强度
return ambient+diffuse+specular; //将三个光照通道最终强度值求和返回
}
void main(){//主函数
outLightQD=pointLight( //将散射光最终强度传递给片元着色器
myConstantVals.mm, //基本变换矩阵
myBufferVals.uCamera.xyz, //摄像机位置
myBufferVals.lightPosition.xyz, //光源位置
myBufferVals.lightAmbient, //环境光强度
myBufferVals.lightDiffuse, //散射光强度
myBufferVals.lightSpecular, //镜面光强度
inNormal, //法向量
pos //顶点位置
);
gl_Position = myConstantVals.mvp * vec4(pos,1.0);//计算顶点最终位置
vposition=pos;//把顶点位置传给片元着色器
}
LightManager.h
#ifndef VULKANEXBASE_LIGHTMANAGER_H
#define VULKANEXBASE_LIGHTMANAGER_H //防止重复引用
class LightManager
{
public:
static float lx, ly, lz; //光源位置
//添加的三种光照
static float lightAmbientR, lightAmbientG, lightAmbientB, lightAmbientA;
static float lightDiffuseR, lightDiffuseG, lightDiffuseB, lightDiffuseA;
static float lightSpecularR, lightSpecularG, lightSpecularB, lightSpecularA; //镜面光强度RGBA 分量
static void setLightPosition(float lxIn, float lyIn, float lzIn);//设置光源位置的方法
//三种光照的函数
static void setlightAmbient(float lightAmbientRIn, float lightAmbientGIn, float lightAmbientBIn, float lightAmbientAIn);
static void setlightDiffuse(float lightDiffuseRIn, float lightDiffuseGIn, float lightDiffuseBIn, float lightDiffuseAIn);
static void setlightSpecular(float lightSpecularRIn, float lightSpecularGIn, float lightSpecularBIn, float lightSpecularAIn);
};
#endif
LightManager.cpp
#include "LightManager.h"
#include <math.h>
float LightManager::lx = 0;//初始化光源位置X
float LightManager::ly = 0;//初始化光源位置Y
float LightManager::lz = 0;//初始化光源位置Z
float LightManager::lightAmbientR = 0;
float LightManager::lightAmbientG = 0;
float LightManager::lightAmbientB = 0;
float LightManager::lightAmbientA = 0;
float LightManager::lightDiffuseR = 0;
float LightManager::lightDiffuseG = 0;
float LightManager::lightDiffuseB = 0;
float LightManager::lightDiffuseA = 0;
float LightManager::lightSpecularR = 0; //初始化镜面光强度R 分量
float LightManager::lightSpecularG = 0; //初始化镜面光强度G 分量
float LightManager::lightSpecularB = 0; //初始化镜面光强度B 分量
float LightManager::lightSpecularA = 0; //初始化镜面光强度A 分量
//设置光源的位置
void LightManager::setLightPosition(float lxIn, float lyIn, float lzIn)
{
lx = lxIn;//设置光源位置X坐标
ly = lyIn;//设置光源位置Y坐标
lz = lzIn;//设置光源位置Z坐标
}
void LightManager::setlightAmbient(float lightAmbientRIn, float lightAmbientGIn, float lightAmbientBIn, float lightAmbientAIn)
{
lightAmbientR = lightAmbientRIn;
lightAmbientG = lightAmbientGIn;
lightAmbientB = lightAmbientBIn;
lightAmbientA = lightAmbientAIn;
}
void LightManager::setlightDiffuse(float lightDiffuseRIn, float lightDiffuseGIn, float lightDiffuseBIn, float lightDiffuseAIn)
{
lightDiffuseR = lightDiffuseRIn;
lightDiffuseG = lightDiffuseGIn;
lightDiffuseB = lightDiffuseBIn;
lightDiffuseA = lightDiffuseAIn;
}
void LightManager::setlightSpecular(float lightSpecularRIn, float lightSpecularGIn, float lightSpecularBIn, float lightSpecularAIn)
{
lightSpecularR = lightSpecularRIn; //设置镜面光强度R 分量
lightSpecularG = lightSpecularGIn; //设置镜面光强度G 分量
lightSpecularB = lightSpecularBIn; //设置镜面光强度B 分量
lightSpecularA = lightSpecularAIn; //设置镜面光强度A 分量
}
//创建Unifirm Buffer
void ShaderQueueSuit_Common::create_uniform_buffer(VkDevice& device, VkPhysicalDeviceMemoryProperties& memoryroperties)
{
//设置为二十
bufferByteCount = sizeof(float) * 20;
定向光
前面介绍的光照效果都是基于定位光光源的,定位光光源类似于现实生活中的白炽灯灯泡,其在某个固定的位置,发出的光向四周发散。定位光照射的一个明显特点就是,在给定光源位置的情况下,对不同位置的物体产生的光照效果不同。
现实世界中并不都是定位光,例如照射到地面上的阳光,光线之间是平行的,这种光称之为定向光。定向光照射的明显特点是,在给定光线方向的情况下,场景中不同位置的物体反映出的光照效果完全一致。图5-16中对定位光与定向光的照射特点进行了比较。
修改
void MyVulkanManager::initMatrixAndLight()
void MyVulkanManager::initMatrixAndLight()
{
MatrixState3D::setCamera(0, 0, 30.0f, 0, 0, 0, 0, 1, 0);
MatrixState3D::setInitStack();//初始化基本变换矩阵
float ratio = (float)screenWidth / (float)screenHeight;//求屏幕长宽比
MatrixState3D::setProjectFrustum(-ratio, ratio, -1, 1, 20.0f, 1000);
//修改为定向光
LightManager::setLightDirection(-0.0f, 0.0f, 1.0f);//调用初始化光源方向的方法
LightManager::setlightAmbient(0.1f, 0.1f, 0.1f, 0.1f);
LightManager::setlightDiffuse(0.6f, 0.6f, 0.6f, 0.6f);
LightManager::setlightSpecular(0.4f, 0.4f, 0.4f, 0.4f);
}
commonTexLigh.vert
#version 400
#extension GL_ARB_separate_shader_objects : enable//开启separate_shader_objects
#extension GL_ARB_shading_language_420pack : enable //开启shading_language_420pack
layout (std140,set = 0, binding = 0) uniform bufferVals
{//一致块
//增加了光源位置一致变量lightPosition
vec4 lightPosition; //光源位置
//散射光强度一致变量lightDiffuse
vec4 lightDiffuse; //散射光强度
} myBufferVals;
layout (push_constant) uniform constantVals
{//一致块
mat4 mvp;//最终变换矩阵
mat4 mm;//基本变换矩阵
} myConstantVals;
layout (location = 0) in vec3 pos;//传入的顶点位置
//增加了传入的顶点法向量inNormal
layout (location = 1) in vec3 inNormal; //传入的顶点法向量
layout (location = 0) out vec4 outLightQD;//输出的光照强度
layout (location = 1) out vec3 vposition;//输出的顶点位置
out gl_PerVertex {//输出接口块
vec4 gl_Position;//顶点最终位置
};
//是增加了调用pointLight方法计算散射光最终强度的相关代码
//vec4 pointLight( //定位光光照计算的方法
vec4 directionalLight ( //定向光光照计算的方法
in mat4 uMMatrix, //基本变换矩阵
//in vec3 lightLocation, //光源位置
in vec3 lightDirection, //定向光方向
in vec4 lightDiffuse, //散射光强度
in vec3 normal, //法向量
in vec3 aPosition
)//顶点位置
{
vec4 diffuse; //散射光最终强度
vec3 normalTarget=aPosition+normal; //计算世界坐标系中的法向量
vec3 newNormal=(uMMatrix*vec4(normalTarget,1)).xyz-(uMMatrix*vec4(aPosition,1)).xyz;
newNormal=normalize(newNormal); //对法向量规格化
//vec3 vp= normalize(lightLocation-(uMMatrix*vec4(aPosition,1)).xyz);//计算表面点到光源位置的向量vp
vec3 vp= normalize(lightDirection); //格式化定向光方向向量
vp=normalize(vp); //格式化vp 向量
float nDotViewPosition=max(0.0,dot(newNormal,vp)); //求法向量与vp 的点积与0 的最大值
diffuse=lightDiffuse*nDotViewPosition; //计算散射光的最终强度
return diffuse; //返回散射光最终强度
}
void main(){//主函数
//outLightQD=pointLight( //将散射光最终强度传递给片元着色器
outLightQD=pointLight( //将散射光最终强度传递给片元着色器
myConstantVals.mm, //基本变换矩阵
//myBufferVals.lightPosition.xyz, //光源位置
myBufferVals. uLightDirection.xyz, //定向光方向
myBufferVals.lightDiffuse, //散射光强度
inNormal, //法向量
pos //顶点位置
);
gl_Position = myConstantVals.mvp * vec4(pos,1.0);//计算顶点最终位置
vposition=pos;//把顶点位置传给片元着色器
}
LightManager.h
#ifndef VULKANEXBASE_LIGHTMANAGER_H
#define VULKANEXBASE_LIGHTMANAGER_H //防止重复引用
class LightManager
{
public:
static float lx, ly, lz; //光源位置
static float lightAmbientR, lightAmbientG, lightAmbientB, lightAmbientA;
static float lightDiffuseR, lightDiffuseG, lightDiffuseB, lightDiffuseA;
static float lightSpecularR, lightSpecularG, lightSpecularB, lightSpecularA; //镜面光强度RGBA 分量
//static void setLightPosition(float lxIn, float lyIn, float lzIn);//设置光源位置的方法
static void setLightDirection(float lxIn, float lyIn, float lzIn); //设置光源方向的方法
static void setlightAmbient(float lightAmbientRIn, float lightAmbientGIn, float lightAmbientBIn, float lightAmbientAIn);
static void setlightDiffuse(float lightDiffuseRIn, float lightDiffuseGIn, float lightDiffuseBIn, float lightDiffuseAIn);
static void setlightSpecular(float lightSpecularRIn, float lightSpecularGIn, float lightSpecularBIn, float lightSpecularAIn);
};
#endif
LightManager.h
//void LightManager::setLightPosition(float lxIn, float lyIn, float lzIn)
void LightManager::setLightDirection(float lxIn, float lyIn, float lzIn) { //设置定向光方向的方法
{
lx = lxIn;//设置光源位置X坐标
ly = lyIn;//设置光源位置Y坐标
lz = lzIn;//设置光源位置Z坐标