Jump to content
Lethal Raptor Games

Texture Arrays

Recommended Posts

As well as trying out texture atlases I'm looking into textures arrays too, but am having trouble finding information on how to set it up.  This is what I have so far;

Material* mat = Material::Load("Materials\\terrain.mat");
Texture* texture = Texture::Load("Terrain\\beach_dirt_b.tex");
OpenGLShader* shader = (OpenGLShader*)mat->GetShader();

int _width = texture->GetWidth(), _height = texture->GetHeight();
char* buf = new char[texture->GetMipmapSize(0) * 4];
texture->GetPixels(buf);

glUseProgram(shader->program);

GLuint _buffer;
glGenTextures(1, &_buffer);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D_ARRAY, _buffer);
glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, _width, _height, 1);

glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, _width, _height, 1, GL_RGBA8, GL_BYTE, buf);

Part of the Fragment Shader;

#version 430
layout (binding = 0) uniform sampler2DArray tex;

//in main()
outcolor = texture(tex, vec3(coords.x, coords.y, 0));

All I get is total blackness on the terrain.  Wondering if any of you guys know how to implement this?  If needed I can make a small test program and upload that.

 

A link I used; https://ferransole.wordpress.com/2014/06/09/array-textures/

Share this post


Link to post

This is how I do it:

glTexImage3D(this->gltarget, 0, internalformat, size.x, size.y, size.z, 0, externalformat, type, nullptr);

glTexStorage3D requires OpenGL 4.2, so I don't use it.

 

And this:

glTexSubImage3D(this->gltarget, miplevel, 0, 0, 0, mipwidth, mipheight, mipdepth, externalformat, type, data);

For external format GL_RGBA8 is not valid, and it doesn't even make sense because you have another parameter that indicates the data is GL_BYTE (you probably want GL_UNSIGNED_BYTE for that).

Quote

Specifies the format of the pixel data. The following symbolic values are accepted: GL_RED, GL_RG, GL_RGB, GL_BGR, GL_RGBA, GL_DEPTH_COMPONENT, and GL_STENCIL_INDEX.

https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glTexSubImage3D.xhtml

  • Thanks 1

Share this post


Link to post

Thanks.  I tried this in a separate project and all I'm getting is a black cube.  glGetError() returns 0 so all good there.  I've uploaded the entire mat and texture file too.

#include "App.h"

using namespace Leadwerks;

App::App() : window(NULL), context(NULL), world(NULL), camera(NULL) {}

App::~App() { delete world; delete window; }

float lookspeed = 0.1, looksmoothing = 0.5;
Vec3 mousepos;

bool wireframe = false;

bool App::Start()
{
	window = Leadwerks::Window::Create();
	context = Context::Create(window);

	world = World::Create();
	world->SetGravity(0, 0, 0);

	camera = Camera::Create();
	camera->Move(0, 2, -4);

	Light* light = DirectionalLight::Create();
	light->SetRotation(35, 35, 0);

	Model* box = Model::Box();
	box->Move(0, 1, 0);
	Material* mat = Material::Load("Materials\\Test.mat");
	box->SetMaterial(mat);

	Texture* texture = Texture::Load("Materials\\bluegrid.tex");

	int _width = texture->GetWidth();
	int _height = texture->GetHeight();
	char* buf = new char[texture->GetMipmapSize(0) * 4];
	texture->GetPixels(buf);

	OpenGLShader* shader = (OpenGLShader*)mat->GetShader();
	glUseProgram(shader->program);

	GLuint _buffer;
	glGenTextures(1, &_buffer);
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D_ARRAY, _buffer);
	glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, _width, _height, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
	GLenum error1 = glGetError();

	glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, _width, _height, 0, GL_RGBA, GL_UNSIGNED_BYTE, buf);

	GLenum error2 = glGetError();


	Model* floor = Model::Box();
	floor->SetShape(Shape::Box());
	floor->SetPosition(5, 0, 0);
	floor->SetScale(30, 0.1, 30);

	mousepos = window->GetMousePosition();
	window->SetMousePosition(context->GetWidth() / 2, context->GetHeight() / 2);

	return true;
}

