C++游戏引擎开发指南:Shader结构体的优雅组织

C++游戏引擎开发指南:Shader结构体的优雅组织

cpp-game-engine-book 从零编写游戏引擎教程 Writing a game engine tutorial from scratch cpp-game-engine-book 项目地址: https://gitcode.com/gh_mirrors/cp/cpp-game-engine-book

引言

在现代游戏引擎开发中,光照系统是一个核心组成部分。在之前的章节中,我们介绍了经典光照模型的基本概念和实现方式。然而,随着光照系统的复杂度增加,Shader代码中的变量管理变得尤为重要。本文将深入探讨如何通过Shader结构体来优雅地组织光照相关变量,提升代码的可维护性和可扩展性。

为什么需要Shader结构体

在传统的光照实现中,我们通常会将光照相关的各个参数(如颜色、强度、位置等)作为独立的uniform变量传递给Shader。这种方式虽然简单直接,但随着光照系统的扩展,会带来两个主要问题:

  1. Uniform变量数量限制:OpenGL对uniform变量的数量有限制,过多的独立变量会很快耗尽可用资源。
  2. 代码组织混乱:大量相关的独立变量缺乏逻辑组织,降低了代码的可读性和可维护性。

GLSL作为类C语言,支持结构体(struct)特性,这为我们提供了更好的代码组织方式。

Shader结构体的实现

1. 定义光照结构体

在片段Shader中,我们可以将环境光和普通灯光分别定义为结构体:

// 环境光结构体
struct Ambient {
    vec3  light_color;      // 环境光颜色
    float light_intensity;  // 环境光强度
};
uniform Ambient u_ambient;

// 灯光结构体
struct Light {
    vec3  pos;        // 光源位置
    vec3  color;      // 光源颜色
    float intensity;  // 光源强度
};
uniform Light u_light;

这种组织方式不仅使代码更加清晰,还更符合面向对象的设计理念。在Shader中使用这些结构体成员时,可以通过点操作符访问:

vec3 ambient_color = u_ambient.light_color * u_ambient.light_intensity;

2. 完整的片段Shader示例

以下是整合了结构体的完整片段Shader代码:

#version 330 core

uniform sampler2D u_diffuse_texture;  // 漫反射纹理

// 环境光结构体
struct Ambient {
    vec3  light_color;
    float light_intensity;
};
uniform Ambient u_ambient;

// 灯光结构体
struct Light {
    vec3  pos;
    vec3  color;
    float intensity;
};
uniform Light u_light;

// 其他uniform变量
uniform vec3 u_view_pos;
uniform sampler2D u_specular_texture;
uniform float u_specular_highlight_shininess;

// 输入变量
in vec4 v_color;
in vec2 v_uv;
in vec3 v_normal;
in vec3 v_frag_pos;

// 输出颜色
layout(location = 0) out vec4 o_fragColor;

void main()
{
    // 环境光计算
    vec3 ambient_color = u_ambient.light_color * u_ambient.light_intensity * texture(u_diffuse_texture,v_uv).rgb;

    // 漫反射计算
    vec3 normal = normalize(v_normal);
    vec3 light_dir = normalize(u_light.pos - v_frag_pos);
    float diffuse_intensity = max(dot(normal,light_dir),0.0);
    vec3 diffuse_color = u_light.color * diffuse_intensity * u_light.intensity * texture(u_diffuse_texture,v_uv).rgb;

    // 镜面高光计算
    vec3 reflect_dir = reflect(-light_dir,v_normal);
    vec3 view_dir = normalize(u_view_pos-v_frag_pos);
    float spec = pow(max(dot(view_dir,reflect_dir),0.0),u_specular_highlight_shininess);
    float specular_highlight_intensity = texture(u_specular_texture,v_uv).r;
    vec3 specular_color = u_light.color * spec * specular_highlight_intensity * texture(u_diffuse_texture,v_uv).rgb;

    // 最终颜色合成
    o_fragColor = vec4(ambient_color + diffuse_color + specular_color,1.0);
}

引擎侧的对应实现

为了与Shader中的结构体对应,我们需要在引擎侧创建相应的类来管理这些光照参数。

1. 环境光管理

环境光是全局的,因此我们设计了一个Environment类来管理:

class Environment {
public:
    Environment();
    
