Added support for alpha maps

This commit is contained in:
Lucas Dower 2022-06-12 02:07:09 +01:00
parent 80967ec945
commit 687ed7159a
6 changed files with 114 additions and 21 deletions

View File

@ -1,6 +1,9 @@
precision mediump float;
uniform sampler2D u_texture;
uniform sampler2D u_alpha;
uniform bool u_useAlphaMap;
uniform bool u_useAlphaChannel;
varying float v_lighting;
varying vec2 v_texcoord;
@ -98,7 +101,12 @@ void main() {
vec2 tex = vec2(v_texcoord.x, 1.0 - v_texcoord.y);
vec4 diffuse = texture2D(u_texture, tex).rgba;
float alpha = dither8x8(gl_FragCoord.xy, diffuse.a);
float alpha = diffuse.a;
if (u_useAlphaMap) {
alpha = u_useAlphaChannel ? texture2D(u_alpha, tex).a : texture2D(u_alpha, tex).r;
}
alpha = dither8x8(gl_FragCoord.xy, alpha);
if (alpha < 0.5)
{
discard;

View File

@ -190,6 +190,7 @@ export class ObjImporter extends IImporter {
private _currentColour: RGBA = RGBAColours.BLACK;
private _currentTexture: string = '';
private _currentTransparencyTexture: string = '';
private _materialReady: boolean = false;
private _mtlParsers = [
{
@ -236,6 +237,20 @@ export class ObjImporter extends IImporter {
this._materialReady = true;
},
},
{
// Transparency map
// e.g. 'map_d my/path/to/file.png'
regex: new RegExpBuilder().add(/^map_d/).add(REGEX_NZ_ANY, 'path').toRegExp(),
delegate: (match: { [key: string]: string }) => {
let texturePath = match.path.trim();
if (!path.isAbsolute(texturePath)) {
ASSERT(this._objPath, 'no obj path');
texturePath = path.join(this._objPath.dir, texturePath);
}
this._currentTransparencyTexture = texturePath;
this._materialReady = true;
},
},
];
override parseFile(filePath: string) {
@ -356,7 +371,9 @@ export class ObjImporter extends IImporter {
this._materials[this._currentMaterialName] = {
type: MaterialType.textured,
path: this._currentTexture,
alphaPath: this._currentTransparencyTexture === '' ? undefined : this._currentTransparencyTexture,
};
this._currentTransparencyTexture = '';
} else {
this._materials[this._currentMaterialName] = {
type: MaterialType.solid,

View File

@ -25,7 +25,11 @@ export interface Tri {
export enum MaterialType { solid, textured }
/* eslint-enable */
export interface SolidMaterial { colour: RGBA; type: MaterialType.solid }
export interface TexturedMaterial { path: string; type: MaterialType.textured }
export interface TexturedMaterial {
path: string;
type: MaterialType.textured;
alphaPath?: string;
}
export type MaterialMap = {[key: string]: (SolidMaterial | TexturedMaterial)};
export class Mesh {
@ -227,7 +231,7 @@ export class Mesh {
const material = this._materials[tri.material];
if (material.type == MaterialType.textured) {
if (!(tri.material in this._loadedTextures)) {
this._loadedTextures[tri.material] = new Texture(material.path);
this._loadedTextures[tri.material] = new Texture(material.path, material.alphaPath);
}
}
}

View File

@ -12,6 +12,7 @@ import { BlockMesh } from './block_mesh';
import * as twgl from 'twgl.js';
import { EAppEvent, EventManager } from './event';
import { RGBA, RGBAUtil } from './colour';
import { Texture } from './texture';
/* eslint-disable */
export enum MeshType {
@ -46,7 +47,7 @@ export class Renderer {
private _materialBuffers: Array<{
buffer: RenderBuffer,
material: (SolidMaterial | (TexturedMaterial & { texture: WebGLTexture }))
material: (SolidMaterial | (TexturedMaterial & { texture: WebGLTexture, alpha?: WebGLTexture, useAlphaChannel?: boolean }))
}>;
public _voxelBuffer?: twgl.BufferInfo;
public _voxelBufferRaw?: any;
@ -183,6 +184,11 @@ export class Renderer {
src: material.path,
mag: this._gl.LINEAR,
}),
alpha: material.alphaPath ? twgl.createTexture(this._gl, {
src: material.alphaPath,
mag: this._gl.LINEAR,
}) : undefined,
useAlphaChannel: material.alphaPath ? new Texture(material.path, material.alphaPath)._useAlphaChannel() : undefined,
},
});
}
@ -285,6 +291,9 @@ export class Renderer {
u_worldViewProjection: ArcballCamera.Get.getWorldViewProjection(),
u_worldInverseTranspose: ArcballCamera.Get.getWorldInverseTranspose(),
u_texture: materialBuffer.material.texture,
u_alpha: materialBuffer.material.alpha,
u_useAlphaMap: materialBuffer.material.alpha !== undefined,
u_useAlphaChannel: materialBuffer.material.useAlphaChannel,
});
} else {
this._drawRegister(materialBuffer.buffer, ShaderManager.Get.solidTriProgram, {

View File

@ -1,4 +1,4 @@
import { UV, ASSERT, AppError } from './util';
import { UV, ASSERT, AppError, LOG } from './util';
import * as fs from 'fs';
import * as jpeg from 'jpeg-js';
@ -21,28 +21,40 @@ export enum TextureFiltering {
}
/* eslint-enable */
export class Texture {
private _image: {
data: Buffer,
width: number,
height: number
};
type ImageData = {
data: Buffer,
width: number,
height: number
}
constructor(filename: string) {
export class Texture {
private _image: ImageData;
private _alphaImage?: ImageData;
constructor(filename: string, transparencyFilename?: string) {
ASSERT(path.isAbsolute(filename));
const filePath = path.parse(filename);
this._image = this._loadImageFile(filename);
if (transparencyFilename) {
this._alphaImage = this._loadImageFile(transparencyFilename);
}
}
private _loadImageFile(filename: string): ImageData {
ASSERT(path.isAbsolute(filename));
const filePath = path.parse(filename);
try {
const data = fs.readFileSync(filename);
if (filePath.ext.toLowerCase() === '.png') {
this._image = PNG.sync.read(data);
return PNG.sync.read(data);
} else if (['.jpg', '.jpeg'].includes(filePath.ext.toLowerCase())) {
this._image = jpeg.decode(data);
this._useAlphaChannelValue = false;
return jpeg.decode(data);
} else {
throw new AppError(`Failed to load ${filename}`);
}
if (this._image.width * this._image.height * 4 !== this._image.data.length) {
throw new AppError(`Unexpected image resolution mismatch: ${filename}`);
}
} catch (err) {
throw new AppError(`Could not read ${filename}`);
}
@ -96,11 +108,50 @@ export class Texture {
}
private _getFromXY(x: number, y: number): RGBA {
x = clamp(x, 0, this._image.width - 1);
y = clamp(y, 0, this._image.height - 1);
const diffuse = Texture._sampleImage(this._image, x, y);
if (this._alphaImage) {
const alpha = Texture._sampleImage(this._alphaImage, x, y);
return {
r: diffuse.r,
g: diffuse.g,
b: diffuse.b,
a: this._useAlphaChannel() ? alpha.a : alpha.r,
};
}
return diffuse;
}
public _useAlphaChannelValue?: boolean;
public _useAlphaChannel() {
ASSERT(this._alphaImage !== undefined);
if (this._useAlphaChannelValue !== undefined) {
return this._useAlphaChannelValue;
}
for (let i = 0; i < this._alphaImage.width; ++i) {
for (let j = 0; j < this._alphaImage.height; ++j) {
const value = Texture._sampleImage(this._alphaImage, i, j);
if (value.a != 1.0) {
LOG(`Using alpha channel`);
this._useAlphaChannelValue = true;
return true;
}
}
}
const index = 4 * (this._image.width * y + x);
const rgba = this._image.data.slice(index, index + 4);
LOG(`Using red channel`);
this._useAlphaChannelValue = false;
return false;
}
private static _sampleImage(image: ImageData, x: number, y: number) {
x = clamp(x, 0, image.width - 1);
y = clamp(y, 0, image.height - 1);
const index = 4 * (image.width * y + x);
const rgba = image.data.slice(index, index + 4);
return {
r: rgba[0] / 255,

View File

@ -52,6 +52,10 @@ export class VoxelMesh {
}
public addVoxel(pos: Vector3, colour: RGBA) {
if (colour.a === 0) {
return;
}
pos.round();
const voxelIndex = this._voxelsHash.get(pos);