Jump to content

[1.14.4] Custom elytra layer not rendering transparency


ReconCubed

Recommended Posts

I've been working on the ability to add banner patterns to elytras, and I've actually gotten it to work. However, when an elytra has a banner attached the transparent parts of the wings are rendered with a gray color. 

 

Photos:

Spoiler

IDsjNhm.png

FqJ0r1Q.png

 

My layer renderer -> https://hastebin.com/roquzivade.hs

My 'banner/elytra textures' cache -> https://hastebin.com/fusewojuva.hs

My texture class (currently the same as a normal LayerColourMaskTexture class) -> https://hastebin.com/tazuwapabe.hs

 

This only happens when I have a banner attached to the elytra, so it leads me to believe there's something I've missed in my texture class. I've tried adding a check on the multiplied colour, testing to see if it that shade of gray was present and then setting the pixel RGBA to a transparent pixel, but that didn't work.

 

I think I'm a bit in over my head here, it'd be great if someone could help push me in the right direction.

 

Cheers!

Edited by ReconCubed
added extra photo
Link to comment
Share on other sites

Howdy

 

Are you sure this blend function is what you intend?

GlStateManager.blendFunc(GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ZERO);

Normally I would expect to see something like

GlStateManager.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA);

 

Your texture generator doesn't appear to handle alpha values explicitly?  It seems to be doing some sort of magic manipulations there, it would help if you named your variables more clearly. 

This in particular just doesn't look right; I presume it is trying to extract the alpha value but it's not clear to me why the alpha value is in the lowest 8 bits initially but needs to be shifted to the upper 8 bits

int j1 = (i1 & 255) << 24 & -16777216;

 

-TGG

 

Link to comment
Share on other sites

19 minutes ago, TheGreyGhost said:

Howdy

 

Are you sure this blend function is what you intend?

GlStateManager.blendFunc(GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ZERO);

Normally I would expect to see something like

GlStateManager.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA);

 

Your texture generator doesn't appear to handle alpha values explicitly?  It seems to be doing some sort of magic manipulations there, it would help if you named your variables more clearly. 

This in particular just doesn't look right; I presume it is trying to extract the alpha value but it's not clear to me why the alpha value is in the lowest 8 bits initially but needs to be shifted to the upper 8 bits

int j1 = (i1 & 255) << 24 & -16777216;

 

-TGG

 

Yeah, the texture generator is currently a direct copy+paste of vanillas LayeredColorMaskTexture, which is used to generate banners.

 I started off with just using that class, but that didn't work so I copied it over to my own to make tweaks. This is my first time playing with texture generators so I don't really understand it very well yet. 

 

I've managed to seperate the transparent pixels, even manipulate their colour. But I can't seem to get the alpha channel to actually work.

https://hastebin.com/ogalajogux.hs

 

I've trief 0x00 and all its counterparts I could find on the internet for just a transparent RGBA value in bytes. No luck there, just renders as white or black.

 

 

As for the blend func change, I've switched my source factor and destination factor to match what you've suggested, but not changes. The original elytra layer uses source factor one & dest factor zero, so I figured it'd be fine.

 

 

Link to comment
Share on other sites

Are banners transparent (cutout)?  That may be the problem if you've copied the texture gen code from banners.

The vanilla elytra isn't really transparent, it's cutout so the blending function is irrelevant, the alpha test is the important function.  i.e. if alpha is less than the cutoff it's transparent, otherwise it's opaque.  I can't tell from your code whether cutout (alpha test) is enabled or not.  But if it renders as opaque no matter what RGBA you write into the texture, then it's probably disabled.

 

The first thing I'd try is something like this:

                            for (int height = 0; height < nativeimage2.getHeight(); ++height) {
                                for (int width = 0; width < nativeimage2.getWidth(); ++width) {
                                    int pixelRGBA = nativeimage2.getPixelRGBA(width, height);
                                    if (height == width) {
                                      nativeimage1.setPixelRGBA(width, height, 0);  // will be transparent
                                    } else {
                                      nativeimage1.setPixelRGBA(width, height, pixelRGBA);
									}	                           
                                }
                            }

If that has cutout pixels in it, you know that the rendering is ok.  Otherwise your rendering mode is wrong (alpha cutoff is not correct).

 

I think your code for generating the texture is not right.  The int RGBA value that you get is actually composed of four components

bits 0 -> 7 are red

bits 8 -> 15 are green

bits 16 -> 23 are blue

bits 24 -> 31 are the alpha value.

 

so you calculate

int alphaValue = (pixelRGBA >> 24) & 0xff;

