feat: re-implement grid overlay

This commit is contained in:
Nick Fisher
2024-12-17 14:00:41 +08:00
parent 852cb58ba9
commit 1b979252db
9 changed files with 2403 additions and 2165 deletions

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
</dict>
</plist>

View File

@@ -1,57 +1,118 @@
material { material {
name : Grid, name : Grid,
parameters : [ parameters : [
{ {
type : float, type: float,
name : maxDistance name: distance
}, },
{ {
type : float3, type: float,
name : color name: lineSize
} }
], ],
depthWrite : false, depthWrite : true,
depthCulling : true, depthCulling : true,
doubleSided: false,
shadingModel : unlit, shadingModel : unlit,
blending: opaque, blending : transparent,
variantFilter : [ skinning, shadowReceiver, vsm ], variantFilter : [ dynamicLighting, directionalLighting, shadowReceiver, skinning, ssr, stereo ],
culling: none, culling : none,
instanced: false, instanced : false,
vertexDomain: object vertexDomain : object
} }
vertex { vertex {
void materialVertex(inout MaterialVertexInputs material) { void materialVertex(inout MaterialVertexInputs material) {
vec4 modelSpace = getPosition(); vec3 position = getPosition().xyz;
vec4 worldSpace = getWorldFromModelMatrix() * modelSpace; position.xz *= materialParams.distance;
vec4 clipSpace = getClipFromWorldMatrix() * worldSpace; material.worldPosition.xz = position.xz;
clipSpace.z = 0.02f;
material.worldPosition = getWorldFromClipMatrix() * clipSpace;
} }
} }
fragment { 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) { void material(inout MaterialInputs material) {
prepareMaterial(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 float angle = V.y;
float4 viewPos = getViewFromWorldMatrix() * float4(getWorldPosition(), 1.0);
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;
}
}
} }
} }

4
materials/shared.h Normal file
View File

@@ -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)

View File

@@ -8,5 +8,5 @@ GRID_PACKAGE:
GRID_GRID_OFFSET: GRID_GRID_OFFSET:
.int 0 .int 0
GRID_GRID_SIZE: GRID_GRID_SIZE:
.int 44564 .int 47596

View File

@@ -8,5 +8,5 @@ _GRID_PACKAGE:
_GRID_GRID_OFFSET: _GRID_GRID_OFFSET:
.int 0 .int 0
_GRID_GRID_SIZE: _GRID_GRID_SIZE:
.int 44564 .int 47596

File diff suppressed because it is too large Load Diff

View File

