游戏引擎 浅入浅出

Introduction

前言












12. 拆分引擎和项目















89. Doxygen生成API文档





代码资源下载


目录

23.9 多光源

  1. 「游戏引擎 浅入浅出」是一本开源电子书,PDF/随书代码/资源下载: https://github.com/ThisisGame/cpp-game-engine-book
  1. CLion项目文件位于 samples\classic_lighting\directional_light

前面介绍了方向光、点光源,游戏里一般是有环境光+1个方向光+多个点光源,这一节就来介绍如何添加多个光源。

提起多个,就想到用数组,那么首先要解决的问题就是,如何在片段Shader里,创建点光源的数组?

1. 在Shader中使用数组

在Shader中定义数组和C语言很相似,也是用中括号。

下面代码在Uniform Block中,定义了长度为128的点光源数组 data

  1. //点光
  2. struct PointLight {
  3. vec3 pos;//位置 alignment:16 offset:0
  4. vec3 color;//颜色 alignment:12 offset:16
  5. float intensity;//强度 alignment:4 offset:28
  6. float constant;//点光衰减常数项 alignment:4 offset:32
  7. float linear;//点光衰减一次项 alignment:4 offset:36
  8. float quadratic;//点光衰减二次项 alignment:4 offset:40
  9. };
  10. #define POINT_LIGHT_MAX_NUM 128
  11. //灯光数组
  12. layout(std140) uniform PointLightBlock {
  13. PointLight data[POINT_LIGHT_MAX_NUM];
  14. }u_point_light_array;

访问数组元素也和C语言相似。

下面代码访问了点光源数组 data 的第0个元素。

  1. vec3 light_dir=normalize(u_point_light_array.data[0].pos - v_frag_pos);

灯光的效果是叠加的,所以在片段着色器中,对每一类型的灯光,要进行遍历,然后计算,然后叠加。

  1. void main()
  2. {
  3. //ambient
  4. vec3 ambient_color = u_ambient.data.color * u_ambient.data.intensity * texture(u_diffuse_texture,v_uv).rgb;
  5. vec3 total_diffuse_color;//总的漫反射光照
  6. vec3 total_specular_color;//总的高光
  7. //directional light
  8. ......
  9. //point light
  10. for(int i=0;i<POINT_LIGHT_MAX_NUM;i++){
  11. PointLight point_light=u_point_light_array.data[i];
  12. //diffuse 计算漫反射光照
  13. vec3 normal=normalize(v_normal);
  14. vec3 light_dir=normalize(point_light.pos - v_frag_pos);
  15. float diffuse_intensity = max(dot(normal,light_dir),0.0);
  16. vec3 diffuse_color = point_light.color * diffuse_intensity * point_light.intensity * texture(u_diffuse_texture,v_uv).rgb;
  17. //specular 计算高光
  18. vec3 reflect_dir=reflect(-light_dir,v_normal);
  19. vec3 view_dir=normalize(u_view_pos-v_frag_pos);
  20. float spec=pow(max(dot(view_dir,reflect_dir),0.0),u_specular_highlight_shininess);
  21. float specular_highlight_intensity = texture(u_specular_texture,v_uv).r;//从纹理中获取高光强度
  22. vec3 specular_color = point_light.color * spec * specular_highlight_intensity * texture(u_diffuse_texture,v_uv).rgb;
  23. //attenuation 计算点光源衰减值
  24. float distance=length(point_light.pos - v_frag_pos);
  25. float attenuation = 1.0 / (point_light.constant + point_light.linear * distance + point_light.quadratic * (distance * distance));
  26. //将每一个点光源的计算结果叠加
  27. total_diffuse_color=total_diffuse_color+diffuse_color*attenuation;
  28. total_specular_color=total_specular_color+specular_color*attenuation;
  29. }
  30. o_fragColor = vec4(ambient_color + total_diffuse_color + total_specular_color,1.0);
  31. }

不用关注具体的光照计算,只需要记住:每一个点光源都要计算一次,然后叠加。

1.1 实际灯光数量

这里定义了长度为128的点光源数组 data ,那么就是说会循环128次计算点光源效果。

那游戏场景里没有灯怎么办,岂不是浪费性能!

