Гайд MSDF font rendering

Начинающий
Статус
Оффлайн
Регистрация
11 Май 2022
Сообщения
70
Реакции[?]
10
Поинты[?]
10K

Перед прочтением основного контента ниже, пожалуйста, обратите внимание на обновление внутри секции Майна на нашем форуме. У нас появились:

  • бесплатные читы для Майнкрафт — любое использование на свой страх и риск;
  • маркетплейс Майнкрафт — абсолютно любая коммерция, связанная с игрой, за исключением продажи читов (аккаунты, предоставления услуг, поиск кодеров читов и так далее);
  • приватные читы для Minecraft — в этом разделе только платные хаки для игры, покупайте группу "Продавец" и выставляйте на продажу свой софт;
  • обсуждения и гайды — всё тот же раздел с вопросами, но теперь модернизированный: поиск нужных хаков, пати с игроками-читерами и другая полезная информация.

Спасибо!

Предисловие
Всех приветствую! В этой статье я хочу рассказать об одной интересной технологии, которая используется для отрисовки шрифтов в OpenGL. Мне показалось, что она мощнее и лучше обычных растровых шрифтов. Данный способ малоизвестен в кубо-комьюнити, но пришло время это исправить. Происходить все будет внутри игры Minecraft (
Пожалуйста, авторизуйтесь для просмотра ссылки.
в моем случае).


Растровые шрифты
Для начала немного поговорим о растровых шрифтах, которые используются большинством для отрисовки текста сейчас.
Вкратце разберемся, в чем заключается данная технология: с помощью инструментов из пакета
java.awt загружаем шрифт и наносим все нужные символы на картинку, используя наш шрифт. Далее загружаем эту картинку в OpenGL в качестве текстуры. В дальнейшем, указывая различные координаты текстуры, мы можем отрисовать любой символ.

Так выглядит итоговая текстура, хранящая все символы:

Снимок экрана 2023-09-11 153038.png
Это достаточно простой и топорный способ. У него есть существенный недостаток: символ выглядит хорошо, только если его размер на текстуре в точности соответствует размеру прямоугольника, на который эта текстура натягивается. То есть при скейлинге весь наш красивый антиалиасинг ломается и текст выглядит очень плохо. Поэтому для изменения размера текста приходится повторять все алгоритм для шрифта с другим размером, что приводит к дополнительным затратам на генерацию и хранение каждой новой текстуры.

Шрифты, основанные на "многоканальном поле расстояния со знаком"
MSDF - multichannel signed distance field
Тут то и начинается все веселье, потому что эта технология почти на 100% решает вышеупомянутую проблему, а также открывает некоторые классные возможности. Как минимум, мы повысим четкость шрифта и сможем менять его размер ничего не пересоздавая.
Итак, сначала я попытаюсь описать, как это работает, а потом приступим к реализации.

Основой этой технологии является "многоканальное поле расстояния со знаком". Давайте посмотрим, как эти поля выглядят:

font.png
На первый взгляд похоже на то, как выглядит окружающий мир в наркотическом трипе, но на самом деле это та же текстура, содержащая все наши символы, только в данном случае эти символы "описаны" не совсем обычно. Для каждого символа было сгенерировано "многоканальное поле расстояния со знаком", мы можем видеть четкие границы между соседними полями.

Многоканальным поле называется потому, что символы описываются 4-мя (
RGBA) цветовыми каналами. Как видно, сами символы не имеют четких границ, они будто размыты. Именно благодаря такой неопределенности мы сможем менять размер шрифта без потери качества, правильно рассчитывав границы символов в будущей реализации.

Упрощая, принцип работы можно описать так: есть специальная картинка, которая описывает как примерно должны выглядеть символы. Высчитывать конкретные границы символов мы будем с помощью специального шейдера.

Вот и все. Приступим к самому интересному - к реализации.


Реализация
Для генерации всего необходимого идем на
Пожалуйста, авторизуйтесь для просмотра ссылки.
и скачиваем последний релиз. Автор программы сделал гибкую настройку и хорошо расписал возможные параметры. Читаем и осмысливаем. Обязательные для нас параметры:
-type msdf/mtsdf; -format png; -pxrange ставим 10, можно больше или чуть меньше, но разницы не будет; и для указания формата метаданных добавляем -json [filename.json]. Остальное сами допишите. Вот
Пожалуйста, авторизуйтесь для просмотра ссылки.
готовый набор, только поменяйте названия входных и выходных файлов. Кидаем в папку с программой и шрифтом и запускаем батник.