int colourWithoutAlpha = (pixelRGBA & 0xffffff);

// blend your colours without Alpha here

then

int newRGBA = (alphaValue << 24) | (newColourWithoutAlpha);

 

using blendPixel probably won't give you what you want because it blends the alpha channels as well which will probably mess up your culling.

 

Perhaps if you describe how exactly you want the two textures to be combined it might help.

Do you want partial blending of the banner texture onto the elytra?  Or are you just wanting the elytra to be coloured to match the banner except where the elytra has a cutout (i.e. all-or-nothing blending)?  In the second case I don't think you need blendPixel at all - just check every pixel in the elytra texture and if the alphaValue is above the cutoff, write the banner pixel to the output texture, otherwise write a transparent pixel (alpha of zero)

 

-TGG

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Link to comment
Share on other sites

On 5/1/2020 at 1:40 AM, TheGreyGhost said:

Are banners transparent (cutout)?  That may be the problem if you've copied the texture gen code from banners.

The vanilla elytra isn't really transparent, it's cutout so the blending function is irrelevant, the alpha test is the important function.  i.e. if alpha is less than the cutoff it's transparent, otherwise it's opaque.  I can't tell from your code whether cutout (alpha test) is enabled or not.  But if it renders as opaque no matter what RGBA you write into the texture, then it's probably disabled.

 

The first thing I'd try is something like this:


                            for (int height = 0; height < nativeimage2.getHeight(); ++height) {
                                for (int width = 0; width < nativeimage2.getWidth(); ++width) {
                                    int pixelRGBA = nativeimage2.getPixelRGBA(width, height);
                                    if (height == width) {
                                      nativeimage1.setPixelRGBA(width, height, 0);  // will be transparent
                                    } else {
                                      nativeimage1.setPixelRGBA(width, height, pixelRGBA);
									}	                           
                                }
                            }

If that has cutout pixels in it, you know that the rendering is ok.  Otherwise your rendering mode is wrong (alpha cutoff is not correct).

 

I think your code for generating the texture is not right.  The int RGBA value that you get is actually composed of four components

bits 0 -> 7 are red

bits 8 -> 15 are green

bits 16 -> 23 are blue

bits 24 -> 31 are the alpha value.

 

so you calculate

int alphaValue = (pixelRGBA >> 24) & 0xff;

int colourWithoutAlpha = (pixelRGBA & 0xffffff);

// blend your colours without Alpha here

then

int newRGBA = (alphaValue << 24) | (newColourWithoutAlpha);

 

using blendPixel probably won't give you what you want because it blends the alpha channels as well which will probably mess up your culling.

 

Perhaps if you describe how exactly you want the two textures to be combined it might help.

Do you want partial blending of the banner texture onto the elytra?  Or are you just wanting the elytra to be coloured to match the banner except where the elytra has a cutout (i.e. all-or-nothing blending)?  In the second case I don't think you need blendPixel at all - just check every pixel in the elytra texture and if the alphaValue is above the cutoff, write the banner pixel to the output texture, otherwise write a transparent pixel (alpha of zero)

 

-TGG

 

Sorry it took a while to reply! Was away over the weekend.

 

I tried adding alpha test in my custom elytra layer, and set the transparent pixels to 0. That ended up just rendering black for those pixels. It seems to be targetting the correct pixels from the original texture images, however just turning them black and making the entire thing a black square with the textures on it (like the photos in my OP).

I also went back to have a 2nd look at the vanilla elytra layer, I believe they do use blendFunc, I cant see anything about the alpha test func in there.

 

Also, to answer your question: I have made some textures using the vanilla Elytra & shield patterns as a base (much like how the banner & shield do it). zUS6QHN.png

 

I'm simply trying to render the proper elytra transparency, like the below picture, but with the custom textures I use above for all the banner patterns & colours. 

150px-Elytra_JE2_BE2.png?version=8329d6c

 

Cheers!

 

 

 

 

Link to comment
Share on other sites

8 minutes ago, TheGreyGhost said:

Hmm ok

If you put your code into a github repository I'll download it in the next few days and have a look, if you like.

 

Cheers

  TGG

 

Thanks!

Here's my repo: https://github.com/ReconCubed/thecommunityupdate

 

The related files are:

https://github.com/ReconCubed/thecommunityupdate/tree/master/src/main/java/me/reconcubed/communityupdate/client/render

https://github.com/ReconCubed/thecommunityupdate/blob/master/src/main/java/me/reconcubed/communityupdate/util/BannerTextures.java

 