@@ -8,6 +8,8 @@
#include <filament/MaterialInstance.h> #include <filament/MaterialInstance.h>
#include <utils/Entity.h> #include <utils/Entity.h>
#include "scene/SceneAsset.hpp" #include "scene/SceneAsset.hpp"
#include "material/grid.h"
namespace thermion { namespace thermion {
@@ -15,7 +17,7 @@ using namespace filament;
class GridOverlay : public SceneAsset { class GridOverlay : public SceneAsset {
public: public:
GridOverlay(Engine& engine); GridOverlay(Engine& engine, Material* material);
~GridOverlay(); ~GridOverlay();
SceneAssetType getType() override { return SceneAsset::SceneAssetType::Gizmo; } SceneAssetType getType() override { return SceneAsset::SceneAssetType::Gizmo; }

View File

@@ -1,17 +1,14 @@
#include "scene/GridOverlay.hpp" #include "scene/GridOverlay.hpp"
#include "scene/SceneManager.hpp" #include "scene/SceneManager.hpp"
#include "material/grid.h"
#include "Log.hpp" #include "Log.hpp"
namespace thermion namespace thermion
{ {
GridOverlay::GridOverlay(Engine &engine) : _engine(engine) GridOverlay::GridOverlay(Engine &engine, Material *material) : _engine(engine), _material(material)
{ {
createGrid(); createGrid();
createSphere(); createSphere();
_childEntities[0] = _gridEntity;
_childEntities[1] = _sphereEntity;
} }
GridOverlay::~GridOverlay() GridOverlay::~GridOverlay()
@@ -31,88 +28,98 @@ namespace thermion
void GridOverlay::createGrid() void GridOverlay::createGrid()
{ {
const int gridSize = 100; const float stepSize = 0.25f;
const float gridSpacing = 1.0f; const int gridSize = 8; // Number of grid cells in each direction (-1 to 1 with 0.25 step = 8 cells)
int vertexCount = (gridSize + 1) * 4; // 2 axes, 2 vertices per line 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]; std::vector<math::float3> *vertices = new std::vector<math::float3>();
int index = 0; std::vector<uint32_t> *indices = new std::vector<uint32_t>();
vertices->reserve(vertexCount);
indices->reserve(indexCount);
// Create grid lines // Generate grid vertices and indices
for (int i = 0; i <= gridSize; ++i) 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 // Add four vertices for this grid cell
gridVertices[index++] = pos; vertices->push_back({x, 0.0f, z}); // Bottom-left
gridVertices[index++] = 0; vertices->push_back({x, 0.0f, z + stepSize}); // Top-left
gridVertices[index++] = -(gridSize * gridSpacing / 2); vertices->push_back({x + stepSize, 0.0f, z + stepSize}); // Top-right
vertices->push_back({x + stepSize, 0.0f, z}); // Bottom-right
gridVertices[index++] = pos; // Add indices for two triangles
gridVertices[index++] = 0; indices->push_back(baseIndex);
gridVertices[index++] = (gridSize * gridSpacing / 2); indices->push_back(baseIndex + 1);
indices->push_back(baseIndex + 2);
// Z-axis lines indices->push_back(baseIndex + 2);
gridVertices[index++] = -(gridSize * gridSpacing / 2); indices->push_back(baseIndex + 3);
gridVertices[index++] = 0; indices->push_back(baseIndex);
gridVertices[index++] = pos; }
gridVertices[index++] = (gridSize * gridSpacing / 2);
gridVertices[index++] = 0;
gridVertices[index++] = pos;
} }
auto vb = VertexBuffer::Builder() auto vb = VertexBuffer::Builder()
.vertexCount(vertexCount) .vertexCount(vertices->size())
.bufferCount(1) .bufferCount(1)
.attribute(VertexAttribute::POSITION, 0, VertexBuffer::AttributeType::FLOAT3) .attribute(VertexAttribute::POSITION, 0, VertexBuffer::AttributeType::FLOAT3)
.build(_engine); .build(_engine);
vb->setBufferAt(_engine, 0, VertexBuffer::BufferDescriptor(gridVertices, vertexCount * sizeof(math::float3), [](void *buffer, size_t size, void *) vb->setBufferAt(_engine, 0,
{ delete[] static_cast<float *>(buffer); })); VertexBuffer::BufferDescriptor(
vertices->data(),
uint32_t *gridIndices = new uint32_t[vertexCount]; vertices->size() * sizeof(math::float3),
for (uint32_t i = 0; i < vertexCount; ++i) [](void *buffer, size_t size, void *user) {
{ delete static_cast<std::vector<math::float3>*>(user);
gridIndices[i] = i; }, vertices));
}
auto ib = IndexBuffer::Builder() auto ib = IndexBuffer::Builder()
.indexCount(vertexCount) .indexCount(indices->size())
.bufferType(IndexBuffer::IndexType::UINT) .bufferType(IndexBuffer::IndexType::UINT)
.build(_engine); .build(_engine);
ib->setBuffer(_engine, IndexBuffer::BufferDescriptor( ib->setBuffer(_engine,
gridIndices, IndexBuffer::BufferDescriptor(
vertexCount * sizeof(uint32_t), indices->data(),
[](void *buffer, size_t size, void *) indices->size() * sizeof(uint32_t),
{ delete[] static_cast<uint32_t *>(buffer); })); [](void *buffer, size_t size, void *user) {
delete static_cast<std::vector<uint32_t>*>(user);
}, indices));
_gridEntity = utils::EntityManager::get().create(); _gridEntity = utils::EntityManager::get().create();
_material = Material::Builder()
.package(GRID_PACKAGE, GRID_GRID_SIZE)
.build(_engine);
_materialInstance = _material->createInstance(); _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) RenderableManager::Builder(1)
.boundingBox({{-gridSize * gridSpacing / 2, 0, -gridSize * gridSpacing / 2}, .boundingBox({{-1.0f, -1.0f, -1.0f}, // Min point
{gridSize * gridSpacing / 2, 0, gridSize * gridSpacing / 2}}) {1.0f, 1.0f, 1.0f}}) // Max point
.geometry(0, RenderableManager::PrimitiveType::TRIANGLES, vb, ib, 0, indices->size())
.material(0, _materialInstance) .material(0, _materialInstance)
.geometry(0, RenderableManager::PrimitiveType::LINES, vb, ib, 0, vertexCount) .priority(1)
.priority(7)
.layerMask(0xFF, 1u << SceneManager::LAYERS::OVERLAY) .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) .receiveShadows(false)
.castShadows(false) .castShadows(false)
.build(_engine, _gridEntity); .build(_engine, _gridEntity);
_childEntities[0] = _gridEntity;
} }
void GridOverlay::createSphere() void GridOverlay::createSphere()
{ {
const float sphereRadius = 0.05f; const float sphereRadius = 1.05f;
const int sphereSegments = 16; const int sphereSegments = 16;
const int sphereRings = 16; const int sphereRings = 16;
@@ -195,14 +202,15 @@ namespace thermion
.receiveShadows(false) .receiveShadows(false)
.castShadows(false) .castShadows(false)
.build(_engine, _sphereEntity); .build(_engine, _sphereEntity);
_childEntities[1] = _sphereEntity;
} }
SceneAsset *GridOverlay::createInstance(MaterialInstance **materialInstances, size_t materialInstanceCount) SceneAsset *GridOverlay::createInstance(MaterialInstance **materialInstances, size_t materialInstanceCount)
{ {
auto instance = std::make_unique<GridOverlay>(_engine); auto instance = std::make_unique<GridOverlay>(_engine, _material);
auto *raw = instance.get(); auto *raw = instance.get();
_instances.push_back(std::move(instance)); _instances.push_back(std::move(instance));
return reinterpret_cast<SceneAsset*>(raw); return reinterpret_cast<SceneAsset *>(raw);
} }
void GridOverlay::addAllEntities(Scene *scene) void GridOverlay::addAllEntities(Scene *scene)