// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen // SPDX-License-Identifier: BSD-3-Clause /** * This test first creates a few non-overlapping triangles and then a bigger triangle that covers * some of the previous triangles. * * The occlusion culler is expected to render the first non-overlapping triangles but the bigger * triangle is expected to occlude some of the smaller triangles and thus they should be culled and * not rendered. * * The number of props rendered by the renderer + compute occlusion culler at each frame is then * compared to a reference list to make sure that the culler indeed culled (or not) props as it was * supposed to. */ #include "vtkActor.h" #include "vtkCamera.h" #include "vtkCullerCollection.h" #include "vtkInteractorStyleTrackballCamera.h" #include "vtkNew.h" #include "vtkObject.h" #include "vtkPointData.h" #include "vtkPolyDataMapper.h" #include "vtkProperty.h" #include "vtkRenderWindow.h" #include "vtkRenderWindowInteractor.h" #include "vtkRenderer.h" #include "vtkSphereSource.h" #include "vtkStringFormatter.h" #include "vtkUnsignedCharArray.h" #include "vtkWebGPUComputeOcclusionCuller.h" #include "vtkWebGPURenderWindow.h" #include "vtkXMLMultiBlockDataReader.h" //------------------------------------------------------------------------------ namespace { vtkSmartPointer CreateTriangle( double x1, double y1, double z1, double x2, double y2, double z2, double x3, double y3, double z3) { vtkSmartPointer polydata = vtkSmartPointer::New(); vtkSmartPointer points = vtkSmartPointer::New(); points->InsertPoint(0, x1, y1, z1); points->InsertPoint(1, x2, y2, z2); points->InsertPoint(2, x3, y3, z3); polydata->SetPoints(points); vtkSmartPointer triangle = vtkSmartPointer::New(); triangle->InsertNextCell({ 0, 1, 2 }); polydata->SetPolys(triangle); vtkSmartPointer colors = vtkSmartPointer::New(); colors->SetNumberOfComponents(4); colors->SetNumberOfTuples(3); for (int i = 0; i < 3; i++) { colors->InsertComponent(i, 0, 255); colors->InsertComponent(i, 1, 255); colors->InsertComponent(i, 2, 255); colors->InsertComponent(i, 3, 255); } polydata->GetPointData()->SetScalars(colors); vtkSmartPointer mapper = vtkSmartPointer::New(); mapper->DebugOn(); mapper->SetInputData(polydata); vtkSmartPointer actor = vtkSmartPointer::New(); actor->SetMapper(mapper); return actor; } void RenderNewTriangle(vtkRenderWindow* renWin, vtkRenderer* renderer, std::vector& renderedPropCounts, double x1, double y1, double z1, double x2, double y2, double z2, double x3, double y3, double z3) { renderer->AddActor(CreateTriangle(x1, y1, z1, x2, y2, z2, x3, y3, z3)); renWin->Render(); renderedPropCounts.push_back(renderer->GetNumberOfPropsRendered()); } void CheckRenderCount( const std::vector& renderedPropCounts, const std::vector& renderedPropCountsReference) { for (std::size_t i = 0; i < renderedPropCounts.size(); i++) { if (renderedPropCounts[i] != renderedPropCountsReference[i]) { std::string expectedSequence; std::string actualSequence; for (std::size_t seqIndex = 0; seqIndex < renderedPropCounts.size(); seqIndex++) { expectedSequence += vtk::to_string(renderedPropCountsReference[seqIndex]) + ", "; actualSequence += vtk::to_string(renderedPropCounts[seqIndex]) + ", "; } vtkLog(ERROR, "The right number of props wasn't rendered. Expected sequence of rendered props was: " << expectedSequence << " but the actual sequence was: " << actualSequence); std::exit(EXIT_FAILURE); } } } } //------------------------------------------------------------------------------ int TestComputeOcclusionCulling(int, char*[]) { // How many props are expected to be rendered at each frame (with modification of the props in // between the frames) std::vector renderedPropCountsReference = { 1, 2, 3, 4, 5, 1 }; // How many props were actually renderer std::vector renderedPropCounts; vtkNew renWin; renWin->SetWindowName(__func__); renWin->SetMultiSamples(0); renWin->SetSize(1280, 720); // Initialize() call necessary when a WebGPU compute class is going to use the render window. // Here, the OcclusionCuller internally uses the resources of the render window so Initialize() // must be called renWin->Initialize(); vtkSmartPointer renderer = vtkSmartPointer::New(); vtkNew camera; camera->SetFocalPoint(0, 0.25, -1); renderer->SetActiveCamera(camera); renderer->SetBackground(0.2, 0.3, 0.4); renWin->AddRenderer(renderer); // Removing the default culler renderer->GetCullers()->RemoveAllItems(); // Adding the WebGPU compute shader occlusion+frustum culler vtkNew webgpuOcclusionCuller; webgpuOcclusionCuller->SetRenderWindow(vtkWebGPURenderWindow::SafeDownCast(renWin)); renderer->GetCullers()->AddItem(webgpuOcclusionCuller); // Small triangle 1 RenderNewTriangle(renWin, renderer, renderedPropCounts, -1, 0, -5, -0.5, 0.0, -5, -0.75, 0.5, -5); CheckRenderCount(renderedPropCounts, renderedPropCountsReference); // Small triangle 2 RenderNewTriangle( renWin, renderer, renderedPropCounts, -0.5, 0, -5, 0.0, 0.0, -5, -0.25, 0.5, -5); CheckRenderCount(renderedPropCounts, renderedPropCountsReference); // Small triangle 3 RenderNewTriangle(renWin, renderer, renderedPropCounts, 0, 0, -5, 0.5, 0.0, -5, 0.25, 0.5, -5); CheckRenderCount(renderedPropCounts, renderedPropCountsReference); // Small triangle 4 RenderNewTriangle(renWin, renderer, renderedPropCounts, 0.5, 0, -5, 1.0, 0.0, -5, 0.75, 0.5, -5); CheckRenderCount(renderedPropCounts, renderedPropCountsReference); // Big triangle that covers all the small triangles. It is expected that that the first frame // rendered with the big triangle doesn't cull the small triangles RenderNewTriangle(renWin, renderer, renderedPropCounts, -1, -0.5, -1, 5.0, -0.5, -1, -1, 1.5, -1); CheckRenderCount(renderedPropCounts, renderedPropCountsReference); // However, if we render another frame, still with the big triangle in front, all the small // triangles should be culled renWin->Render(); renderedPropCounts.push_back(renderer->GetNumberOfPropsRendered()); CheckRenderCount(renderedPropCounts, renderedPropCountsReference); return EXIT_SUCCESS; }