Снимок экрана 2023-09-10 211533.png

У нас появилось два файла font.png и font.json (файлы будут называться так, как вы указали). font.png это наша текстура с символами, а файл font.json содержит метаданные, которые нам очень сильно помогут. Посмотрим на структуру json'а. Сделать это можно например
Пожалуйста, авторизуйтесь для просмотра ссылки.
. В нем содержится информация о шрифте, текстуре, расположении каждого символа на текстуре и его единичные размеры, а также кернинги. Кернинг описывает то, на сколько следует сместить символ 1 по отношению к символу 2. Это своего рода дополнительная настройка отступов, чтобы ваш текст выглядел идеально (
Пожалуйста, авторизуйтесь для просмотра ссылки.
). Реализовывать и учитывать кернинги совсем необязательно.

Теперь нам нужно загрузить текстуру и необходимые метаданные. Открываем Eclipse и начинаем писать код (только эклипс!! в интеллидж не получится!).

Текстуру загружаем через обычный майнкрафтовский текстур лоадер. Для загрузки метаданных из json'а воспользуемся возможностями имеющейся библиотеки, а именно вот
Пожалуйста, авторизуйтесь для просмотра ссылки.
методом. Он десериализует json-строку в инстанс класса, в котором мы повторили структуру json'а (ненужные поля отбрасываем).


Java:
 public final class IOUtils {

    private static final IResourceManager RES_MANAGER = Provider.getResourceManager(); // Minecraft.getResourceManager();
    private static final TextureManager TEX_MANAGER = Provider.getTextureManager();  // Minecraft.getTextureManager();
    private static final Gson GSON = new Gson();
 
    private IOUtils() {}
 
    public static String toString(ResourceLocation location) {
        return toString(location, "\n");
    }
 
    public static String toString(ResourceLocation location, String delimiter) {
        try (IResource resource = RES_MANAGER.getResource(location);
                BufferedReader reader = new BufferedReader(new InputStreamReader(resource.getInputStream()))) {
            return reader.lines().collect(Collectors.joining(delimiter));
        } catch (IOException e) {
            e.printStackTrace();
            return "";
        }
    }

    public static <T> T fromJsonToInstance(ResourceLocation location, Class<T> clazz) {
        try {
            return GSON.fromJson(toString(location), clazz);
        } catch (JsonSyntaxException e) {
            e.printStackTrace();
            return null;
        }
    }

    public static Texture toTexture(ResourceLocation location) {
        Texture texture = TEX_MANAGER.getTexture(location);
        if (texture == null) {
            texture = new SimpleTexture(location);
            TEX_MANAGER.register(location, texture);
        }

        return texture;
    }

}
Java:
// the same class structure as in json file generated by https://github.com/Chlumsky/msdf-atlas-gen (01.08.2023). Excluding all unnecessary.
public final class FontData {

    private AtlasData atlas;
    private MetricsData metrics;
    private List<GlyphData> glyphs;
    @SerializedName("kerning")
    private List<KerningData> kernings;

    public AtlasData atlas() {
        return this.atlas;
    }
 
    public MetricsData metrics() {
        return this.metrics;
    }
 
    public List<GlyphData> glyphs() {
        return this.glyphs;
    }
 
    public List<KerningData> kernings() {
        return this.kernings;
    }
 
    public static final class AtlasData {

        @SerializedName("distanceRange")
        private float range;
        private float width;
        private float height;

        public float range() {
            return this.range;
        }

        public float width() {
            return this.width;
        }

        public float height() {
            return this.height;
        }

    }
 
    public static final class MetricsData {

        private float lineHeight;
        private float ascender;
        private float descender;

        public float lineHeight() {
            return this.lineHeight;
        }

        public float ascender() {
            return this.ascender;
        }

        public float descender() {
            return this.descender;
        }

        public float baselineHeight() {
            return this.lineHeight + this.descender;
        }

    }
 
    public static final class GlyphData {

        private int unicode;
        private float advance;
        private BoundsData planeBounds;
        private BoundsData atlasBounds;

        public int unicode() {
            return this.unicode;
        }

        public float advance() {
            return this.advance;
        }

        public BoundsData planeBounds() {
            return this.planeBounds;
        }

        public BoundsData atlasBounds() {
            return this.atlasBounds;
        }

    }
 
    public static final class BoundsData {

        private float left;
        private float top;
        private float right;
        private float bottom;