I appreciate the help!

 

Link to comment
Share on other sites

Well after a bit of refactoring I figured out the problem

It's more obvious when I replaced the magic numbers with the proper OpenGL constants

 

            TextureUtil.prepareImage(this.getGlTextureId(), overlaidElytra.getWidth(), overlaidElytra.getHeight());
            GlStateManager.pixelTransfer(GL11.GL_ALPHA_BIAS, Float.MAX_VALUE);  // Sets alpha of final image to max.... delete this line and it works fine.
            overlaidElytra.uploadTextureSub(0, 0, 0, false);
            GlStateManager.pixelTransfer(GL11.GL_ALPHA_BIAS, 0.0F);

 

-TGG

 

PS the refactored code FYI

 

package me.reconcubed.communityupdate.client.render;

import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.platform.TextureUtil;

import java.io.IOException;
import java.util.List;

import net.minecraft.client.renderer.texture.NativeImage;
import net.minecraft.client.renderer.texture.Texture;
import net.minecraft.item.DyeColor;
import net.minecraft.resources.IResource;
import net.minecraft.resources.IResourceManager;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.MathHelper;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL14;

@OnlyIn(Dist.CLIENT)
public class LayeredColorMaskTextureCustom extends Texture {
    private static final Logger LOGGER = LogManager.getLogger();
    private final ResourceLocation textureLocation;
    private final List<String> listTextures;
    private final List<DyeColor> listDyeColors;

    public LayeredColorMaskTextureCustom(ResourceLocation textureLocationIn, List<String> p_i46101_2_, List<DyeColor> p_i46101_3_) {
        this.textureLocation = textureLocationIn;
        this.listTextures = p_i46101_2_;
        this.listDyeColors = p_i46101_3_;
    }

    public void loadTexture(IResourceManager manager) throws IOException {
        try (
                IResource iresource = manager.getResource(this.textureLocation);
                NativeImage baseElytra = NativeImage.read(iresource.getInputStream());
                NativeImage overlaidElytra = new NativeImage(baseElytra.getWidth(), baseElytra.getHeight(), false);
        ) {
            overlaidElytra.copyImageData(baseElytra);

            for (int i = 0; i < 17 && i < this.listTextures.size() && i < this.listDyeColors.size(); ++i) {
                String bannerTextureRL = this.listTextures.get(i);
                if (bannerTextureRL != null) {
                    try (
                            NativeImage bannerLayer = net.minecraftforge.client.MinecraftForgeClient.getImageLayer(new ResourceLocation(bannerTextureRL), manager);
                    ) {
                        int bannerLayerColour = this.listDyeColors.get(i).getSwappedColorValue();
                        if (bannerLayer.getWidth() == overlaidElytra.getWidth() && bannerLayer.getHeight() == overlaidElytra.getHeight()) {
                            for (int height = 0; height < bannerLayer.getHeight(); ++height) {
                                for (int width = 0; width < bannerLayer.getWidth(); ++width) {

                                    int alphaBanner = bannerLayer.getPixelRGBA(width, height) & 0xff;  // extract the red channel, could have used green or blue also.
                                    int alphaElytra = baseElytra.getPixelLuminanceOrAlpha(width, height) & 0xff;
                                    //  algorithm is:
                                    //  if elytra pixel is transparent, do nothing
                                    //  otherwise:
                                    //    the banner blend layer is a greyscale which is converted to a transparency:
                                    //     blend the banner's colour into elytra pixel using the banner blend transparency

                                    if (alphaElytra != 0 && alphaBanner != 0) {
                                      int elytraPixelRGBA = baseElytra.getPixelRGBA(width, height);
                                      int multipliedColorRGB = MathHelper.multiplyColor(elytraPixelRGBA, bannerLayerColour) & 0xFFFFFF;
                                      int multipliedColorRGBA = multipliedColorRGB | (alphaBanner << 24);
                                      overlaidElytra.blendPixel(width, height, multipliedColorRGBA);
                                    }

                                }
                            }
                        }
                    }
                }
            }

            TextureUtil.prepareImage(this.getGlTextureId(), overlaidElytra.getWidth(), overlaidElytra.getHeight());
          GlStateManager.pixelTransfer(GL11.GL_ALPHA_BIAS, 0.0F);
            overlaidElytra.uploadTextureSub(0, 0, 0, false);
        } catch (IOException ioexception) {
            LOGGER.error("Couldn't load layered color mask image", (Throwable) ioexception);
        }

    }
}

 

 

 

Link to comment
Share on other sites

5 hours ago, TheGreyGhost said:

