From fd23da6efae39e619d9b399e147497ddeedcf627 Mon Sep 17 00:00:00 2001 From: Donovan Hutchence Date: Mon, 5 Aug 2024 16:15:18 +0100 Subject: [PATCH] grid sphere shape --- package-lock.json | 4 +- package.json | 2 +- src/infinite-grid.ts | 5 +- src/sphere-shape.ts | 147 +++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 149 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index f34837d2..f84039fe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "supersplat", - "version": "1.0.0", + "version": "1.0.0-alpha.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "supersplat", - "version": "1.0.0", + "version": "1.0.0-alpha.0", "license": "MIT", "devDependencies": { "@playcanvas/eslint-config": "^1.7.1", diff --git a/package.json b/package.json index 77fcfd16..81bdc789 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "supersplat", - "version": "1.0.0", + "version": "1.0.0-alpha.0", "author": "PlayCanvas", "homepage": "https://playcanvas.com/supersplat/editor", "description": "3D Gaussian Splat Editor", diff --git a/src/infinite-grid.ts b/src/infinite-grid.ts index 048bbe4e..acd81caf 100644 --- a/src/infinite-grid.ts +++ b/src/infinite-grid.ts @@ -7,7 +7,8 @@ import { Mat4, QuadRender, Shader, - createShaderFromCode + createShaderFromCode, + FUNC_LESSEQUAL } from 'playcanvas'; import { Element, ElementType } from './element'; import { Serializer } from './serializer'; @@ -161,7 +162,7 @@ class InfiniteGrid extends Element { shader: Shader; quadRender: QuadRender; blendState = new BlendState(false); - depthState = new DepthState(FUNC_ALWAYS, true); + depthState = new DepthState(FUNC_LESSEQUAL, true); visible = true; diff --git a/src/sphere-shape.ts b/src/sphere-shape.ts index 6b1bcd71..b0cef1e5 100644 --- a/src/sphere-shape.ts +++ b/src/sphere-shape.ts @@ -1,22 +1,157 @@ -import { BoundingBox, Color, Entity, Vec3 } from 'playcanvas'; +import { + createShaderFromCode, + CULLFACE_NONE, + BoundingBox, + Entity, + Material, + Vec3 +} from 'playcanvas'; import { Element, ElementType } from './element'; import { Serializer } from './serializer'; +const vsCode = /* glsl */ ` + attribute vec3 vertex_position; + + uniform mat4 matrix_model; + uniform mat4 matrix_viewProjection; + + varying vec3 fragWorld; + + void main() { + vec4 world = matrix_model * vec4(vertex_position, 1.0); + gl_Position = matrix_viewProjection * world; + fragWorld = world.xyz; + } +`; + +const fsCode = /* glsl */ ` + bool intersectSphere(out float t0, out float t1, vec3 pos, vec3 dir, vec4 sphere) { + vec3 L = sphere.xyz - pos; + float tca = dot(L, dir); + + float d2 = sphere.w * sphere.w - (dot(L, L) - tca * tca); + if (d2 < 0.0) { + return false; + } + + float thc = sqrt(d2); + t0 = tca - thc; + t1 = tca + thc; + if (t1 < 0.0) { + return false; + } + + return true; + } + + float calcDepth(in vec3 pos, in mat4 viewProjection) { + vec4 v = viewProjection * vec4(pos, 1.0); + return (v.z / v.w) * 0.5 + 0.5; + } + + float noise(vec2 fragCoord, sampler2D noiseTex) { + vec2 uv = fract(fragCoord / 32.0); + return texture2DLodEXT(noiseTex, uv, 0.0).y; + } + + vec2 calcAzimuthElev(in vec3 dir) { + float azimuth = atan(dir.z, dir.x); + float elev = asin(dir.y); + return vec2(azimuth, elev) * 180.0 / 3.14159; + } + + bool strips(vec3 lp) { + vec2 ae = calcAzimuthElev(normalize(lp)); + + float spacing = 10.0; + float size = 0.25 / spacing; + return fract(ae.x / spacing) > size && + fract(ae.y / spacing) > size; + } + + uniform sampler2D blueNoiseTex32; + uniform vec3 view_position; + uniform mat4 matrix_viewProjection; + uniform vec4 sphere; + + varying vec3 fragWorld; + + void behind(vec3 ray, float t) { + vec3 wp = view_position + ray * t; + if (strips(wp - sphere.xyz) || noise(gl_FragCoord.yx, blueNoiseTex32) < 0.125) { + discard; + } + + gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0); + gl_FragDepth = calcDepth(wp, matrix_viewProjection); + } + + void front(vec3 ray, float t0, float t1) { + if (t0 < 0.0) { + behind(ray, t1); + } else { + vec3 wp = view_position + ray * t0; + if (strips(wp - sphere.xyz) || noise(gl_FragCoord.xy, blueNoiseTex32) < 0.6) { + behind(ray, t1); + } else { + gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0); + gl_FragDepth = calcDepth(wp, matrix_viewProjection); + } + } + } + + void main() { + vec3 ray = normalize(fragWorld - view_position); + + float t0, t1; + if (!intersectSphere(t0, t1, view_position, ray, sphere)) { + discard; + } + + front(ray, t0, t1); + } +`; + const v = new Vec3(); const bound = new BoundingBox(); class SphereShape extends Element { - pivot: Entity; _radius = 1; + pivot: Entity; + material: Material; constructor() { super(ElementType.debug); this.pivot = new Entity('spherePivot'); + this.pivot.addComponent('render', { + type: 'box' + }); + const r = this._radius * 2; + this.pivot.setLocalScale(r, r, r); } add() { + const device = this.scene.app.graphicsDevice; + + const shader = createShaderFromCode( + device, + vsCode, + fsCode, + 'sphere-shape' + ); + + const material = new Material(); + material.shader = shader; + material.cull = CULLFACE_NONE; + material.update(); + + this.pivot.render.meshInstances[0].material = material; + + this.material = material; + this.scene.contentRoot.addChild(this.pivot); + this.updateBound(); } @@ -35,8 +170,8 @@ class SphereShape extends Element { } onPreRender() { - this.pivot.getWorldTransform().getTranslation(v) - this.scene.app.drawWireSphere(v, this.radius, Color.RED, 40); + this.pivot.getWorldTransform().getTranslation(v); + this.material.setParameter('sphere', [v.x, v.y, v.z, this.radius]); } moved() { @@ -55,6 +190,10 @@ class SphereShape extends Element { set radius(radius: number) { this._radius = radius; + + const r = this._radius * 2; + this.pivot.setLocalScale(r, r, r); + this.updateBound(); }