        public float left() {
            return this.left;
        }

        public float top() {
            return this.top;
        }

        public float right() {
            return this.right;
        }

        public float bottom() {
            return this.bottom;
        }

    }
 
    public static final class KerningData {
     
        @SerializedName("unicode1")
        private int leftChar;
        @SerializedName("unicode2")
        private int rightChar;
        private float advance;

        public int leftChar() {
            return this.leftChar;
        }

        public int rightChar() {
            return this.rightChar;
        }

        public float advance() {
            return this.advance;
        }

    }
 
}

Отлично! Как загружать разобрались, теперь все это собираем. Надеюсь не нужно объяснять как пользоваться ResourceLocation и что наши файлы нужно закинуть в ассеты.

Java:
public final class MsdfFont {

    private final String name;
    private final Texture texture;
    private final AtlasData atlas;
    private final MetricsData metrics;
    private final Map<Integer, MsdfGlyph> glyphs;
    private final Map<Integer, Map<Integer, Float>> kernings;
    private boolean filtered = false;

    private MsdfFont(String name, Texture texture, AtlasData atlas, MetricsData metrics, Map<Integer, MsdfGlyph> glyphs, Map<Integer, Map<Integer, Float>> kernings) {
        this.name = name;
        this.texture = texture;
        this.atlas = atlas;
        this.metrics = metrics;
        this.glyphs = glyphs;
        this.kernings = kernings;
    }
 
    public void bind() {
        GlStateManager._bindTexture(this.texture.getId());
        if (!this.filtered) {
            this.texture.setFilter(true, false); // **IMPORTANT**: to correct msdf font anti-aliasing we should set GL_TEXTURE_MAG_FILTER and GL_TEXTURE_MIN_FILTER to GL_LINEAR.
            this.filtered = true;
        }
    }
 
    public void unbind() {
        GlStateManager._bindTexture(0);
    }
 
    public void applyGlyphs(Matrix4f matrix, IVertexProcessor processor, float size, String text, float thickness, float x, float y, float z, int red, int green, int blue, int alpha) {
        int prevChar = -1;
        for (int i = 0; i < text.length(); i++) {
         
            int _char = (int) text.charAt(i);
            MsdfGlyph glyph = this.glyphs.get(_char);
         
            if (glyph == null)
                continue;

            Map<Integer, Float> kerning = this.kernings.get(prevChar);
            if (kerning != null) {
                x += kerning.getOrDefault(_char, 0.0f) * size;
            }

            x += glyph.apply(matrix, processor, size, x, y, z, red, green, blue, alpha) + thickness;
            prevChar = _char;
        }
    }
 
    public float getWidth(String text, float size) {
        int prevChar = -1;
        float width = 0.0f;
        for (int i = 0; i < text.length(); i++) {
            int _char = (int) text.charAt(i);
            MsdfGlyph glyph = this.glyphs.get(_char);
         
            if (glyph == null)
                continue;
         
            Map<Integer, Float> kerning = this.kernings.get(prevChar);
            if (kerning != null) {
                width += kerning.getOrDefault(_char, 0.0f) * size;
            }
         
            width += glyph.getWidth(size);
            prevChar = _char;
        }
     
        return width;
    }
 
    public String getName() {
        return this.name;
    }
 
    public AtlasData getAtlas() {
        return this.atlas;
    }
 
    public MetricsData getMetrics() {
        return this.metrics;
    }
 
    public static MsdfFont.Builder builder() {
        return new Builder();
    }
 
    public static class Builder {
     
        public static final String MSDF_PATH = Provider.getFontsPath() + "msdf/";
        private String name = "undefined";
        private ResourceLocation dataFile;
        private ResourceLocation atlasFile;
     
        private Builder() {}
     
        public MsdfFont.Builder withName(String name) {
            this.name = name;
            return this;
        }
     
        // should be json file
        public MsdfFont.Builder withData(String dataFile) {
            this.dataFile = Provider.getLocation(MSDF_PATH + dataFile);
            return this;
        }
     
        // should be png file
        public MsdfFont.Builder withAtlas(String atlasFile) {
            this.atlasFile = Provider.getLocation(MSDF_PATH + atlasFile);
            return this;
        }
     
