• 引擎设计跟踪(九.14.3) deferred shading 准备


    目前做的一些准备工作

    1.depth prepass for forward shading.

    做depth prepass的原因是为了完善渲染流程, 虽然架构上支持多个pass, 但实际上从来没有测过. 等流程完善的差不多了, 理论上只需要修改xml配置和shader, 不需要修改C++代码就可以实现自定义渲染流程.

    2.linear depth

    默认情况下, 顶点经过mvp变换, 再单位化到NDC以后, 深度是非线性的, 其函数是一个(-) 1/(a + bx)的曲线, 导致靠近near clip plane的z值精度高, 远处精度低. 使用linear depth 以后深度变化都是均匀的.

    linear depth通常使用view space的z 再除以max view distance即far clip distance得到单位化的z,这个z值可以直接写入zbuffer, 用于depth test, 不过需要所有的shader都输出linear depth才能正常使用, 除了那些不需要深度写和深度测试的对象, 可以不用修改.

    目前已经将forward shading里面所有的depth都改成linear depth, 来做深度测试.

    另外需要注意的是如果是右手坐标系, 那么view 向量朝向-z轴, 所以view space的z都是负值, 如果按负值比较的话, depth test是反的. 所以shader里面需要取反.
    还有, 这个linear depth也可以用于线性插值, 所以可以在vertex shader里面计算出结果, 在pixel shader里面输出到深度.
    还有一个问题就是depth bias会出问题, 因为硬件中使用bias的公式是基于非线性depth来的. 解决方法: 因为depth在shader里面输出, 所以可以直接在shader里面直接修改depth的偏移.

    3.如果不用linear depth, 需要将NDC坐标unproject到world space, 即乘以 invViewProjection, 一次矩阵运算来得到world space.

    使用linear depth的话, 单位化的深度z*far clip distance就得到目标点距离视点的距离. 使用linear depth另外的一个好处就是, 可以使用一个ray向量, 乘以深度就可以得到坐标.

    通常这个操作习惯于在view space计算, 即
    viewPos = [view space origin +] view space ray * viewdepth
    viewPos = ray * viewDistance (后面有备注)

    由于view space的origin就是(0,0,0) 所以只需要一个ray就可以.

    但是由于历史原因, Blade的forward shading使用的全部是世界空间法线和世界空间位置, 为了使原有shader不做太大的修改, 所以想在在deferred shading时不在view space, 而再世界空间计算. 即将view space ray变换到世界空间, 即

    world pos = eyePos + worldRay * viewdepth  viewDistance
    实际上不需要将view space ray变换到world space. 因为在世界空间计算光照, 在vertex shader里面计算world pos, 那么

    worldRay = normalize(world pos - view pos);

    只需要在vertexshader里多传入一个uniform, 把相机的视点坐标传过来即可, 而后在pixel shader里面, 传入同样的uniform, 还原世界坐标:

    float3 worldRay : TEXCOORD1
    uniform float4 eye_position : EYE_POS    //eye pos in world space 
    
    ...
    //expand INTZ
    float depth = dot(tex2D(depthBuffer, depthUV).arg, float3(0.996093809371817670572857294849, 0.0038909914428586627756752238080039, 1.5199185323666651467481343000015e-5));
    float3 worldPos = eye_position.xyz + worldRay * depth;

    Directional light的优化

    如果方向光很多, 如果对每一个directional light都单独绘制一个quad, 做一个pass, 那么IO量太大. 所以目前使用的方法是一次IO, 迭代所有方向光.

    uniform int direcitonal_count;
    uniform float4 light_drections[MAX_DIR_LIGHT_COUNT];
    uniform float4 directional_diffuses[MAX_DIR_LIGHT_COUNT];
    uniform float4 directional_speculars[MAX_DIR_LIGHT_COUNT];
    for(int i = 0; i < directional_count; ++i)
    {
        color += shading(light_drections[i], diretional_diffuses[i], directioanl_speculars[i], normal, depth);
    }

    这样对于多个方向光, 只需要绘制一次quad, 采样一次GBuffer法线, 然后迭代多个光源.

    另外, 因为方向光比较特殊, 他的光照方向不需要world pos, 而计算half vector需要的viewdir已经由vertex shader计算好, 并完成插值, 实际上不需要world position, 所以也不需要采样depth buffer:

    for(int i = 0; i < light_directional_count; ++i)
    {
        float3 light_dir = light_directions[i].xyz;
        //direcitonal lights doesn't need world position, thus doesn't need depth sampling,
        //the depthBuffer sampling will be optimized by compiler.
        float3 half_vec = normalize(light_dir + (-worldRay));
        ... // calculate lighting
    }

    这样就能完成和forward shading兼容的, 使用世界空间计算光照的defered shading.


    目前deferred shading流程正在整理, deferred的方向光已经可以使用, 最终效果也跟forward shading一样. 因为编辑器里面还没有动态添加光源的功能, 所以后面会慢慢加上point和spot的支持.

    问题: forward shading现在vertex shader里面既要计算view space pos (linear depth的需要), 又要计算world space pos(计算光照), 这样矩阵乘法太多. 还是把forward sahding的光照改在view space计算光照比较好, 这样deferred shading也要改在view space.

    更新: linear viewdepth = mul(pos, worldViewMatrix).z / farclipDist
    实际上shader里面只需要 worldViewMatrix的第三列, 所以shader里面只要传入第三列, 那么

    viewDepth = dot(pos, worldViewMatrixCol3) / faclipDist, 而且worldViewMatrixCol3可以在CPU端预乘1/farClipDist, 这样计算view space normalized depth只需要一条指令, 这样的话没有多余的计算, 就不需要考虑在view space计算光照了.
    这是在CPU端计算view depth vector的方式: shader 里面一条dot指令就搞定了, 如果使用"mul(pos, worldViewMatrix).z / farclipDist"的话, 优化开到O3指令数为3, 目测为transpose+dot+除法.

    //////////////////////////////////////////////////////////////////////////
    //vector to calculate view space normalized depth
    //this is an optimization for shader, use dot(v, viewdepthvector) instead of mul(v,world_view_matrix).z / far_clip_distance
    class ViewDepthVectorUpdater : public InstanceVariableUpdater
    {
    public:
        ViewDepthVectorUpdater(WorldViewMatrixUpdater* worldViewUpdater)
            :InstanceVariableUpdater(SCT_FLOAT4, 1)
            ,mWorldViewUpdater(worldViewUpdater)
            ,mWorldViewMatrixCol3(Vector4::ZERO)
        {
    
        }
    
        /** @brief  */
        virtual const void* updateData() const
        {
            const ICamera* camera = IShaderVariableSource::getSingleton().getCamera();
            if( camera == NULL )
                return &Vector4::ZERO;
    
            const Matrix44& worldViewMat = *(const Matrix44*)mWorldViewUpdater->getVariable()->getData();
            mWorldViewMatrixCol3[0] = worldViewMat[0][2];
            mWorldViewMatrixCol3[1] = worldViewMat[1][2];
            mWorldViewMatrixCol3[2] = worldViewMat[2][2];
            mWorldViewMatrixCol3[3] = worldViewMat[3][2];
            //note: since we're using Right Hand axis, the view direction points at -Z,
            //view space position's z values are negative.
            //use this negative value to generate a positive z value into z buffer
            mWorldViewMatrixCol3 *= -1.0f / camera->getFarClipDistance();
            return &mWorldViewMatrixCol3;
        }
    
    protected:
        WorldViewMatrixUpdater* mWorldViewUpdater;
        mutable Vector4    mWorldViewMatrixCol3;
    };

    更新2:

    如果在pixel shdaer里面输出depth, 那么意味着pipeline必须走完pixel shader才能做depth test, 所以early Z完全失效, 同时也无法真正避免overdraw, 因为所有的pixel必须处理完成才能做depth dest, 这个时候depth pre pass已经毫无意义.

    所以尝试在vertex shader里面直接输出深度, 即POSITION.z.
    然而结果在某些情况下(靠近near plane的一些mesh, 地形倒没发现)会出现问题.

    改用了这个方法: https://www.mvps.org/directx/articles/linear_z/linearz.htm 结果仍然一样(其实思路差不多, 不同的是Blade里面 view.z = 0 对应depth的0, 而文中view.z = znear对应depth的0.

    最后发现, 因为虽然单个顶点输出了线性深度, 但相邻顶点的插值会有问题:

    http://www.yosoygames.com.ar/wp/2014/01/linear-depth-buffer-my-ass/ 

    文章中提到, 如果一定要在PixelShader写深度的话, 也建议使用高精度的 logarithmic z-buffer, 不过对于defered shading, 需要解出原始深度, 所以需要逆向求解, 目测可以实现, 需要使用pow.

    等研究一下有没有更好的办法再继续.


    更新3:

    由于无法在vertex shader里面输出linear depth, 而又不想在pixel shader输出深度(效率上的问题), 所以目前已经改回常规的depth, 并在deferred shading阶段把zbuffer的z值转换为view space的z, 这个根据projection matrix反向计算就可以得出, 需要注意的是采样zbuffer得出的z是[0,1], 符合d3d的NDC范围, 但是OGL是[-1,1]. 这个转换参数可以在CPU端计算, 不影响shader.

    具体的计算这里有个链接, 跟我的思路一样: http://www.derschmale.com/2014/01/26/reconstructing-positions-from-the-depth-buffer/

    RAWZ v.s. INTZ

    根据G80的doc, INTZ直接采样就得到深度, 只有RAWZ才需要从新构建深度. (http://developer.download.nvidia.com/GPU_Programming_Guide/GPU_Programming_Guide_G80.pdf)

    所以前面的INTZ的展开是不需要的, 直接用任意一个通道就可以.

  • 相关阅读:
    ORACLE创建数据库时无法创建目录
    KindEditor:Ajax提交表单时获取不到HTML内容
    mysql重置root密码
    假设检验-单样本检验
    推论统计分析-如何避免偏见和抽样分布
    推论统计分析1
    共享单车数据分析
    Kaggle泰坦尼克号生存情况预测
    R-长尾词练习
    R期望
  • 原文地址:https://www.cnblogs.com/crazii/p/5076469.html
Copyright © 2020-2023  润新知