bool App::Loop()
{
	if (window->Closed() || window->KeyDown(Key::Escape)) return false;
	if (window->KeyHit(Key::F3) == true) { camera->GetDebugPhysicsMode() == true ? camera->SetDebugPhysicsMode(false) : camera->SetDebugPhysicsMode(true); }
	if (window->KeyHit(Key::F4) == true) { camera->GetDebugEntityBoxesMode() == true ? camera->SetDebugEntityBoxesMode(false) : camera->SetDebugEntityBoxesMode(true); }
	if (window->KeyHit(Key::F2) == true)
	{
		if (wireframe == true)
		{
			camera->SetDrawMode(0);
			wireframe = false;
		}
		else
		{
			camera->SetDrawMode(2);
			wireframe = true;
		}
	}

	float cx = Math::Round(context->GetWidth() / 2);
	float cy = Math::Round(context->GetHeight() / 2);
	Vec3 mpos = window->GetMousePosition();
	window->SetMousePosition(cx, cy);
	mpos = mpos * looksmoothing + mousepos * (1 - looksmoothing);
	float dx = (mpos.x - cx) * lookspeed;
	float dy = (mpos.y - cy) * lookspeed;

	Vec3 camrot = camera->GetRotation();
	camrot.x += dy;
	camrot.y += dx;
	camera->SetRotation(camrot);
	mousepos = mpos;

	float _time = Time::GetSpeed();
	float camspeed = 0.2f * _time;
	if (window->KeyDown(Key::Shift) == true) { camspeed = camspeed * 5.0f; }
	if (window->KeyDown(Key::W) == true) { camera->Move(0, 0, camspeed); }
	else if (window->KeyDown(Key::S) == true) { camera->Move(0, 0, -camspeed); }
	if (window->KeyDown(Key::A) == true) { camera->Move(-camspeed, 0, 0); }
	else if (window->KeyDown(Key::D) == true) { camera->Move(camspeed, 0, 0); }
	if (window->KeyDown(Key::T) == true) { camera->Move(0, camspeed, 0); }
	else if (window->KeyDown(Key::G) == true) { camera->Move(0, -camspeed, 0); }


	Leadwerks::Time::Update();
	world->Update();
	world->Render();

	context->Sync();

	return true;
}

 

The texture is uncompressed and has no mipmaps.  It might have to do with the way I'm binding it, maybe it can't find the right location in the shader?

Test.zip

Share this post


Link to post

The uniform texture0 might not be getting set to '0' because the engine does not recognize texture arrays.

You would have to cast the shader to an OpenGLShader object, then you can retrieve the right uniform, then you can set it yourself using OpenGL commands. Make sure the shader is enabled before setting the uniform value.

  • Thanks 1

Share this post


Link to post
3 hours ago, Josh said:

You would have to cast the shader to an OpenGLShader object, then you can retrieve the right uniform

This part I've done and seems to work;

OpenGLShader* shader = (OpenGLShader*)mat->GetShader();
Uniform* uniform = shader->GetUniform("arrayMap");

Not too sure what commands there are that can bind to the uniform.  Google has not been too helpful... Is this right?

glUseProgram(shader->program);

GLuint _buffer;
glGenTextures(1, &_buffer);
glActiveTexture(GL_TEXTURE0 + uniform->index);
glBindTexture(GL_TEXTURE_2D_ARRAY, _buffer);

There are no errors being thrown by any of these GL commands either.

 

I can't help but think the uniform itself needs to be activated so that the 2D_ARRAY can be bound to it rather than "_buffer"

Share this post


Link to post

I can see how it's supposed to work, but I can tell why it's not... 🤔

Texture* texture = Texture::Load("Materials\\bluegrid.tex");

int _width = texture->GetWidth();
int _height = texture->GetHeight();
char* buf = new char[texture->GetMipmapSize(0) * 4];
texture->GetPixels(buf);

OpenGLShader* shader = (OpenGLShader*)mat->GetShader();
Uniform* uniform = shader->GetUniform("arrayMap");

glUseProgram(shader->program);

