diff --git a/renderer/viewer/three/shadersThree.ts b/renderer/viewer/three/shadersThree.ts new file mode 100644 index 00000000..052b9009 --- /dev/null +++ b/renderer/viewer/three/shadersThree.ts @@ -0,0 +1,94 @@ +import { ShaderChunk } from 'three' + +// Original simple shader for non-animated blocks +export const BLOCK_VERTEX_SHADER = ` +#include +${ShaderChunk.logdepthbuf_pars_vertex} + +attribute vec3 color; + +varying vec2 vUv; +varying vec3 vColor; + +void main() { + vUv = uv; + vColor = color; + gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); + + ${ShaderChunk.logdepthbuf_vertex} +} +` + +export const BLOCK_FRAGMENT_SHADER = ` +#include +${ShaderChunk.logdepthbuf_pars_fragment} + +uniform sampler2D map; +varying vec2 vUv; +varying vec3 vColor; + +void main() { + vec4 texColor = texture2D(map, vUv); + if (texColor.a < 0.1) discard; + gl_FragColor = vec4(vColor * texColor.rgb, texColor.a); + + ${ShaderChunk.logdepthbuf_fragment} +} +` + +// New shader for animated blocks +export const ANIMATED_BLOCK_VERTEX_SHADER = ` +#include +${ShaderChunk.logdepthbuf_pars_vertex} + +uniform float animationFrameHeight; +uniform float animationFrameIndex; +uniform float animationInterpolationFrameIndex; +uniform float animationInterpolation; + +attribute vec3 color; +varying vec2 vUv; +varying vec3 vColor; +varying float vAnimationInterpolation; + +void main() { + vUv = uv; + vColor = color; + vAnimationInterpolation = animationInterpolation; + gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); + + ${ShaderChunk.logdepthbuf_vertex} +} +` + +export const ANIMATED_BLOCK_FRAGMENT_SHADER = ` +#include +${ShaderChunk.logdepthbuf_pars_fragment} + +uniform sampler2D map; +uniform float animationFrameHeight; +uniform float animationFrameIndex; +uniform float animationInterpolationFrameIndex; + +varying vec2 vUv; +varying vec3 vColor; +varying float vAnimationInterpolation; + +void main() { + // Calculate UV coordinates for current frame + vec2 currentFrameUv = vec2(vUv.x, animationFrameHeight * (vUv.y + animationFrameIndex)); + vec4 currentFrame = texture2D(map, currentFrameUv); + + // If interpolation is enabled, calculate UV for next frame and mix + if (vAnimationInterpolation > 0.0) { + vec2 nextFrameUv = vec2(vUv.x, animationFrameHeight * (vUv.y + animationInterpolationFrameIndex)); + vec4 nextFrame = texture2D(map, nextFrameUv); + currentFrame = mix(currentFrame, nextFrame, vAnimationInterpolation); + } + + if (currentFrame.a < 0.1) discard; + gl_FragColor = vec4(vColor * currentFrame.rgb, currentFrame.a); + + ${ShaderChunk.logdepthbuf_fragment} +} +` diff --git a/renderer/viewer/three/worldrendererThree.ts b/renderer/viewer/three/worldrendererThree.ts index 14270367..117d631d 100644 --- a/renderer/viewer/three/worldrendererThree.ts +++ b/renderer/viewer/three/worldrendererThree.ts @@ -25,10 +25,49 @@ import { Entities } from './entities' import { ThreeJsSound } from './threeJsSound' import { CameraShake } from './cameraShake' import { ThreeJsMedia } from './threeJsMedia' +import { BLOCK_VERTEX_SHADER, BLOCK_FRAGMENT_SHADER, ANIMATED_BLOCK_VERTEX_SHADER, ANIMATED_BLOCK_FRAGMENT_SHADER } from './shadersThree' import { Fountain } from './threeJsParticles' type SectionKey = string +class CustomBlockMaterial extends THREE.ShaderMaterial { + constructor () { + super({ + uniforms: { + map: { value: null } + }, + vertexShader: BLOCK_VERTEX_SHADER, + fragmentShader: BLOCK_FRAGMENT_SHADER, + transparent: true, + alphaTest: 0.1, + depthWrite: true, + depthTest: true, + side: THREE.FrontSide + }) + } +} + +class AnimatedBlockMaterial extends THREE.ShaderMaterial { + constructor () { + super({ + uniforms: { + map: { value: null }, + animationFrameHeight: { value: 1 }, + animationFrameIndex: { value: 0 }, + animationInterpolationFrameIndex: { value: 0 }, + animationInterpolation: { value: 0 } + }, + vertexShader: ANIMATED_BLOCK_VERTEX_SHADER, + fragmentShader: ANIMATED_BLOCK_FRAGMENT_SHADER, + transparent: true, + alphaTest: 0.1, + depthWrite: true, + depthTest: true, + side: THREE.FrontSide + }) + } +} + export class WorldRendererThree extends WorldRendererCommon { outputFormat = 'threeJs' as const sectionObjects: Record = {} @@ -36,14 +75,15 @@ export class WorldRendererThree extends WorldRendererCommon { signsCache = new Map() starField: StarField cameraSectionPos: Vec3 = new Vec3(0, 0, 0) - holdingBlock: HoldingBlock - holdingBlockLeft: HoldingBlock + holdingBlock: HoldingBlock | undefined + holdingBlockLeft: HoldingBlock | undefined scene = new THREE.Scene() ambientLight = new THREE.AmbientLight(0xcc_cc_cc) directionalLight = new THREE.DirectionalLight(0xff_ff_ff, 0.5) entities = new Entities(this) cameraObjectOverride?: THREE.Object3D // for xr - material = new THREE.MeshLambertMaterial({ vertexColors: true, transparent: true, alphaTest: 0.1 }) + material = new CustomBlockMaterial() + animatedMaterial = new AnimatedBlockMaterial() itemsTexture: THREE.Texture cursorBlock = new CursorBlock(this) onRender: Array<() => void> = [] @@ -168,23 +208,23 @@ export class WorldRendererThree extends WorldRendererCommon { changeHandSwingingState (isAnimationPlaying: boolean, isLeft = false) { const holdingBlock = isLeft ? this.holdingBlockLeft : this.holdingBlock if (isAnimationPlaying) { - holdingBlock.startSwing() + holdingBlock?.startSwing() } else { - holdingBlock.stopSwing() + holdingBlock?.stopSwing() } } async updateAssetsData (): Promise { const resources = this.resourcesManager.currentResources! - const oldTexture = this.material.map + const oldTexture = this.material.uniforms.map.value const oldItemsTexture = this.itemsTexture const texture = await new THREE.TextureLoader().loadAsync(resources.blocksAtlasParser.latestImage) texture.magFilter = THREE.NearestFilter texture.minFilter = THREE.NearestFilter texture.flipY = false - this.material.map = texture + this.material.uniforms.map.value = texture const itemsTexture = await new THREE.TextureLoader().loadAsync(resources.itemsAtlasParser.latestImage) itemsTexture.magFilter = THREE.NearestFilter @@ -208,10 +248,14 @@ export class WorldRendererThree extends WorldRendererCommon { } onAllTexturesLoaded () { - this.holdingBlock.ready = true - this.holdingBlock.updateItem() - this.holdingBlockLeft.ready = true - this.holdingBlockLeft.updateItem() + if (this.holdingBlock) { + this.holdingBlock.ready = true + this.holdingBlock.updateItem() + } + if (this.holdingBlockLeft) { + this.holdingBlockLeft.ready = true + this.holdingBlockLeft.updateItem() + } } changeBackgroundColor (color: [number, number, number]): void { @@ -464,8 +508,8 @@ export class WorldRendererThree extends WorldRendererCommon { this.renderer.render(this.scene, cam) if (this.displayOptions.inWorldRenderingConfig.showHand/* && !this.freeFlyMode */) { - this.holdingBlock.render(this.camera, this.renderer, this.ambientLight, this.directionalLight) - this.holdingBlockLeft.render(this.camera, this.renderer, this.ambientLight, this.directionalLight) + this.holdingBlock?.render(this.camera, this.renderer, this.ambientLight, this.directionalLight) + this.holdingBlockLeft?.render(this.camera, this.renderer, this.ambientLight, this.directionalLight) } for (const fountain of this.fountains) {