@plasius/renderer 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +59 -0
- package/CODE_OF_CONDUCT.md +79 -0
- package/CONTRIBUTORS.md +27 -0
- package/LICENSE +203 -0
- package/README.md +70 -0
- package/SECURITY.md +17 -0
- package/dist/adaptivedpr.d.ts +2 -0
- package/dist/adaptivedpr.d.ts.map +1 -0
- package/dist/adaptivedpr.js +65 -0
- package/dist/camera/cameraRigProfile.d.ts +12 -0
- package/dist/camera/cameraRigProfile.d.ts.map +1 -0
- package/dist/camera/cameraRigProfile.js +18 -0
- package/dist/camera/managedCameraController.d.ts +49 -0
- package/dist/camera/managedCameraController.d.ts.map +1 -0
- package/dist/camera/managedCameraController.js +271 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/landscape.d.ts +2 -0
- package/dist/landscape.d.ts.map +1 -0
- package/dist/landscape.js +120 -0
- package/dist/player/player.d.ts +8 -0
- package/dist/player/player.d.ts.map +1 -0
- package/dist/player/player.js +203 -0
- package/dist/player/playerstore.d.ts +205 -0
- package/dist/player/playerstore.d.ts.map +1 -0
- package/dist/player/playerstore.js +500 -0
- package/dist/renderStateProvider.d.ts +57 -0
- package/dist/renderStateProvider.d.ts.map +1 -0
- package/dist/renderStateProvider.js +50 -0
- package/dist/renderer.d.ts +9 -0
- package/dist/renderer.d.ts.map +1 -0
- package/dist/renderer.js +165 -0
- package/dist/scene.d.ts +7 -0
- package/dist/scene.d.ts.map +1 -0
- package/dist/scene.js +10 -0
- package/dist/shaders/fragment/landscapeFragmentShader.js +141 -0
- package/dist/shaders/landscapeShader.d.ts +13 -0
- package/dist/shaders/landscapeShader.d.ts.map +1 -0
- package/dist/shaders/landscapeShader.js +25 -0
- package/dist/shaders/vertex/landscapeVertexShader.js +67 -0
- package/dist/styles/renderer.module.css +90 -0
- package/dist/worldSpaceCompositor.d.ts +50 -0
- package/dist/worldSpaceCompositor.d.ts.map +1 -0
- package/dist/worldSpaceCompositor.js +159 -0
- package/dist/xr/rendererXrBridge.d.ts +12 -0
- package/dist/xr/rendererXrBridge.d.ts.map +1 -0
- package/dist/xr/rendererXrBridge.js +17 -0
- package/docs/adrs/adr-0001-renderer-package-scope.md +21 -0
- package/docs/adrs/adr-0002-public-repo-governance.md +24 -0
- package/docs/adrs/adr-0003-world-space-compositor-contracts.md +34 -0
- package/docs/adrs/adr-template.md +35 -0
- package/docs/design/0001-public-package-scope.md +18 -0
- package/docs/tdrs/index.md +3 -0
- package/docs/tdrs/tdr-0001-renderer-public-package-standards-alignment.md +19 -0
- package/legal/CLA-REGISTRY.csv +1 -0
- package/legal/CLA.md +22 -0
- package/legal/CORPORATE_CLA.md +57 -0
- package/legal/INDIVIDUAL_CLA.md +91 -0
- package/package.json +117 -0
- package/src/adaptivedpr.tsx +74 -0
- package/src/camera/cameraRigProfile.ts +29 -0
- package/src/camera/managedCameraController.tsx +401 -0
- package/src/global.d.ts +10 -0
- package/src/index.ts +3 -0
- package/src/landscape.tsx +321 -0
- package/src/player/player.tsx +257 -0
- package/src/player/playerstore.tsx +733 -0
- package/src/renderStateProvider.tsx +121 -0
- package/src/renderer.tsx +294 -0
- package/src/scene.tsx +42 -0
- package/src/shaders/fragment/landscapeFragmentShader.d.ts +4 -0
- package/src/shaders/fragment/landscapeFragmentShader.js +141 -0
- package/src/shaders/landscapeShader.tsx +39 -0
- package/src/shaders/vertex/landscapeVertexShader.d.ts +4 -0
- package/src/shaders/vertex/landscapeVertexShader.js +67 -0
- package/src/styles/renderer.module.css +90 -0
- package/src/worldSpaceCompositor.ts +265 -0
- package/src/xr/rendererXrBridge.ts +44 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { createScopedStoreContext } from "@plasius/react-state";
|
|
2
|
+
import type { IState } from "@plasius/react-state";
|
|
3
|
+
import type { CameraRigProfile } from "./camera/cameraRigProfile.js";
|
|
4
|
+
import { VIEW_PROFILE } from "./camera/cameraRigProfile.js";
|
|
5
|
+
|
|
6
|
+
type RenderAction =
|
|
7
|
+
| {
|
|
8
|
+
type: "set_camera_profile";
|
|
9
|
+
payload: CameraRigProfile;
|
|
10
|
+
}
|
|
11
|
+
| {
|
|
12
|
+
type: "set_frame";
|
|
13
|
+
payload: number;
|
|
14
|
+
}
|
|
15
|
+
| {
|
|
16
|
+
type: "set_last_render_time";
|
|
17
|
+
payload: number;
|
|
18
|
+
}
|
|
19
|
+
| {
|
|
20
|
+
type: "update";
|
|
21
|
+
}
|
|
22
|
+
| {
|
|
23
|
+
type: "reset";
|
|
24
|
+
}
|
|
25
|
+
| {
|
|
26
|
+
type: "set_frame_rate";
|
|
27
|
+
payload: number;
|
|
28
|
+
}
|
|
29
|
+
| {
|
|
30
|
+
type: "increment_accumulated_frames";
|
|
31
|
+
}
|
|
32
|
+
| {
|
|
33
|
+
type: "set_performance_tier";
|
|
34
|
+
payload: "low" | "medium" | "high" | "ultra";
|
|
35
|
+
}
|
|
36
|
+
| {
|
|
37
|
+
type: "set_is_animating";
|
|
38
|
+
payload: boolean;
|
|
39
|
+
}
|
|
40
|
+
| {
|
|
41
|
+
type: "set_pause_render";
|
|
42
|
+
payload: boolean;
|
|
43
|
+
}
|
|
44
|
+
| {
|
|
45
|
+
type: "set_scene_hash";
|
|
46
|
+
payload: string;
|
|
47
|
+
}
|
|
48
|
+
| {
|
|
49
|
+
type: "set_vr_mode";
|
|
50
|
+
payload: boolean;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
interface RenderState extends IState {
|
|
54
|
+
frame: number;
|
|
55
|
+
lastRenderTime: number;
|
|
56
|
+
debugEnabled: boolean;
|
|
57
|
+
frameRate: number;
|
|
58
|
+
accumulatedFrames: number;
|
|
59
|
+
performanceTier: "low" | "medium" | "high" | "ultra";
|
|
60
|
+
isAnimating: boolean;
|
|
61
|
+
pauseRender: boolean;
|
|
62
|
+
sceneHash: string;
|
|
63
|
+
cameraRigProfile: CameraRigProfile;
|
|
64
|
+
useVR: boolean;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const initialState: RenderState = {
|
|
68
|
+
frame: 0,
|
|
69
|
+
lastRenderTime: 0,
|
|
70
|
+
debugEnabled: false,
|
|
71
|
+
frameRate: 0,
|
|
72
|
+
accumulatedFrames: 0,
|
|
73
|
+
performanceTier: "high",
|
|
74
|
+
isAnimating: true,
|
|
75
|
+
pauseRender: false,
|
|
76
|
+
sceneHash: "",
|
|
77
|
+
cameraRigProfile: VIEW_PROFILE,
|
|
78
|
+
useVR: false,
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const reducer = (state: RenderState, action: RenderAction): RenderState => {
|
|
82
|
+
switch (action.type) {
|
|
83
|
+
case "update":
|
|
84
|
+
return { ...state };
|
|
85
|
+
case "reset":
|
|
86
|
+
return initialState;
|
|
87
|
+
case "set_frame_rate":
|
|
88
|
+
return { ...state, frameRate: action.payload };
|
|
89
|
+
case "increment_accumulated_frames":
|
|
90
|
+
return { ...state, accumulatedFrames: state.accumulatedFrames + 1 };
|
|
91
|
+
case "set_performance_tier":
|
|
92
|
+
return { ...state, performanceTier: action.payload };
|
|
93
|
+
case "set_is_animating":
|
|
94
|
+
return { ...state, isAnimating: action.payload };
|
|
95
|
+
case "set_pause_render":
|
|
96
|
+
return { ...state, pauseRender: action.payload };
|
|
97
|
+
case "set_scene_hash":
|
|
98
|
+
return { ...state, sceneHash: action.payload };
|
|
99
|
+
case "set_camera_profile":
|
|
100
|
+
return { ...state, cameraRigProfile: action.payload };
|
|
101
|
+
case "set_frame":
|
|
102
|
+
return { ...state, frame: action.payload };
|
|
103
|
+
case "set_last_render_time":
|
|
104
|
+
return { ...state, lastRenderTime: action.payload };
|
|
105
|
+
case "set_vr_mode":
|
|
106
|
+
return { ...state, useVR: action.payload };
|
|
107
|
+
default:
|
|
108
|
+
return state;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export type RenderStoreContext = ReturnType<
|
|
113
|
+
typeof createScopedStoreContext<RenderState, RenderAction>
|
|
114
|
+
>;
|
|
115
|
+
|
|
116
|
+
export const RenderStore: RenderStoreContext =
|
|
117
|
+
createScopedStoreContext<RenderState, RenderAction>(reducer, initialState);
|
|
118
|
+
|
|
119
|
+
export const RenderProvider = ({ children }: { children: React.ReactNode }) => {
|
|
120
|
+
return <RenderStore.Provider>{children}</RenderStore.Provider>;
|
|
121
|
+
};
|
package/src/renderer.tsx
ADDED
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { type Vector3, type Group } from "three";
|
|
2
|
+
import * as THREE from "three/webgpu";
|
|
3
|
+
import {
|
|
4
|
+
Canvas,
|
|
5
|
+
//type ThreeToJSXElements,
|
|
6
|
+
type RootState,
|
|
7
|
+
} from "@react-three/fiber";
|
|
8
|
+
/*
|
|
9
|
+
declare module "@react-three/fiber" {
|
|
10
|
+
interface ThreeElements extends ThreeToJSXElements<typeof THREE> {
|
|
11
|
+
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
*/
|
|
15
|
+
// Use the constructor parameter type as our renderer parameters type
|
|
16
|
+
type WebGPURendererParameters = ConstructorParameters<
|
|
17
|
+
typeof THREE.WebGPURenderer
|
|
18
|
+
>[0];
|
|
19
|
+
import { useRef, useEffect, Suspense } from "react";
|
|
20
|
+
|
|
21
|
+
import { Html, useProgress } from "@react-three/drei";
|
|
22
|
+
import { createXrManager } from "@plasius/gpu-xr";
|
|
23
|
+
import type { CameraManager } from "@plasius/gpu-camera";
|
|
24
|
+
|
|
25
|
+
import { BsHeadsetVr } from "react-icons/bs";
|
|
26
|
+
import { CgWebsite } from "react-icons/cg";
|
|
27
|
+
|
|
28
|
+
import { RenderStore } from "./renderStateProvider.js";
|
|
29
|
+
|
|
30
|
+
import styles from "./styles/renderer.module.css";
|
|
31
|
+
import { Scene } from "./scene.js";
|
|
32
|
+
|
|
33
|
+
import { Player } from "./player/player.js";
|
|
34
|
+
|
|
35
|
+
import { AdaptiveDPR } from "./adaptivedpr.js";
|
|
36
|
+
import {
|
|
37
|
+
bindSessionToRenderer,
|
|
38
|
+
rendererVrSessionInit,
|
|
39
|
+
} from "./xr/rendererXrBridge.js";
|
|
40
|
+
import {
|
|
41
|
+
createRendererCameraManager,
|
|
42
|
+
ManagedCameraController,
|
|
43
|
+
} from "./camera/managedCameraController.js";
|
|
44
|
+
|
|
45
|
+
interface WebGPURendererWithContext extends THREE.WebGPURenderer {
|
|
46
|
+
getContextAttributes: () => {
|
|
47
|
+
antialias: boolean;
|
|
48
|
+
alpha: boolean;
|
|
49
|
+
stencil: boolean;
|
|
50
|
+
depth: boolean;
|
|
51
|
+
powerPreference: string;
|
|
52
|
+
xrCompatible: boolean;
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface RendererProps {
|
|
57
|
+
cameraPosition: Vector3;
|
|
58
|
+
cameraRotation: Vector3;
|
|
59
|
+
multiview: boolean;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function Loader() {
|
|
63
|
+
const { progress } = useProgress();
|
|
64
|
+
return <Html center>{progress} % loaded</Html>;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
enum Perf {
|
|
68
|
+
Low,
|
|
69
|
+
Medium,
|
|
70
|
+
High,
|
|
71
|
+
Ultra,
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function Renderer({
|
|
75
|
+
cameraPosition,
|
|
76
|
+
cameraRotation,
|
|
77
|
+
multiview = true,
|
|
78
|
+
children,
|
|
79
|
+
}: React.PropsWithChildren<RendererProps>) {
|
|
80
|
+
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
81
|
+
const sceneRef = useRef<Group | null>(null);
|
|
82
|
+
const rendererRef = useRef<WebGPURendererWithContext | null>(null);
|
|
83
|
+
const xrManagerRef = useRef<ReturnType<typeof createXrManager> | null>(null);
|
|
84
|
+
const cameraManagerRef = useRef<CameraManager | null>(null);
|
|
85
|
+
|
|
86
|
+
const { useVR, cameraRigProfile } = RenderStore.useStore();
|
|
87
|
+
const dispatch = RenderStore.useDispatch();
|
|
88
|
+
|
|
89
|
+
if (!cameraManagerRef.current) {
|
|
90
|
+
cameraManagerRef.current = createRendererCameraManager({
|
|
91
|
+
maxParallelViews: multiview ? 2 : 1,
|
|
92
|
+
maxHotCameras: 3,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
const cameraManager = cameraManagerRef.current as CameraManager;
|
|
96
|
+
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
return () => {
|
|
99
|
+
cameraManager.clear();
|
|
100
|
+
};
|
|
101
|
+
}, [cameraManager]);
|
|
102
|
+
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
const xrManager = createXrManager({
|
|
105
|
+
onSessionStart: (session) => {
|
|
106
|
+
void bindSessionToRenderer(rendererRef.current, session).catch(() => {
|
|
107
|
+
dispatch({ type: "set_vr_mode", payload: false });
|
|
108
|
+
});
|
|
109
|
+
},
|
|
110
|
+
onSessionEnd: () => {
|
|
111
|
+
dispatch({ type: "set_vr_mode", payload: false });
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
xrManagerRef.current = xrManager;
|
|
116
|
+
|
|
117
|
+
void xrManager.probeSupport(["immersive-vr"]).catch(() => {
|
|
118
|
+
// Ignore probe failures; entering VR will surface actionable errors.
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
return () => {
|
|
122
|
+
const current = xrManagerRef.current;
|
|
123
|
+
xrManagerRef.current = null;
|
|
124
|
+
if (current) {
|
|
125
|
+
void current.dispose();
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
}, [dispatch]);
|
|
129
|
+
|
|
130
|
+
useEffect(() => {
|
|
131
|
+
const canvas = canvasRef.current;
|
|
132
|
+
const xrManager = xrManagerRef.current;
|
|
133
|
+
|
|
134
|
+
if (!canvas || !xrManager) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const fullscreenChangeHandler = () => {
|
|
139
|
+
if (!document.fullscreenElement) {
|
|
140
|
+
dispatch({ type: "set_vr_mode", payload: false });
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const enterVRHandler = async () => {
|
|
145
|
+
try {
|
|
146
|
+
if (!document.fullscreenElement) {
|
|
147
|
+
await canvas.requestFullscreen();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
canvas.addEventListener("fullscreenchange", fullscreenChangeHandler);
|
|
151
|
+
await xrManager.enterVr(rendererVrSessionInit);
|
|
152
|
+
} catch {
|
|
153
|
+
dispatch({ type: "set_vr_mode", payload: false });
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const exitVRHandler = async () => {
|
|
158
|
+
await xrManager.exitSession();
|
|
159
|
+
|
|
160
|
+
if (document.fullscreenElement === canvas) {
|
|
161
|
+
await document.exitFullscreen().catch(() => {});
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
if (!useVR) {
|
|
166
|
+
void exitVRHandler();
|
|
167
|
+
return () => {
|
|
168
|
+
canvas.removeEventListener("fullscreenchange", fullscreenChangeHandler);
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
void enterVRHandler();
|
|
173
|
+
|
|
174
|
+
return () => {
|
|
175
|
+
canvas.removeEventListener("fullscreenchange", fullscreenChangeHandler);
|
|
176
|
+
};
|
|
177
|
+
}, [useVR, dispatch]);
|
|
178
|
+
|
|
179
|
+
return (
|
|
180
|
+
<>
|
|
181
|
+
<button
|
|
182
|
+
title={useVR ? "Exit VR" : "Enter VR"}
|
|
183
|
+
onClick={() => dispatch({ type: "set_vr_mode", payload: !useVR })}
|
|
184
|
+
className={styles.vrButton}
|
|
185
|
+
>
|
|
186
|
+
{useVR ? <CgWebsite size={24} /> : <BsHeadsetVr size={24} />}
|
|
187
|
+
</button>
|
|
188
|
+
|
|
189
|
+
<Suspense fallback={<div style={{ height: "100%" }} />}>
|
|
190
|
+
<Canvas
|
|
191
|
+
shadows={"soft"}
|
|
192
|
+
tabIndex={0}
|
|
193
|
+
ref={canvasRef}
|
|
194
|
+
className={`${styles.canvas} ${
|
|
195
|
+
!useVR ? styles.fixed : styles.absolute
|
|
196
|
+
}`}
|
|
197
|
+
style={{ width: "100%", height: "100%" }}
|
|
198
|
+
camera={{
|
|
199
|
+
position: cameraPosition.toArray(),
|
|
200
|
+
rotation: cameraRotation.toArray(),
|
|
201
|
+
near: 0.1,
|
|
202
|
+
far: 1000,
|
|
203
|
+
fov: 50,
|
|
204
|
+
}}
|
|
205
|
+
dpr={useVR ? 1 : [1, 4]}
|
|
206
|
+
gl={async (props) => {
|
|
207
|
+
const params = {
|
|
208
|
+
...props,
|
|
209
|
+
multiview,
|
|
210
|
+
} as WebGPURendererParameters;
|
|
211
|
+
const renderer = new THREE.WebGPURenderer(
|
|
212
|
+
params
|
|
213
|
+
) as WebGPURendererWithContext;
|
|
214
|
+
|
|
215
|
+
await renderer.init();
|
|
216
|
+
|
|
217
|
+
if (useVR && "setPixelRatio" in renderer) {
|
|
218
|
+
renderer.setPixelRatio(1);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if ("setClearColor" in renderer) {
|
|
222
|
+
renderer.setClearColor("lightblue");
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if ("outputColorSpace" in renderer) {
|
|
226
|
+
renderer.outputColorSpace = THREE.SRGBColorSpace;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if ("xr" in renderer) {
|
|
230
|
+
renderer.xr.enabled = true;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
renderer.getContextAttributes = () => ({
|
|
234
|
+
antialias: props.antialias ?? true,
|
|
235
|
+
alpha: props.alpha ?? true,
|
|
236
|
+
stencil: props.stencil ?? true,
|
|
237
|
+
depth: props.depth ?? true,
|
|
238
|
+
powerPreference: props.powerPreference ?? "high-performance",
|
|
239
|
+
xrCompatible: true,
|
|
240
|
+
});
|
|
241
|
+
return renderer;
|
|
242
|
+
}}
|
|
243
|
+
onCreated={(state: RootState) => {
|
|
244
|
+
// In XR, keep DPR stable to avoid stereo stitching issues
|
|
245
|
+
if (useVR && "setPixelRatio" in state.gl) {
|
|
246
|
+
// Cap DPR to a reasonable value for HMDs; many runtimes manage internal resolution
|
|
247
|
+
state.gl.setPixelRatio(1);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if ("setClearColor" in state.gl) {
|
|
251
|
+
state.gl.setClearColor("lightblue");
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if ("setSize" in state.gl) {
|
|
255
|
+
state.gl.setSize(state.size.width, state.size.height);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if ("outputColorSpace" in state.gl) {
|
|
259
|
+
state.gl.outputColorSpace = THREE.SRGBColorSpace;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
rendererRef.current = state.gl as unknown as WebGPURendererWithContext;
|
|
263
|
+
}}
|
|
264
|
+
>
|
|
265
|
+
{!useVR && <AdaptiveDPR />}
|
|
266
|
+
|
|
267
|
+
<Suspense fallback={Loader()}>
|
|
268
|
+
{!useVR && (
|
|
269
|
+
<ManagedCameraController
|
|
270
|
+
manager={cameraManager}
|
|
271
|
+
profile={cameraRigProfile}
|
|
272
|
+
cameraId="main"
|
|
273
|
+
/>
|
|
274
|
+
)}
|
|
275
|
+
<Scene ref={sceneRef}>
|
|
276
|
+
<Player cameraManager={cameraManager} cameraId="main" />
|
|
277
|
+
{children}
|
|
278
|
+
</Scene>
|
|
279
|
+
</Suspense>
|
|
280
|
+
</Canvas>
|
|
281
|
+
</Suspense>
|
|
282
|
+
</>
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function WrappedRenderer(props: React.PropsWithChildren<RendererProps>) {
|
|
287
|
+
return (
|
|
288
|
+
<RenderStore.Provider>
|
|
289
|
+
<Renderer {...props} />
|
|
290
|
+
</RenderStore.Provider>
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export { WrappedRenderer as Renderer };
|
package/src/scene.tsx
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { forwardRef, type ReactNode } from "react";
|
|
2
|
+
import { Physics } from "@react-three/rapier";
|
|
3
|
+
import { Environment } from "@react-three/drei";
|
|
4
|
+
import { Landscape } from "./landscape.js";
|
|
5
|
+
import { Vector3, Group } from "three";
|
|
6
|
+
|
|
7
|
+
export interface SceneProps {
|
|
8
|
+
children?: ReactNode[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Use forwardRef to make ref work
|
|
12
|
+
export const Scene = forwardRef<Group, SceneProps>(function Scene(props, ref) {
|
|
13
|
+
return (
|
|
14
|
+
<group ref={ref}>
|
|
15
|
+
<Environment
|
|
16
|
+
preset={"dawn"}
|
|
17
|
+
background={false}
|
|
18
|
+
environmentIntensity={0.5}
|
|
19
|
+
/>
|
|
20
|
+
<ambientLight color={0xe1e1e1} intensity={0.3} />
|
|
21
|
+
<directionalLight
|
|
22
|
+
color={0xf0f0f0}
|
|
23
|
+
intensity={1.0}
|
|
24
|
+
position={new Vector3(5, 10, -10)}
|
|
25
|
+
castShadow
|
|
26
|
+
shadow-mapSize-width={2048}
|
|
27
|
+
shadow-mapSize-height={2048}
|
|
28
|
+
shadow-bias={-0.0005}
|
|
29
|
+
shadow-camera-left={-20}
|
|
30
|
+
shadow-camera-right={20}
|
|
31
|
+
shadow-camera-top={20}
|
|
32
|
+
shadow-camera-bottom={-20}
|
|
33
|
+
shadow-camera-near={0.5}
|
|
34
|
+
shadow-camera-far={100}
|
|
35
|
+
/>
|
|
36
|
+
<Physics gravity={[0, -9.81, 0]}>
|
|
37
|
+
<Landscape />
|
|
38
|
+
{props.children}
|
|
39
|
+
</Physics>
|
|
40
|
+
</group>
|
|
41
|
+
);
|
|
42
|
+
});
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import * as TSL from "three/tsl";
|
|
2
|
+
|
|
3
|
+
// Fragment shader
|
|
4
|
+
export const landscapeFragmentShader = TSL.Fn((shader) => {
|
|
5
|
+
const vHeight = shader.input.float("vHeight");
|
|
6
|
+
const fragColor = shader.uniforms.vec4("fragColor");
|
|
7
|
+
|
|
8
|
+
const lowColor = TSL.vec3(0.5, 0.8, 0.3); // Grass
|
|
9
|
+
const highColor = TSL.vec3(1.0, 1.0, 1.0); // Snow
|
|
10
|
+
|
|
11
|
+
const mixFactor = TSL.smoothstep(0.0, 2.0, vHeight);
|
|
12
|
+
const baseColor = TSL.mix(lowColor, highColor, mixFactor);
|
|
13
|
+
|
|
14
|
+
TSL.assign(fragColor, TSL.vec4(baseColor, 1.0));
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
/*import * as TSL from "three/tsl";
|
|
18
|
+
|
|
19
|
+
// Fragment shader
|
|
20
|
+
export const landscapeFragmentShader = TSL.Fn((shader) => {
|
|
21
|
+
const vUv = shader.input.vec2("vUv");
|
|
22
|
+
const vHeight = shader.input.float("vHeight");
|
|
23
|
+
const vPos = shader.input.vec3("vPos");
|
|
24
|
+
|
|
25
|
+
const time = shader.uniforms.float("time");
|
|
26
|
+
const fragColor = shader.uniforms.vec4("fragColor");
|
|
27
|
+
|
|
28
|
+
const applyWind = (normal, pos) => {
|
|
29
|
+
const windDir = TSL.vec2(
|
|
30
|
+
TSL.sin(TSL.add(TSL.mul(pos.x, 0.1), TSL.mul(time, 0.3))),
|
|
31
|
+
TSL.cos(TSL.add(TSL.mul(pos.y, 0.1), TSL.mul(time, 0.3)))
|
|
32
|
+
);
|
|
33
|
+
const strength = TSL.add(
|
|
34
|
+
0.2,
|
|
35
|
+
TSL.mul(
|
|
36
|
+
0.1,
|
|
37
|
+
TSL.sin(TSL.add(TSL.dot(pos, TSL.vec2(0.3, 0.7)), TSL.mul(time, 0.5)))
|
|
38
|
+
)
|
|
39
|
+
);
|
|
40
|
+
const wind = TSL.mul(
|
|
41
|
+
TSL.normalize(TSL.vec3(windDir.x, windDir.y, 0.0)),
|
|
42
|
+
strength
|
|
43
|
+
);
|
|
44
|
+
return TSL.normalize(TSL.add(normal, TSL.mul(wind, 0.4)));
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const calculateNormal = (height, uv, pos) => {
|
|
48
|
+
const eps = 0.01;
|
|
49
|
+
const hL = TSL.smoothstep(-2.0, 4.5, TSL.sub(height, eps));
|
|
50
|
+
const hR = TSL.smoothstep(-2.0, 4.5, TSL.add(height, eps));
|
|
51
|
+
const hD = TSL.smoothstep(-2.0, 4.5, TSL.sub(height, eps));
|
|
52
|
+
const hU = TSL.smoothstep(-2.0, 4.5, TSL.add(height, eps));
|
|
53
|
+
const normal = TSL.normalize(
|
|
54
|
+
TSL.vec3(TSL.sub(hL, hR), TSL.sub(hD, hU), 2.0)
|
|
55
|
+
);
|
|
56
|
+
return applyWind(normal, pos.xz);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const grassWave = (uv, height) => {
|
|
60
|
+
const baseFreq = 40.0;
|
|
61
|
+
const speed = 0.5;
|
|
62
|
+
const wave1 = TSL.sin(
|
|
63
|
+
TSL.mul(TSL.add(TSL.add(uv.x, uv.y), TSL.mul(time, speed)), baseFreq)
|
|
64
|
+
);
|
|
65
|
+
const wave2 = TSL.cos(
|
|
66
|
+
TSL.mul(
|
|
67
|
+
TSL.add(TSL.sub(uv.x, uv.y), TSL.mul(time, TSL.mul(speed, 1.2))),
|
|
68
|
+
TSL.mul(baseFreq, 0.8)
|
|
69
|
+
)
|
|
70
|
+
);
|
|
71
|
+
const pattern = TSL.mul(TSL.add(wave1, wave2), 0.03);
|
|
72
|
+
const density = TSL.smoothstep(0.3, 0.7, height);
|
|
73
|
+
return TSL.mul(pattern, density);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const grassDensityFactor = (coords) =>
|
|
77
|
+
TSL.smoothstep(
|
|
78
|
+
0.4,
|
|
79
|
+
0.9,
|
|
80
|
+
TSL.fract(
|
|
81
|
+
TSL.mul(
|
|
82
|
+
TSL.sin(TSL.dot(TSL.mul(coords, 0.2), TSL.vec2(12.9898, 78.233))),
|
|
83
|
+
43758.5453
|
|
84
|
+
)
|
|
85
|
+
)
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const sand = TSL.vec3(0.76, 0.7, 0.5);
|
|
89
|
+
const grass = TSL.vec3(0.4, 0.8, 0.2);
|
|
90
|
+
const stone = TSL.vec3(0.5, 0.5, 0.5);
|
|
91
|
+
const snow = TSL.vec3(0.95, 0.95, 0.95);
|
|
92
|
+
|
|
93
|
+
const low = TSL.smoothstep(-2.0, 0.0, vHeight);
|
|
94
|
+
const high = TSL.smoothstep(1.0, 3.0, vHeight);
|
|
95
|
+
|
|
96
|
+
let base = TSL.mix(sand, grass, low);
|
|
97
|
+
base = TSL.mix(base, stone, high);
|
|
98
|
+
base = TSL.mix(base, snow, TSL.smoothstep(2.5, 4.5, vHeight));
|
|
99
|
+
|
|
100
|
+
const normal = calculateNormal(vHeight, vUv, vPos);
|
|
101
|
+
const slope = TSL.dot(TSL.vec3(0.0, 0.0, 1.0), normal);
|
|
102
|
+
const snowBlend = TSL.smoothstep(0.8, 1.0, slope);
|
|
103
|
+
base = TSL.mix(
|
|
104
|
+
base,
|
|
105
|
+
snow,
|
|
106
|
+
TSL.mul(snowBlend, TSL.smoothstep(1.5, 4.5, vHeight))
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
const tintNoise = TSL.fract(
|
|
110
|
+
TSL.mul(TSL.sin(TSL.dot(vPos.xz, TSL.vec2(91.91, 47.47))), 43758.5453)
|
|
111
|
+
);
|
|
112
|
+
const tint = TSL.mix(
|
|
113
|
+
TSL.vec3(-0.05, 0.05, -0.02),
|
|
114
|
+
TSL.vec3(0.05, -0.03, 0.02),
|
|
115
|
+
tintNoise
|
|
116
|
+
);
|
|
117
|
+
base = TSL.add(base, tint);
|
|
118
|
+
|
|
119
|
+
const density = grassDensityFactor(vPos.xz);
|
|
120
|
+
base = TSL.mix(base, TSL.vec3(0.25, 0.4, 0.15), TSL.sub(1.0, density));
|
|
121
|
+
|
|
122
|
+
const lightDir = TSL.normalize(TSL.vec3(0.3, 0.5, 1.0));
|
|
123
|
+
let lighting = TSL.clamp(TSL.dot(lightDir, normal), 0.3, 1.0);
|
|
124
|
+
lighting = TSL.mul(lighting, TSL.add(0.8, TSL.mul(0.4, density)));
|
|
125
|
+
|
|
126
|
+
const grassMotion = TSL.mul(grassWave(vUv, vHeight), density);
|
|
127
|
+
const animatedGrass = TSL.vec3(0.0, TSL.mul(grassMotion, 0.4), 0.0);
|
|
128
|
+
|
|
129
|
+
const fresnel = TSL.pow(
|
|
130
|
+
TSL.sub(1.0, TSL.dot(TSL.normalize(TSL.vec3(0.0, 0.0, 1.0)), normal)),
|
|
131
|
+
3.0
|
|
132
|
+
);
|
|
133
|
+
const fresnelColor = TSL.mul(TSL.vec3(0.2, 0.5, 0.1), fresnel);
|
|
134
|
+
|
|
135
|
+
const finalColor = TSL.mul(
|
|
136
|
+
TSL.add(TSL.add(base, animatedGrass), fresnelColor),
|
|
137
|
+
lighting
|
|
138
|
+
);
|
|
139
|
+
TSL.assign(fragColor, TSL.vec4(finalColor, 1.0));
|
|
140
|
+
});
|
|
141
|
+
*/
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import * as THREE from "three/webgpu";
|
|
2
|
+
import { extend, ThreeElements } from "@react-three/fiber";
|
|
3
|
+
import { landscapeVertexShader } from "./vertex/landscapeVertexShader.js";
|
|
4
|
+
import { landscapeFragmentShader } from "./fragment/landscapeFragmentShader.js";
|
|
5
|
+
import { add } from "three/tsl";
|
|
6
|
+
|
|
7
|
+
// ✅ Define a class that extends the material
|
|
8
|
+
class LandscapeShaderMaterial extends THREE.MeshStandardNodeMaterial {
|
|
9
|
+
constructor() {
|
|
10
|
+
super({ color: new THREE.Color(0.4, 0.7, 0.2) });
|
|
11
|
+
/*
|
|
12
|
+
// Append your vertex shader to the existing position pipeline
|
|
13
|
+
if (this.positionNode)
|
|
14
|
+
this.positionNode = shader(() =>
|
|
15
|
+
add(this.positionNode!, landscapeVertexShader)
|
|
16
|
+
);
|
|
17
|
+
else this.positionNode = landscapeVertexShader;
|
|
18
|
+
// Append your fragment shader to the existing color pipeline
|
|
19
|
+
if (this.colorNode)
|
|
20
|
+
this.colorNode = shader(() =>
|
|
21
|
+
add(this.colorNode!, landscapeFragmentShader)
|
|
22
|
+
);
|
|
23
|
+
else this.colorNode = landscapeFragmentShader;
|
|
24
|
+
*/
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ✅ Register the class with R3F so you can use it as a JSX tag
|
|
29
|
+
extend({ LandscapeShaderMaterial });
|
|
30
|
+
|
|
31
|
+
export { LandscapeShaderMaterial };
|
|
32
|
+
|
|
33
|
+
declare module "@react-three/fiber" {
|
|
34
|
+
interface ThreeElements {
|
|
35
|
+
landscapeShaderMaterial: ThreeElements["meshStandardMaterial"] & {
|
|
36
|
+
ref?: React.Ref<THREE.MeshStandardNodeMaterial>;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
}
|