GLuint _buffer;
glGenTextures(1, &_buffer);
glBindTexture(GL_TEXTURE_2D_ARRAY, _buffer);
glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, _width, _height, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, _width, _height, 0, GL_RGBA, GL_UNSIGNED_BYTE, buf);
glUniform1i(uniform->index, _buffer);
uniform sampler2DArray arrayMap;

//main
outcolor *= texture(arrayMap,vec3(ex_texcoords0.x,ex_texcoords0.y,0));

_buffer is an unsigned int, but when I use glUnfiorm1ui() it throws an GL_INVAILD_VALUE error.

buf is a SIGNED_BYTE array but passing it to the OpenGL commands you recommend GL_UNSIGNED_BYTE.

The texture is uncompressed and has no mipmaps.

uniform->index is 0.  So the uniform is being found because uniform is not equal to NULL.

As far as I can tell the arguments for glTexImage3D and glTexSubImage3D conform to what the wiki says.

Nothing is throwing an error.

 

Maybe there's something overwriting it later on as the program runs?

Share this post


Link to post

glUniform1i(uniform->index, _buffer);

The uniform is a texture unit, not a texture! Set it to 0 or something, and then bind the texture to texture unit 0.

Share this post


Link to post

Thanks.  But still nothing.

OpenGLShader* shader = (OpenGLShader*)mat->GetShader();
Uniform* uniform = shader->GetUniform("arrayMap");

glUseProgram(shader->program);

GLuint _buffer;
glGenTextures(1, &_buffer);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D_ARRAY, _buffer);
glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, _width, _height, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);

glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, _width, _height, 0, GL_RGBA, GL_UNSIGNED_BYTE, buf);
glUniform1i(uniform->index, 0);

 

Share this post


Link to post

Oh, I see you used GLSL 4.3, which allows you to specify the texture unit in the shader. So setting the uniform is redundant.

i dont see anything wrong.

Share this post


Link to post

I googled "texture arrays are black" and found this...

Quote

Now, here is a very common mistake made by every beginner OpenGL programmer:

Code: [View]

    ...gen and bind texture
    glTexImage2D(GL_TEXTURE_2D, level0... width, height... pixels)
    ...draw with that texture

The result is all black, because this texture is incomplete. What is "complete"? It means a complex combination of the mipmap geometry, the mipmap image formats, the sampler filtering modes, and sampler wrap modes are all compatible and will make sense when the texture is sampled in a shader

So it may need more mipmaps at least, or other information about how to use the image...

Share this post


Link to post

