|
| 1 | +<script setup lang="ts"> |
| 2 | +import { shaderMaterial } from './shaderMaterial' |
| 3 | +import type { ColorRepresentation, PlaneGeometry, ShaderMaterial, Side, Uniform } from 'three' |
| 4 | +import { BackSide, Color, Mesh, Plane, Vector3 } from 'three' |
| 5 | +import { extend, useLoop } from '@tresjs/core' |
| 6 | +import { shallowRef } from 'vue' |
| 7 | +
|
| 8 | +/** |
| 9 | + Based on |
| 10 | + https://github.com/Fyrestar/THREE.InfiniteGridHelper by https://github.com/Fyrestar |
| 11 | + and https://github.com/threlte/threlte/blob/main/packages/extras/src/lib/components/Grid/Grid.svelte |
| 12 | + by https://github.com/grischaerbe and https://github.com/jerzakm |
| 13 | +*/ |
| 14 | +
|
| 15 | +export interface GridMaterialType { |
| 16 | + /** Cell size, default: 0.5 */ |
| 17 | + cellSize?: number |
| 18 | + /** Cell thickness, default: 0.5 */ |
| 19 | + cellThickness?: number |
| 20 | + /** Cell color, default: black */ |
| 21 | + cellColor?: ColorRepresentation |
| 22 | + /** Section size, default: 1 */ |
| 23 | + sectionSize?: number |
| 24 | + /** Section thickness, default: 1 */ |
| 25 | + sectionThickness?: number |
| 26 | + /** Section color, default: #2080ff */ |
| 27 | + sectionColor?: ColorRepresentation |
| 28 | + /** Follow camera, default: false */ |
| 29 | + followCamera?: boolean |
| 30 | + /** Display the grid infinitely, default: false */ |
| 31 | + infiniteGrid?: boolean |
| 32 | + /** Fade distance, default: 100 */ |
| 33 | + fadeDistance?: number |
| 34 | + /** Fade strength, default: 1 */ |
| 35 | + fadeStrength?: number |
| 36 | + /** Fade from camera (1) or origin (0), or somewhere in between, default: camera */ |
| 37 | + fadeFrom?: number |
| 38 | + /** Material side, default: THREE.BackSide */ |
| 39 | + side?: Side |
| 40 | +} |
| 41 | +
|
| 42 | +export type GridProps = GridMaterialType & { |
| 43 | + /** Default plane-geometry arguments */ |
| 44 | + args?: ConstructorParameters<typeof PlaneGeometry> |
| 45 | +} |
| 46 | +
|
| 47 | +const props = withDefaults(defineProps<GridProps>(), { |
| 48 | + cellColor: '#000000', |
| 49 | + sectionColor: '#0000ff', |
| 50 | + cellSize: 0.5, |
| 51 | + sectionSize: 1, |
| 52 | + followCamera: false, |
| 53 | + infiniteGrid: false, |
| 54 | + fadeDistance: 100, |
| 55 | + fadeStrength: 1, |
| 56 | + fadeFrom: 1, |
| 57 | + cellThickness: 0.5, |
| 58 | + sectionThickness: 1, |
| 59 | + side: BackSide, |
| 60 | +}) |
| 61 | +
|
| 62 | +const GridMaterial = shaderMaterial( |
| 63 | + { |
| 64 | + cellSize: 0.5, |
| 65 | + sectionSize: 1, |
| 66 | + fadeDistance: 100, |
| 67 | + fadeStrength: 1, |
| 68 | + fadeFrom: 1, |
| 69 | + cellThickness: 0.5, |
| 70 | + sectionThickness: 1, |
| 71 | + cellColor: new Color(), |
| 72 | + sectionColor: new Color(), |
| 73 | + infiniteGrid: false, |
| 74 | + followCamera: false, |
| 75 | + worldCamProjPosition: new Vector3(), |
| 76 | + worldPlanePosition: new Vector3(), |
| 77 | + }, |
| 78 | + /* glsl */ ` |
| 79 | + varying vec3 localPosition; |
| 80 | + varying vec4 worldPosition; |
| 81 | +
|
| 82 | + uniform vec3 worldCamProjPosition; |
| 83 | + uniform vec3 worldPlanePosition; |
| 84 | + uniform float fadeDistance; |
| 85 | + uniform bool infiniteGrid; |
| 86 | + uniform bool followCamera; |
| 87 | +
|
| 88 | + void main() { |
| 89 | + localPosition = position.xzy; |
| 90 | + if (infiniteGrid) localPosition *= 1.0 + fadeDistance; |
| 91 | + |
| 92 | + worldPosition = modelMatrix * vec4(localPosition, 1.0); |
| 93 | + if (followCamera) { |
| 94 | + worldPosition.xyz += (worldCamProjPosition - worldPlanePosition); |
| 95 | + localPosition = (inverse(modelMatrix) * worldPosition).xyz; |
| 96 | + } |
| 97 | +
|
| 98 | + gl_Position = projectionMatrix * viewMatrix * worldPosition; |
| 99 | + } |
| 100 | + `, |
| 101 | + /* glsl */ ` |
| 102 | + varying vec3 localPosition; |
| 103 | + varying vec4 worldPosition; |
| 104 | +
|
| 105 | + uniform vec3 worldCamProjPosition; |
| 106 | + uniform float cellSize; |
| 107 | + uniform float sectionSize; |
| 108 | + uniform vec3 cellColor; |
| 109 | + uniform vec3 sectionColor; |
| 110 | + uniform float fadeDistance; |
| 111 | + uniform float fadeStrength; |
| 112 | + uniform float fadeFrom; |
| 113 | + uniform float cellThickness; |
| 114 | + uniform float sectionThickness; |
| 115 | +
|
| 116 | + float getGrid(float size, float thickness) { |
| 117 | + vec2 r = localPosition.xz / size; |
| 118 | + vec2 grid = abs(fract(r - 0.5) - 0.5) / fwidth(r); |
| 119 | + float line = min(grid.x, grid.y) + 1.0 - thickness; |
| 120 | + return 1.0 - min(line, 1.0); |
| 121 | + } |
| 122 | +
|
| 123 | + void main() { |
| 124 | + float g1 = getGrid(cellSize, cellThickness); |
| 125 | + float g2 = getGrid(sectionSize, sectionThickness); |
| 126 | +
|
| 127 | + vec3 from = worldCamProjPosition*vec3(fadeFrom); |
| 128 | + float dist = distance(from, worldPosition.xyz); |
| 129 | + float d = 1.0 - min(dist / fadeDistance, 1.0); |
| 130 | + vec3 color = mix(cellColor, sectionColor, min(1.0, sectionThickness * g2)); |
| 131 | +
|
| 132 | + gl_FragColor = vec4(color, (g1 + g2) * pow(d, fadeStrength)); |
| 133 | + gl_FragColor.a = mix(0.75 * gl_FragColor.a, gl_FragColor.a, g2); |
| 134 | + if (gl_FragColor.a <= 0.0) discard; |
| 135 | +
|
| 136 | + #include <tonemapping_fragment> |
| 137 | + #include <colorspace_fragment> |
| 138 | + } |
| 139 | + `, |
| 140 | +) |
| 141 | +extend({ GridMaterial }) |
| 142 | +
|
| 143 | +const ref = shallowRef<Mesh>(new Mesh()) |
| 144 | +const plane = new Plane() |
| 145 | +const upVector = new Vector3(0, 1, 0) |
| 146 | +const zeroVector = new Vector3(0, 0, 0) |
| 147 | +
|
| 148 | +const { onBeforeRender } = useLoop() |
| 149 | +
|
| 150 | +onBeforeRender((state) => { |
| 151 | + if (!state.camera) { return } |
| 152 | + plane.setFromNormalAndCoplanarPoint(upVector, zeroVector).applyMatrix4(ref.value.matrixWorld) |
| 153 | +
|
| 154 | + const gridMaterial = ref.value.material as ShaderMaterial |
| 155 | + const worldCamProjPosition = gridMaterial.uniforms.worldCamProjPosition as Uniform<Vector3> |
| 156 | + const worldPlanePosition = gridMaterial.uniforms.worldPlanePosition as Uniform<Vector3> |
| 157 | +
|
| 158 | + plane.projectPoint(state.camera.value!.position, worldCamProjPosition.value) |
| 159 | + worldPlanePosition.value.set(0, 0, 0).applyMatrix4(ref.value.matrixWorld) |
| 160 | +}) |
| 161 | +</script> |
| 162 | + |
| 163 | +<template> |
| 164 | + <TresMesh ref="ref" :frustum-culled="false"> |
| 165 | + <TresGridMaterial |
| 166 | + :transparent="true" |
| 167 | + :extensions-derivatives="true" |
| 168 | + :side="props.side" |
| 169 | + :cell-size="props.cellSize" |
| 170 | + :section-size="props.sectionSize" |
| 171 | + :cell-color="props.cellColor" |
| 172 | + :section-color="props.sectionColor" |
| 173 | + :cell-thickness="props.cellThickness" |
| 174 | + :section-thickness="props.sectionThickness" |
| 175 | + :fade-distance="props.fadeDistance" |
| 176 | + :fade-strength="props.fadeStrength" |
| 177 | + :fade-from="props.fadeFrom" |
| 178 | + :infinite-grid="props.infiniteGrid" |
| 179 | + :follow-camera="props.followCamera" |
| 180 | + /> |
| 181 | + <TresPlaneGeometry :args="props.args" /> |
| 182 | + </TresMesh> |
| 183 | +</template> |
0 commit comments