rendering: improve texture loading, load font from minecraft assets, render single char

This commit is contained in:
Bixilon 2021-02-09 22:08:48 +01:00
parent 9a1c4aace2
commit d7a10ae082
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
11 changed files with 259 additions and 73 deletions

View File

@ -56,10 +56,11 @@ object ChunkPreparer {
section.getBlock(position.getLocationByDirection(Directions.EAST))
}
fun drawBlock() {
block.blockModel.render(position, data, arrayOf(blockBelow, blockAbove, blockNorth, blockSouth, blockWest, blockEast))
}
drawBlock()
// if(block.fullIdentifier != "minecraft:dropper"){
// continue
// }
block.blockModel.render(position, data, arrayOf(blockBelow, blockAbove, blockNorth, blockSouth, blockWest, blockEast))
}
return data.toFloatArray()
}

View File

@ -12,6 +12,7 @@ import de.bixilon.minosoft.gui.rendering.textures.TextureArray
import de.bixilon.minosoft.protocol.network.Connection
import de.bixilon.minosoft.protocol.packets.serverbound.play.PacketPlayerPositionAndRotationSending
import de.bixilon.minosoft.util.CountUpAndDownLatch
import glm_.vec2.Vec2
import org.lwjgl.*
import org.lwjgl.glfw.Callbacks
import org.lwjgl.glfw.GLFW.*
@ -110,17 +111,14 @@ class RenderWindow(private val connection: Connection) {
// Make the window visible
glfwShowWindow(windowId)
GL.createCapabilities()
glClearColor(137 / 256f, 207 / 256f, 240 / 256f, 1.0f)
glEnable(GL_DEPTH_TEST)
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
minecraftTextures = TextureArray(connection.version.assetsManager, resolveBlockTextureIds(connection.version.mapping.blockMap.values))
minecraftTextures = TextureArray.createTextureArray(connection.version.assetsManager, resolveBlockTextureIds(connection.version.mapping.blockMap.values), 16, 16) // ToDo :Remove fixed size
minecraftTextures.load()
fontAtlasTexture = TextureArray(connection.version.assetsManager, listOf(Texture("font/unicode_page_00", 0)), 256)
fontAtlasTexture.load()
chunkShader = Shader("chunk_vertex.glsl", "chunk_fragment.glsl")
chunkShader.load()
@ -132,18 +130,37 @@ class RenderWindow(private val connection: Connection) {
fontShader = Shader("font_vertex.glsl", "font_fragment.glsl")
fontShader.load()
fontShader.use()
fontShader.setFloat("atlasSize", 256f)
val font = Font()
val char = font.chars['§']!!
font2DToDraw.add(Font2DMesh(floatArrayOf(
-0.5f, -0.5f, char.column * 16f, char.row * 16f + char.width, font.atlasOffset + char.atlasPage.toFloat(),
-0.5f, 0f, char.column * 16f, char.row * 16f, font.atlasOffset + char.atlasPage.toFloat(),
0f, -0.5f, char.column * 16f + char.width, char.row * 16f + char.width, font.atlasOffset + char.atlasPage.toFloat(),
0f, -0.5f, char.column * 16f + char.width, char.row * 16f + char.width, font.atlasOffset + char.atlasPage.toFloat(),
-0.5f, 0f, char.column * 16f, char.row * 16f, font.atlasOffset + char.atlasPage.toFloat(),
0f, 0f, char.column * 16f + char.width, char.row * 16f, font.atlasOffset + char.atlasPage.toFloat(),
)))
font.load(connection.version.assetsManager)
fun drawLetterVertex(position: Vec2, uv: Vec2, atlasPage: Int, meshData: MutableList<Float>) {
meshData.add(position.x)
meshData.add(position.y)
meshData.add(uv.x)
meshData.add(uv.y)
meshData.add(atlasPage.toFloat())
}
fontAtlasTexture = font.createAtlasTexture()
fontAtlasTexture.load()
fun drawLetter(position: Vec2, char: Char) {
val fontChar = font.getChar(char)
val meshData: MutableList<Float> = mutableListOf()
drawLetterVertex(Vec2(-0.5f, -0.5f), fontChar.uvLeftDown, fontChar.atlasTextureIndex, meshData)
drawLetterVertex(Vec2(-0.5f, 0f), fontChar.uvLeftUp, fontChar.atlasTextureIndex, meshData)
drawLetterVertex(Vec2(0f, -0.5f), fontChar.uvRightDown, fontChar.atlasTextureIndex, meshData)
drawLetterVertex(Vec2(0f, -0.5f), fontChar.uvRightDown, fontChar.atlasTextureIndex, meshData)
drawLetterVertex(Vec2(-0.5f, 0f), fontChar.uvLeftUp, fontChar.atlasTextureIndex, meshData)
drawLetterVertex(Vec2(0f, 0f), fontChar.uvRightUp, fontChar.atlasTextureIndex, meshData)
font2DToDraw.add(Font2DMesh(meshData.toFloatArray()))
}
drawLetter(Vec2(0, 0), 'ä')
glfwSetWindowSizeCallback(windowId, object : GLFWWindowSizeCallback() {
@ -155,6 +172,8 @@ class RenderWindow(private val connection: Connection) {
}
})
glfwShowWindow(windowId)
latch?.countDown()
}

View File

@ -1,17 +1,44 @@
package de.bixilon.minosoft.gui.rendering.font
import de.bixilon.minosoft.data.assets.AssetsManager
import de.bixilon.minosoft.gui.rendering.textures.Texture
import de.bixilon.minosoft.gui.rendering.textures.TextureArray
class Font {
val chars: MutableMap<Char, FontChar> = mutableMapOf()
val atlasOffset = 0
lateinit var providers: List<FontProvider>
init {
for (page in 0 until 256) {
for (x in 0 until 16) {
for (y in 0 until 16) {
chars[(page * 256 + (x * 16 + y)).toChar()] = FontChar(page, x, y)
}
fun load(assetsManager: AssetsManager) {
providers = FontLoader.loadFontProviders(assetsManager)
}
fun getChar(char: Char): FontChar {
for (provider in providers) {
provider.chars[char]?.let {
return it
}
}
throw IllegalStateException("$char can not be rendered!")
}
fun createAtlasTexture(): TextureArray {
val textures: MutableList<Texture> = mutableListOf()
for (provider in providers) {
for (atlasPage in provider.atlasTextures) {
textures.add(atlasPage)
}
}
val textureArray = TextureArray.createTextureArray(textures = textures)
val atlasWidthSinglePixel = 1f / textureArray.maxWidth
val atlasHeightSinglePixel = 1f / textureArray.maxHeight
for (provider in providers) {
for (char in provider.chars.values) {
char.calculateUV(provider.width, atlasWidthSinglePixel, atlasHeightSinglePixel)
}
}
return textureArray
}
}

View File

@ -1,6 +1,25 @@
package de.bixilon.minosoft.gui.rendering.font
class FontChar(val atlasPage: Int, val row: Int, val column: Int) {
val width = 16
import glm_.vec2.Vec2
data class FontChar(
val atlasPage: Int,
val atlasTextureIndex: Int,
val row: Int,
val column: Int,
val startPixel: Int,
val endPixel: Int,
val height: Int,
) {
lateinit var uvLeftUp: Vec2
lateinit var uvRightUp: Vec2
lateinit var uvRightDown: Vec2
lateinit var uvLeftDown: Vec2
fun calculateUV(letterWidth: Int, atlasWidthSinglePixel: Float, atlasHeightSinglePixel: Float) {
uvLeftUp = Vec2(atlasWidthSinglePixel * (letterWidth * column + startPixel), atlasHeightSinglePixel * (height * row))
uvRightUp = Vec2(atlasWidthSinglePixel * (letterWidth * column + endPixel), atlasHeightSinglePixel * (height * row))
uvRightDown = Vec2(atlasWidthSinglePixel * (letterWidth * column + endPixel), atlasHeightSinglePixel * (height * (row + 1)))
uvLeftDown = Vec2(atlasWidthSinglePixel * (letterWidth * column + startPixel), atlasHeightSinglePixel * (height * (row + 1)))
}
}

View File

@ -0,0 +1,89 @@
package de.bixilon.minosoft.gui.rendering.font
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import de.bixilon.minosoft.data.assets.AssetsManager
import de.bixilon.minosoft.data.mappings.ModIdentifier
import de.bixilon.minosoft.gui.rendering.textures.Texture
import de.bixilon.minosoft.gui.rendering.textures.TextureArray
import java.io.InputStream
object FontLoader {
private const val FONT_ATLAS_SIZE = 16
private val MISSING_UNICODE_PAGES = listOf(0x08, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xEE, 0xED, 0xEE, 0xEF, 0xF0, 0xF1, 0xF2, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8)
private fun getCharArray(data: JsonArray): List<Char> {
val ret: MutableList<Char> = mutableListOf()
for (string in data) {
ret.addAll(string.asString.toCharArray().toList())
}
return ret
}
private fun loadBitmapFontProvider(atlasPath: ModIdentifier, height: Int? = 8, ascent: Int, chars: List<Char>, assetsManager: AssetsManager, atlasOffset: Int): FontProvider {
val width = if (ascent == 7) {
8
} else {
9
}
val provider = FontProvider(width)
val atlasTexture = Texture((atlasPath.mod + "/textures/" + atlasPath.identifier), atlasOffset)
atlasTexture.load(assetsManager)
provider.atlasTextures.add(atlasTexture)
val height = height ?: atlasTexture.width / 16
for ((i, char) in chars.withIndex()) {
val fontChar = FontChar(0, atlasOffset, i / FONT_ATLAS_SIZE, i % FONT_ATLAS_SIZE, 0, width, height)
provider.chars[char] = fontChar
}
return provider
}
private fun loadUnicodeFontProvider(template: ModIdentifier, sizes: InputStream, assetsManager: AssetsManager, atlasOffset: Int): FontProvider {
val provider = FontProvider(16)
var i = 0
lateinit var currentAtlasTexture: Texture
while (sizes.available() > 0) {
if (i % 256 == 0) {
currentAtlasTexture = if (MISSING_UNICODE_PAGES.contains(i / 256)) {
// ToDo: Why is this texture missing in minecraft?
Texture(TextureArray.DEBUG_TEXTURE.name, i / 256)
} else {
// new page (texture)
Texture((template.mod + "/textures/" + template.identifier).format("%02x".format(i / 256)), atlasOffset + (i / 256))
}
currentAtlasTexture.load(assetsManager)
provider.atlasTextures.add(currentAtlasTexture)
}
val sizeByte = sizes.read()
val fontChar = FontChar((i / 256), atlasOffset + (i / 256), (i % 256) / FONT_ATLAS_SIZE, (i % 256) % FONT_ATLAS_SIZE, (sizeByte shr 4) and 0x0F, sizeByte and 0x0F, 16)
provider.chars[i.toChar()] = fontChar
i++
}
return provider
}
fun loadFontProvider(data: JsonObject, assetsManager: AssetsManager, atlasTextureOffset: Int): FontProvider {
return when (data["type"].asString) {
"bitmap" -> {
loadBitmapFontProvider(ModIdentifier(data["file"].asString), data["height"]?.asInt, data["ascent"].asInt, getCharArray(data["chars"].asJsonArray), assetsManager, atlasTextureOffset)
}
"legacy_unicode" -> {
loadUnicodeFontProvider(ModIdentifier(data["template"].asString), assetsManager.readAssetAsStream(ModIdentifier(data["sizes"].asString)), assetsManager, atlasTextureOffset)
}
else -> throw IllegalArgumentException("${data["type"]} is not a valid font provider type!")
}
}
fun loadFontProviders(assetsManager: AssetsManager): List<FontProvider> {
val ret: MutableList<FontProvider> = mutableListOf()
var atlasTextureOffset = 0
for (providerElement in assetsManager.readJsonAsset("minecraft/font/default.json").asJsonObject["providers"].asJsonArray) {
val provider = loadFontProvider(providerElement.asJsonObject, assetsManager, atlasTextureOffset)
atlasTextureOffset += provider.atlasTextures.size
ret.add(provider)
}
return ret
}
}

View File

@ -0,0 +1,8 @@
package de.bixilon.minosoft.gui.rendering.font
import de.bixilon.minosoft.gui.rendering.textures.Texture
class FontProvider(val width: Int) {
val chars: MutableMap<Char, FontChar> = mutableMapOf()
val atlasTextures: MutableList<Texture> = mutableListOf()
}

View File

@ -1,9 +1,40 @@
package de.bixilon.minosoft.gui.rendering.textures
import de.bixilon.minosoft.data.assets.AssetsManager
import de.matthiasmann.twl.utils.PNGDecoder
import org.lwjgl.BufferUtils
import java.nio.ByteBuffer
class Texture(
val name: String,
val id: Int,
) {
var width: Int = 0
var height: Int = 0
var isTransparent: Boolean = false
lateinit var buffer: ByteBuffer
var loaded = false
fun load(assetsManager: AssetsManager) {
if (loaded) {
return
}
val texturePath = if (name.endsWith(".png")) {
name
} else {
"minecraft/textures/${name}.png"
}
val decoder = PNGDecoder(assetsManager.readAssetAsStream(texturePath))
buffer = BufferUtils.createByteBuffer(decoder.width * decoder.height * PNGDecoder.Format.RGBA.numComponents)
decoder.decode(buffer, decoder.width * PNGDecoder.Format.RGBA.numComponents, PNGDecoder.Format.RGBA)
width = decoder.width
height = decoder.height
buffer.flip()
if (TextureArray.TRANSPARENT_TEXTURES.contains(name)) { // ToDo: this should not be hardcoded!
isTransparent = true
}
buffer.flip()
loaded = true
}
}

View File

@ -9,7 +9,7 @@ import org.lwjgl.opengl.GL30.GL_TEXTURE_2D_ARRAY
import org.lwjgl.opengl.GL30.glGenerateMipmap
import java.nio.ByteBuffer
class TextureArray(private val assetsManager: AssetsManager, private val textures: List<Texture>, private val size: Int = 16) {
class TextureArray(private val textures: List<Texture>, val maxWidth: Int, val maxHeight: Int) {
var textureId = 0
fun load(): Int {
@ -21,12 +21,10 @@ class TextureArray(private val assetsManager: AssetsManager, private val texture
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
// load and generate the texture
val textureMap: Map<Texture, ByteBuffer> = TextureLoader.loadTextureArray(assetsManager, textures)
glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, size, size, textures.size, 0, GL_RGBA, GL_UNSIGNED_BYTE, null as ByteBuffer?)
glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, maxWidth, maxHeight, textures.size, 0, GL_RGBA, GL_UNSIGNED_BYTE, null as ByteBuffer?)
for ((key, value) in textureMap) {
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, key.id, size, size, 1, GL_RGBA, GL_UNSIGNED_BYTE, value)
for (texture in textures) {
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, texture.id, texture.width, texture.height, 1, GL_RGBA, GL_UNSIGNED_BYTE, texture.buffer)
}
glGenerateMipmap(GL_TEXTURE_2D_ARRAY)
return textureId
@ -40,5 +38,29 @@ class TextureArray(private val assetsManager: AssetsManager, private val texture
companion object {
val DEBUG_TEXTURE = Texture("block/debug", 0)
val TRANSPARENT_TEXTURES = listOf("block/glass")
fun createTextureArray(assetsManager: AssetsManager? = null, textures: List<Texture>, maxWidth: Int = -1, maxHeight: Int = -1): TextureArray {
var calculatedMaxWidth = 0
var calculatedMaxHeight = 0
for (texture in textures) {
if (!texture.loaded) {
texture.load(assetsManager!!)
}
if (texture.width > calculatedMaxWidth) {
calculatedMaxWidth = texture.width
}
if (texture.height > calculatedMaxHeight) {
calculatedMaxHeight = texture.height
}
}
if (maxWidth != -1) {
calculatedMaxWidth = maxWidth
}
if (maxHeight != -1) {
calculatedMaxHeight = maxWidth
}
return TextureArray(textures, calculatedMaxWidth, calculatedMaxHeight)
}
}
}

View File

@ -1,30 +0,0 @@
package de.bixilon.minosoft.gui.rendering.textures
import de.bixilon.minosoft.data.assets.AssetsManager
import de.matthiasmann.twl.utils.PNGDecoder
import org.lwjgl.BufferUtils
import java.nio.ByteBuffer
object TextureLoader {
private val TRANSPARENT_TEXTURES = listOf("block/glass")
fun loadTextureArray(assetsManager: AssetsManager, textures: List<Texture>): Map<Texture, ByteBuffer> {
val result: MutableMap<Texture, ByteBuffer> = mutableMapOf()
for (texture in textures) {
result[texture] = loadTexture(assetsManager, texture)
}
return result.toMap()
}
private fun loadTexture(assetsManager: AssetsManager, texture: Texture): ByteBuffer {
val decoder = PNGDecoder(assetsManager.readAssetAsStream("minecraft/textures/${texture.name}.png"))
val buffer = BufferUtils.createByteBuffer(decoder.width * decoder.height * PNGDecoder.Format.RGBA.numComponents)
decoder.decode(buffer, decoder.width * PNGDecoder.Format.RGBA.numComponents, PNGDecoder.Format.RGBA)
buffer.flip()
if (TRANSPARENT_TEXTURES.contains(texture.name)) { // ToDo: this should not be hardcoded!
texture.isTransparent = true
}
buffer.flip()
return buffer
}
}

View File

@ -8,12 +8,13 @@ uniform sampler2DArray texureArray;
void main() {
vec4 texColor = texture(texureArray, passTextureCoordinates);
if (texColor.a == 0) {
discard;
vec4 textureColor = texture(texureArray, passTextureCoordinates);
if (textureColor.a == 0) {
textureColor.a = 0.4f;
//discard;
}
texColor.r = 0.8f;
texColor.g = 0.8f;
texColor.b = 0.0f;
outColor = texColor;
textureColor.r = 0.8f;
textureColor.g = 0.8f;
textureColor.b = 0.0f;
outColor = textureColor;
}

View File

@ -5,9 +5,8 @@ layout (location = 2) in float textureLayer;
out vec3 passTextureCoordinates;
uniform float atlasSize;
void main() {
gl_Position = vec4(inPosition, 0.0f, 1.0f);
passTextureCoordinates = vec3(textureIndex / atlasSize, textureLayer);
passTextureCoordinates = vec3(textureIndex, textureLayer);
}