所以在点光源的Uniform Block里加上实际创建的灯光数量。

  1. //灯光数组
  2. layout(std140) uniform PointLightBlock {
  3. PointLight data[POINT_LIGHT_MAX_NUM];
  4. int actually_used_count;//实际创建的灯光数量
  5. }u_point_light_array;

遍历总数就只有实际创建的灯光数量。

  1. void main()
  2. {
  3. ......
  4. //point light
  5. for(int i=0;i<u_point_light_array.actually_used_count;i++){
  6. //diffuse 计算漫反射光照
  7. ......
  8. //specular 计算高光
  9. ......
  10. //attenuation 计算点光源衰减值
  11. ......
  12. //将每一个点光源的计算结果叠加
  13. ......
  14. }
  15. o_fragColor = vec4(ambient_color + total_diffuse_color + total_specular_color,1.0);
  16. }

2. 设置灯光数组参数

2.1 访问数组元素

要从逻辑代码中去设置Shader数组的值,需要对每一个元素进行设置。

怎么理解 每一个元素 这5个字?

下面的代码创建了长度为2的点光源数组。

  1. //file:data/shader/multi_light.frag line:30
  2. //点光
  3. struct PointLight {
  4. vec3 pos;//位置 alignment:16 offset:0
  5. vec3 color;//颜色 alignment:12 offset:16
  6. float intensity;//强度 alignment:4 offset:28
  7. float constant;//点光衰减常数项 alignment:4 offset:32
  8. float linear;//点光衰减一次项 alignment:4 offset:36
  9. float quadratic;//点光衰减二次项 alignment:4 offset:40
  10. };
  11. //灯光数组
  12. layout(std140) uniform PointLightBlock {
  13. PointLight data[2];
  14. int actually_used_count;//实际创建的灯光数量
  15. }u_point_light_array;

这段代码在Uniform Block中定义了长度为2的数组,而数组类型本身是Struct。

那么要用如下的形式,先遍历数组,然后逐个设置Struct成员变量。

  1. std::string uniform_block_member_name;
  2. for(int i=0;i<2;i++)
  3. {
  4. //设置颜色
  5. uniform_block_member_name=fmt::format("data[{}].color",i);
  6. UniformBufferObjectManager::UpdateUniformBlockSubData3f("u_point_light_array",uniform_block_member_name,color_);
  7. uniform_block_member_name=fmt::format("data[{}].intensity",light_id_);
  8. UniformBufferObjectManager::UpdateUniformBlockSubData1f("u_point_light_array",uniform_block_member_name,intensity_);
  9. ......
  10. uniform_block_member_name=fmt::format("data[{}].quadratic",light_id_);
  11. UniformBufferObjectManager::UpdateUniformBlockSubData1f("u_point_light_array",uniform_block_member_name,attenuation_quadratic_);
  12. }
  13. ......

也就是说,逻辑代码只能访问到Shader最外层的、以layout/varying/uniform修饰的变量,如果这个变量是Struct,那么就需要构造字符串去访问。

如上面的点光源数组,逻辑代码只能访问到 u_point_light_array 这个 layout 修饰的变量,而对于数组中的某一个元素,就需要构造字符串了。

2.2 Uniform Block的尺寸

上面Uniform Block中,定义了一个长度为2的点光源Struct数组。

回顾std140内存布局的规则:

  1. N=4
  2. 1. float1N
  3. 2. 单独vec34N
  4. 3. vec3vec34N
  5. 4. vec3后面跟float,满足4N,那么(vec3+float)就算3N(vec3)+float(1N)=4N
  6. 5. struct需要满足4N的倍数
  7. 6. strcut[]按struct单个规则,满足4N倍数

匹配到第6条,那么 sizeof(PointLightBlock) = sizeof(PointLight) x 2 + sizeof(actually_used_count)

2.3 记录Uniform Block结构

为了方便设置Uniform Block中的变量值,我们在UniformBufferObjectManager中,记录Uniform Block结构。

