首页 > 其他分享 >从零开始做一个软渲染器——视锥剔除、齐次裁剪和背面剔除

从零开始做一个软渲染器——视锥剔除、齐次裁剪和背面剔除

时间:2024-03-04 23:36:17浏览次数:32  
标签:ScreenPos 渲染器 v1 v2 v3 视锥 return 剔除 V2F

从零开始做一个软渲染器——视锥剔除、齐次裁剪和背面剔除

项目地址:https://github.com/DogWealth/PIRenderer

1. 视锥剔除

最简单的视锥剔除只需要在齐次空间(也就是透视投影变换之后,透视除法之前)中对三角形的顶点判断是否满足如下条件

\[-w\le x \le w \\ -w\le y \le w \\ -w\le z \le w \]

如果有顶点不满足这个条件,可以直接简单粗暴的把整个三角形给剔除掉。

bool Renderer::AllInsidePlane(const V2F& v1, const V2F& v2, const V2F& v3)
{
    if (v1.m_ScreenPos.x > v1.m_ScreenPos.w || v1.m_ScreenPos.x < -v1.m_ScreenPos.w) return false;
    if (v1.m_ScreenPos.y > v1.m_ScreenPos.w || v1.m_ScreenPos.y < -v1.m_ScreenPos.w) return false;
    if (v1.m_ScreenPos.z > v1.m_ScreenPos.w || v1.m_ScreenPos.z < -v1.m_ScreenPos.w) return false;
    if (v2.m_ScreenPos.x > v2.m_ScreenPos.w || v2.m_ScreenPos.x < -v2.m_ScreenPos.w) return false;
    if (v2.m_ScreenPos.y > v2.m_ScreenPos.w || v2.m_ScreenPos.y < -v2.m_ScreenPos.w) return false;
    if (v2.m_ScreenPos.z > v2.m_ScreenPos.w || v2.m_ScreenPos.z < -v2.m_ScreenPos.w) return false;
    if (v3.m_ScreenPos.x > v3.m_ScreenPos.w || v3.m_ScreenPos.x < -v3.m_ScreenPos.w) return false;
    if (v3.m_ScreenPos.y > v3.m_ScreenPos.w || v3.m_ScreenPos.y < -v3.m_ScreenPos.w) return false;
    if (v3.m_ScreenPos.z > v3.m_ScreenPos.w || v3.m_ScreenPos.z < -v3.m_ScreenPos.w) return false;

    return true;
}
void Renderer::DrawMesh(Mesh* mesh)
{
    const std::vector<Vertex>& vertexs = mesh->GetVertexBuffer();

    if (vertexs.size() == 0) return;

    for (int i = 0; i < vertexs.size(); i += 3)
    {
        V2F v1 = m_Shader->VertexShader(vertexs[i]);
        V2F v2 = m_Shader->VertexShader(vertexs[i + 1]);
        V2F v3 = m_Shader->VertexShader(vertexs[i + 2]);
        
		//视锥剔除
        if (!AllInsidePlane(v1, v2, v3)) continue;

        PerspectiveDivision(&v1);
        PerspectiveDivision(&v2);
        PerspectiveDivision(&v3);

        //back face culling
        if (!FaceCulling(v1.m_ScreenPos, v2.m_ScreenPos, v3.m_ScreenPos)) continue;

        ViewPort(&v1.m_ScreenPos);
        ViewPort(&v2.m_ScreenPos);
        ViewPort(&v3.m_ScreenPos);

        DrawTriangle(&v1, &v2, &v3);
    }
}

效果如下图,过度很不自然,整个地板突然就消失了

culling.gif

2. 齐次裁剪

更好的做法是对超出视锥体的三角形进行裁剪,裁剪后会生成更多的三角形。

原理参考下面的文章:

bool Renderer::InsidePlane(const Vector3f& plane, const Vector3f& pos)
{
    return (plane.x * pos.x + plane.y * pos.y + plane.z * pos.z + plane.w * pos.w) >= 0;
}