        public MsdfFont build() {
            FontData data = IOUtils.fromJsonToInstance(this.dataFile, FontData.class); // I LIKE JSON
            Texture texture = IOUtils.toTexture(this.atlasFile);
         
            if (data == null)
                throw new RuntimeException("Failed to read font data file: " + this.dataFile.toString() +
                        "; Are you sure this is json file? Try to check the correctness of its syntax.");

            float aWidth = data.atlas().width();
            float aHeight = data.atlas().height();
            Map<Integer, MsdfGlyph> glyphs = data.glyphs().stream()
                    .collect(Collectors.<GlyphData, Integer, MsdfGlyph>toMap(
                            (glyphData) -> glyphData.unicode(),
                            (glyphData) -> new MsdfGlyph(glyphData, aWidth, aHeight)
                    ));
 
            Map<Integer, Map<Integer, Float>> kernings = new HashMap<>();
            data.kernings().forEach((kerning) -> {
                Map<Integer, Float> map = kernings.get(kerning.leftChar());
                if (map == null) {
                    map = new HashMap<>();
                    kernings.put(kerning.leftChar(), map);
                }

                map.put(kerning.rightChar(), kerning.advance());
            });

            return new MsdfFont(this.name, texture, data.atlas(), data.metrics(), glyphs, kernings);
        }

    }

}
Java:
public final class MsdfGlyph {

    private final int code;
    private final float minU, maxU, minV, maxV;
    private final float advance, topPosition, width, height;
 
    public MsdfGlyph(GlyphData data, float atlasWidth, float atlasHeight) {
        this.code = data.unicode();
        this.advance = data.advance();
     
        BoundsData atlasBounds = data.atlasBounds();
        if (atlasBounds != null) {
            this.minU = atlasBounds.left() / atlasWidth;
            this.maxU = atlasBounds.right() / atlasWidth;
            this.minV = 1.0F - atlasBounds.top() / atlasHeight;
            this.maxV = 1.0F - atlasBounds.bottom() / atlasHeight;
        } else {
            this.minU = 0.0f;
            this.maxU = 0.0f;
            this.minV = 0.0f;
            this.maxV = 0.0f;
        }

        BoundsData planeBounds = data.planeBounds();
        if (planeBounds != null) {
            this.width = planeBounds.right() - planeBounds.left();
            this.height = planeBounds.top() - planeBounds.bottom();
            this.topPosition = planeBounds.top();
        } else {
            this.width = 0.0f;
            this.height = 0.0f;
            this.topPosition = 0.0f;
        }
    }
 
    public float apply(Matrix4f matrix, IVertexProcessor processor, float size, float x, float y, float z, int red, int green, int blue, int alpha) {
        y -= this.topPosition * size;
        float width = this.width * size;
        float height = this.height * size;
        processor.pos(matrix, x, y, z).color(red, green, blue, alpha).tex(this.minU, this.minV).endVertex();
        processor.pos(matrix, x, y + height, z).color(red, green, blue, alpha).tex(this.minU, this.maxV).endVertex();
        processor.pos(matrix, x + width, y + height, z).color(red, green, blue, alpha).tex(this.maxU, this.maxV).endVertex();
        processor.pos(matrix, x + width, y, z).color(red, green, blue, alpha).tex(this.maxU, this.minV).endVertex();
     
        return this.advance * size;
    }
 
    public float getWidth(float size) {
        return this.advance * size;
    }

    public int getCharCode() {
        return code;
    }

}

Обратите внимание, очень важно установить GL_TEXTURE_MAG_FILTER и GL_TEXTURE_MIN_FILTER на GL_LINEAR для нашей текстуры, иначе антиалиасинг не будет работать. Лучшей идеи, чем реализовать кернинги через вложенные мапы, у меня не появилось. Если вы придумали более рациональный подход - напишите.

Последняя важнейшая деталь - шейдер. Работает он на чистой математике. Из названий юниформов должно быть понятно, за что они отвечают. Ниже будет пример. Отмечу только, что "Range" это тот самый
-pxrange, который мы указывали при генерации текстуры. Также есть возможность регулировать толщину текста и сразу добавить аутлайн.
C-like:
#version 120

// thanks https://github.com/Blatko1/awesome-msdf & LabyMod

uniform sampler2D Sampler;
uniform vec2 TextureSize;
uniform float Range; // distance field range of the msdf font texture
uniform float EdgeStrength;
uniform float Thickness;
uniform bool Outline; // if false, outline computation will be ignored (and its uniforms)
uniform float OutlineThickness;
uniform vec4 OutlineColor;

in vec4 OutColor;
in vec2 TexCoord;

float median(float red, float green, float blue) {
  return max(min(red, green), min(max(red, green), blue));
}

