diff --git a/demo/main.ts b/demo/main.ts index 9e35bfd8..65014d22 100644 --- a/demo/main.ts +++ b/demo/main.ts @@ -1,6 +1,6 @@ import { mat4 } from 'gl-matrix' import type { ItemRendererResources, ItemRenderingContext, NbtTag, Resources, Voxel } from '../src/index.js' -import { BlockDefinition, BlockModel, Identifier, ItemRenderer, ItemStack, jsonToNbt, NormalNoise, Structure, StructureRenderer, TextureAtlas, upperPowerOfTwo, VoxelRenderer, XoroshiroRandom } from '../src/index.js' +import { BlockDefinition, BlockModel, Identifier, ItemRenderer, ItemStack, NormalNoise, Structure, StructureRenderer, TextureAtlas, VoxelRenderer, XoroshiroRandom, jsonToNbt, upperPowerOfTwo } from '../src/index.js' import { } from '../src/nbt/Util.js' import { ItemModel } from '../src/render/ItemModel.js' @@ -136,6 +136,7 @@ Promise.all([ getBlockModel(id) { return blockModels[id.toString()] }, getTextureUV(id) { return textureAtlas.getTextureUV(id) }, getTextureAtlas() { return textureAtlas.getTextureAtlas() }, + getPixelSize() { return textureAtlas.getPixelSize() }, getBlockFlags(id) { return { opaque: false } }, getBlockProperties(id) { return null }, getDefaultBlockProperties(id) { return null }, @@ -174,13 +175,14 @@ Promise.all([ // === Structure rendering === - const structure = new Structure([3, 2, 1]) + const structure = new Structure([3, 2, 2]) const size = structure.getSize() structure.addBlock([1, 0, 0], 'minecraft:grass_block', { snowy: 'false' }) structure.addBlock([2, 0, 0], 'minecraft:stone') structure.addBlock([1, 1, 0], 'minecraft:skeleton_skull', { rotation: '15' }) structure.addBlock([2, 1, 0], 'minecraft:acacia_fence', { waterlogged: 'true', north: 'true' }) structure.addBlock([0, 0, 0], 'minecraft:wall_torch', { facing: 'west' }) + structure.addBlock([1, 0, 1], 'minecraft:oak_trapdoor', { facing: 'south', half: 'bottom', open: 'true', powered: 'false', waterlogged: 'false' }) const structureCanvas = document.getElementById('structure-display') as HTMLCanvasElement const structureGl = structureCanvas.getContext('webgl')! diff --git a/src/render/BlockModel.ts b/src/render/BlockModel.ts index 672badc4..a865d2f2 100644 --- a/src/render/BlockModel.ts +++ b/src/render/BlockModel.ts @@ -74,7 +74,6 @@ export class BlockModel { private static readonly BUILTIN_GENERATED = Identifier.create('builtin/generated') private static readonly GENERATED_LAYERS = ['layer0', 'layer1', 'layer2', 'layer3', 'layer4'] private generationMarker = false - private uvEpsilon = 1/16 constructor( private parent: Identifier | undefined, @@ -135,18 +134,17 @@ export class BlockModel { const [u0, v0, u1, v1] = atlas.getTextureUV(this.getTexture(face.texture)) const du = (u1 - u0) / 16 const dv = (v1 - v0) / 16 - const duu = du * this.uvEpsilon - const dvv = dv * this.uvEpsilon - uv[0] = (face.uv?.[0] ?? uv[0]) * du + duu - uv[1] = (face.uv?.[1] ?? uv[1]) * dv + dvv - uv[2] = (face.uv?.[2] ?? uv[2]) * du - duu - uv[3] = (face.uv?.[3] ?? uv[3]) * dv - dvv + uv[0] = (face.uv?.[0] ?? uv[0]) * du + uv[1] = (face.uv?.[1] ?? uv[1]) * dv + uv[2] = (face.uv?.[2] ?? uv[2]) * du + uv[3] = (face.uv?.[3] ?? uv[3]) * dv const r = faceRotations[face.rotation ?? 0] quad.setTexture([ u0 + uv[r[0]], v0 + uv[r[1]], u0 + uv[r[2]], v0 + uv[r[3]], u0 + uv[r[4]], v0 + uv[r[5]], - u0 + uv[r[6]], v0 + uv[r[7]]]) + u0 + uv[r[6]], v0 + uv[r[7]], + ], [u0 + Math.min(uv[0], uv[2]), v0 + Math.min(uv[1], uv[3]), u0 + Math.max(uv[0], uv[2]), v0 + Math.max(uv[1], uv[3])]) mesh.quads.push(quad) } @@ -196,11 +194,6 @@ export class BlockModel { return Identifier.parse(textureRef) } - public withUvEpsilon(epsilon: number) { - this.uvEpsilon = epsilon - return this - } - public flatten(accessor: BlockModelProvider) { if (!this.parent) { return diff --git a/src/render/ItemRenderer.ts b/src/render/ItemRenderer.ts index 2b5ae6a9..2ad94bb1 100644 --- a/src/render/ItemRenderer.ts +++ b/src/render/ItemRenderer.ts @@ -1,6 +1,6 @@ import { mat4 } from 'gl-matrix' -import { Identifier } from '../core/index.js' import type { ItemComponentsProvider, ItemStack } from '../core/ItemStack.js' +import { Identifier } from '../core/index.js' import type { Color } from '../index.js' import type { BlockModelProvider, Display } from './BlockModel.js' import type { ItemModelProvider } from './ItemModel.js' @@ -90,7 +90,7 @@ export class ItemRenderer extends Renderer { mat4.translate(view, view, [0, 0, -32]) this.setShader(this.shaderProgram) - this.setTexture(this.atlasTexture) + this.setTexture(this.atlasTexture, this.resources.getPixelSize?.()) this.prepareDraw(view) this.drawMesh(this.mesh, { pos: true, color: true, texture: true, normal: true }) } diff --git a/src/render/Mesh.ts b/src/render/Mesh.ts index 0da5567f..f6f86ec7 100644 --- a/src/render/Mesh.ts +++ b/src/render/Mesh.ts @@ -9,6 +9,7 @@ export class Mesh { public posBuffer: WebGLBuffer | undefined public colorBuffer: WebGLBuffer | undefined public textureBuffer: WebGLBuffer | undefined + public textureLimitBuffer: WebGLBuffer | undefined public normalBuffer: WebGLBuffer | undefined public blockPosBuffer: WebGLBuffer | undefined public indexBuffer: WebGLBuffer | undefined @@ -126,6 +127,7 @@ export class Mesh { } if (options.texture) { this.textureBuffer = rebuildBufferV(this.quads, this.textureBuffer, v => v.texture) + this.textureLimitBuffer = rebuildBufferV(this.quads, this.textureLimitBuffer, v => v.textureLimit) } if (options.normal) { this.normalBuffer = rebuildBufferV(this.quads, this.normalBuffer, v => v.normal?.components()) diff --git a/src/render/Quad.ts b/src/render/Quad.ts index cf666e13..6ee0229b 100644 --- a/src/render/Quad.ts +++ b/src/render/Quad.ts @@ -44,7 +44,12 @@ export class Quad { return this } - public setTexture(texture: number[]) { + public setTexture(texture: number[], textureLimit?: [number, number, number, number]) { + this.v1.textureLimit = textureLimit + this.v2.textureLimit = textureLimit + this.v3.textureLimit = textureLimit + this.v4.textureLimit = textureLimit + this.v1.texture = [texture[0], texture[1]] this.v2.texture = [texture[2], texture[3]] this.v3.texture = [texture[4], texture[5]] diff --git a/src/render/Renderer.ts b/src/render/Renderer.ts index 645996fb..1b5b7f6b 100644 --- a/src/render/Renderer.ts +++ b/src/render/Renderer.ts @@ -5,6 +5,7 @@ import { ShaderProgram } from './ShaderProgram.js' const vsSource = ` attribute vec4 vertPos; attribute vec2 texCoord; + attribute vec4 texLimit; attribute vec3 vertColor; attribute vec3 normal; @@ -12,12 +13,14 @@ const vsSource = ` uniform mat4 mProj; varying highp vec2 vTexCoord; + varying highp vec4 vTexLimit; varying highp vec3 vTintColor; varying highp float vLighting; void main(void) { gl_Position = mProj * mView * vertPos; vTexCoord = texCoord; + vTexLimit = texLimit; vTintColor = vertColor; vLighting = normal.y * 0.2 + abs(normal.z) * 0.1 + 0.8; } @@ -26,13 +29,18 @@ const vsSource = ` const fsSource = ` precision highp float; varying highp vec2 vTexCoord; + varying highp vec4 vTexLimit; varying highp vec3 vTintColor; varying highp float vLighting; uniform sampler2D sampler; + uniform highp float pixelSize; void main(void) { - vec4 texColor = texture2D(sampler, vTexCoord); + vec4 texColor = texture2D(sampler, clamp(vTexCoord, + vTexLimit.xy + vec2(0.5, 0.5) * pixelSize, + vTexLimit.zw - vec2(0.5, 0.5) * pixelSize + )); if(texColor.a < 0.01) discard; gl_FragColor = vec4(texColor.xyz * vTintColor * vLighting, texColor.a); } @@ -43,9 +51,10 @@ export class Renderer { protected projMatrix: mat4 private activeShader: WebGLProgram + private pixelSize: number = 0 constructor( - protected readonly gl: WebGLRenderingContext, + protected readonly gl: WebGLRenderingContext ) { this.shaderProgram = new ShaderProgram(gl, vsSource, fsSource).getProgram() this.activeShader = this.shaderProgram @@ -95,9 +104,10 @@ export class Renderer { this.gl.uniformMatrix4fv(location, false, value) } - protected setTexture(texture: WebGLTexture) { + protected setTexture(texture: WebGLTexture, pixelSize?: number) { this.gl.activeTexture(this.gl.TEXTURE0) this.gl.bindTexture(this.gl.TEXTURE_2D, texture) + this.pixelSize = pixelSize ?? 0 } protected createAtlasTexture(image: ImageData) { @@ -112,13 +122,18 @@ export class Renderer { protected prepareDraw(viewMatrix: mat4) { this.setUniform('mView', viewMatrix) this.setUniform('mProj', this.projMatrix) + const location = this.gl.getUniformLocation(this.activeShader, 'pixelSize') + this.gl.uniform1f(location, this.pixelSize) } protected drawMesh(mesh: Mesh, options: { pos?: boolean, color?: boolean, texture?: boolean, normal?: boolean, blockPos?: boolean }) { if (mesh.quadVertices() > 0) { if (options.pos) this.setVertexAttr('vertPos', 3, mesh.posBuffer) if (options.color) this.setVertexAttr('vertColor', 3, mesh.colorBuffer) - if (options.texture) this.setVertexAttr('texCoord', 2, mesh.textureBuffer) + if (options.texture){ + this.setVertexAttr('texCoord', 2, mesh.textureBuffer) + this.setVertexAttr('texLimit', 4, mesh.textureLimitBuffer) + } if (options.normal) this.setVertexAttr('normal', 3, mesh.normalBuffer) if (options.blockPos) this.setVertexAttr('blockPos', 3, mesh.blockPosBuffer) diff --git a/src/render/SpecialRenderer.ts b/src/render/SpecialRenderer.ts index 7e081dca..5e0fec9a 100644 --- a/src/render/SpecialRenderer.ts +++ b/src/render/SpecialRenderer.ts @@ -272,7 +272,7 @@ export namespace SpecialRenderers { down: {uv: [7.5, 0, 7.375, 0.25], texture: '#0'}, }, }, - ]).withUvEpsilon(1/256).getMesh(atlas, Cull.none()).transform(transformation) + ]).getMesh(atlas, Cull.none()).transform(transformation) } } @@ -355,7 +355,7 @@ export namespace SpecialRenderers { down: {uv: [14.25, 1.5, 14, 2.5], texture: '#0'}, }, }, - ]).withUvEpsilon(1/128).getMesh(atlas, Cull.none()) + ]).getMesh(atlas, Cull.none()) } } @@ -388,7 +388,7 @@ export namespace SpecialRenderers { down: {uv: [1.5, 7, 1, 8], texture: '#0'}, }, }, - ]).withUvEpsilon(1/128).getMesh(atlas, Cull.none()) + ]).getMesh(atlas, Cull.none()) } } @@ -409,7 +409,7 @@ export namespace SpecialRenderers { down: {uv: [12.5, 0, 6.5, 1], texture: '#0'}, }, }, - ]).withUvEpsilon(1/128).getMesh(atlas, Cull.none()) + ]).getMesh(atlas, Cull.none()) } } @@ -439,7 +439,7 @@ export namespace SpecialRenderers { south: {uv: [3.5, 3, 6.5, 6], texture: '#0'}, }, }, - ]).withUvEpsilon(1/128).getMesh(atlas, Cull.none()) + ]).getMesh(atlas, Cull.none()) } return new BlockModel(undefined, { 0: texture.withPrefix('entity/signs/hanging/').toString(), @@ -492,7 +492,7 @@ export namespace SpecialRenderers { west: {uv: [1.5, 3, 2.25, 6], texture: '#0'}, }, }, - ]).withUvEpsilon(1/128).getMesh(atlas, Cull.none()) + ]).getMesh(atlas, Cull.none()) } } @@ -561,7 +561,7 @@ export namespace SpecialRenderers { west: {uv: [1.5, 3, 2.25, 6], texture: '#0'}, }, }, - ]).withUvEpsilon(1/128).getMesh(atlas, Cull.none()) + ]).getMesh(atlas, Cull.none()) } } @@ -581,7 +581,7 @@ export namespace SpecialRenderers { down: {uv: [9, 0, 6, 6], texture: '#0'}, }, }, - ]).withUvEpsilon(1/128).getMesh(atlas, Cull.none()) + ]).getMesh(atlas, Cull.none()) } export function shulkerBoxRenderer(texture: Identifier) { @@ -613,7 +613,7 @@ export namespace SpecialRenderers { down: {uv: [12, 0, 8, 4], texture: '#0'}, }, }, - ]).withUvEpsilon(1/128).getMesh(atlas, Cull.none()) + ]).getMesh(atlas, Cull.none()) } } @@ -740,7 +740,7 @@ export namespace SpecialRenderers { down: {uv: [12, 6.5, 8, 10.5], texture: '#0'}, }, }, - ]).withUvEpsilon(1 / 64).getMesh(atlas, Cull.none()) + ]).getMesh(atlas, Cull.none()) } export function bedRenderer(texture: Identifier) { @@ -784,7 +784,7 @@ export namespace SpecialRenderers { down: {uv: [14, 3, 14.75, 3.75], texture: '#0'}, }, }, - ]).withUvEpsilon(1/128).getMesh(atlas, Cull.none()) + ]).getMesh(atlas, Cull.none()) } return new BlockModel(undefined, { 0: texture.withPrefix('entity/bed/').toString(), @@ -824,7 +824,7 @@ export namespace SpecialRenderers { down: {uv: [14, 1.5, 14.75, 2.25], texture: '#0'}, }, }, - ]).withUvEpsilon(1/128).getMesh(atlas, Cull.none()) + ]).getMesh(atlas, Cull.none()) } } diff --git a/src/render/StructureRenderer.ts b/src/render/StructureRenderer.ts index b5435082..21624d12 100644 --- a/src/render/StructureRenderer.ts +++ b/src/render/StructureRenderer.ts @@ -209,7 +209,7 @@ export class StructureRenderer extends Renderer { public drawStructure(viewMatrix: mat4) { this.setShader(this.shaderProgram) - this.setTexture(this.atlasTexture) + this.setTexture(this.atlasTexture, this.resources.getPixelSize?.()) this.prepareDraw(viewMatrix) this.chunkBuilder.getMeshes().forEach(mesh => { diff --git a/src/render/TextureAtlas.ts b/src/render/TextureAtlas.ts index e07a75a5..db1592a5 100644 --- a/src/render/TextureAtlas.ts +++ b/src/render/TextureAtlas.ts @@ -6,6 +6,7 @@ export type UV = [number, number, number, number] export interface TextureAtlasProvider { getTextureAtlas(): ImageData getTextureUV(texture: Identifier): UV + getPixelSize?(): number; } export class TextureAtlas implements TextureAtlasProvider { @@ -29,6 +30,10 @@ export class TextureAtlas implements TextureAtlasProvider { return this.idMap[id.toString()] ?? [0, 0, this.part, this.part] } + public getPixelSize() { + return this.part / 16 + } + public static async fromBlobs(textures: { [id: string]: Blob }): Promise { const initialWidth = Math.sqrt(Object.keys(textures).length + 1) const width = upperPowerOfTwo(initialWidth) diff --git a/src/render/Vertex.ts b/src/render/Vertex.ts index e72d789c..9c6e5343 100644 --- a/src/render/Vertex.ts +++ b/src/render/Vertex.ts @@ -10,6 +10,7 @@ export class Vertex { public pos: Vector, public color: Color, public texture: [number, number] | undefined, + public textureLimit: [number, number, number, number] | undefined, public normal: Vector | undefined, public blockPos: Vector | undefined, ) {} @@ -24,6 +25,6 @@ export class Vertex { } public static fromPos(pos: Vector) { - return new Vertex(pos, [0, 0, 0], [0, 0], undefined, undefined) + return new Vertex(pos, [0, 0, 0], [0, 0], [0, 0, 0, 0], undefined, undefined) } }