From 1b979252db08e620b038b1e105f244fa167a1be9 Mon Sep 17 00:00:00 2001 From: Nick Fisher Date: Tue, 17 Dec 2024 14:00:41 +0800 Subject: [PATCH] feat: re-implement grid overlay --- .../macos/Runner/RunnerDebug.entitlements | 12 + materials/grid.mat | 127 +- materials/shared.h | 4 + thermion_dart/native/include/material/grid.S | 2 +- .../native/include/material/grid.apple.S | 2 +- .../native/include/material/grid.bin | Bin 44564 -> 47596 bytes thermion_dart/native/include/material/grid.c | 4297 +++++++++-------- .../native/include/scene/GridOverlay.hpp | 4 +- thermion_dart/native/src/GridOverlay.cpp | 120 +- 9 files changed, 2403 insertions(+), 2165 deletions(-) create mode 100644 examples/flutter/camera_manipulation/macos/Runner/RunnerDebug.entitlements create mode 100644 materials/shared.h diff --git a/examples/flutter/camera_manipulation/macos/Runner/RunnerDebug.entitlements b/examples/flutter/camera_manipulation/macos/Runner/RunnerDebug.entitlements new file mode 100644 index 00000000..dddb8a30 --- /dev/null +++ b/examples/flutter/camera_manipulation/macos/Runner/RunnerDebug.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/materials/grid.mat b/materials/grid.mat index e07d7345..e224df3c 100644 --- a/materials/grid.mat +++ b/materials/grid.mat @@ -1,57 +1,118 @@ + material { name : Grid, parameters : [ { - type : float, - name : maxDistance + type: float, + name: distance }, { - type : float3, - name : color + type: float, + name: lineSize } ], - depthWrite : false, + depthWrite : true, depthCulling : true, + doubleSided: false, shadingModel : unlit, - blending: opaque, - variantFilter : [ skinning, shadowReceiver, vsm ], - culling: none, - instanced: false, - vertexDomain: object + blending : transparent, + variantFilter : [ dynamicLighting, directionalLighting, shadowReceiver, skinning, ssr, stereo ], + culling : none, + instanced : false, + vertexDomain : object } vertex { + void materialVertex(inout MaterialVertexInputs material) { - vec4 modelSpace = getPosition(); - vec4 worldSpace = getWorldFromModelMatrix() * modelSpace; - vec4 clipSpace = getClipFromWorldMatrix() * worldSpace; - clipSpace.z = 0.02f; - material.worldPosition = getWorldFromClipMatrix() * clipSpace; + vec3 position = getPosition().xyz; + position.xz *= materialParams.distance; + material.worldPosition.xz = position.xz; } } fragment { + + #include "shared.h" + + /* the below has been adapted from Blender's overlay_grid_frag.glsl */ + + #define _1_DIV_SQRTPI 0.5641895835477563 + #define RADIUS (_1_DIV_SQRTPI * 1.05) + #define GRID_START (0.5 + RADIUS) + #define GRID_END (0.5 - RADIUS) + #define GRID_STEP(dist) smoothstep(GRID_START, GRID_END, dist) + + float getGrid(vec2 point, vec2 fwidthCos, vec2 gridScale, float lineSize) { + vec2 halfSize = gridScale / 2.0; + vec2 gridDomain = abs(mod(point + halfSize, gridScale) - halfSize); + gridDomain /= fwidthCos; + float lineDist = min(gridDomain.x, gridDomain.y); + return GRID_STEP(lineDist - lineSize); + } + + vec3 getAxes(vec3 point, vec3 fwidthCos, float line_size) + { + vec3 axes_domain = abs(point); + axes_domain /= fwidthCos; + return GRID_STEP(axes_domain - (line_size + materialParams.lineSize)); + } + void material(inout MaterialInputs material) { prepareMaterial(material); + + vec3 P = getWorldPosition().xyz; + vec3 dFdxPos = dFdx(P); + vec3 dFdyPos = dFdy(P); + vec3 fwidthPos = abs(dFdxPos) + abs(dFdyPos); + + P += mulMat4x4Float3(getUserWorldFromWorldMatrix(), getWorldCameraPosition()).xyz; + + vec3 V = getWorldPosition().xyz; + float dist = length(V); + V /= dist; - // Convert world position to view space - float4 viewPos = getViewFromWorldMatrix() * float4(getWorldPosition(), 1.0); + float angle = V.y; + + angle = 1.0 - abs(angle); + angle *= angle; + float fade = 1.0 - angle; + fade *= 0.5 - smoothstep(0.0, materialParams.distance, dist - materialParams.distance); + + float gridA = getGrid(P.xz, fwidthPos.xz, vec2(1.0, 1.0), materialParams.lineSize); + + vec3 planeAxes = vec3(1.0f, 0.0f, 1.0f); + + vec3 distanceToAxes = vec3( + dot(P.yz, planeAxes.yz), + 0.0f, + dot(P.xy, planeAxes.xy) + ); + + vec3 dAxes = vec3( + dot(fwidthPos.yz, planeAxes.yz), + 0.0f, + dot(fwidthPos.xy, planeAxes.xy) + ); + + vec3 axes = getAxes(distanceToAxes, dAxes, 0.1); + + vec4 color = vec4( + 0.1f, + 0.1f, + 0.1f, + gridA + ); + + color.a = max(color.a, axes.x); + color.rgb = (axes.x < 1e-8) ? color.rgb : AXIS_COLOR_X; + + color.a = max(color.a, axes.z); + color.rgb = (axes.z < 1e-8) ? color.rgb : AXIS_COLOR_Z; + + color.a *= fade; + + material.baseColor = color; - // Calculate distance in view space (camera is at 0,0,0 in view space) - float distance = length(viewPos.xz); - - // Discard fragment if it's too far from the camera - if (distance > materialParams.maxDistance) { - material.baseColor = float4(0.0); - } else { - material.baseColor = float4(materialParams.color, 1.0); - - // Optional: fade out as we approach maxDistance - float fadeStart = materialParams.maxDistance * 0.8; - if (distance > fadeStart) { - float fade = 1.0 - (distance - fadeStart) / (materialParams.maxDistance - fadeStart); - material.baseColor.a = fade; - } - } } } \ No newline at end of file diff --git a/materials/shared.h b/materials/shared.h new file mode 100644 index 00000000..1a382997 --- /dev/null +++ b/materials/shared.h @@ -0,0 +1,4 @@ +#define linearstep(p0, p1, v) (clamp(((v) - (p0)) / abs((p1) - (p0)), 0.0, 1.0)) +#define AXIS_COLOR_X vec3(1.0f, 0.0f, 0.0f) +#define AXIS_COLOR_Y vec3(0.0f, 1.0f, 0.0f) +#define AXIS_COLOR_Z vec3(0.0f, 0.0f, 1.0f) \ No newline at end of file diff --git a/thermion_dart/native/include/material/grid.S b/thermion_dart/native/include/material/grid.S index 8250823f..1b5b59e8 100644 --- a/thermion_dart/native/include/material/grid.S +++ b/thermion_dart/native/include/material/grid.S @@ -8,5 +8,5 @@ GRID_PACKAGE: GRID_GRID_OFFSET: .int 0 GRID_GRID_SIZE: - .int 44564 + .int 47596 diff --git a/thermion_dart/native/include/material/grid.apple.S b/thermion_dart/native/include/material/grid.apple.S index 936ce2e2..895340fe 100644 --- a/thermion_dart/native/include/material/grid.apple.S +++ b/thermion_dart/native/include/material/grid.apple.S @@ -8,5 +8,5 @@ _GRID_PACKAGE: _GRID_GRID_OFFSET: .int 0 _GRID_GRID_SIZE: - .int 44564 + .int 47596 diff --git a/thermion_dart/native/include/material/grid.bin b/thermion_dart/native/include/material/grid.bin index 2a5e25da24746617a31c759bdbd0399c069608a9..b84dc9d2d10eba62b69173fd81408fb12241e951 100644 GIT binary patch literal 47596 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 literal 44564 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 #include #include "scene/SceneAsset.hpp" +#include "material/grid.h" + namespace thermion { @@ -15,7 +17,7 @@ using namespace filament; class GridOverlay : public SceneAsset { public: - GridOverlay(Engine& engine); + GridOverlay(Engine& engine, Material* material); ~GridOverlay(); SceneAssetType getType() override { return SceneAsset::SceneAssetType::Gizmo; } diff --git a/thermion_dart/native/src/GridOverlay.cpp b/thermion_dart/native/src/GridOverlay.cpp index dc0208a4..5f32574e 100644 --- a/thermion_dart/native/src/GridOverlay.cpp +++ b/thermion_dart/native/src/GridOverlay.cpp @@ -1,17 +1,14 @@ #include "scene/GridOverlay.hpp" #include "scene/SceneManager.hpp" -#include "material/grid.h" #include "Log.hpp" namespace thermion { - GridOverlay::GridOverlay(Engine &engine) : _engine(engine) + GridOverlay::GridOverlay(Engine &engine, Material *material) : _engine(engine), _material(material) { createGrid(); createSphere(); - _childEntities[0] = _gridEntity; - _childEntities[1] = _sphereEntity; } GridOverlay::~GridOverlay() @@ -31,88 +28,98 @@ namespace thermion void GridOverlay::createGrid() { - const int gridSize = 100; - const float gridSpacing = 1.0f; - int vertexCount = (gridSize + 1) * 4; // 2 axes, 2 vertices per line + const float stepSize = 0.25f; + const int gridSize = 8; // Number of grid cells in each direction (-1 to 1 with 0.25 step = 8 cells) + const int vertexCount = gridSize * gridSize * 4; // 4 vertices per grid cell + const int indexCount = gridSize * gridSize * 6; // 6 indices (2 triangles) per grid cell - float *gridVertices = new float[vertexCount * 3]; - int index = 0; + std::vector *vertices = new std::vector(); + std::vector *indices = new std::vector(); + vertices->reserve(vertexCount); + indices->reserve(indexCount); - // Create grid lines - for (int i = 0; i <= gridSize; ++i) + // Generate grid vertices and indices + for (float x = -1.0f; x < 1.0f; x += stepSize) { - float pos = i * gridSpacing - (gridSize * gridSpacing / 2); + for (float z = -1.0f; z < 1.0f; z += stepSize) + { + uint32_t baseIndex = vertices->size(); - // X-axis lines - gridVertices[index++] = pos; - gridVertices[index++] = 0; - gridVertices[index++] = -(gridSize * gridSpacing / 2); + // Add four vertices for this grid cell + vertices->push_back({x, 0.0f, z}); // Bottom-left + vertices->push_back({x, 0.0f, z + stepSize}); // Top-left + vertices->push_back({x + stepSize, 0.0f, z + stepSize}); // Top-right + vertices->push_back({x + stepSize, 0.0f, z}); // Bottom-right - gridVertices[index++] = pos; - gridVertices[index++] = 0; - gridVertices[index++] = (gridSize * gridSpacing / 2); - - // Z-axis lines - gridVertices[index++] = -(gridSize * gridSpacing / 2); - gridVertices[index++] = 0; - gridVertices[index++] = pos; - - gridVertices[index++] = (gridSize * gridSpacing / 2); - gridVertices[index++] = 0; - gridVertices[index++] = pos; + // Add indices for two triangles + indices->push_back(baseIndex); + indices->push_back(baseIndex + 1); + indices->push_back(baseIndex + 2); + indices->push_back(baseIndex + 2); + indices->push_back(baseIndex + 3); + indices->push_back(baseIndex); + } } auto vb = VertexBuffer::Builder() - .vertexCount(vertexCount) + .vertexCount(vertices->size()) .bufferCount(1) .attribute(VertexAttribute::POSITION, 0, VertexBuffer::AttributeType::FLOAT3) .build(_engine); - vb->setBufferAt(_engine, 0, VertexBuffer::BufferDescriptor(gridVertices, vertexCount * sizeof(math::float3), [](void *buffer, size_t size, void *) - { delete[] static_cast(buffer); })); - - uint32_t *gridIndices = new uint32_t[vertexCount]; - for (uint32_t i = 0; i < vertexCount; ++i) - { - gridIndices[i] = i; - } + vb->setBufferAt(_engine, 0, + VertexBuffer::BufferDescriptor( + vertices->data(), + vertices->size() * sizeof(math::float3), + [](void *buffer, size_t size, void *user) { + delete static_cast*>(user); + }, vertices)); auto ib = IndexBuffer::Builder() - .indexCount(vertexCount) + .indexCount(indices->size()) .bufferType(IndexBuffer::IndexType::UINT) .build(_engine); - ib->setBuffer(_engine, IndexBuffer::BufferDescriptor( - gridIndices, - vertexCount * sizeof(uint32_t), - [](void *buffer, size_t size, void *) - { delete[] static_cast(buffer); })); + ib->setBuffer(_engine, + IndexBuffer::BufferDescriptor( + indices->data(), + indices->size() * sizeof(uint32_t), + [](void *buffer, size_t size, void *user) { + delete static_cast*>(user); + }, indices)); _gridEntity = utils::EntityManager::get().create(); - _material = Material::Builder() - .package(GRID_PACKAGE, GRID_GRID_SIZE) - .build(_engine); _materialInstance = _material->createInstance(); - _materialInstance->setParameter("maxDistance", 50.0f); - _materialInstance->setParameter("color", math::float3{0.05f, 0.05f, 0.05f}); + + // Set material parameters to match Dart implementation + _materialInstance->setParameter("distance", 10000.0f); + _materialInstance->setParameter("lineSize", 0.01f); + _materialInstance->setCullingMode(MaterialInstance::CullingMode::NONE); RenderableManager::Builder(1) - .boundingBox({{-gridSize * gridSpacing / 2, 0, -gridSize * gridSpacing / 2}, - {gridSize * gridSpacing / 2, 0, gridSize * gridSpacing / 2}}) + .boundingBox({{-1.0f, -1.0f, -1.0f}, // Min point + {1.0f, 1.0f, 1.0f}}) // Max point + .geometry(0, RenderableManager::PrimitiveType::TRIANGLES, vb, ib, 0, indices->size()) .material(0, _materialInstance) - .geometry(0, RenderableManager::PrimitiveType::LINES, vb, ib, 0, vertexCount) - .priority(7) + .priority(1) .layerMask(0xFF, 1u << SceneManager::LAYERS::OVERLAY) - .culling(true) + /* + We disable culling here because we calculate the quad's world-space coordinates + manually in the shader (see grid.mat). Without this, the quad would be culled before + rendered. + */ + .culling(false) .receiveShadows(false) .castShadows(false) .build(_engine, _gridEntity); + + _childEntities[0] = _gridEntity; } void GridOverlay::createSphere() { - const float sphereRadius = 0.05f; + const float sphereRadius = 1.05f; const int sphereSegments = 16; const int sphereRings = 16; @@ -195,14 +202,15 @@ namespace thermion .receiveShadows(false) .castShadows(false) .build(_engine, _sphereEntity); + _childEntities[1] = _sphereEntity; } SceneAsset *GridOverlay::createInstance(MaterialInstance **materialInstances, size_t materialInstanceCount) { - auto instance = std::make_unique(_engine); + auto instance = std::make_unique(_engine, _material); auto *raw = instance.get(); _instances.push_back(std::move(instance)); - return reinterpret_cast(raw); + return reinterpret_cast(raw); } void GridOverlay::addAllEntities(Scene *scene)