void main() {
    vec3 texColor = texture2D(Sampler, TexCoord).rgb;

    float dx = dFdx(TexCoord.x) * TextureSize.x;
    float dy = dFdy(TexCoord.y) * TextureSize.y;
    float toPixels = Range * inversesqrt(dx * dx + dy * dy);

    float sigDist = median(texColor.r, texColor.g, texColor.b) - 0.5 + Thickness;
    float alpha = smoothstep(-EdgeStrength, EdgeStrength, sigDist * toPixels);
 
    if (Outline) {
        float outlineAlpha = smoothstep(-EdgeStrength, EdgeStrength, (sigDist + OutlineThickness) * toPixels) - alpha;
        float finalAlpha = alpha * OutColor.a + outlineAlpha * OutlineColor.a;
     
        gl_FragColor = vec4(mix(OutlineColor.rgb, OutColor.rgb, alpha), finalAlpha);
        return;
    }
 
    gl_FragColor = vec4(OutColor.rgb, OutColor.a * alpha);
}
Java:
public void draw() {
        AtlasData atlas = this.font.getAtlas();
        this.shader.storeUniform("Sampler", 0);
        this.shader.storeUniform("EdgeStrength", 0.5f);
        this.shader.storeUniform("TextureSize", atlas.width(), atlas.height());
        this.shader.storeUniform("Range", atlas.range());
        this.shader.storeUniform("Thickness", this.thickness);
        this.shader.storeUniform("Outline", this.outline); // boolean
        this.shader.storeUniform("OutlineThickness", this.outlineThickness);
        float[] oColor = ColorUtils.normalize(this.outlineColor);
        this.shader.storeUniform("OutlineColor", oColor[0], oColor[1], oColor[2], oColor[3]);
      
        int[] color = ColorUtils.unpack(this.color);
      
        this.format.setupRenderState(); // default blending, no culling, no alpha test
        this.font.bind();
      
        this.core.begin(this.mode, this.shader); // BufferBuilder.begin(QUADS, POSITION_COLOR_TEX);
        this.font.applyGlyphs(matrix, core, size, text, (thickness + outlineThickness * 0.5f) * 0.5f * size,
                x, y + font.getMetrics().baselineHeight() * size, z, color[0], color[1], color[2], color[3]);
        this.core.draw(); // Tessellator.draw();
      
        this.font.unbind();
        this.format.clearRenderState();
      
        this.reset();
    }
Java:
public static final MsdfFont DEFAULT = MsdfFont.builder().withName("Biko").withAtlas("font.png").withData("font.json").build();

private void onRender(RenderGameOverlayEvent.Post event) {
        if (event.getType() == ElementType.ALL) {
            MatrixStack stack = event.getMatrixStack();
            Drawer.text().matrix(stack.last().pose()).pos(150, 80).font(DEFAULT).text("MSDF font rendering test!").color(Color.WHITE).size(10).draw();
            Drawer.text().matrix(stack.last().pose()).pos(150, 100).font(DEFAULT).thickness(0.2f).text("MSDF font rendering test!").color(Color.WHITE).size(15).draw();
            Drawer.text().matrix(stack.last().pose()).pos(150, 130).font(DEFAULT).text("MSDF font rendering test!").outline(0.4f, Color.BLACK).color(Color.WHITE).size(25).draw();

        }
    }

Пожалуйста, авторизуйтесь для просмотра ссылки.
(качество плохое только на картинке)

Теперь я разочарую некоторых из вас: готовый код, который можно скопировать и вставить, я дать не могу. Дело в том, что эта система является частью другого большого проекта. Остальной код я раскрыть не могу, а переписывать отдельно не хочу. По этой же причине я не могу просто загрузить все на гит. Чтобы у вас все заработало, вам придется изменить шейдер, так как тот, который дал я, работает с вертекс шейдером, который основан на атрибутах вершин.
Замените
IVertexProcessor на BufferBuilder, комментарии по остальному я там добавил. По идее все должно быть интуитивно понятно.
При использовании слишком жирного текста или аутлайна, у вас могут возникнуть баги. В таком случае вам нужно перегенерировать вашу текстуру с символами с бОльшим
-pxrange и все заработает.

Всем спасибо за прочтение! Кто блять выбирал эти цвета для джава кода на форуме?
 
Последнее редактирование:
эксперт в майнкрафт апи
Read Only
Статус
Оффлайн
Регистрация
25 Янв 2023
Сообщения
684
Реакции[?]
287
Поинты[?]
22K
1694580224846.png
предоставлю то, что считаю не совсем правильным

