Search Unity

Question How to control point light shadow cull mode?

Discussion in 'High Definition Render Pipeline' started by Jasper-Flick, Jun 28, 2020.

  1. Jasper-Flick

    Jasper-Flick

    Joined:
    Jan 17, 2011
    Posts:
    959
    By default, ScriptableRenderContext.DrawShadows changes the cull mode when rendering shadows for point lights. Cull Front becomes Cull Back and vice versa. This has been the (undocumented) behavior for a long time. It appears that this doesn't happen for HDRP.

    Can this be controlled somehow? I want to turn the inversion off for a custom SRP.

    How does HDRP do it? I haven't figured this out. There is CommandBuffer.SetInvertCulling, but ScriptableRenderContext.DrawShadows overrules it. Any way to do this other than force culling off for all shadows?
     
    JoNax97 likes this.
  2. Jasper-Flick

    Jasper-Flick

    Joined:
    Jan 17, 2011
    Posts:
    959
  3. Jasper-Flick

    Jasper-Flick

    Joined:
    Jan 17, 2011
    Posts:
    959
    @Aras I know you haven't been on graphics for a long time, but might you have a hunch about where the point light shadows cull mode reversal could be controlled?
     
  4. Aras

    Aras

    Unity Technologies

    Joined:
    Nov 7, 2005
    Posts:
    4,770
    I've no idea! I'll ask around.
     
  5. SebLagarde

    SebLagarde

    Unity Technologies

    Joined:
    Dec 30, 2015
    Posts:
    934
    Hi, so the work done have been done a very long time ago, and thus we don't have clear answer...
    I will just give you the information we were able to gather, but not sure which one will help.

    HDRP use an atlas and don't render a cubemap. We render six camera in an atlas.

    We use this code to get the matrix

    // Cubemap faces with flipped z coordinate.
    // These matrices do NOT match what we have in Skybox.cpp.
    // The C++ runtime flips y as well and requires patching up
    // the culling state. Using these matrices keeps the winding
    // order, but may need some special treatment if rendering
    // into an actual cubemap.
    public static readonly Matrix4x4[] kCubemapFaces = new Matrix4x4[]


    static Matrix4x4 ExtractPointLightMatrix(VisibleLight vl, uint faceIdx, float nearPlane, float guardAngle, out Matrix4x4 view, out Matrix4x4 proj, out Matrix4x4 deviceProj, out Matrix4x4 vpinverse, out Vector4 lightDir, out ShadowSplitData splitData)
    (...)
    // get lightDir
    lightDir = vl.GetForward();
    // calculate the view matrices
    Vector3 lpos = vl.GetPosition();
    view = kCubemapFaces[faceIdx];
    Vector3 inverted_viewpos = kCubemapFaces[faceIdx].MultiplyPoint(-lpos);
    view.SetColumn(3, new Vector4(inverted_viewpos.x, inverted_viewpos.y, inverted_viewpos.z, 1.0f));
    float nearZ = Mathf.Max(nearPlane, k_MinShadowNearPlane);
    proj = Matrix4x4.Perspective(90.0f + guardAngle, 1.0f, nearZ, vl.range);
    // and the compound (deviceProj will potentially inverse-Z)
    deviceProj = GL.GetGPUProjectionMatrix(proj, false);
    proj = GL.GetGPUProjectionMatrix(proj, true);
    InvertPerspective(ref deviceProj, ref view, out vpinverse);

    (...)

    on the c++ side it call

    Matrix4x4f GLGetGPUProjectionMatrix(const Matrix4x4f& proj, bool renderIntoTexture)
    {
    const bool openGLStyle = GetGraphicsCaps().usesOpenGLTextureCoords;
    Matrix4x4f m = proj;
    GetUncheckedRealGfxDevice().CalculateDeviceProjectionMatrix(m, openGLStyle, !openGLStyle && renderIntoTexture);
    return m;
    }

    The builtin unity use this code below

    // Rendering into a cubemap requires flipping the culling mode.
    // Do set regular culling mode for other light types as well, in case
    // the calling camera is already rendering into a cubemap and culling mode is
    // flipped. Restore the mode afterwards.
    bool oldBackfaceMode = device.GetUserBackfaceMode();
    device.SetUserBackfaceMode(kLightPoint == header->lightType);

    but this isn't used by HDRP

    ----------------------

    As a side note, for reflection cubemap we used:

    for (int i = 0; i < 6; ++i)
    {
    var lookAt = Matrix4x4.LookAt(Vector3.zero, CoreUtils.lookAtList, CoreUtils.upVectorList);
    var worldToView = lookAt * Matrix4x4.Scale(new Vector3(1.0f, 1.0f, -1.0f)); // Need to scale -1.0 on Z to match what is being done in the camera.wolrdToCameraMatrix API. ... m_facePixelCoordToViewDirMatrices = HDUtils.ComputePixelCoordToWorldSpaceViewDirectionMatrix(0.5f * Mathf.PI, Vector2.zero, m_CubemapScreenSize, worldToView, true);
    m_CameraRelativeViewMatrices = worldToView;
    }

    --------------

    internal static Matrix4x4 ComputePixelCoordToWorldSpaceViewDirectionMatrix(float verticalFoV, Vector2 lensShift, Vector4 screenSize, Matrix4x4 worldToViewMatrix, bool renderToCubemap, float aspectRatio = -1)
    {
    aspectRatio = aspectRatio < 0 ? screenSize.x * screenSize.w : aspectRatio; // Compose the view space version first.
    // V = -(X, Y, Z), s.t. Z = 1,
    // X = (2x / resX - 1) * tan(vFoV / 2) * ar = x * [(2 / resX) * tan(vFoV / 2) * ar] + [-tan(vFoV / 2) * ar] = x * [-m00] + [-m20]
    // Y = (2y / resY - 1) * tan(vFoV / 2) = y * [(2 / resY) * tan(vFoV / 2)] + [-tan(vFoV / 2)] = y * [-m11] + [-m21] float tanHalfVertFoV = Mathf.Tan(0.5f * verticalFoV); // Compose the matrix.
    float m21 = (1.0f - 2.0f * lensShift.y) * tanHalfVertFoV;
    float m11 = -2.0f * screenSize.w * tanHalfVertFoV; float m20 = (1.0f - 2.0f * lensShift.x) * tanHalfVertFoV * aspectRatio;
    float m00 = -2.0f * screenSize.z * tanHalfVertFoV * aspectRatio; if (renderToCubemap) <====
    {
    // Flip Y.
    m11 = -m11;
    m21 = -m21;
    } var viewSpaceRasterTransform = new Matrix4x4(new Vector4(m00, 0.0f, 0.0f, 0.0f),
    new Vector4(0.0f, m11, 0.0f, 0.0f),
    new Vector4(m20, m21, -1.0f, 0.0f),
    new Vector4(0.0f, 0.0f, 0.0f, 1.0f)); // Remove the translation component.
    var homogeneousZero = new Vector4(0, 0, 0, 1);
    worldToViewMatrix.SetColumn(3, homogeneousZero); // Flip the Z to make the coordinate system left-handed.
    worldToViewMatrix.SetRow(2, -worldToViewMatrix.GetRow(2)); // Transpose for HLSL.
    return Matrix4x4.Transpose(worldToViewMatrix.transpose * viewSpaceRasterTransform);
    }


    Hope those few pointer will help you. Getting thing correct with all the winding order and flip is a kind of black art.
     
  6. Jasper-Flick

    Jasper-Flick

    Joined:
    Jan 17, 2011
    Posts:
    959
    Thanks guys! That made me find it.

    HDRP deals with the old behavior by flipping a view dimension, which reverses triangle winding order. In doing so it gets around the built-in cull mode flipping for point-light shadows.

    I wanted to figure this out for my Point and Spot Shadows tutorial.
     
    Last edited: Jul 3, 2020