Well after a bit of refactoring I figured out the problem

It's more obvious when I replaced the magic numbers with the proper OpenGL constants

 


            TextureUtil.prepareImage(this.getGlTextureId(), overlaidElytra.getWidth(), overlaidElytra.getHeight());
            GlStateManager.pixelTransfer(GL11.GL_ALPHA_BIAS, Float.MAX_VALUE);  // Sets alpha of final image to max.... delete this line and it works fine.
            overlaidElytra.uploadTextureSub(0, 0, 0, false);
            GlStateManager.pixelTransfer(GL11.GL_ALPHA_BIAS, 0.0F);

 

-TGG

 

PS the refactored code FYI

 


package me.reconcubed.communityupdate.client.render;

import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.platform.TextureUtil;

import java.io.IOException;
import java.util.List;

import net.minecraft.client.renderer.texture.NativeImage;
import net.minecraft.client.renderer.texture.Texture;
import net.minecraft.item.DyeColor;
import net.minecraft.resources.IResource;
import net.minecraft.resources.IResourceManager;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.MathHelper;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL14;

@OnlyIn(Dist.CLIENT)
public class LayeredColorMaskTextureCustom extends Texture {
    private static final Logger LOGGER = LogManager.getLogger();
    private final ResourceLocation textureLocation;
    private final List<String> listTextures;
    private final List<DyeColor> listDyeColors;

    public LayeredColorMaskTextureCustom(ResourceLocation textureLocationIn, List<String> p_i46101_2_, List<DyeColor> p_i46101_3_) {
        this.textureLocation = textureLocationIn;
        this.listTextures = p_i46101_2_;
        this.listDyeColors = p_i46101_3_;
    }

    public void loadTexture(IResourceManager manager) throws IOException {
        try (
                IResource iresource = manager.getResource(this.textureLocation);
                NativeImage baseElytra = NativeImage.read(iresource.getInputStream());
                NativeImage overlaidElytra = new NativeImage(baseElytra.getWidth(), baseElytra.getHeight(), false);
        ) {
            overlaidElytra.copyImageData(baseElytra);

            for (int i = 0; i < 17 && i < this.listTextures.size() && i < this.listDyeColors.size(); ++i) {
                String bannerTextureRL = this.listTextures.get(i);
                if (bannerTextureRL != null) {
                    try (
                            NativeImage bannerLayer = net.minecraftforge.client.MinecraftForgeClient.getImageLayer(new ResourceLocation(bannerTextureRL), manager);
                    ) {
                        int bannerLayerColour = this.listDyeColors.get(i).getSwappedColorValue();
                        if (bannerLayer.getWidth() == overlaidElytra.getWidth() && bannerLayer.getHeight() == overlaidElytra.getHeight()) {
                            for (int height = 0; height < bannerLayer.getHeight(); ++height) {
                                for (int width = 0; width < bannerLayer.getWidth(); ++width) {

                                    int alphaBanner = bannerLayer.getPixelRGBA(width, height) & 0xff;  // extract the red channel, could have used green or blue also.
                                    int alphaElytra = baseElytra.getPixelLuminanceOrAlpha(width, height) & 0xff;
                                    //  algorithm is:
                                    //  if elytra pixel is transparent, do nothing
                                    //  otherwise:
                                    //    the banner blend layer is a greyscale which is converted to a transparency:
                                    //     blend the banner's colour into elytra pixel using the banner blend transparency

                                    if (alphaElytra != 0 && alphaBanner != 0) {
                                      int elytraPixelRGBA = baseElytra.getPixelRGBA(width, height);
                                      int multipliedColorRGB = MathHelper.multiplyColor(elytraPixelRGBA, bannerLayerColour) & 0xFFFFFF;
                                      int multipliedColorRGBA = multipliedColorRGB | (alphaBanner << 24);
                                      overlaidElytra.blendPixel(width, height, multipliedColorRGBA);
                                    }

                                }
                            }
                        }
                    }
                }
            }

            TextureUtil.prepareImage(this.getGlTextureId(), overlaidElytra.getWidth(), overlaidElytra.getHeight());
          GlStateManager.pixelTransfer(GL11.GL_ALPHA_BIAS, 0.0F);
            overlaidElytra.uploadTextureSub(0, 0, 0, false);
        } catch (IOException ioexception) {
            LOGGER.error("Couldn't load layered color mask image", (Throwable) ioexception);
        }

    }
}

 

 

 

You're an absolute legend mate. Thank you! It worked perfectly. 

 

Have a good one :)

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Announcements



×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.