浏览代码

Implement frustum-OBB culling

/main
Evgenii Golubev 7 年前
当前提交
840edb50
共有 4 个文件被更改,包括 171 次插入23 次删除
  1. 40
      ScriptableRenderPipeline/HDRenderPipeline/HDRP/Camera/HDCamera.cs
  2. 3
      ScriptableRenderPipeline/HDRenderPipeline/HDRP/HDRenderPipeline.cs
  3. 8
      ScriptableRenderPipeline/HDRenderPipeline/HDRP/Lighting/Volumetrics/HomogeneousFog.cs
  4. 143
      ScriptableRenderPipeline/HDRenderPipeline/HDRP/Lighting/Volumetrics/VolumetricLighting.cs

40
ScriptableRenderPipeline/HDRenderPipeline/HDRP/Camera/HDCamera.cs


public Vector4 screenSize;
public Plane[] frustumPlanes;
public Vector4[] frustumPlaneEquations;
public Vector3[] frustumCorners;
public Camera camera;
public uint taaFrameIndex;
public Vector2 taaFrameRotation;

public HDCamera(Camera cam)
{
camera = cam;
frustumPlanes = new Plane[6];
frustumPlaneEquations = new Vector4[6];
camera = cam;
m_AdditionalCameraData = cam.GetComponent<HDAdditionalCameraData>();
frustumPlanes = new Plane[6];
frustumPlaneEquations = new Vector4[6];
frustumCorners = new Vector3[8];
m_AdditionalCameraData = cam.GetComponent<HDAdditionalCameraData>();
Reset();
}

}
// Warning: near and far planes appear to be broken (or rather far plane seems broken)
// Also note: frustum plane normals are inward-facing.
for (int i = 0; i < 4; i++)
Vector3 forward = -viewMatrix.GetRow(2); // camera.forward is not always available
// Near, far.
frustumPlanes[4].normal = forward;
frustumPlanes[4].distance = -Vector3.Dot(forward, relPos) - camera.nearClipPlane;
frustumPlanes[5].normal = -forward;
frustumPlanes[5].distance = Vector3.Dot(forward, relPos) + camera.farClipPlane;
// Left, right, top, bottom, near, far.
for (int i = 0; i < 6; i++)
// Left, right, top, bottom.
// Near, far.
Vector4 forward = (camera.cameraType == CameraType.Reflection) ? camera.worldToCameraMatrix.GetRow(2) : new Vector4(camera.transform.forward.x, camera.transform.forward.y, camera.transform.forward.z, 0.0f);
// We need to switch forward direction based on handness (Reminder: Regular camera have a negative determinant in Unity and reflection probe follow DX convention and have a positive determinant)
forward = viewParam.x < 0.0f ? forward : -forward;
frustumPlaneEquations[4] = new Vector4( forward.x, forward.y, forward.z, -Vector3.Dot(forward, relPos) - camera.nearClipPlane);
frustumPlaneEquations[5] = new Vector4(-forward.x, -forward.y, -forward.z, Vector3.Dot(forward, relPos) + camera.farClipPlane);
Matrix4x4 invViewProjMatrix = viewProjMatrix.inverse;
// Unproject 8 frustum points.
frustumCorners[0] = invViewProjMatrix.MultiplyPoint(new Vector3(-1, -1, 1));
frustumCorners[1] = invViewProjMatrix.MultiplyPoint(new Vector3( 1, -1, 1));
frustumCorners[2] = invViewProjMatrix.MultiplyPoint(new Vector3(-1, 1, 1));
frustumCorners[3] = invViewProjMatrix.MultiplyPoint(new Vector3( 1, 1, 1));
frustumCorners[4] = invViewProjMatrix.MultiplyPoint(new Vector3(-1, -1, 0));
frustumCorners[5] = invViewProjMatrix.MultiplyPoint(new Vector3( 1, -1, 0));
frustumCorners[6] = invViewProjMatrix.MultiplyPoint(new Vector3(-1, 1, 0));
frustumCorners[7] = invViewProjMatrix.MultiplyPoint(new Vector3( 1, 1, 0));
m_LastFrameActive = Time.frameCount;

