Hazel游戏引擎(109)渲染2D圆Circle

文中若有代码、术语等错误,欢迎指正

前言

  • 此节目的

    给引擎添加渲染Circle图形

  • 如何实现

    对项目来说:用批处理,同quad的顶点坐标与布局,但是shader的fragment阶段,控制在一个圆圈输出颜色

    抛开项目说:用Quad顶点包围一个范围作为画布(Canvas)用glsl控制画出一个圆alpha为1,圆外的范围的alpha为0。

  • 实现细节

    1. 运行时需要拷贝circle组件,序列化和解析yaml-cpp时都得加上circle组件的代码。
    2. circle的shader具有localposition顶点属性。
  • 如何画圆

    点这

关键代码+代码流程

关键代码

  • circle的glsl

    // Basic Texture Shader
    #type vertex
    #version 450 core
    layout(location = 0) in vec3 a_WorldPosition;
    layout(location = 1) in vec3 a_LocalPosition;// 具有**localposition**顶点属性
    layout(location = 2) in vec4 a_Color;
    layout(location = 3) in float a_Thickness;
    layout(location = 4) in float a_Fade;
    layout(location = 5) in int a_EntityID;
    layout(std140, binding = 0) uniform Camera{
    	mat4 u_ViewProjection;
    };
    struct VertexOutput{
    	vec3 LocalPosition; // 输出这个为了不管处于任何世界坐标,相对于圆心的长度不变
    	vec4 Color;
    	float Thickness;
    	float Fade;
    };
    layout(location = 0) out VertexOutput Output;
    layout(location = 4) out flat int v_EntityID;
    void main(){
    	Output.LocalPosition = a_LocalPosition;
    	Output.Color = a_Color;
    	Output.Thickness = a_Thickness;
    	Output.Fade = a_Fade;
    	v_EntityID = a_EntityID;
    	gl_Position = u_ViewProjection * vec4(a_WorldPosition, 1.0);
    }
    #type fragment
    #version 450 core
    layout(location = 0) out vec4 o_Color;
    layout(location = 1) out int o_EntityID;
    struct VertexOutput{
    	vec3 LocalPosition; // 
    	vec4 Color;
    	float Thickness;
    	float Fade;
    };
    layout(location = 0) in VertexOutput Input;
    layout(location = 4) in flat int v_EntityID;
    void main(){
    		/*
    		函数解释:
    		step(edge0, x); 当x > edge0,返回1,当x < edge0 返回0。阶梯函数
    		smoothstep(edge0, edge1, x);当x > edg1时,返回1,当x < edg0时,返回0,当x在edg0和edg1之间时,返回x。平滑的阶梯函数
    		length(a); 返回向量a的长度。sqrt(x*x, y*y);
    
    		LocalPosition拿圆心(0, 0)、中心点(0, 0.5)、边缘点(0,1)说明
    		Input.Thickness=1; Input.Fade=0
    		LocalPosition(0, 0);
    			distance = 1 - 0 = 1;
    			circle = smoothstep(0, 0, 1) = 1;
    			cirle = cirle * smoothstep(1 + 0, 1, 1) = 1;
    			所以圆心像素显示
    		LocalPosition(0, 0.5);
    			distance = 1 - 0.5 = 0.5;
    			circle = smoothstep(0, 0, 0.5) = 1;
    			cirle = cirle * smoothstep(1 + 0, 1, 1) = 1;
    			所以中间点像素显示
    		LocalPosition(0, 1);
    			distance = 1 - 1 = 0;
    			circle = smoothstep(0, 0, 0) = 0;
    			cirle = cirle * smoothstep(1 + 0, 1, 0) = 0;
    			所以边缘的像素透明不显示
    	*/
    	float distance = 1.0 - length(Input.LocalPosition);// 根据与圆心的距离计算
    	float circle = smoothstep(0.0, Input.Fade, distance);
    	circle *= smoothstep(Input.Thickness + Input.Fade, Input.Thickness, distance);
    	if (circle == 0.0) {
    		discard;
    	}
    	o_Color = Input.Color;
    	o_Color.a *= circle;
    	// EntityID
    	o_EntityID = v_EntityID;
    }
    
    • 其中smoothstep很容易误解

      误解1

      • 错误

        smoothstep(0, 0.1, x);以为x小于0的为0,大于0.1的为0.1,其它x在(0,0.1)之间插值

      • 正确

        smoothstep(0, 0.1, x); 是 x小于0的为0,大于0.1的为1,其它x在(0,0.1)之间插值

      误解2

      • smoothstep(0.5, 0.2, x);以为x小于0.2的为0,大于0.5的为1,其它x在(0.2,0.5)之间插值
      • smoothstep(0.5, 0.2, x);是x小于0.2的为1大于0.5的为0,其它x在(0.2,0.5)之间插值

      小结

      // 当edge0 < edge1时,当x < edg0时,返回0,当x > edg1时,返回1,当x在edg0和edg1之间时,返回x。
      // 当edge1 < edge0时,当x < edg1时,返回1,当x > edg0时,返回0,当x在edg0和edg1之间时,返回x。
      // 当edge0 = edge1 ,smoothstep退化成step(0除外)
      // 当edge0 = edge1 ,smoothstep退化成step
      // 但当edge0=edge1=0,smoothstep(edge0,edge1,x);无论x是什么都返回0!
      smoothstep(edge0, edge1, x);
      

    详细解释如何画的:请点这