因为这里Uniform Block中的变量是数组,所以成员变量就要乘以数组元素个数了,Uniform Block尺寸也需要乘以数组元素个数。

  1. //file:source/render_device/uniform_buffer_object_manager.cpp line:12
  2. #define DIRECTIONAL_LIGHT_MAX_NUM 128 //最大方向光数量
  3. #define POINT_LIGHT_MAX_NUM 128 //最大点光源数量
  4. std::vector<UniformBlockInstanceBindingInfo> UniformBufferObjectManager::kUniformBlockInstanceBindingInfoArray={
  5. ......
  6. {"u_directional_light_array","DirectionalLightBlock",32*DIRECTIONAL_LIGHT_MAX_NUM+sizeof(int),1,0},
  7. {"u_point_light_array","PointLightBlock",48*POINT_LIGHT_MAX_NUM+sizeof(int),2,0}
  8. };
  9. std::unordered_map<std::string,UniformBlock> UniformBufferObjectManager::kUniformBlockMap;
  10. void UniformBufferObjectManager::Init(){
  11. ......
  12. //方向光
  13. kUniformBlockMap["DirectionalLightBlock"]={{}};
  14. {
  15. std::vector<UniformBlockMember>& uniform_block_member_vec=kUniformBlockMap["DirectionalLightBlock"].uniform_block_member_vec_;
  16. for(int i=0;i<POINT_LIGHT_MAX_NUM;i++){
  17. uniform_block_member_vec.push_back({fmt::format("data[{}].dir",i),32*i+0,sizeof(glm::vec3)});
  18. uniform_block_member_vec.push_back({fmt::format("data[{}].color",i),32*i+16,sizeof(glm::vec3)});
  19. uniform_block_member_vec.push_back({fmt::format("data[{}].intensity",i),32*i+28,sizeof(float)});
  20. }
  21. uniform_block_member_vec.push_back({"actually_used_count",32*POINT_LIGHT_MAX_NUM,sizeof(int)});
  22. }
  23. //点光源数组
  24. kUniformBlockMap["PointLightBlock"]={{}};
  25. {
  26. std::vector<UniformBlockMember>& uniform_block_member_vec=kUniformBlockMap["PointLightBlock"].uniform_block_member_vec_;
  27. for(int i=0;i<POINT_LIGHT_MAX_NUM;i++){
  28. uniform_block_member_vec.push_back({fmt::format("data[{}].pos",i),48*i+0,sizeof(glm::vec3)});
  29. uniform_block_member_vec.push_back({fmt::format("data[{}].color",i),48*i+16,sizeof(glm::vec3)});
  30. uniform_block_member_vec.push_back({fmt::format("data[{}].intensity",i),48*i+28,sizeof(float)});
  31. uniform_block_member_vec.push_back({fmt::format("data[{}].constant",i),48*i+32,sizeof(float)});
  32. uniform_block_member_vec.push_back({fmt::format("data[{}].linear",i),48*i+36,sizeof(float)});
  33. uniform_block_member_vec.push_back({fmt::format("data[{}].quadratic",i),48*i+40,sizeof(float)});
  34. }
  35. uniform_block_member_vec.push_back({"actually_used_count",48*POINT_LIGHT_MAX_NUM,sizeof(int)});
  36. }
  37. }

2.4 更新UBO

定义变量 unsigned short light_id_; 来作为光源数组的下标。

定义变量 unsigned int light_count_; 来表示实际创建的光源数量。

点光源更新UBO。

  1. //source/lighting/point_light.cpp
  2. unsigned short PointLight::light_count_=0;
  3. PointLight::PointLight():Light(),attenuation_constant_(0),attenuation_linear_(0),attenuation_quadratic_(0)
  4. {
  5. light_id_=light_count_;
  6. light_count_++;
  7. UniformBufferObjectManager::UpdateUniformBlockSubData1i("u_point_light_array","actually_used_count",light_count_);
  8. }
  9. ......
  10. void PointLight::set_color(glm::vec3 color){
  11. Light::set_color(color);
  12. std::string uniform_block_member_name=fmt::format("data[{}].color",light_id_);
  13. UniformBufferObjectManager::UpdateUniformBlockSubData3f("u_point_light_array",uniform_block_member_name,color_);
  14. };
  15. void PointLight::set_intensity(float intensity){
  16. Light::set_intensity(intensity);
  17. std::string uniform_block_member_name=fmt::format("data[{}].intensity",light_id_);
  18. UniformBufferObjectManager::UpdateUniformBlockSubData1f("u_point_light_array",uniform_block_member_name,intensity_);
  19. };
  20. void PointLight::set_attenuation_constant(float attenuation_constant){
  21. attenuation_constant_ = attenuation_constant;
  22. std::string uniform_block_member_name=fmt::format("data[{}].constant",light_id_);
  23. UniformBufferObjectManager::UpdateUniformBlockSubData1f("u_point_light_array",uniform_block_member_name,attenuation_constant_);
  24. }
  25. void PointLight::set_attenuation_linear(float attenuation_linear){
  26. attenuation_linear_ = attenuation_linear;
  27. std::string uniform_block_member_name=fmt::format("data[{}].linear",light_id_);
  28. UniformBufferObjectManager::UpdateUniformBlockSubData1f("u_point_light_array",uniform_block_member_name,attenuation_linear_);
  29. }
  30. void PointLight::set_attenuation_quadratic(float attenuation_quadratic){
  31. attenuation_quadratic_ = attenuation_quadratic;
  32. std::string uniform_block_member_name=fmt::format("data[{}].quadratic",light_id_);
  33. UniformBufferObjectManager::UpdateUniformBlockSubData1f("u_point_light_array",uniform_block_member_name,attenuation_quadratic_);
  34. }
  35. void PointLight::Update(){
  36. glm::vec3 light_position=game_object()->GetComponent<Transform>()->position();
  37. std::string uniform_block_member_name=fmt::format("data[{}].pos",light_id_);
  38. UniformBufferObjectManager::UpdateUniformBlockSubData3f("u_point_light_array",uniform_block_member_name,light_position);
  39. }