главные ошибки :

игнорирование исключений.хороший код так не пишется.исключение должно быть проброшено клиенткоду и по возможности логироваться.
предпочтительный вариант - свои unchecked exceptions(где то ты их используешь, но не свои.теоретически плохая практика, если клиент код захочет поймать конкретное исключение а будет ловить RuntimeException), checked может плохо влиять на Open-closed принцип = ставить занозу при изменении throws сигнатуры.
много где игнорируешь, не вижу смысла все вставлять.

ну кмон, разбей методы на подметоды.используй extract method.в intellij это автоматизировано, про eclipse не знаю.некоторые методы очень трудно понимать из-за огромного непрерывного кода в них.

ошибки которые тоже желательно исправить :
порой странные названия методов, где за капотом происходит то что не совсем логично для их названия
порой странные комментарии, почему то не используешь javadoc(прочитай про implNote, хоть его присутствие больше и подходит к людям которые имплементируют интерфейс, а ты пишешь объектнопроцедурофункциональную залупку).я понимаю что это как бы в паблик идет что б челсы врубались, но лучше все таки распиши их в статье, чем в коде.при этом некоторые комментарии действительно оставлены очень обоснованно, например про "
**IMPORTANT**: to correct msdf font anti-aliasing we should set GL_TEXTURE_MAG_FILTER and GL_TEXTURE_MIN_FILTER to GL_LINEAR.
".это очень хороший вид комментария, но сразу же добавлю про то, что комментарии лучше оставлять до лайна с кодом, потому что такой огромный комментарий выезжает за размер монитора.также **IMPORTANT** скорее всего лучше бы и убрать.комментарии должны использоваться в экстренных случаях и их появление в коде итак "**IMPORTANT**".

подход :
я все таки бы отдал предпочтение к оо подходу где бы все разбил на интерфейсы и их имлпементацию.хоть оптимизация из коробки будет уступать функционально-процедурному подходу, но у самих имплементаций в итоге будет возможность отъебать в оптимизации пред-стоящих ещё как, не говоря уж о гибкости и так далее.ну и код разумеется будет выглядеть более качественно, более понятно.

about GRASP, SOLID не буду заикаться.все таки маленький проект, да и их нарушение иногда приносит больше пользы(в маленьких проектах опять же).
 
эксперт в майнкрафт апи
Read Only
Статус
Оффлайн
Регистрация
25 Янв 2023
Сообщения
684
Реакции[?]
287
Поинты[?]
22K
эксперт в майнкрафт апи
Read Only
Статус
Оффлайн
Регистрация
25 Янв 2023
Сообщения
684
Реакции[?]
287
Поинты[?]
22K
у меня экспандятся
глиф?экспандится через увелечение размера матрицы?а у тебя случаем алгос не нарушает правила здравого смысла что фотография без метаинформации сама по себе расшириться и дорисоваться не может?
 
Пользователь
Статус
Оффлайн
Регистрация
23 Авг 2021
Сообщения
521
Реакции[?]
53
Поинты[?]
20K
если ты имеешь в виду про скейл именно самого текста а не чара, то да, оно размытое, только вопрос а зачем оно нужно, просто указываешь размер побольше и всё
по объемности поста мне кажется это не чуть не легче выйдет чем юзать отдельные размеры

хотел ещё сказать, что пост не особо понятен, в том плане что просто написано что именно делать, но не объяснено как именно, например почему в интеллиже не сработает, зачем нужно кидать именно в ассетсы, если можно юзнуть к примеру обычный инпутстреам? ещё не понял про GL_TEXTURE_MAG_FILTER, какие-то текстуры, в общем абсолютно ничего не понял из всего поста, конечно некоторые могут сослаться на то что я очередной пастер и поэтому я ничерта не понял или что-то в этом роде но нет
глиф?экспандится через увелечение размера матрицы?а у тебя случаем алгос не нарушает правила здравого смысла что фотография без метаинформации сама по себе расшириться и дорисоваться не может?
зачем экспандить текст через скейл
 