bool Renderer::AllInsidePlane(const V2F& v1, const V2F& v2, const V2F& v3)
{
    if (v1.m_ScreenPos.x > v1.m_ScreenPos.w || v1.m_ScreenPos.x < -v1.m_ScreenPos.w) return false;
    if (v1.m_ScreenPos.y > v1.m_ScreenPos.w || v1.m_ScreenPos.y < -v1.m_ScreenPos.w) return false;
    if (v1.m_ScreenPos.z > v1.m_ScreenPos.w || v1.m_ScreenPos.z < -v1.m_ScreenPos.w) return false;
    if (v2.m_ScreenPos.x > v2.m_ScreenPos.w || v2.m_ScreenPos.x < -v2.m_ScreenPos.w) return false;
    if (v2.m_ScreenPos.y > v2.m_ScreenPos.w || v2.m_ScreenPos.y < -v2.m_ScreenPos.w) return false;
    if (v2.m_ScreenPos.z > v2.m_ScreenPos.w || v2.m_ScreenPos.z < -v2.m_ScreenPos.w) return false;
    if (v3.m_ScreenPos.x > v3.m_ScreenPos.w || v3.m_ScreenPos.x < -v3.m_ScreenPos.w) return false;
    if (v3.m_ScreenPos.y > v3.m_ScreenPos.w || v3.m_ScreenPos.y < -v3.m_ScreenPos.w) return false;
    if (v3.m_ScreenPos.z > v3.m_ScreenPos.w || v3.m_ScreenPos.z < -v3.m_ScreenPos.w) return false;

    return true;
}

V2F Renderer::Intersect(const V2F& v1, const V2F& v2, const Vector3f& plane)
{
    float d1 =	v1.m_ScreenPos.x * plane.x + 
                v1.m_ScreenPos.y * plane.y + 
                v1.m_ScreenPos.z * plane.z + 
                v1.m_ScreenPos.w * plane.w;

    float d2 =	v2.m_ScreenPos.x * plane.x +
                v2.m_ScreenPos.y * plane.y +
                v2.m_ScreenPos.z * plane.z +
                v2.m_ScreenPos.w * plane.w;

    float t = d1 / (d1 - d2);

    V2F v;
    V2F::Interpolate(&v, v1, v2, t);
    return v;
}

std::vector<V2F> Renderer::SutherlandHodgeman(const V2F& v1, const V2F& v2, const V2F& v3)
{
    std::vector<V2F> output = { v1, v2, v3 };

    if (AllInsidePlane(v1, v2, v3)) return output;

    for (auto& plane : Viewplanes)
    {
        std::vector<V2F> input = output;
        output.clear();
        for (int i = 0; i < input.size(); i++)
        {
            V2F cur = input[i];
            V2F nex = input[(i + 1) % input.size()];
            if (InsidePlane(plane, cur.m_ScreenPos))
            {
                output.push_back(input[i]);
                if (!InsidePlane(plane, nex.m_ScreenPos))
                {
                    output.push_back(Intersect(cur, nex, plane));
                }
            }
            else if (InsidePlane(plane, nex.m_ScreenPos))
            {
                output.push_back(Intersect(cur, nex, plane));
            }
        }
    }

    return output;
}
void Renderer::DrawMesh(Mesh* mesh)
{
    const std::vector<Vertex>& vertexs = mesh->GetVertexBuffer();

    if (vertexs.size() == 0) return;

    for (int i = 0; i < vertexs.size(); i += 3)
    {
        V2F v1 = m_Shader->VertexShader(vertexs[i]);
        V2F v2 = m_Shader->VertexShader(vertexs[i + 1]);
        V2F v3 = m_Shader->VertexShader(vertexs[i + 2]);


        //视锥剔除,齐次裁剪
        auto vertices = SutherlandHodgeman(v1, v2, v3);

        for (int j = 0; j < int(vertices.size() - 2); j++)
        {
            v1 = vertices[0];
            v2 = vertices[j + 1];
            v3 = vertices[j + 2];

            PerspectiveDivision(&v1);
            PerspectiveDivision(&v2);
            PerspectiveDivision(&v3);

            //back face culling
            if (!FaceCulling(v1.m_ScreenPos, v2.m_ScreenPos, v3.m_ScreenPos)) continue;

            ViewPort(&v1.m_ScreenPos);
            ViewPort(&v2.m_ScreenPos);
            ViewPort(&v3.m_ScreenPos);

            DrawTriangle(&v1, &v2, &v3);
        }
    }
}

最终效果:

Homogeneous_clipping.gif

需要注意vector中生成点的排列顺序很重要,不然乱序的三角形的节点会影响三角形的绘制以及背面剔除,如下所示:

Homogeneous_clipping_err.gif

3. 背面剔除

参考文章:

//背面剔除
bool Renderer::FaceCulling(const Vector3f& v1, const Vector3f v2, const Vector3f v3)
{
    Vector3f v12 = v2 - v1;
    Vector3f v13 = v3 - v1;
    Vector3f view = { 0, 0, 1 };

    Vector3f normal = Vector3f::CrossProduct(v12, v13);

    return (normal * view) > 0;
}