    // 设置/获取环境光颜色
    void set_ambient_color(const glm::vec3 &ambient_color);
    const glm::vec3 &ambient_color() const;
    
    // 设置/获取环境光强度
    void set_ambient_color_intensity(float ambient_color_intensity);
    float ambient_color_intensity() const;
    
    // 更新所有材质的环境光参数
    void Update();

private:
    glm::vec3 ambient_color_;         // 环境光颜色
    float ambient_color_intensity_;   // 环境光强度
};

在Update方法中,我们遍历所有游戏对象,为每个材质设置环境光参数:

void Environment::Update() {
    GameObject::Foreach([this](GameObject* game_object){
        if(!game_object->active()) return;
        
        MeshRenderer* mesh_renderer = game_object->GetComponent<MeshRenderer>();
        if(!mesh_renderer) return;
        
        Material* material = mesh_renderer->material();
        material->SetUniform3f("u_ambient.light_color", ambient_color_);
        material->SetUniform1f("u_ambient.light_intensity", ambient_color_intensity_);
    });
}

2. 灯光管理

场景中可以有多个光源,因此我们将灯光设计为组件形式:

class Light: public Component {
public:
    Light();
    ~Light();
    
    // 颜色设置/获取
    glm::vec3 color() const;
    void set_color(glm::vec3 color);
    
    // 强度设置/获取
    float intensity() const;
    void set_intensity(float intensity);
    
    // 每帧更新
    void Update() override;

private:
    glm::vec3 color_;    // 灯光颜色
    float intensity_;    // 灯光强度
};

在Update方法中,我们同样遍历所有游戏对象,设置灯光参数:

void Light::Update() {
    GameObject::Foreach([this](GameObject* iterate_game_object){
        if(!iterate_game_object->active()) return;
        
        MeshRenderer* mesh_renderer = iterate_game_object->GetComponent<MeshRenderer>();
        if(!mesh_renderer) return;
        
        Material* material = mesh_renderer->material();
        // 设置灯光位置、颜色、强度
        glm::vec3 light_position = game_object()->GetComponent<Transform>()->position();
        material->SetUniform3f("u_light.pos", light_position);
        material->SetUniform3f("u_light.color", color_);
        material->SetUniform1f("u_light.intensity", intensity_);
    });
}

实际应用示例

在游戏场景中,我们可以这样创建和使用光照:

function LoginScene:Awake()
    self:CreateEnvironment()
    self:CreateLight()
end

-- 创建环境光
function LoginScene:CreateEnvironment()
    self.environment_ = Environment.new()
    self.environment_:set_ambient_color(glm.vec3(1.0, 1.0, 1.0))
    self.environment_:set_ambient_color_intensity(0.3)
end

-- 创建灯光
function LoginScene:CreateLight()
    local go_light = GameObject.new("light")
    go_light:AddComponent(Transform):set_position(glm.vec3(0, 0, 20))
    local light = go_light:AddComponent(Light)
    light:set_color(glm.vec3(1.0, 1.0, 1.0))
    light:set_intensity(1.0)
end

function LoginScene:Update()
    self.environment_:Update()
end

结构体组织的优势

使用结构体组织Shader变量带来了多方面的好处:

  1. 代码清晰度:相关的变量被组织在一起,提高了代码的可读性。
  2. 维护便利性:修改或扩展光照属性时,只需在结构体中添加或修改成员。
  3. 性能优化:减少了uniform变量的数量,更有效地利用有限的GPU资源。
  4. 设计一致性:引擎侧的类设计与Shader中的结构体一一对应,保持了设计的一致性。

总结

通过使用Shader结构体,我们实现了光照参数的有序组织,使代码更加清晰、易于维护。这种模式不仅适用于光照系统,也可以推广到其他需要组织多个相关uniform变量的场景中。在后续的引擎开发中,我们可以进一步扩展这种结构体模式,例如添加点光源、聚光灯等更多类型的光源结构体,构建更加丰富的光照系统。

结构体的使用是现代游戏引擎开发中的一个重要技巧,它帮助我们构建更加模块化、可扩展的渲染系统,为后续实现更复杂的渲染效果奠定了良好的基础。

cpp-game-engine-book 从零编写游戏引擎教程 Writing a game engine tutorial from scratch cpp-game-engine-book 项目地址: https://gitcode.com/gh_mirrors/cp/cpp-game-engine-book

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

时煜青

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值