You need to set the filter mode:

			if (filtermode == TEXTURE_SMOOTH)
			{
				if (hasmipmaps)
				{
					//if (trilinearfiltermode == true or target == TEXTURE_CUBEMAP)//textureLOD needs trilinear filter for linear filtering on Intel cards
					//{
						glTexParameteri(gltarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);//yes trilinear filter
					//}
					//else
					//{
					//	glTexParameteri(gltarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);//no trilinear filter
					//}
					glTexParameteri(gltarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
					//if (driver->maxanisotropy>1.0)
					//{
					glTexParameterf(gltarget, GL_TEXTURE_MAX_ANISOTROPY_EXT, anisotropy);// Math::Clamp(anisotropy, 1.0, driver->maxanisotropy));
					//}
				}
				else
				{
					glTexParameteri(gltarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
					glTexParameteri(gltarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
				}

 

Share this post


Link to post

Thanks, but still no joy.  It's odd, I've been able to use GL commands to set a shader_storage_object before.  But not textures... sampler_2D or arrays...

Model* box = Model::Box();
box->Move(0, 1, 0);
Material* mat = Material::Load("Materials\\Test.mat");
box->SetMaterial(mat);


Texture* texture = Texture::Load("Materials\\bluegrid.tex");

int _width = texture->GetWidth();
int _height = texture->GetHeight();
char* buf = new char[texture->GetMipmapSize(0) * 4];
texture->GetPixels(buf);

OpenGLShader* shader = (OpenGLShader*)mat->GetShader();

glUseProgram(shader->program);

GLuint _buffer;
glGenTextures(1, &_buffer);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D_ARRAY, _buffer);
GLenum e4 = glGetError();
glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, _width, _height, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
GLenum e5 = glGetError();

glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, _width, _height, 0, GL_RGBA, GL_UNSIGNED_BYTE, buf);

GLenum e7 = glGetError();
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
GLenum e8 = glGetError();

 

Share this post


Link to post

FYI, you can use this command:

glCheckError()

If an error occurs it will print it out and generate a Leadwerks error, but it only works in Debug mode. In Release mode it will ignore OpenGL errors.

Share this post


Link to post
15 minutes ago, Josh said:

You are giving glTexSubImage3D a depth of zero. Shouldn't depth be one?

Ah okay.   I thought it was an index referring to the position in the array, 0 being the start.  But that would probably be the zoffset?

So I set depth to 1;

glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, _width, _height, 1, GL_RGBA, GL_UNSIGNED_BYTE, buf);

But now glTexSubImage3D() throws this error every time;

Exception thrown at 0x04BF5160 (nvoglv32.dll) in TestingGrounds.debug.exe: 0xC0000005: Access violation reading location 0x0CCF1000.

I will google the error after work.

 

16 minutes ago, Josh said:

glCheckError()

Thanks.  I'll use this one instead.

Share this post


Link to post

Okay I solved the crash;

Changed this; 

texture->GetMipmapSize(0) * 4

To this;

texture->GetMipmapSize(0) * 8

Curious as to why I need to multiple by 8 though... Is GetMipmapSize() in bytes?

 

After fixing the crash the result was still black... I will conquer this...

Share this post


Link to post

GetMipMapSize() is the data size, and does not need to be multiplied by anything. If the format isn't uncompressed RGBA then it would be less than 4 BPP.

You have two possible sources of error. You don't really know what's stored in that char buffer. Why not just create a buffer of 255 values that is width * height * 4 in size,  and see if you can make the texture white?

Share this post


Link to post

Thanks.  I was getting the size of a compressed image for one of the tests, so that was one issue.   But still having no success even with defining the pixels manually.

int _width = 1024;
int _height = 1024;
unsigned char* buf = new unsigned char[_width * _height * 4];

int index = 0;
for (int x = 0; x < _width; x++) {
	for (int y = 0; y < _height; y++) {
		buf[index] = 255;
		buf[index + 1] = 255;
		buf[index + 2] = 255;
		buf[index + 3] = 255;
		index+=4;
	}
}


OpenGLShader* shader = (OpenGLShader*)mat->GetShader();

glUseProgram(shader->program);

GLuint _buffer;
glGenTextures(1, &_buffer);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D_ARRAY, _buffer);
glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, _width, _height, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, _width, _height, 1, GL_RGBA, GL_UNSIGNED_BYTE, buf);

glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

 

Share this post


Link to post

Finally got it to work in a dedicated project, but not in my game.  Here's the code that works;

Model* box = Model::Box();
box->Move(0, 1, 0);
Material* mat = Material::Load("Materials\\Test.mat");
box->SetMaterial(mat);


Texture* texture = Texture::Load("Materials\\bluegrid.tex");

int _width = texture->GetWidth();
int _height = texture->GetHeight();
int sze = texture->GetMipmapSize(0);
char* buf = new char[sze];
texture->GetPixels(buf);

OpenGLShader* shader = (OpenGLShader*)mat->GetShader();

glUseProgram(shader->program);

GLuint _buffer;
glGenTextures(1, &_buffer);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D_ARRAY, _buffer);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAX_LEVEL, 0);

glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, _width, _height, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);

glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, _width, _height, 1, GL_RGBA, GL_UNSIGNED_BYTE, buf);

glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

However the exact same code in my game project fails with an access violation at this line in assembly;

0371FCE9  movzx       ebx,byte ptr [esi+2]  

I'm using the same material and shader as the testing project, and am setting things up the same and at the begging in the Start() function after setting up the window, context and camera.  I'm wondering if it could be something setup wrong in the solution settings?  Any ideas?

Share this post


Link to post

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  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.

×
×
  • Create New...