Added bilinear texture filtering for voxel colours

This commit is contained in:
Lucas Dower 2022-02-13 15:23:23 +00:00
parent 0537f75169
commit bb11207e70
5 changed files with 81 additions and 13 deletions

View File

@ -8,6 +8,7 @@ import { ASSERT, CustomError, CustomWarning, LOG, LOG_ERROR } from './util';
import { remote } from 'electron';
import { VoxelMesh } from './voxel_mesh';
import { BlockMesh } from './block_mesh';
import { TextureFiltering } from './texture';
/* eslint-disable */
export enum ActionReturnType {
@ -156,10 +157,11 @@ export class AppContext {
const desiredHeight = UI.Get.layout.build.elements.height.getValue();
const ambientOcclusion = UI.Get.layout.build.elements.ambientOcclusion.getValue() === 'on';
const multisampleColouring = UI.Get.layout.build.elements.multisampleColouring.getValue() === 'on';
const textureFiltering = UI.Get.layout.build.elements.textureFiltering.getValue() === 'nearest' ? TextureFiltering.Nearest : TextureFiltering.Linear;
ASSERT(this._loadedMesh);
this._loadedVoxelMesh = new VoxelMesh(desiredHeight);
this._loadedVoxelMesh.voxelise(this._loadedMesh, multisampleColouring);
this._loadedVoxelMesh.voxelise(this._loadedMesh, multisampleColouring, textureFiltering);
Renderer.Get.useVoxelMesh(this._loadedVoxelMesh, ambientOcclusion);
}

View File

@ -33,4 +33,9 @@ export const roundToNearest = (value: number, base: number) => {
return Math.round(value / base) * base;
};
export const wayThrough = (value: number, min: number, max: number) => {
// ASSERT(value >= min && value <= max);
return (value - min) / (max - min);
};
export const degreesToRadians = Math.PI / 180;

View File

@ -1,10 +1,12 @@
import { UV, ASSERT, CustomError } from './util';
import { UV, ASSERT, CustomError, LOG } from './util';
import { RGB } from './util';
import * as fs from 'fs';
import * as jpeg from 'jpeg-js';
import { PNG } from 'pngjs';
import path from 'path';
import { Vector3 } from './vector';
import { clamp, wayThrough } from './math';
/* eslint-disable */
export enum TextureFormat {
@ -13,6 +15,13 @@ export enum TextureFormat {
}
/* eslint-enable */
/* eslint-disable */
export enum TextureFiltering {
Linear,
Nearest
}
/* eslint-enable */
export class Texture {
private _image: {
data: Buffer,
@ -40,10 +49,58 @@ export class Texture {
}
}
getRGB(uv: UV): RGB {
getRGB(uv: UV, filtering: TextureFiltering): RGB {
if (filtering === TextureFiltering.Nearest) {
return this._getNearestRGB(uv);
} else {
return this._getLinearRGB(uv);
}
}
private _getLinearRGB(uv: UV): RGB {
uv.v = 1.0 - uv.v;
if (uv.u === 0 || uv.u === 1 || uv.v === 0 || uv.v === 1) {
LOG('bad');
}
const x = uv.u * this._image.width;
const y = uv.v * this._image.height;
const xL = Math.floor(x);
const xU = Math.ceil(x);
const yL = Math.floor(y);
const yU = Math.ceil(y);
const u = wayThrough(x, xL, xU);
const v = wayThrough(y, yL, yU);
ASSERT(u >= 0.0 && u <= 1.0 && v >= 0.0 && v <= 1.0);
const A = this._getFromXY(xL, yU).toVector3();
const B = this._getFromXY(xU, yU).toVector3();
const midAB = Vector3.mulScalar(B, u).add(Vector3.mulScalar(A, 1.0-u));
const C = this._getFromXY(xL, yL).toVector3();
const D = this._getFromXY(xU, yL).toVector3();
const midCD = Vector3.mulScalar(D, u).add(Vector3.mulScalar(C, 1.0-u));
const mid = Vector3.mulScalar(midAB, v).add(Vector3.mulScalar(midCD, 1.0-v));
if (mid.equals(RGB.black.toVector3())) {
LOG('bad');
}
return RGB.fromVector3(mid);
}
private _getNearestRGB(uv: UV): RGB {
const x = Math.floor(uv.u * this._image.width);
const y = Math.floor((1 - uv.v) * this._image.height);
return this._getFromXY(x, y);
}
private _getFromXY(x: number, y: number): RGB {
x = clamp(x, 0, this._image.width - 1);
y = clamp(y, 0, this._image.height - 1);
const index = 4 * (this._image.width * y + x);
const rgba = this._image.data.slice(index, index + 4);

View File

@ -46,12 +46,16 @@ export class UI {
{ id: 'on', displayText: 'On (recommended)' },
{ id: 'off', displayText: 'Off (faster)' },
]),
'multisampleColouring': new ComboBoxElement('Multisample Colouring', [
'multisampleColouring': new ComboBoxElement('Multisample colouring', [
{ id: 'on', displayText: 'On (recommended)' },
{ id: 'off', displayText: 'Off (faster)' },
]),
'textureFiltering': new ComboBoxElement('Texture filtering', [
{ id: 'linear', displayText: 'Linear (recommended)' },
{ id: 'nearest', displayText: 'Nearest (faster)' },
]),
},
elementsOrder: ['height', 'ambientOcclusion', 'multisampleColouring'],
elementsOrder: ['height', 'ambientOcclusion', 'multisampleColouring', 'textureFiltering'],
submitButton: new ButtonElement('Voxelise mesh', () => {
AppContext.Get.do(Action.Voxelise);
}),

View File

@ -5,7 +5,7 @@ import { HashMap } from './hash_map';
import { MaterialType, Mesh, SolidMaterial, TexturedMaterial } from './mesh';
import { OcclusionManager } from './occlusion';
import { Axes, generateRays, rayIntersectTriangle } from './ray';
import { Texture } from './texture';
import { Texture, TextureFiltering } from './texture';
import { Triangle, UVTriangle } from './triangle';
import { Bounds, LOG, RGB, UV } from './util';
import { Vector3 } from './vector';
@ -41,7 +41,7 @@ export class VoxelMesh {
return this._voxelsHash.has(pos);
}
public voxelise(mesh: Mesh, multisampleColouring: boolean) {
public voxelise(mesh: Mesh, multisampleColouring: boolean, filtering: TextureFiltering) {
LOG('Voxelising');
mesh.tris.forEach((tri, index) => {
@ -52,11 +52,11 @@ export class VoxelMesh {
}
}
const uvTriangle = mesh.getUVTriangle(index);
this._voxeliseTri(uvTriangle, material, tri.material, multisampleColouring);
this._voxeliseTri(uvTriangle, material, tri.material, multisampleColouring, filtering);
});
}
private _voxeliseTri(triangle: UVTriangle, material: (SolidMaterial | TexturedMaterial), materialName: string, multisampleColouring: boolean) {
private _voxeliseTri(triangle: UVTriangle, material: (SolidMaterial | TexturedMaterial), materialName: string, multisampleColouring: boolean, filtering: TextureFiltering) {
const v0Scaled = Vector3.divScalar(triangle.v0, this._voxelSize);
const v1Scaled = Vector3.divScalar(triangle.v1, this._voxelSize);
const v2Scaled = Vector3.divScalar(triangle.v2, this._voxelSize);
@ -83,18 +83,18 @@ export class VoxelMesh {
const samples: RGB[] = [];
for (let i = 0; i < AppConfig.MULTISAMPLE_COUNT; ++i) {
const samplePosition = Vector3.mulScalar(Vector3.add(voxelPosition, Vector3.random().addScalar(-0.5)), this._voxelSize);
samples.push(this._getVoxelColour(triangle, material, materialName, samplePosition));
samples.push(this._getVoxelColour(triangle, material, materialName, samplePosition, filtering));
}
voxelColour = RGB.averageFrom(samples);
} else {
voxelColour = this._getVoxelColour(triangle, material, materialName, Vector3.mulScalar(voxelPosition, this._voxelSize));
voxelColour = this._getVoxelColour(triangle, material, materialName, Vector3.mulScalar(voxelPosition, this._voxelSize), filtering);
}
this._addVoxel(voxelPosition, voxelColour);
}
});
}
private _getVoxelColour(triangle: UVTriangle, material: (SolidMaterial | TexturedMaterial), materialName: string, location: Vector3): RGB {
private _getVoxelColour(triangle: UVTriangle, material: (SolidMaterial | TexturedMaterial), materialName: string, location: Vector3, filtering: TextureFiltering): RGB {
if (material.type == MaterialType.solid) {
return material.colour;
}
@ -113,7 +113,7 @@ export class VoxelMesh {
triangle.uv0.v * w0 + triangle.uv1.v * w1 + triangle.uv2.v * w2,
);
return this._loadedTextures[materialName].getRGB(uv);
return this._loadedTextures[materialName].getRGB(uv, filtering);
}
private _addVoxel(pos: Vector3, colour: RGB) {