开启背面剔除后帧率稳定在80FPS作用,不开的时候70FPS

标签:ScreenPos,渲染器,v1,v2,v3,视锥,return,剔除,V2F
From: https://www.cnblogs.com/dogwealth/p/18053008

相关文章

  • 从0开始做一个软渲染器——透视投影和投影矫正
    从0开始做一个软渲染器——透视投影和投影矫正已经做了一段时间了,一直都没记录。最近实现了一个透视投影的相机,从这一部分记录。项目地址:https://github.com/DogWealth/PIRenderer需要注意的是:以下代码的运算都将向量考虑成行向量,进行从左往右的乘法运算。相比列向量,对应的相......
  • 再聊阴影裁剪与高性能视锥剔除
    【USparkle专栏】如果你深怀绝技,爱“搞点研究”,乐于分享也博采众长,我们期待你的加入,让智慧的火花碰撞交织,让知识的传递生生不息!一、实际需求因为项目的树与草都采用ComputeShader剔除的GPUInstance绘制,所以需要自己实现阴影投递物的裁剪方法。也就是每一帧具体让哪些物体绘......
  • # yyds干货盘点 # Pandas中想剔除字符串中的【第】和【批】这两个字如何做?
    大家好,我是皮皮。一、前言前几天在Python白银交流群【东哥】问了一个Pandas数据处理的问题。问题如下所示:大佬们,有个奇怪的问题请教下,我想剔除字符串中的【第】和【批】这两个字,我写成df["合同名称"]=df["合同名称"].str.replace("第","").replace("批",""),结果只是替换了【第......
  • oc渲染器初始参数怎么设置?oc渲染器初始参数怎么弄
    ​OC渲染器以其用户友好的界面、卓越的渲染品质而受到众多初学者的欢迎,而且它使得创建逼真的视觉效果变得轻而易举。对于产品展示、建筑设计以及室内布局渲染来说,OC渲染器都能表现出优异的性能。下面,我们将介绍新手如何进行OC渲染器的基本初始设置,以便顺利开始他们的渲染之旅!oc渲......
  • 如何在FBX剔除Lit.shader依赖
    1)如何在FBX剔除Lit.shader依赖2)Unity出AAB包(PlayAssetDelivery)模式下加载资源过慢问题3)如何在URP中正确打出Shader变体4)XLua打包Lua文件粒度问题这是第371篇UWA技术知识分享的推送,精选了UWA社区的热门话题,涵盖了UWA问答、社区帖子等技术知识点,助力大家更全面地掌握和学习。UWA......
  • 一个简单的Vulkan渲染器管线组织
    目录预计算管线逐帧管线逐帧预计算部分ShadowDepthPass逐帧计算部分G-BufferPassAOPassSSAOShadowPassCSMLightingPassPBR-IBLPassCompositePass总结问题参考资料延迟渲染管线核心在于先记录一遍每个像素上的信息,如Material、Normal、Albedo等,即G-Buffer,世界空间坐标可通......
  • rocketmq--如何做路由发现、注册、剔除的
    RocketMQ的NameServer是一个轻量级的服务,负责维护关于Broker的路由信息和提供路由查询服务。以下是NameServer在Broker管理、路由发现、路由注册和路由剔除方面的工作机制:Broker管理:Broker在启动时会向所有的NameServer发送注册请求,包含自己的地址、存储的队列......
  • 剔除任意指定参数配置
    只需要修改需要剔除的参数key(如:redirectUrl)#剔除$args中的redirectUrl参数server{listen80;server_namewww.mynginx.rewrite;location/search{#在参数前加入&,并赋值给args_tmpset$args_tmp&${args};#正则判断进行剔......
  • RTSP流截图并剔除花屏图片
    大致代码如下:importcv2importnumpyasnpfromfastapiimportHTTPExceptionRgbRangeType=tuple[tuple[int,int,int],tuple[int,int,int]]classValidationError(HTTPException):def__init__(self,detail:str,status_code=400)->None:supe......
  • NLog 配置文件中布局渲染器(layout renderers)
    ​ NLog配置文件中,布局渲染器(layoutrenderers)是一种机制,用于在日志消息中插入动态内容或格式化信息。它们允许您将变量、属性、日期时间信息等添加到日志消息中,以便更详细地记录和分析日志。布局渲染器是在${}中包含的占位符,会在运行时替换为实际值。1、所有的布局参数......