Начинающий
Статус
Оффлайн
Регистрация
11 Май 2022
Сообщения
70
Реакции[?]
10
Поинты[?]
10K
если ты имеешь в виду про скейл именно самого текста а не чара
А в чем разница то?
только вопрос а зачем оно нужно, просто указываешь размер побольше и всё
по объемности поста мне кажется это не чуть не легче выйдет чем юзать отдельные размеры
Ты точно читал пост? Смысл не в сложности задачи, а в эффективности и удобстве подхода
почему в интеллиже не сработает, зачем нужно кидать именно в ассетсы, если можно юзнуть к примеру обычный инпутстреам
:CoolStoryBob::CoolStoryBob:
в общем абсолютно ничего не понял из всего поста
Может ты просто очередной пастер и поэтому ни черта не понял из поста?
 
эксперт в майнкрафт апи
Read Only
Статус
Оффлайн
Регистрация
25 Янв 2023
Сообщения
684
Реакции[?]
287
Поинты[?]
22K
Пользователь
Статус
Оффлайн
Регистрация
23 Авг 2021
Сообщения
521
Реакции[?]
53
Поинты[?]
20K
А в чем разница то?

Ты точно читал пост? Смысл не в сложности задачи, а в эффективности и удобстве подхода

:CoolStoryBob::CoolStoryBob:

Может ты просто очередной пастер и поэтому ни черта не понял из поста?
ну, дефолт
пост для неизвестно кого, если человек шарит в этом всем, то он наверное сам и догадается до этого? а если этот пост не для них, то для кого тогда, для новичков?
к примеру я и есть он. я попросил нормально объяснить в чем суть, что я получил?
1. реакцию клоуна на мое сообщение, 2. ответ от автора поста, где он назвал меня пастером, без приведения явных аргументов, скинув всю вину на меня якобы я ничерта не понял и виноват здесь только я
очень удобный подход, когда пост для хер пойми кого сделан, огромное спасибо за СУПЕР полезный пост, продолжай в том же духе :forsenGun::NotLikeThis:
 
эксперт в майнкрафт апи
Read Only
Статус
Оффлайн
Регистрация
25 Янв 2023
Сообщения
684
Реакции[?]
287
Поинты[?]
22K
ну, дефолт
пост для неизвестно кого, если человек шарит в этом всем, то он наверное сам и догадается до этого? а если этот пост не для них, то для кого тогда, для новичков?
к примеру я и есть он. я попросил нормально объяснить в чем суть, что я получил?
1. реакцию клоуна на мое сообщение, 2. ответ от автора поста, где он назвал меня пастером, без приведения явных аргументов, скинув всю вину на меня якобы я ничерта не понял и виноват здесь только я
очень удобный подход, когда пост для хер пойми кого сделан, огромное спасибо за СУПЕР полезный пост, продолжай в том же духе :forsenGun::NotLikeThis:
клоуна я влепил за
хотел ещё сказать, что пост не особо понятен, в том плане что просто написано что именно делать, но не объяснено как именно, например почему в интеллиже не сработает
 
Пользователь
Статус
Оффлайн
Регистрация
23 Авг 2021
Сообщения
521
Реакции[?]
53
Поинты[?]
20K
клоуна я влепил за
объясняю еще раз, если человек не вашего уровня развития - не значит, что нужно его как-то пытаться высмеивать или оскорблять
в данном случае ты влепил клоуна за то что я недопёр
вопрос, в чем состоится моё "клоунство" в том сообщении, если я банально не понял поста?
клоуна я влепил за
одно дело когда человек реально пастер, а другое когда вы просто присвоили эту "роль" человеку, ибо вам так показалось, но вы же не общались с ним чтобы делать точные выводы
видимо вам просто ваше чсв мешает это понять, вы думаете что вы среди других пастеров на фоне очень крутой, потому-что отличаетесь от них своими знаниями и более высоким айкью, при виде любого человека который не понял сути чего-либо, которого поняли сути вы - считаете этого человека хуже вас и соответственно присваиваете его к пастерам потому-что вам так показалось при первом виде?
 
Последнее редактирование:
Начинающий
Статус
Оффлайн
Регистрация
11 Май 2022
Сообщения
70
Реакции[?]
10
Поинты[?]
10K
ответ от автора поста, где он назвал меня пастером, без приведения явных аргументов
Это ирония, не расстраивайся
я попросил нормально объяснить в чем суть, что я получил?
Что я блять должен был тебе ответить? В первой части предложения, где ты расписывал что тебе непонятно, ты написал какую-то хуйню, а во-второй ты спросил очевидные вещи.
ещё не понял про GL_TEXTURE_MAG_FILTER, какие-то текстуры
Какого ответа ты ожидал на это? Хоть конкретики добавь. На хуевый вопрос - хуевый ответ.
 
