feat: custom shaders

This commit is contained in:
TrèsAbhi committed 2024-01-09 22:31:34 -06:00
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}
@@ -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>
); );
}, },
@@ -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>
); );
@@ -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} />
</> </>
); );
} }
+42
View File
@@ -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);
}
`,
},
]}
/>
);
}