UNPKG

prophetk-3d-viewer

Version:

A Three.js r128 based GLB/GLTF model viewer for WeChat Mini Program and Web.

115 lines (107 loc) • 4.58 kB
const THREE = require('../vendor/three-r128.js'); const VERTEX_SHADER = ` varying vec3 vGridWorldPosition; void main() { vec4 worldPosition = modelMatrix * vec4(position, 1.0); vGridWorldPosition = worldPosition.xyz; gl_Position = projectionMatrix * viewMatrix * worldPosition; } `; const FRAGMENT_SHADER = ` uniform vec3 gridColor; uniform float gridSpacing; uniform float pixelAngle; uniform vec3 viewRight; uniform vec3 viewUp; uniform vec3 viewForward; varying vec3 vGridWorldPosition; float gridLines(float spacing) { vec2 coordinate = vGridWorldPosition.xz / spacing; vec2 width; #if GRID_DERIVATIVES width = fwidth(coordinate); #else // WebGL 1 fallback for devices without OES_standard_derivatives. vec3 offset = vGridWorldPosition - cameraPosition; float depth = max(dot(offset, viewForward), 0.0001); float heightDelta = abs(offset.y) < 0.00001 ? 0.00001 : offset.y; vec3 dx = viewRight * (depth * pixelAngle); vec3 dy = viewUp * (depth * pixelAngle); // Analytic screen-to-horizontal-plane derivatives. Unlike an isotropic // distance estimate, these keep the two line directions equally thin. dx -= offset * (dx.y / heightDelta); dy -= offset * (dy.y / heightDelta); width = (abs(dx.xz) + abs(dy.xz)) / spacing; #endif width = max(width, vec2(0.00001)); vec2 distanceToLine = abs(fract(coordinate - 0.5) - 0.5); vec2 lines = 1.0 - smoothstep(width * 0.35, width * 1.35, distanceToLine); // Fade each family of lines before it becomes subpixel and aliases near // the horizon. This avoids a visible model-sized rectangular boundary. lines *= 1.0 - smoothstep(vec2(0.25), vec2(0.5), width); return max(lines.x, lines.y); } void main() { float alpha = max(gridLines(gridSpacing) * 0.22, gridLines(gridSpacing * 5.0) * 0.34); if (alpha < 0.001) discard; gl_FragColor = vec4(gridColor, alpha); } `; // A two-triangle horizontal surface covering the whole camera frustum. The // repeating pattern uses world coordinates, so resizing/recentering this // surface never moves the grid lines or ties their extent to model size. class DragPlaneGrid extends THREE.Mesh { constructor(height, spacing, derivatives) { const geometry = new THREE.PlaneGeometry(1, 1); geometry.rotateX(-Math.PI / 2); const material = new THREE.ShaderMaterial({ vertexShader: VERTEX_SHADER, fragmentShader: FRAGMENT_SHADER, defines: { GRID_DERIVATIVES: derivatives ? 1 : 0 }, extensions: { derivatives: !!derivatives }, uniforms: { gridColor: { value: new THREE.Color(0x80cfff) }, gridSpacing: { value: spacing }, pixelAngle: { value: 0.001 }, viewRight: { value: new THREE.Vector3(1, 0, 0) }, viewUp: { value: new THREE.Vector3(0, 1, 0) }, viewForward: { value: new THREE.Vector3(0, 0, -1) }, }, transparent: true, depthWrite: false, depthTest: false, side: THREE.DoubleSide, toneMapped: false, }); super(geometry, material); this.position.y = height; this.frustumCulled = false; this.raycast = () => {}; this._coverage = new THREE.Box3(); this._corner = new THREE.Vector3(); } update(camera, viewportHeight) { camera.updateMatrixWorld(true); this._coverage.makeEmpty(); // All points of the visible plane lie inside these world X/Z bounds, // including when top-of-screen rays miss the plane across the horizon. for (const x of [-1, 1]) { for (const y of [-1, 1]) { for (const z of [-1, 1]) { this._coverage.expandByPoint(this._corner.set(x, y, z).unproject(camera)); } } } const { min, max } = this._coverage; const padding = this.material.uniforms.gridSpacing.value * 2; this.position.x = (min.x + max.x) / 2; this.position.z = (min.z + max.z) / 2; this.scale.set(max.x - min.x + padding, 1, max.z - min.z + padding); this.material.uniforms.pixelAngle.value = 2 * Math.tan(camera.fov * Math.PI / 360) / (camera.zoom * Math.max(1, viewportHeight)); this.material.uniforms.viewRight.value.setFromMatrixColumn(camera.matrixWorld, 0); this.material.uniforms.viewUp.value.setFromMatrixColumn(camera.matrixWorld, 1); this.material.uniforms.viewForward.value.setFromMatrixColumn(camera.matrixWorld, 2).negate(); } } module.exports = DragPlaneGrid;