Пользователь
Статус
Оффлайн
Регистрация
23 Авг 2021
Сообщения
521
Реакции[?]
53
Поинты[?]
20K
Это ирония, не расстраивайся

Что я блять должен был тебе ответить? В первой части предложения, где ты расписывал что тебе непонятно, ты написал какую-то хуйню, а во-второй ты спросил очевидные вещи.

Какого ответа ты ожидал на это? Хоть конкретики добавь. На хуевый вопрос - хуевый ответ.
весело будет, когда тебе также ответят, когда ты спросишь что-то, тогда ты поймешь что чувствовал я в тот момент, и наверняка может быть для себя сделаешь какие-либо выводы, а может и нет, тут зависит от уровня твоего ума
я спросил два вопроса, ты на них ответил? нет, при этом ты продолжаешь во первых грубить, во вторых ты мог за место этого просто ответить на те два вопроса, но нет, твой ум не может догадаться до такой простой вещи - ты тратишь время на то чтобы нагрубить человеку больше, чем просто ответив на два вопроса.
у тебя в посте нету ни слова про текстурки, в коде тоже
 
Начинающий
Статус
Оффлайн
Регистрация
11 Май 2022
Сообщения
70
Реакции[?]
10
Поинты[?]
10K
одно дело когда человек реально пастер, а другое когда вы просто присвоили эту "роль" человеку, ибо вам так показалось, но вы же не общались с ним чтобы делать точные выводы
видимо вам просто ваше чсв мешает это понять, вы думаете что вы среди других пастеров на фоне очень крутой, потому-что отличаетесь от них своими знаниями и более высоким айкью, при виде любого человека который не понял сути чего-либо, которого поняли сути вы - считаете этого человека хуже вас и соответственно присваиваете его к пастерам потому-что вам так показалось при первом виде?
Да никто тебя всерьез не называл ПАСТЕРОМ, это была ШУТКА, потому что ты сам так написал в своем сообщении. Чет тебя совсем понесло нахуй, успокойся.
 
Пользователь
Статус
Оффлайн
Регистрация
23 Авг 2021
Сообщения
521
Реакции[?]
53
Поинты[?]
20K
Да никто тебя всерьез не называл ПАСТЕРОМ, это была ШУТКА, потому что ты сам так написал в своем сообщении. Чет тебя совсем понесло нахуй, успокойся.
шутка, над которой никто не смеялся? это называется высмеивание человека, а не шутка - так, если что
меня не понесло, просто меня бесит это коммьюнити, состоящее из людей таких как ты, конечно ты не самый худший вариант, но всё же и далеко не лучший
у меня один вопрос, ты ответишь на вопросы мои или нет, если нет то и смысла продолжать тоже, просто буду знать что от тебя бесполезно что-то ждать и спрашивать тебя о чем-то
 
Начинающий
Статус
Оффлайн
Регистрация
11 Май 2022
Сообщения
70
Реакции[?]
10
Поинты[?]
10K
я спросил два вопроса, ты на них ответил?
Хорошо, я отвечу.
почему в интеллиже не сработает
Это была шутка. Сработает.
зачем нужно кидать именно в ассетсы, если можно юзнуть к примеру обычный инпутстреам?
Вопрос абсолютно лишен логики. ASSETS - это папка. Поместить файлы туда необходимо для того, чтобы до них можно было добраться ResourceLocation'ном.
 
Пользователь
Статус
Оффлайн
Регистрация
18 Фев 2022
Сообщения
584
Реакции[?]
99
Поинты[?]
38K
одно дело когда человек реально пастер, а другое когда вы просто присвоили эту "роль" человеку, ибо вам так показалось, но вы же не общались с ним чтобы делать точные выводы
видимо вам просто ваше чсв мешает это понять
Это кто ещё чсв, ты че считаешь себя выше других пастеров югейма, ты такой же как и они, с хуя ли ты решил что ты особенный
 
Пользователь
Статус
Оффлайн
Регистрация
23 Авг 2021
Сообщения
521
Реакции[?]
53
Поинты[?]
20K
Хорошо, я отвечу.

Это была шутка. Сработает.

Вопрос абсолютно лишен логики. ASSETS - это папка. Поместить файлы туда необходимо для того, чтобы до них можно было добраться ResourceLocation'ном.
а смысл ResourceLocation'a, именно он нужен? без него не обойтись? просто я делаю чит туда, где нельзя засунуть в ассетсы что-то
 
Сверху Снизу