UNPKG

@playcanvas/react

Version:

A React renderer for PlayCanvas – build interactive 3D applications using React's declarative paradigm.

196 lines (171 loc) 7.3 kB
import { CULLFACE_NONE, SEMANTIC_POSITION, BlendState, DepthState, Mat4, QuadRender, Script, createShaderFromCode, BLENDMODE_ONE, BLENDMODE_ONE_MINUS_SRC_ALPHA, BLENDMODE_SRC_ALPHA, BLENDEQUATION_ADD } from 'playcanvas'; const vsCode = /* glsl*/ ` uniform mat4 camera_matrix; uniform vec2 camera_params; attribute vec2 vertex_position; varying vec3 worldFar; void main(void) { gl_Position = vec4(vertex_position, 0.0, 1.0); vec4 v = camera_matrix * vec4(vertex_position * camera_params, -1.0, 1.0); worldFar = v.xyz; } `; const fsCode = /* glsl*/ ` uniform vec3 camera_position; uniform mat4 camera_viewProjection; uniform sampler2D blueNoiseTex32; varying vec3 worldFar; bool intersectPlane(inout float t, vec3 pos, vec3 dir, vec4 plane) { float d = dot(dir, plane.xyz); if (abs(d) < 1e-06) { return false; } float n = -(dot(pos, plane.xyz) + plane.w) / d; if (n < 0.0) { return false; } t = n; return true; } // https://bgolus.medium.com/the-best-darn-grid-shader-yet-727f9278b9d8#1e7c float pristineGrid(in vec2 uv, in vec2 ddx, in vec2 ddy, vec2 lineWidth) { vec2 uvDeriv = vec2(length(vec2(ddx.x, ddy.x)), length(vec2(ddx.y, ddy.y))); bvec2 invertLine = bvec2(lineWidth.x > 0.5, lineWidth.y > 0.5); vec2 targetWidth = vec2( invertLine.x ? 1.0 - lineWidth.x : lineWidth.x, invertLine.y ? 1.0 - lineWidth.y : lineWidth.y ); vec2 drawWidth = clamp(targetWidth, uvDeriv, vec2(0.5)); vec2 lineAA = uvDeriv * 1.5; vec2 gridUV = abs(fract(uv) * 2.0 - 1.0); gridUV.x = invertLine.x ? gridUV.x : 1.0 - gridUV.x; gridUV.y = invertLine.y ? gridUV.y : 1.0 - gridUV.y; vec2 grid2 = smoothstep(drawWidth + lineAA, drawWidth - lineAA, gridUV); grid2 *= clamp(targetWidth / drawWidth, 0.0, 1.0); grid2 = mix(grid2, targetWidth, clamp(uvDeriv * 2.0 - 1.0, 0.0, 1.0)); grid2.x = invertLine.x ? 1.0 - grid2.x : grid2.x; grid2.y = invertLine.y ? 1.0 - grid2.y : grid2.y; return mix(grid2.x, 1.0, grid2.y); } float calcDepth(vec3 p) { vec4 v = camera_viewProjection * vec4(p, 1.0); return (v.z / v.w) * 0.5 + 0.5; } bool writeDepth(float alpha) { vec2 uv = fract(gl_FragCoord.xy / 32.0); float noise = texture2DLod(blueNoiseTex32, uv, 0.0).y; return alpha > noise; } void main(void) { vec3 p = camera_position; vec3 v = normalize(worldFar - camera_position); // intersect ray with the world xz plane float t; if (!intersectPlane(t, p, v, vec4(0, 1, 0, 0))) { discard; } // calculate grid intersection vec3 pos = p + v * t; vec2 ddx = dFdx(pos.xz); vec2 ddy = dFdy(pos.xz); float epsilon = 1.0 / 255.0; // calculate fade float fade = 1.0 - smoothstep(400.0, 1000.0, length(pos - camera_position)); if (fade < epsilon) { discard; } vec3 levelPos; float levelSize; float levelAlpha; // 10m grid with colored main axes levelPos = pos * 0.1; levelSize = 2.0 / 1000.0; levelAlpha = pristineGrid(levelPos.xz, ddx * 0.1, ddy * 0.1, vec2(levelSize)) * fade; if (levelAlpha > epsilon) { vec3 color; vec2 loc = max(vec2(0.0), abs(levelPos.xz) - abs(ddx * 0.1) - abs(ddy * 0.1)); if (loc.x < levelSize) { if (loc.y < levelSize) { color = vec3(1.0); } else { color = vec3(0.2, 0.2, 1.0); } } else if (loc.y < levelSize) { color = vec3(1.0, 0.2, 0.2); } else { color = vec3(0.9); } gl_FragColor = vec4(color, levelAlpha); gl_FragDepth = writeDepth(levelAlpha) ? calcDepth(pos) : 1.0; return; } // 1m grid levelPos = pos; levelSize = 1.0 / 100.0; levelAlpha = pristineGrid(levelPos.xz, ddx, ddy, vec2(levelSize)) * fade; if (levelAlpha > epsilon) { gl_FragColor = vec4(vec3(0.7), levelAlpha); gl_FragDepth = writeDepth(levelAlpha) ? calcDepth(pos) : 1.0; return; } // 0.1m grid levelPos = pos * 10.0; levelSize = 1.0 / 100.0; levelAlpha = pristineGrid(levelPos.xz, ddx * 10.0, ddy * 10.0, vec2(levelSize)) * fade; if (levelAlpha > epsilon) { gl_FragColor = vec4(vec3(0.7), levelAlpha); gl_FragDepth = writeDepth(levelAlpha) ? calcDepth(pos) : 1.0; return; } discard; } `; const calcHalfSize = (fov, aspect, fovIsHorizontal) => { let x, y; if (fovIsHorizontal) { x = Math.tan(fov * Math.PI / 360); y = x / aspect; } else { y = Math.tan(fov * Math.PI / 360); x = y * aspect; } return [x, y]; }; const attributes = { vertex_position: SEMANTIC_POSITION }; export class Grid extends Script { initialize() { const layerName = 'World'; const device = this.app.graphicsDevice; const shader = createShaderFromCode(device, vsCode, fsCode, 'infinite-grid', attributes); const quadRender = new QuadRender(shader); const cameraMatrixId = device.scope.resolve('camera_matrix'); const cameraParamsId = device.scope.resolve('camera_params'); const cameraPositionId = device.scope.resolve('camera_position'); const cameraViewProjectionId = device.scope.resolve('camera_viewProjection'); const blendState = new BlendState(true, BLENDEQUATION_ADD, BLENDMODE_SRC_ALPHA, BLENDMODE_ONE_MINUS_SRC_ALPHA, BLENDEQUATION_ADD, BLENDMODE_ONE, BLENDMODE_ONE_MINUS_SRC_ALPHA); const onPreRenderLayer = (camera, layer, transparent) => { if (this.enabled && layer.name === layerName && !transparent) { device.setBlendState(blendState); device.setCullMode(CULLFACE_NONE); device.setDepthState(DepthState.NODEPTH); device.setStencilState(null, null); // update viewProjectionInverse matrix const cameraMatrix = camera.entity.getWorldTransform().clone(); const cameraParams = calcHalfSize(camera.fov, camera.aspectRatio, camera.horizontalFov); const cameraPosition = cameraMatrix.getTranslation(); const cameraViewProjection = new Mat4().mul2(camera.projectionMatrix, camera.viewMatrix); cameraMatrixId.setValue(cameraMatrix.data); cameraParamsId.setValue(cameraParams); cameraPositionId.setValue([cameraPosition.x, cameraPosition.y, cameraPosition.z]); cameraViewProjectionId.setValue(cameraViewProjection.data); quadRender.render(); } }; this.app.scene.on('prerender:layer', onPreRenderLayer); this.on('destroy', () => this.app?.scene?.off('prerender:layer', onPreRenderLayer)); } } //# sourceMappingURL=infinite-grid.js.map