代码流程

  • 批处理代码加上circle的顶点数组等信息

    circle的顶点数组使用的索引缓冲区是和quad的索引缓冲区一样,所以不需要再建

    struct CircleVertex {
        glm::vec3 WorldPosition;
        glm::vec3 LocalPosition;
        glm::vec4 Color;
        float Thickness;
        float Fade;
        // Editor-only;
        int EntityID;
    };
    ....
    // circle
    uint32_t CircleIndexCount = 0;
    CircleVertex* CircleVertexBufferBase = nullptr;
    CircleVertex* CircleVertexBufferPtr = nullptr;
    ....
    // 1.1设置顶点数组使用的缓冲区,并且在这个缓冲区中设置布局
    s_Data.CircleVertexArray->AddVertexBuffer(s_Data.CircleVertexBuffer);
    
    // 3.索引缓冲-和quad使用的是同一个,不需要重新建
    
    // 1.2顶点数组设置索引缓冲区
    s_Data.CircleVertexArray->SetIndexBuffer(quadIB); // 这里写错过,s_Data.QuadVertexArray,即没有给circle的顶点数组设置索引
    
    s_Data.CircleShader = Shader::Create("assets/shaders/Renderer2D_Circle.glsl");
    ....
    
  • 绘画函数使用quad的顶点位置,顶点信息中有localPosition

    void Renderer2D::DrawCircle(const glm::mat4& transform, const glm::vec4& color, float thickness, float fade, int entityID)
    {
        HZ_PROFILE_FUNCTION();
        // 这里注释是因为,circle一般不会超。。。
        //if (s_Data.QuadIndexCount >= Renderer2DData::MaxIndices) {
        //	NextBatch();
        //}
        constexpr size_t quadVertexCount = 4;
        // quad的左下角为起点
        // 使用的是quad顶点信息
        for (size_t i = 0; i < quadVertexCount; i++) {
            s_Data.CircleVertexBufferPtr->WorldPosition = transform * s_Data.QuadVertexPosition[i]; 
            s_Data.CircleVertexBufferPtr->LocalPosition = s_Data.QuadVertexPosition[i] * 2.0f; // 2 * 0.5 = 1
            s_Data.CircleVertexBufferPtr->Color = color;
            s_Data.CircleVertexBufferPtr->Thickness = thickness;
            s_Data.CircleVertexBufferPtr->Fade = fade;
            s_Data.CircleVertexBufferPtr->EntityID = entityID;
            s_Data.CircleVertexBufferPtr++;
        }
        s_Data.CircleIndexCount += 6;// 每一个quad用6个索引
    
        s_Data.Stats.QuadCount++;
    }
    
  • 写Circle的组件

    struct CircleRendererComponent {
        glm::vec4 Color{ 1.0f, 1.0f, 1.0f, 1.0f };
        float Thickness = 1.0f;
        float Fade = 0.005f;
    
        CircleRendererComponent() = default;
        CircleRendererComponent(const CircleRendererComponent&) = default;
    };
    
  • Scene扫描当前场景有Circle的组件,遍历然后调用draw绘制

    void Scene::OnUpdateEditor(Timestep ts, EditorCamera& camera)
    {
        Renderer2D::BeginScene(camera);
        // 绘画sprite
        {
            auto group = m_Registry.group<TransformComponent>(entt::get<SpriteRendererComponent>);
            for (auto entity : group) {
                // get返回的tuple里本是引用
                auto [tfc, sprite] = group.get<TransformComponent, SpriteRendererComponent>(entity);
                Renderer2D::DrawSprite(tfc.GetTransform(), sprite, (int)entity);
            }
        }
        // 绘画circles
        {
            auto view = m_Registry.view<TransformComponent, CircleRendererComponent>();
            for (auto entity : view) {
                // get返回的tuple里本是引用
                auto [tfc, circle] = view.get<TransformComponent, CircleRendererComponent>(entity);
                Renderer2D::DrawCircle(tfc.GetTransform(), circle.Color, circle.Thickness, circle.Fade, (int)entity);
            }
        }
        Renderer2D::EndScene();
    }
    