3
ScriptableRenderPipeline/HDRenderPipeline/HDRP/HDRenderPipeline.cs


}
}
// The pass only requires the volume properties, and can run async.
m_VolumetricLightingModule.VoxelizeDensityVolumes(hdCamera, cmd);
// Render the volumetric lighting.
// The pass requires the volume properties, the light list and the shadows, and can run async.
m_VolumetricLightingModule.VolumetricLightingPass(hdCamera, cmd, m_FrameSettings);

8
ScriptableRenderPipeline/HDRenderPipeline/HDRP/Lighting/Volumetrics/HomogeneousFog.cs


void OnDrawGizmos()
{
if (volumeParameters != null && !volumeParameters.IsVolumeUnbounded())
if (volumeParameters.IsLocalVolume())
Gizmos.DrawWireCube(volumeParameters.bounds.center, volumeParameters.bounds.size);
Gizmos.color = volumeParameters.albedo;
Gizmos.matrix = transform.localToWorldMatrix;
Gizmos.DrawWireCube(Vector3.zero, Vector3.one);
}
}

foreach (HomogeneousFog fogComponent in fogComponents)
{
if (fogComponent.enabled && fogComponent.volumeParameters.IsVolumeUnbounded())
if (fogComponent.enabled && !fogComponent.volumeParameters.IsLocalVolume())
{
globalFogComponent = fogComponent;
break;

143
ScriptableRenderPipeline/HDRenderPipeline/HDRP/Lighting/Volumetrics/VolumetricLighting.cs


[Serializable]
public class VolumeParameters
{
public Bounds bounds; // Position and dimensions in meters
public bool isLocal; // Enables voxelization
public Color albedo; // Single scattering albedo [0, 1]
public float meanFreePath; // In meters [1, inf]. Should be chromatic - this is an optimization!
public float asymmetry; // Single global parameter for all volumes. TODO: UX

bounds = new Bounds(Vector3.zero, Vector3.positiveInfinity);
isLocal = true;
public bool IsVolumeUnbounded()
public bool IsLocalVolume()
return bounds.size.x == float.PositiveInfinity &&
bounds.size.y == float.PositiveInfinity &&
bounds.size.z == float.PositiveInfinity;
return isLocal;
}
public Vector3 GetAbsorptionCoefficient()

public void Constrain()
{
bounds.size = Vector3.Max(bounds.size, Vector3.zero);
albedo.r = Mathf.Clamp01(albedo.r);
albedo.g = Mathf.Clamp01(albedo.g);
albedo.b = Mathf.Clamp01(albedo.b);

public long viewID = -1; // -1 is invalid; positive for Game Views, 0 otherwise
public RenderTexture[] lightingRTEX = null;
public RenderTargetIdentifier[] lightingRTID = null;
public RenderTexture densityRTEX = null;
public RenderTargetIdentifier densityRTID = -1; // RenderTargetIdentifier cannot be NULL
public RenderTargetIdentifier GetLightingIntegralBuffer() // Of the current frame
{

{
Debug.Assert(viewID > 0); // Game View only
return lightingRTID[1 + ((Time.renderedFrameCount + 1) & 1)];
}
public RenderTargetIdentifier GetDensityBuffer()
{
Debug.Assert(viewID >= 0);
return densityRTID;
}
public void Create(long viewID, int w, int h, int d)

cmd.SetGlobalVector( HDShaderIDs._VBufferDepthEncodingParams, ComputeLogarithmicDepthEncodingParams(m_VBufferNearPlane, m_VBufferFarPlane, k_LogScale));
cmd.SetGlobalVector( HDShaderIDs._VBufferDepthDecodingParams, ComputeLogarithmicDepthDecodingParams(m_VBufferNearPlane, m_VBufferFarPlane, k_LogScale));
cmd.SetGlobalTexture(HDShaderIDs._VBufferLighting, vBuffer.GetLightingIntegralBuffer());
}
struct OrientedBBox
{
// TODO: in the shader, merge axes and extents, and 16-byte align the data structure.
Vector3 center;
Vector3 right, up, forward; // X, Y, Z, normalized
Vector3 extents; // Size of the half-diagonal
public static OrientedBBox Create(Transform t)
{
return Create(t, Vector3.zero);
}
public static OrientedBBox Create(Transform t, Vector3 offset)
{
OrientedBBox obb = new OrientedBBox();
obb.center = t.position + offset;
obb.right = t.right;
obb.up = t.up;
obb.forward = t.forward;
obb.extents = t.localScale * 0.5f;
return obb;
}
// Returns 'true' if the OBB intersects (or is inside) the frustum, 'false' otherwise.
public bool OverlapsFrustum(Plane[] frustumPlanes, int numPlanes,
Vector3[] frustumCorners, int numCorners)
{
bool overlap = true;
// Test the OBB against frustum planes. Frustum planes have inward-facing.
// The OBB is outside if it's entirely behind one of the frustum planes.
// See "Real-Time Rendering", 3rd Edition, 16.10.2.
for (int i = 0; overlap && (i < numPlanes); i++)
{
Vector3 n = frustumPlanes[i].normal;
float d = frustumPlanes[i].distance;
// Max projection of the half-diagonal onto the normal (always positive).
float maxHalfDiagProj = extents.x * Mathf.Abs(Vector3.Dot(n, right))
+ extents.y * Mathf.Abs(Vector3.Dot(n, up))
+ extents.z * Mathf.Abs(Vector3.Dot(n, forward));
// Negative distance -> center behind the plane (outside).
float centerToPlaneDist = Vector3.Dot(n, center) + d;
// outside = maxHalfDiagProj < -centerToPlaneDist
// outside = maxHalfDiagProj + centerToPlaneDist < 0
// overlap = overlap && !outside
overlap = overlap && (maxHalfDiagProj + centerToPlaneDist >= 0);
}
if (numCorners == 0) return overlap;
// Test the frustum corners against OBB planes. The OBB planes are outward-facing.
// The frustum is outside if all of its corners are entirely in front of one of the OBB planes.
// See "Correct Frustum Culling" by Inigo Quilez.
// We can exploit the symmetry of the box by only testing against 3 planes rather than 6.
Plane[] planes = new Plane[3];
planes[0].normal = right;
planes[0].distance = extents.x;
planes[1].normal = up;
planes[1].distance = extents.y;
planes[2].normal = forward;
planes[2].distance = extents.z;
for (int i = 0; overlap && (i < 3); i++)
{
Plane plane = planes[i];
// We need a separate counter for the "box fully inside frustum" case.
bool outsidePos = true; // Positive normal
bool outsideNeg = true; // Reversed normal
// Merge 2 loops. Continue as long as all points are outside either plane.
for (int j = 0; (outsidePos || outsideNeg) && (j < numCorners); j++)
{
float proj = Vector3.Dot(plane.normal, frustumCorners[j] - center);
outsidePos = outsidePos && ( proj > plane.distance);
outsideNeg = outsideNeg && (-proj > plane.distance);
}
overlap = overlap && !(outsidePos || outsideNeg);
}
return overlap;
}
}
public void VoxelizeDensityVolumes(HDCamera camera, CommandBuffer cmd)
{
if (camera.camera.cameraType != CameraType.SceneView) return;
Vector3 camPosition = camera.camera.transform.position;
Vector3 camOffset = Vector3.zero; // World-origin-relative
if (ShaderConfig.s_CameraRelativeRendering != 0)
{
camOffset = -camPosition; // Camera-relative
}
HomogeneousFog[] fogComponents = Object.FindObjectsOfType(typeof(HomogeneousFog)) as HomogeneousFog[];
foreach (HomogeneousFog fogComponent in fogComponents)
{
// Only test active finite volumes.
if (fogComponent.enabled && fogComponent.volumeParameters.IsLocalVolume())
{
// Convert to the camera-relative coordinates if necessary.
// TODO: cache these?
var obb = OrientedBBox.Create(fogComponent.transform, camOffset);
// Frustum cull on the CPU for now. TODO: do it on the GPU.
if (!obb.OverlapsFrustum(camera.frustumPlanes, 6, camera.frustumCorners, 8))
{
Debug.Log("Culled.");
}
}
}
}
// Ref: https://en.wikipedia.org/wiki/Close-packing_of_equal_spheres

正在加载...
取消
保存