方向光同理。

3. 测试

创建2个方向光(右侧、上方)、2个点光源(左侧红色、右侧绿色)。

  1. --file:example/login_scene.lua
  2. function LoginScene:Awake()
  3. ......
  4. self:CreateDirectionalLight1()
  5. self:CreateDirectionalLight2()
  6. self:CreatePointLight1()
  7. self:CreatePointLight2()
  8. ......
  9. end
  10. ......
  11. --- 创建方向光1 右侧
  12. function LoginScene:CreateDirectionalLight1()
  13. self.go_directional_light_1_= GameObject.new("directional_light_1")
  14. self.go_directional_light_1_:AddComponent(Transform)
  15. self.go_directional_light_1_:GetComponent(Transform):set_rotation(glm.vec3(0,60,0))
  16. local light=self.go_directional_light_1_:AddComponent(DirectionalLight)
  17. light:set_color(glm.vec3(1.0,1.0,1.0))
  18. light:set_intensity(1.0)
  19. end
  20. --- 创建方向光2 上方
  21. function LoginScene:CreateDirectionalLight2()
  22. self.go_directional_light_2_= GameObject.new("directional_light_2")
  23. self.go_directional_light_2_:AddComponent(Transform)
  24. self.go_directional_light_2_:GetComponent(Transform):set_rotation(glm.vec3(240,0,0))
  25. local light=self.go_directional_light_2_:AddComponent(DirectionalLight)
  26. light:set_color(glm.vec3(1.0,1.0,1.0))
  27. light:set_intensity(1.0)
  28. end
  29. --- 创建点光源1 左侧 红色
  30. function LoginScene:CreatePointLight1()
  31. self.go_point_light_1_= GameObject.new("point_light_1")
  32. self.go_point_light_1_:AddComponent(Transform):set_position(glm.vec3(-2,0,5))
  33. ---@type PointLight
  34. local light=self.go_point_light_1_:AddComponent(PointLight)
  35. light:set_color(glm.vec3(1.0,0.0,0.0))
  36. light:set_intensity(1.0)
  37. light:set_attenuation_constant(1.0)
  38. light:set_attenuation_linear( 0.35)
  39. light:set_attenuation_quadratic( 0.44)
  40. end
  41. --- 创建点光源2 右侧 绿色
  42. function LoginScene:CreatePointLight2()
  43. self.go_point_light_2_= GameObject.new("point_light_2")
  44. self.go_point_light_2_:AddComponent(Transform):set_position(glm.vec3(2,0,5))
  45. ---@type PointLight
  46. local light=self.go_point_light_2_:AddComponent(PointLight)
  47. light:set_color(glm.vec3(0.0,1.0,0.0))
  48. light:set_intensity(1.0)
  49. light:set_attenuation_constant(1.0)
  50. light:set_attenuation_linear( 0.35)
  51. light:set_attenuation_quadratic( 0.44)
  52. end

效果如下图:

Introduction

前言












12. 拆分引擎和项目















89. Doxygen生成API文档