mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 10:47:04 +02:00
feat: custom shaders
This commit is contained in:
1 parent
a7d8f08522
commit
4a6b5e3aa9
9 files changed
+176
-46
No files matched your search
@@ -0,0 +1,19 @@
|
|||||||
|
export function Lighting() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<directionalLight
|
||||||
|
position={[1, 1, -1]}
|
||||||
|
intensity={2}
|
||||||
|
castShadow
|
||||||
|
color={'rgb(225, 225, 255)'}
|
||||||
|
/>
|
||||||
|
<directionalLight
|
||||||
|
position={[-1, 1, 1]}
|
||||||
|
intensity={1}
|
||||||
|
castShadow
|
||||||
|
color={'rgb(225, 225, 255)'}
|
||||||
|
/>
|
||||||
|
<ambientLight intensity={0.25} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -17,9 +17,9 @@ import mutateTankopedia, {
|
|||||||
} from '../../../../../../stores/tankopedia';
|
} from '../../../../../../stores/tankopedia';
|
||||||
import { Loader } from '../../../../components/Loader';
|
import { Loader } from '../../../../components/Loader';
|
||||||
import { Controls } from '../Control';
|
import { Controls } from '../Control';
|
||||||
|
import { Lighting } from '../Lighting';
|
||||||
import { RotationInputs } from '../RotationInputs';
|
import { RotationInputs } from '../RotationInputs';
|
||||||
import { SceneProps } from '../SceneProps';
|
import { SceneProps } from '../SceneProps';
|
||||||
import { TankArmor } from './components/TankArmor';
|
|
||||||
import { TankModel } from './components/TankModel';
|
import { TankModel } from './components/TankModel';
|
||||||
|
|
||||||
export function TankDisplay() {
|
export function TankDisplay() {
|
||||||
@@ -117,9 +117,11 @@ export function TankDisplay() {
|
|||||||
ref={canvas}
|
ref={canvas}
|
||||||
camera={{ fov: 20 }}
|
camera={{ fov: 20 }}
|
||||||
onPointerDown={handlePointerDown}
|
onPointerDown={handlePointerDown}
|
||||||
|
gl={{ logarithmicDepthBuffer: true }}
|
||||||
>
|
>
|
||||||
<Controls />
|
<Controls />
|
||||||
<SceneProps />
|
<SceneProps />
|
||||||
|
<Lighting />
|
||||||
|
|
||||||
<Suspense
|
<Suspense
|
||||||
fallback={
|
fallback={
|
||||||
@@ -132,7 +134,6 @@ export function TankDisplay() {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<TankModel ref={hullContainer} />
|
<TankModel ref={hullContainer} />
|
||||||
{mode === 'armor' && <TankArmor />}
|
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</Canvas>
|
</Canvas>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ export const TankArmor = forwardRef<Group>((_props, ref) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<mesh
|
<mesh
|
||||||
|
scale={1 + 1e-3}
|
||||||
key={node.uuid}
|
key={node.uuid}
|
||||||
geometry={(node as Mesh).geometry}
|
geometry={(node as Mesh).geometry}
|
||||||
position={position}
|
position={position}
|
||||||
|
|||||||
+25
-3
@@ -1,6 +1,7 @@
|
|||||||
import { ThreeEvent, useThree } from '@react-three/fiber';
|
import { ThreeEvent, useThree } from '@react-three/fiber';
|
||||||
import { RefObject, forwardRef, useImperativeHandle, useRef } from 'react';
|
import { RefObject, forwardRef, useImperativeHandle, useRef } from 'react';
|
||||||
import { Group, Mesh, Object3D, Vector3 } from 'three';
|
import { Group, Mesh, Object3D, Vector3 } from 'three';
|
||||||
|
import { ArmorHighlightMaterial } from '../../../../../../../../../components/ArmorHighlightMaterial';
|
||||||
import { applyPitchYawLimits } from '../../../../../../../../../core/blitz/applyPitchYawLimits';
|
import { applyPitchYawLimits } from '../../../../../../../../../core/blitz/applyPitchYawLimits';
|
||||||
import {
|
import {
|
||||||
InitialTurretRotation,
|
InitialTurretRotation,
|
||||||
@@ -15,7 +16,8 @@ interface GunContainerProps {
|
|||||||
gunOrigin: Vector3;
|
gunOrigin: Vector3;
|
||||||
pitch: number;
|
pitch: number;
|
||||||
yaw: number;
|
yaw: number;
|
||||||
objects: Object3D[];
|
modelObjects: Object3D[];
|
||||||
|
armorObjects: Object3D[];
|
||||||
model: number;
|
model: number;
|
||||||
yawLimits?: YawLimits;
|
yawLimits?: YawLimits;
|
||||||
pitchLimits: PitchLimits;
|
pitchLimits: PitchLimits;
|
||||||
@@ -33,10 +35,11 @@ export const GunContainer = forwardRef<Group, GunContainerProps>(
|
|||||||
pitch,
|
pitch,
|
||||||
initialTurretRotation,
|
initialTurretRotation,
|
||||||
yaw,
|
yaw,
|
||||||
objects,
|
modelObjects,
|
||||||
yawLimits,
|
yawLimits,
|
||||||
onPitchStart,
|
onPitchStart,
|
||||||
turretContainer,
|
turretContainer,
|
||||||
|
armorObjects,
|
||||||
pitchLimits,
|
pitchLimits,
|
||||||
model,
|
model,
|
||||||
onPitchEnd,
|
onPitchEnd,
|
||||||
@@ -59,7 +62,7 @@ export const GunContainer = forwardRef<Group, GunContainerProps>(
|
|||||||
rotation={[pitch, 0, 0]}
|
rotation={[pitch, 0, 0]}
|
||||||
ref={gunContainer}
|
ref={gunContainer}
|
||||||
>
|
>
|
||||||
{objects.map((object) => {
|
{modelObjects.map((object) => {
|
||||||
const isCurrentMantlet =
|
const isCurrentMantlet =
|
||||||
object.name === `gun_${model.toString().padStart(2, '0')}_mask`;
|
object.name === `gun_${model.toString().padStart(2, '0')}_mask`;
|
||||||
const isCurrentGun =
|
const isCurrentGun =
|
||||||
@@ -143,6 +146,25 @@ export const GunContainer = forwardRef<Group, GunContainerProps>(
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
|
{armorObjects.map((object) => {
|
||||||
|
const isCurrentGun = object.name.startsWith(
|
||||||
|
`gun_${model.toString().padStart(2, '0')}`,
|
||||||
|
);
|
||||||
|
const isVisible = isCurrentGun;
|
||||||
|
|
||||||
|
if (!isVisible) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<mesh
|
||||||
|
position={turretOrigin.clone().add(gunOrigin)}
|
||||||
|
key={object.uuid}
|
||||||
|
geometry={(object as Mesh).geometry}
|
||||||
|
>
|
||||||
|
<ArmorHighlightMaterial />
|
||||||
|
</mesh>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</group>
|
</group>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
+37
-5
@@ -1,13 +1,22 @@
|
|||||||
import { ThreeEvent, useThree } from '@react-three/fiber';
|
import { ThreeEvent, useThree } from '@react-three/fiber';
|
||||||
import { ReactNode, forwardRef, useImperativeHandle, useRef } from 'react';
|
import { ReactNode, forwardRef, useImperativeHandle, useRef } from 'react';
|
||||||
import { Group, Mesh, MeshStandardMaterial, Object3D, Vector2 } from 'three';
|
import {
|
||||||
|
Euler,
|
||||||
|
Group,
|
||||||
|
Mesh,
|
||||||
|
MeshStandardMaterial,
|
||||||
|
Object3D,
|
||||||
|
Vector2,
|
||||||
|
} from 'three';
|
||||||
|
import { ArmorHighlightMaterial } from '../../../../../../../../../components/ArmorHighlightMaterial';
|
||||||
import { resolveJsxTree } from '../../../../../../../../../core/blitzkrieg/resolveJsxTree';
|
import { resolveJsxTree } from '../../../../../../../../../core/blitzkrieg/resolveJsxTree';
|
||||||
import { normalizeAnglePI } from '../../../../../../../../../core/math/normalizeAngle180';
|
import { normalizeAnglePI } from '../../../../../../../../../core/math/normalizeAngle180';
|
||||||
|
|
||||||
interface HullContainerProps {
|
interface HullContainerProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
yaw: number;
|
yaw: number;
|
||||||
objects: Object3D[];
|
modelObjects: Object3D[];
|
||||||
|
armorObjects: Object3D[];
|
||||||
onYawStart: () => void;
|
onYawStart: () => void;
|
||||||
onYawEnd: (yaw: number) => void;
|
onYawEnd: (yaw: number) => void;
|
||||||
onTrackStart: () => void;
|
onTrackStart: () => void;
|
||||||
@@ -16,17 +25,27 @@ interface HullContainerProps {
|
|||||||
|
|
||||||
export const HullContainer = forwardRef<Group, HullContainerProps>(
|
export const HullContainer = forwardRef<Group, HullContainerProps>(
|
||||||
(
|
(
|
||||||
{ children, yaw, objects, onYawStart, onYawEnd, onTrackEnd, onTrackStart },
|
{
|
||||||
|
children,
|
||||||
|
yaw,
|
||||||
|
armorObjects,
|
||||||
|
modelObjects,
|
||||||
|
onYawStart,
|
||||||
|
onYawEnd,
|
||||||
|
onTrackEnd,
|
||||||
|
onTrackStart,
|
||||||
|
},
|
||||||
ref,
|
ref,
|
||||||
) => {
|
) => {
|
||||||
const canvas = useThree((state) => state.gl.domElement);
|
const canvas = useThree((state) => state.gl.domElement);
|
||||||
const hullContainer = useRef<Group>(null);
|
const hullContainer = useRef<Group>(null);
|
||||||
|
const rotation = [-Math.PI / 2, 0, yaw] as unknown as Euler;
|
||||||
|
|
||||||
useImperativeHandle(ref, () => hullContainer.current!);
|
useImperativeHandle(ref, () => hullContainer.current!);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<group ref={hullContainer} rotation={[-Math.PI / 2, 0, yaw]}>
|
<group ref={hullContainer} rotation={rotation}>
|
||||||
{objects.map((object) => {
|
{modelObjects.map((object) => {
|
||||||
const isHull = object.name === 'hull';
|
const isHull = object.name === 'hull';
|
||||||
const isWheel = object.name.startsWith('chassis_wheel_');
|
const isWheel = object.name.startsWith('chassis_wheel_');
|
||||||
const isTrack = object.name.startsWith('chassis_track_');
|
const isTrack = object.name.startsWith('chassis_track_');
|
||||||
@@ -96,6 +115,19 @@ export const HullContainer = forwardRef<Group, HullContainerProps>(
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
|
{armorObjects.map((object) => {
|
||||||
|
const isHull = object.name.startsWith('hull_');
|
||||||
|
const isVisible = isHull;
|
||||||
|
|
||||||
|
if (!isVisible) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<mesh key={object.uuid} geometry={(object as Mesh).geometry}>
|
||||||
|
<ArmorHighlightMaterial />
|
||||||
|
</mesh>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
{children}
|
{children}
|
||||||
</group>
|
</group>
|
||||||
);
|
);
|
||||||
|
|||||||
+25
-3
@@ -7,6 +7,7 @@ import {
|
|||||||
useRef,
|
useRef,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { Euler, Group, Mesh, Object3D, Vector3 } from 'three';
|
import { Euler, Group, Mesh, Object3D, Vector3 } from 'three';
|
||||||
|
import { ArmorHighlightMaterial } from '../../../../../../../../../components/ArmorHighlightMaterial';
|
||||||
import { applyPitchYawLimits } from '../../../../../../../../../core/blitz/applyPitchYawLimits';
|
import { applyPitchYawLimits } from '../../../../../../../../../core/blitz/applyPitchYawLimits';
|
||||||
import {
|
import {
|
||||||
InitialTurretRotation,
|
InitialTurretRotation,
|
||||||
@@ -19,7 +20,8 @@ import { normalizeAnglePI } from '../../../../../../../../../core/math/normalize
|
|||||||
interface TurretContainerProps {
|
interface TurretContainerProps {
|
||||||
turretOrigin: Vector3;
|
turretOrigin: Vector3;
|
||||||
gunOrigin: Vector3;
|
gunOrigin: Vector3;
|
||||||
objects: Object3D[];
|
modelObjects: Object3D[];
|
||||||
|
armorObjects: Object3D[];
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
yaw: number;
|
yaw: number;
|
||||||
pitch: number;
|
pitch: number;
|
||||||
@@ -39,12 +41,13 @@ export const TurretContainer = forwardRef(
|
|||||||
turretOrigin,
|
turretOrigin,
|
||||||
yaw,
|
yaw,
|
||||||
children,
|
children,
|
||||||
objects,
|
modelObjects,
|
||||||
yawLimits,
|
yawLimits,
|
||||||
model,
|
model,
|
||||||
onYawEnd,
|
onYawEnd,
|
||||||
pitch,
|
pitch,
|
||||||
gunOrigin,
|
gunOrigin,
|
||||||
|
armorObjects,
|
||||||
pitchLimits,
|
pitchLimits,
|
||||||
onYawStart,
|
onYawStart,
|
||||||
initialTurretRotation,
|
initialTurretRotation,
|
||||||
@@ -78,7 +81,7 @@ export const TurretContainer = forwardRef(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<group position={position} rotation={rotation} ref={turretContainer}>
|
<group position={position} rotation={rotation} ref={turretContainer}>
|
||||||
{objects.map((object) => {
|
{modelObjects.map((object) => {
|
||||||
const isTurret = object.name.startsWith('turret_');
|
const isTurret = object.name.startsWith('turret_');
|
||||||
const isCurrentTurret =
|
const isCurrentTurret =
|
||||||
object.name === `turret_${model.toString().padStart(2, '0')}`;
|
object.name === `turret_${model.toString().padStart(2, '0')}`;
|
||||||
@@ -162,6 +165,25 @@ export const TurretContainer = forwardRef(
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
|
{armorObjects.map((object) => {
|
||||||
|
const isCurrentTurret = object.name.startsWith(
|
||||||
|
`turret_${model.toString().padStart(2, '0')}`,
|
||||||
|
);
|
||||||
|
const isVisible = isCurrentTurret;
|
||||||
|
|
||||||
|
if (!isVisible) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<mesh
|
||||||
|
position={turretOrigin}
|
||||||
|
key={object.uuid}
|
||||||
|
geometry={(object as Mesh).geometry}
|
||||||
|
>
|
||||||
|
<ArmorHighlightMaterial />
|
||||||
|
</mesh>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
{children}
|
{children}
|
||||||
</group>
|
</group>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -34,10 +34,14 @@ export const TankModel = forwardRef<Group>((_props, ref) => {
|
|||||||
|
|
||||||
if (!protagonist) return null;
|
if (!protagonist) return null;
|
||||||
|
|
||||||
const gltf = useLoader(
|
const modelGltf = useLoader(
|
||||||
GLTFLoader,
|
GLTFLoader,
|
||||||
asset(`3d/tanks/models/${protagonist.tank.id}.glb`),
|
asset(`3d/tanks/models/${protagonist.tank.id}.glb`),
|
||||||
);
|
);
|
||||||
|
const armorGltf = useLoader(
|
||||||
|
GLTFLoader,
|
||||||
|
asset(`3d/tanks/armor/${protagonist.tank.id}.glb`),
|
||||||
|
);
|
||||||
const model = useTankopedia((state) => state.model);
|
const model = useTankopedia((state) => state.model);
|
||||||
const hullContainer = useRef<Group>(null);
|
const hullContainer = useRef<Group>(null);
|
||||||
const turretContainer = useRef<Group>(null);
|
const turretContainer = useRef<Group>(null);
|
||||||
@@ -69,7 +73,8 @@ export const TankModel = forwardRef<Group>((_props, ref) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<HullContainer
|
<HullContainer
|
||||||
objects={Object.values(gltf.nodes)}
|
modelObjects={Object.values(modelGltf.nodes)}
|
||||||
|
armorObjects={Object.values(armorGltf.nodes)}
|
||||||
yaw={model.hullYaw}
|
yaw={model.hullYaw}
|
||||||
ref={hullContainer}
|
ref={hullContainer}
|
||||||
onYawStart={() =>
|
onYawStart={() =>
|
||||||
@@ -95,10 +100,11 @@ export const TankModel = forwardRef<Group>((_props, ref) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TurretContainer
|
<TurretContainer
|
||||||
|
armorObjects={Object.values(armorGltf.nodes)}
|
||||||
initialTurretRotation={tankModelDefinition.turretRotation}
|
initialTurretRotation={tankModelDefinition.turretRotation}
|
||||||
gunOrigin={gunOrigin}
|
gunOrigin={gunOrigin}
|
||||||
ref={turretContainer}
|
ref={turretContainer}
|
||||||
objects={Object.values(gltf.nodes)}
|
modelObjects={Object.values(modelGltf.nodes)}
|
||||||
model={turretModelDefinition.model}
|
model={turretModelDefinition.model}
|
||||||
onYawStart={() =>
|
onYawStart={() =>
|
||||||
mutateTankopedia((state) => {
|
mutateTankopedia((state) => {
|
||||||
@@ -121,6 +127,7 @@ export const TankModel = forwardRef<Group>((_props, ref) => {
|
|||||||
>
|
>
|
||||||
<GunContainer
|
<GunContainer
|
||||||
ref={gunContainer}
|
ref={gunContainer}
|
||||||
|
armorObjects={Object.values(armorGltf.nodes)}
|
||||||
initialTurretRotation={tankModelDefinition.turretRotation}
|
initialTurretRotation={tankModelDefinition.turretRotation}
|
||||||
onPitchStart={() => {
|
onPitchStart={() => {
|
||||||
mutateTankopedia((state) => {
|
mutateTankopedia((state) => {
|
||||||
@@ -141,7 +148,7 @@ export const TankModel = forwardRef<Group>((_props, ref) => {
|
|||||||
gunOrigin={gunOrigin}
|
gunOrigin={gunOrigin}
|
||||||
pitch={model.gunPitch}
|
pitch={model.gunPitch}
|
||||||
model={gunModelDefinition.model}
|
model={gunModelDefinition.model}
|
||||||
objects={Object.values(gltf.nodes)}
|
modelObjects={Object.values(modelGltf.nodes)}
|
||||||
turretOrigin={turretOrigin}
|
turretOrigin={turretOrigin}
|
||||||
/>
|
/>
|
||||||
</TurretContainer>
|
</TurretContainer>
|
||||||
|
|||||||
@@ -5,39 +5,23 @@ import { useTankopedia } from '../../../../../stores/tankopedia';
|
|||||||
export function SceneProps() {
|
export function SceneProps() {
|
||||||
const showGrid = useTankopedia((state) => state.model.showGrid);
|
const showGrid = useTankopedia((state) => state.model.showGrid);
|
||||||
|
|
||||||
|
if (!showGrid) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{showGrid && (
|
<InfiniteGridHelper
|
||||||
<>
|
size1={1 / 5}
|
||||||
<InfiniteGridHelper
|
size2={1}
|
||||||
size1={1 / 5}
|
distance={20}
|
||||||
size2={1}
|
color={new Color('#ffffff')}
|
||||||
distance={20}
|
|
||||||
color={new Color('#ffffff')}
|
|
||||||
/>
|
|
||||||
<InfiniteGridHelper
|
|
||||||
position={[0, 1e-4, 0]}
|
|
||||||
size1={0}
|
|
||||||
size2={100}
|
|
||||||
distance={25}
|
|
||||||
color={new Color('red')}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<directionalLight
|
|
||||||
position={[1, 1, -1]}
|
|
||||||
intensity={2}
|
|
||||||
castShadow
|
|
||||||
color={'rgb(225, 225, 255)'}
|
|
||||||
/>
|
/>
|
||||||
<directionalLight
|
<InfiniteGridHelper
|
||||||
position={[-1, 1, 1]}
|
position={[0, 1e-4, 0]}
|
||||||
intensity={1}
|
size1={0}
|
||||||
castShadow
|
size2={100}
|
||||||
color={'rgb(225, 225, 255)'}
|
distance={25}
|
||||||
|
color={new Color('red')}
|
||||||
/>
|
/>
|
||||||
<ambientLight intensity={0.25} />
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { GreaterEqualDepth } from 'three';
|
||||||
|
|
||||||
|
const INFLATE = '0.0';
|
||||||
|
|
||||||
|
export function ArmorHighlightMaterial() {
|
||||||
|
return (
|
||||||
|
<shaderMaterial
|
||||||
|
depthFunc={GreaterEqualDepth}
|
||||||
|
args={[
|
||||||
|
{
|
||||||
|
transparent: true,
|
||||||
|
|
||||||
|
vertexShader: `
|
||||||
|
varying vec3 vNormal;
|
||||||
|
varying vec3 vViewPosition; // Camera position in view space
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vNormal = normalMatrix * normal; // Transform normal to view space
|
||||||
|
vViewPosition = (modelViewMatrix * vec4(position, 1.0)).xyz; // Camera position in view space
|
||||||
|
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
|
||||||
|
fragmentShader: `
|
||||||
|
// Fragment Shader
|
||||||
|
varying vec3 vNormal;
|
||||||
|
varying vec3 vViewPosition;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
// Use vNormal for your fragment shader calculations
|
||||||
|
vec3 normal = normalize(vNormal);
|
||||||
|
float uhhh = dot(normal, vViewPosition) * 0.5 + 1.0;
|
||||||
|
|
||||||
|
// ... rest of your fragment shader code ...
|
||||||
|
gl_FragColor = vec4(1.0, 0.0, 0.0, uhhh);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in new issue
Block a user