效果

请添加图片描述

Bug

  • 圆背景黑色

    原因:shader代码原因,没有指定alpha

    float distance = 1.0 - length(Input.LocalPosition);
    vec3 color = vec3(smoothstep(0.0, Input.Fade, distance));
    color *= vec3(smoothstep(Input.Thickness + Input.Fade, Input.Thickness, distance));
    
    o_Color = Input.Color;
    o_Color.rgb *= color;// 这里写错,应该为:o_Color.a *= circle;
    
  • draw没有渲染任何东西

    circle和quad都有自己的顶点数组,若没有切换绑定,即会draw没有渲染任何东西

    应在代码加上绑定

    void OpenGLRendererAPI::DrawIndexed(const Ref<VertexArray>& vertexArray, uint32_t indexCount)
    {
        vertexArray->Bind(); // 加上顶点数组绑定
        uint32_t count = indexCount == 0 ? vertexArray->GetIndexBuffer()->GetCount() : indexCount;
        glDrawElements(GL_TRIANGLES, count, GL_UNSIGNED_INT, nullptr);
    }
    
  • 当一个Quad和Circle重叠,在Circle的四周无法点击后面Quad的entity

    因为circle是quad形状,四个角范围虽然alpha为0,点击后仍是自己circle的entityid

    在fragment检测到alpha为0,就丢弃这个像素点,从而被后面的quad的entityid填充。

    void main()
    {
    	// 根据与圆心的距离计算
    	float distance = 1.0 - length(Input.LocalPosition);
    	float circle = smoothstep(0.0, Input.Fade, distance);
    	circle *= smoothstep(Input.Thickness + Input.Fade, Input.Thickness, distance);
    	if (circle == 0.0) {
    		discard;// 丢弃
    	}
    

自己遇到的错

  • 报错如下:nvoglv64.dll

    写错批处理opengl代码

    // 1.2顶点数组设置索引缓冲区
    s_Data.CircleVertexArray->SetIndexBuffer(quadIB); // 这里写错过,s_Data.QuadVertexArray,即没有给circle的顶点数组缓存设置索引
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

刘建杰

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

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

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

打赏作者

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

抵扣说明:

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

余额充值