Libgdx custom shader per-vertex attribute - libgdx
After several days of struggling I came here. I'm trying to pass a custom per-vertex vec3 attribute to a custom shader based on this tutorial. The tutorial describes how to pass a custom uniform which actually works fine. However when I'm trying to modify the code to pass my custom per-vertex attribute it seems that nothing is transferred to vertex shader and I can't figure out how to make it to work.
So far I've done the following:
I've created several boxes with modelBuilder.createBox() (so I know for sure every model has 24 vertexes)
Then I'v generated a FloatBuffer containing actual attribute data like this:
int[] data = new int[]{x1, y1, z1, x1, y1, z1, ...}
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(data.length * 4);
byteBuffer.order(ByteOrder.nativeOrder());
mAttributeBuffer = byteBuffer.asFloatBuffer();
mAttributeBuffer.put(data);
mAttributeBuffer.position(0);
Then I'm initializing the corresponding attribute location variable (successfully, a_coord >= 0):
a_coord = program.getAttributeLocation("a_coord");
After that on libgdx side in custom shader's render(Renderable) method I'm passing the buffer to OpenGL like this:
program.setVertexAttribute(a_coord, 3, Gdx.gl20.GL_FLOAT, false, 0, mAttributeBuffer);
My custom vertex shader is as the following:
attribute vec3 a_position;
attribute vec3 a_normal;
attribute vec2 a_texCoord0;
uniform mat4 u_worldTrans;
uniform mat4 u_projTrans;
varying vec2 v_texCoord0;
//my custom attribute
attribute vec2 a_coord;
void main() {
v_texCoord0 = a_texCoord0;
float posY = a_position.y + a_coord.y;
gl_Position = u_projTrans * u_worldTrans * vec4(a_position.x, posY, a_position.z, 1.0);
}
The problem
At the moment a_coord is 0 for every vertex. What am I missing and how to correctly pass custom attribute to vertex shader?
I'm guessing the problem is somewhere in VBO field and the way libGDX passes attribute data to vertexes but I still can't figure out how to make it work.
I'll be glad if anyone can point me in the right direction on this question.
Complete code:
Main AplicationListener class:
public class ProtoGame implements ApplicationListener {
public ProtoGame()
{
super();
}
public PerspectiveCamera cam;
public CameraInputController camController;
public Model model;
public Array<ModelInstance> instances = new Array<ModelInstance>();
public ModelBatch modelBatch;
#Override
public void create () {
cam = new PerspectiveCamera(67, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
cam.position.set(0f, 8f, 8f);
cam.lookAt(0,0,0);
cam.near = 1f;
cam.far = 300f;
cam.update();
camController = new CameraInputController(cam);
Gdx.input.setInputProcessor(camController);
ModelBuilder modelBuilder = new ModelBuilder();
model = modelBuilder.createBox(1f, 1f, 1f,
new Material(),
VertexAttributes.Usage.Position | VertexAttributes.Usage.Normal | VertexAttributes.Usage.TextureCoordinates);
Color colorU = new Color(), colorV = new Color();
for (int x = -5; x <= 5; x+=2) {
for (int z = -5; z<=5; z+=2) {
ModelInstance instance = new ModelInstance(model, x, 0, z);
//this is where I'll put per-vertex attribute data for every instance
//but for now it's hardcoded in the Shader class so the data is the same across instances
TestShader.DoubleColorAttribute attr = new TestShader.DoubleColorAttribute(TestShader.DoubleColorAttribute.DiffuseUV,
colorU.set((x+5f)/10f, 1f - (z+5f)/10f, 0, 1),
colorV.set(1f - (x+5f)/10f, 0, (z+5f)/10f, 1));
instance.materials.get(0).set(attr);
instances.add(instance);
}
}
modelBatch = new ModelBatch(new BaseShaderProvider() {
#Override
protected Shader createShader(Renderable renderable) {
return new TestShader();
}
});
}
#Override
public void render () {
camController.update();
Gdx.gl.glViewport(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
Gdx.gl.glClearColor(1, 1, 1, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT);
modelBatch.begin(cam);
for (ModelInstance instance : instances)
modelBatch.render(instance);
modelBatch.end();
}
#Override
public void dispose () {
model.dispose();
modelBatch.dispose();
}
}
Custom libgdx shader class:
public class TestShader implements Shader {
private FloatBuffer mAttributeBuffer;
ShaderProgram program;
Camera camera;
RenderContext context;
int u_projTrans;
int u_worldTrans;
int u_colorU;
int u_colorV;
int a_coord;
private static String getCustomVertexShader() {
return Gdx.files.internal("shader/test.vertex.glsl").readString();
}
private static String getCustomFragmentShader() {
return Gdx.files.internal("shader/test.fragment.glsl").readString();
}
#Override
public void init() {
program = new ShaderProgram(getCustomVertexShader(), getCustomFragmentShader());
if (!program.isCompiled())
throw new GdxRuntimeException(program.getLog());
//tutorial's logic to init custom uniform locations
u_projTrans = program.getUniformLocation("u_projTrans");
u_worldTrans = program.getUniformLocation("u_worldTrans");
u_colorU = program.getUniformLocation("u_colorU");
u_colorV = program.getUniformLocation("u_colorV");
//initing custom attribute location
a_coord = program.getAttributeLocation("a_coord");
//generating data and passing it to nio Buffer
float data[] = generateData();
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(data.length * 4);
byteBuffer.order(ByteOrder.nativeOrder());
mAttributeBuffer = byteBuffer.asFloatBuffer();
mAttributeBuffer.put(data);
mAttributeBuffer.position(0);
}
private float[] generateData() {
Vector3[] dataArray = new Vector3[1];
dataArray[0] = new Vector3(2, 2, 2);
int components = 3;
int vertexPerModel = 24;
float[] data = new float[dataArray.length * components * vertexPerModel];
for(int i = 0; i < dataArray.length; ++i){
int i3 = i * components;
for(int j = 0; j < vertexPerModel; ++j) {
int j3 = j * components;
data[i3 + 0 + j3] = dataArray[i].x;
data[i3 + 1 + j3] = dataArray[i].y;
data[i3 + 2 + j3] = dataArray[i].z;
}
}
return data;
}
#Override
public void dispose() {
program.dispose();
}
#Override
public void begin(Camera camera, RenderContext context) {
this.camera = camera;
this.context = context;
program.begin();
program.setUniformMatrix(u_projTrans, camera.combined);
context.setDepthTest(GL20.GL_LEQUAL);
context.setCullFace(GL20.GL_BACK);
}
#Override
public void render(Renderable renderable) {
program.setUniformMatrix(u_worldTrans, renderable.worldTransform);
//tutorial's logic to pass uniform
DoubleColorAttribute attribute = ((DoubleColorAttribute) renderable.material.get(DoubleColorAttribute.DiffuseUV));
program.setUniformf(u_colorU, attribute.color1.r, attribute.color1.g, attribute.color1.b);
program.setUniformf(u_colorV, attribute.color2.r, attribute.color2.g, attribute.color2.b);
//passing my custom attributes to the vertex shader
program.setVertexAttribute(a_coord, 3, Gdx.gl20.GL_FLOAT, false, 0, mAttributeBuffer);
renderable.mesh.render(program, renderable.primitiveType,
renderable.meshPartOffset, renderable.meshPartSize);
}
#Override
public void end() {
program.end();
}
#Override
public int compareTo(Shader other) {
return 0;
}
#Override
public boolean canRender(Renderable renderable) {
return renderable.material.has(DoubleColorAttribute.DiffuseUV);
}
}
Finally I was able to pass a custom attribute to vertex shader! Thanks a lot to #Xoppa for pointing me in the right direction.
This is the working solution I've got so far (I'm open for any further advices on how to implement it in a more elegant way):
First of all, as Xoppa stated in the comment it's required to create a model providing custom vertex structure while building it. So model creation may look like this:
VertexAttribute posAttr = new VertexAttribute(VertexAttributes.Usage.Position, 3, ShaderProgram.POSITION_ATTRIBUTE);
...
VertexAttribute customVertexAttr = new VertexAttribute(512, 3, "a_custom");
VertexAttributes vertexAttributes = new VertexAttributes(
posAttr,
...
customVertexAttr);
ModelBuilder modelBuilder = new ModelBuilder();
modelBuilder.begin();
modelBuilder.
part("box", GL20.GL_TRIANGLES, vertexAttributes, new Material()).
box(1f, 1f, 1f);
model = modelBuilder.end();
Or the same with MeshBuilder:
MeshBuilder meshBuilder = new MeshBuilder();
VertexAttributes vertexAttributes = new VertexAttributes(...);
meshBuilder.begin(vertexAttributes);
meshBuilder.part("box", GL20.GL_TRIANGLES);
meshBuilder.setColor(color);
meshBuilder.box(1f, 1f, 1f);
Mesh mesh = meshBuilder.end();
This code will create model with vertices containing additional data according to the provided attributes. It's time to fill the corresponding vertex array. You need a mesh for this - it stores vertices array - a flat array of packed attributes one after another vertex by vertex. So what you need is a number of attributes per vertex as well as an offset for attribute which needs to be modified. Mesh stores all that data:
Mesh mesh = model.meshes.get(0);
int numVertices = mesh.getNumVertices();
// vertex size and offset are in byte so we need to divide it by 4
int vertexSize = mesh.getVertexAttributes().vertexSize / 4;
//it's possible to use usage int here passed previously to VertexAttribute constructor.
VertexAttribute customAttribute = mesh.getVertexAttribute(512)
int offset = customAttribute.offset / 4;
float[] vertices = new float[numVertices * vertexSize];
mesh.getVertices(vertices);
We are ready to pass the data:
List<Vector3> customData ...
for(int i = 0; i < numVertices; ++i){
int index = i * vertexSize + offset;
vertices[index + 0] = customData.get(i).x;
vertices[index + 1] = customData.get(i).y;
vertices[index + 2] = customData.get(i).z;
}
And don't forget to pass the updated vertices array back to the mesh:
mesh.updateVertices(0, vertices);
That's it.
Here's also an implementation of a helper method to create a mix of default attributes using Usage flags alongside with custom attributes:
private VertexAttributes createMixedVertexAttribute(int defaultAtributes, List<VertexAttribute> customAttributes){
VertexAttributes defaultAttributes = MeshBuilder.createAttributes(defaultAtributes);
List<VertexAttribute> attributeList = new ArrayList<VertexAttribute>();
for(VertexAttribute attribute: defaultAttributes){
attributeList.add(attribute);
}
attributeList.addAll(customAttributes);
VertexAttribute[] typeArray = new VertexAttribute[0];
VertexAttributes mixedVertexAttributes = new VertexAttributes(attributeList.toArray(typeArray));
return mixedVertexAttributes;
}
The full source:
public class ProtoGame implements ApplicationListener {
private static final int CUSTOM_ATTRIBUTE_USAGE = 512;
public ProtoGame()
{
super();
}
public PerspectiveCamera cam;
public CameraInputController camController;
public Model model;
public Array<ModelInstance> instances = new Array<ModelInstance>();
public ModelBatch modelBatch;
#Override
public void create () {
cam = new PerspectiveCamera(67, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
cam.position.set(0f, 8f, 8f);
cam.lookAt(0, 0, 0);
cam.near = 1f;
cam.far = 300f;
cam.update();
camController = new CameraInputController(cam);
Gdx.input.setInputProcessor(camController);
Model model = createModelWithCustomAttributes();
Mesh mesh = model.meshes.get(0);
setCustomAttributeData(mesh);
Color colorU = new Color(), colorV = new Color();
for (int x = -5; x <= 5; x+=2) {
for (int z = -5; z<=5; z+=2) {
ModelInstance instance = new ModelInstance(model, x, 0, z);
TestShader.DoubleColorAttribute attr = new TestShader.DoubleColorAttribute(TestShader.DoubleColorAttribute.DiffuseUV,
colorU.set((x+5f)/10f, 1f - (z+5f)/10f, 0, 1),
colorV.set(1f - (x+5f)/10f, 0, (z+5f)/10f, 1));
instance.materials.get(0).set(attr);
instances.add(instance);
}
}
modelBatch = new ModelBatch(new BaseShaderProvider() {
#Override
protected Shader createShader(Renderable renderable) {
return new TestShader();
}
});
}
#Override
public void render () {
camController.update();
Gdx.gl.glViewport(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
Gdx.gl.glClearColor(1, 1, 1, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT);
modelBatch.begin(cam);
for (ModelInstance instance : instances)
modelBatch.render(instance);
modelBatch.end();
}
private Model createModelWithCustomAttributes() {
int defaultAttributes = VertexAttributes.Usage.Position | VertexAttributes.Usage.Normal | VertexAttributes.Usage.TextureCoordinates;
VertexAttribute customVertexAttr = new VertexAttribute(CUSTOM_ATTRIBUTE_USAGE, 3, "a_custom");
List<VertexAttribute> customAttributeList = new ArrayList<VertexAttribute>();
customAttributeList.add(customVertexAttr);
VertexAttributes vertexAttributes = createMixedVertexAttribute(defaultAttributes, customAttributeList);
ModelBuilder modelBuilder = new ModelBuilder();
modelBuilder.begin();
modelBuilder.
part("box", GL20.GL_TRIANGLES, vertexAttributes, new Material()).
box(1f, 1f, 1f);
return modelBuilder.end();
}
private void setCustomAttributeData(Mesh mesh) {
int numVertices = mesh.getNumVertices();
int vertexSize = mesh.getVertexAttributes().vertexSize / 4;
int offset = mesh.getVertexAttribute(CUSTOM_ATTRIBUTE_USAGE).offset / 4;
float[] vertices = new float[numVertices * vertexSize];
mesh.getVertices(vertices);
for(int i = 0; i < numVertices; ++i){
int index = i * vertexSize + offset;
vertices[index + 0] = i;
vertices[index + 1] = i;
vertices[index + 2] = i;
}
mesh.updateVertices(0, vertices);
}
#Override
public void dispose () {
model.dispose();
modelBatch.dispose();
}
private VertexAttributes createMixedVertexAttribute(int defaultAtributes, List<VertexAttribute> customAttributes){
VertexAttributes defaultAttributes = MeshBuilder.createAttributes(defaultAtributes);
List<VertexAttribute> attributeList = new ArrayList<VertexAttribute>();
for(VertexAttribute attribute: defaultAttributes){
attributeList.add(attribute);
}
attributeList.addAll(customAttributes);
VertexAttribute[] typeArray = new VertexAttribute[0];
VertexAttributes mixedVertexAttributes = new VertexAttributes(attributeList.toArray(typeArray));
return mixedVertexAttributes;
}
#Override
public void resize(int width, int height) {
}
#Override
public void pause() {
}
#Override
public void resume() {
}
}
Vertex shader:
attribute vec3 a_position;
attribute vec3 a_normal;
attribute vec2 a_texCoord0;
uniform mat4 u_worldTrans;
uniform mat4 u_projTrans;
varying vec2 v_texCoord0;
attribute vec3 a_custom;
void main() {
v_texCoord0 = a_texCoord0;
float posX = a_position.x + a_custom.x;
float posY = a_position.y + a_custom.y;
float posZ = a_position.z + a_custom.z;
gl_Position = u_projTrans * u_worldTrans * vec4(posX, posY, posZ, 1.0);
}
Fragment shader
#ifdef GL_ES
precision mediump float;
#endif
uniform vec3 u_colorU;
uniform vec3 u_colorV;
varying vec2 v_texCoord0;
void main() {
gl_FragColor = vec4(v_texCoord0.x * u_colorU + v_texCoord0.y * u_colorV, 1.0);
}
Based on Xoppa's comment, I managed to get custom attributes working. It's actually so simple! Here is an ES 3.0 example:
#version 300 es
//v30.glsl
in vec3 vertexPosition;
in vec3 vertexColor;
out vec3 outColor;
void main()
{
outColor = vertexColor;
gl_Position = vec4(vertexPosition, 1.0);
}
fragment shader:
#version 300 es
//f30.glsl
precision mediump float;
in vec3 outColor;
out vec4 fragColor;
void main()
{
fragColor = vec4(outColor, 1.0);
}
Gl30Mesh.java
package com.example.jgles;
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL30;
import com.badlogic.gdx.graphics.Mesh;
import com.badlogic.gdx.graphics.VertexAttribute;
import com.badlogic.gdx.graphics.VertexAttributes;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
public class Gl30Mesh extends ApplicationAdapter
{
private Mesh triangleMesh;
private ShaderProgram passShader;
#Override
public void create()
{
final float [] combinedData = {
-0.8f, -0.8f, 0.0f, 1.0f, 0.0f, 0.0f,
0.8f, -0.8f, 0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.8f, 0.0f, 0.0f, 0.0f, 1.0f
};
VertexAttribute vertexChannel = new VertexAttribute(VertexAttributes.Usage.Generic, 3, "vertexPosition");
VertexAttribute colorChannel = new VertexAttribute(VertexAttributes.Usage.Generic, 3, "vertexColor");
String vertexShader = Gdx.files.internal("v30.glsl").readString();
String fragmentShader = Gdx.files.internal("f30.glsl").readString();
passShader = new ShaderProgram(vertexShader, fragmentShader);
if (!passShader.isCompiled()){
throw new IllegalStateException(passShader.getLog());
}
passShader.bind();
triangleMesh = new Mesh(true, 3, 0, vertexChannel, colorChannel);
triangleMesh.setVertices(combinedData);
}
#Override
public void render()
{
Gdx.gl.glViewport(0, 0, 640, 480);
Gdx.gl.glClearColor(0, 0, 1.0f, 0);
Gdx.gl.glClear(GL30.GL_COLOR_BUFFER_BIT);
triangleMesh.render(passShader, GL30.GL_TRIANGLES);
}
}
It seems to me when you are already using custom shaders, you use whatever is named in your shaders instead of POSITION_ATTRIBUTE, NORMAL_ATTRIBUTE etc. And as Xoppa said, ShaderProgram.setVertexAttribute simply won't work.
Actually it is possible to call setVertexAttributes directly and have it work without needing a mesh. Just need to extend ShaderProgram like so
public class ShaderProgramExtended extends ShaderProgram {
public ShaderProgramExtended(String v, String f){
super(v,f);
}
/*
This is VERY NAUGHTY. Mario and Nathan probably made it private for a reason
*/
public int getProgram(){
int result;
try{
Field field = ShaderProgram.class.getDeclaredField("program");
field.setAccessible(true);
Object value = field.get(this);
field.setAccessible(false);
if (value == null) {
result = 0;
}else{
result = (Integer) value;
}
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
return result;
}
public void begin(int program){
Gdx.gl20.glUseProgram(program);
}
public void draw(int mode, int first, int count){
Gdx.gl20.glDrawArrays(mode,first,count);
}
}
And then call it as you would normally with the "minor" change of sending the shader object integer to the new begin method
public class TestlibGDXv2 extends ApplicationAdapter {
private final String tag = (this).getClass().getSimpleName();
String message = "";
private static final int FLOAT_BYTES = 4;
FloatBuffer vertexData;
FloatBuffer colorData;
ShaderProgramExtended shader;
private static final String COLOR_ATTRIBUTE = ShaderProgram.COLOR_ATTRIBUTE;
private int aColourLocation;
private static final String POSITION_ATTRIBUTE = ShaderProgram.POSITION_ATTRIBUTE;
private int aPositionLocation;
/*
Anti-clockwise winding order. Note, we could share two of the vertices. Haven't for clarity.
*/
float[] vertices = {
-0.5f, -0.5f,
0.5f, -0.5f,
-0.5f, 0.5f,
-0.5f, 0.5f,
0.5f, -0.5f,
0.5f, 0.5f
};
/*
Need to colour each vertex, so need 6.
*/
float[] colors = {1.0f, 0.0f, 0.0f, 1.0f,
0.0f,1.0f,0.0f,1.0f,
0.0f,0.0f,1.0f,1.0f,
0.0f,0.0f,1.0f,1.0f,
0.0f,1.0f,0.0f,1.0f,
1.0f,0.0f,0.0f,1.0f
};
#Override
public void create() {
/*
Convert from Dalvik VM to OpenGL native
*/
vertexData = ByteBuffer.allocateDirect(vertices.length * FLOAT_BYTES)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
colorData = ByteBuffer.allocateDirect(colors.length * FLOAT_BYTES)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
vertexData.put(vertices).position(0);
colorData.put(colors).position(0);
initialiseShaders();
}
#Override
public void render() {
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
shader.begin(shader.getProgram());
shader.setVertexAttribute(aPositionLocation, 2, GL20.GL_FLOAT, false, 0, vertexData);
shader.enableVertexAttribute(aPositionLocation);
shader.setVertexAttribute(aColourLocation, 4, GL20.GL_FLOAT, false, 0, colorData);
shader.enableVertexAttribute(aColourLocation);
shader.draw(GL20.GL_TRIANGLES, 0, 6);
shader.end();
}
private void initialiseShaders() {
String vertexShaderSource =
"#version 130\n" +
"attribute vec4 " + POSITION_ATTRIBUTE + ";\n" + // x,y,z and w
"attribute vec4 " + COLOR_ATTRIBUTE + ";\n" + // r,g,b and a
"varying vec4 v_color;\n" + // pass to fragment shader
"void main(){\n" +
" v_color = "+ COLOR_ATTRIBUTE + ";\n" +
" gl_Position = " + POSITION_ATTRIBUTE + ";\n" +
"}";
String fragmentShaderSource =
"#version 130\n" +
"#ifdef GL_ES\n" +
" precision mediump float;\n" + // medium a good balance between speed and quality
"#endif\n" +
"varying vec4 v_color;\n" + // incoming from vertex shader
"void main(){\n" +
" gl_FragColor = v_color;\n" +
"}";
shader = new ShaderProgramExtended(vertexShaderSource,fragmentShaderSource);
aPositionLocation = shader.getAttributeLocation(POSITION_ATTRIBUTE);
aColourLocation = shader.getAttributeLocation(COLOR_ATTRIBUTE);
}
}
The attribute "a_color" (from ShaderProgram.COLOR_ATTRIBUTE) could just as easily have been "a_custom".
I used this method when I was trying to learn OpenGL ES from WebGL books - there are a lot more of them than libGDX OpenGL books. However, only use the above for learning, the shader is now no longer being managed by libGDX and so will not work nicely on Android when context is lost. OK for desktop, though.
Cheers
John
Related
libgdx isometric tiled map screen to world with viewport
I wanted to create a simple 30x30 isometric tiled map and add a listener to be able to click on the tiles. I looked up a lot of the articles and posts here but none of them helped me. My issue is, i have a viewport, 30x17, a camera, a stage and a tiled map with tileWidth = 32 and tileHeight = 16 pixels. Now when i render the tiled map it looks fine. When i click on stage and try to get the world coordinates i see some really weird coordinates. This is the code: private static final float TILE_WIDTH = 32; private static final float TILE_HEIGHT = 16; private OrthographicCamera camera; private Viewport viewport; private Stage stage; private IsometricTiledMapRenderer isometricTiledMapRenderer; private Matrix4 isoTransform; private Matrix4 invIsotransform; public void load(AssetManagerLoaderV2 assetManagerLoader) { assetManagerLoader.load(); init(); camera = new OrthographicCamera(); viewport = new FitViewport(30, 17, camera); stage = new Stage(viewport); TiledMap tiledMap = new TiledMapGenerator(assetManagerLoader).generate(30, 30); isometricTiledMapRenderer = new IsometricTiledMapRenderer(tiledMap, 1/32f); stage.addListener(new InputListener() { #Override public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) { System.out.println(screenToCell(x, y)); return true; } }); } #Override public void show() { Gdx.input.setInputProcessor(stage); } #Override public void render(float delta) { DrawUtils.clearScreen(); viewport.apply(); isometricTiledMapRenderer.setView(camera); isometricTiledMapRenderer.render(); } #Override public void resize(int width, int height) { stage.getViewport().update(width, height, true); } #Override public void pause() { } #Override public void resume() { } #Override public void hide() { } #Override public void dispose() { isometricTiledMapRenderer.dispose(); stage.dispose(); } public void init () { //create the isometric transform isoTransform = new Matrix4(); isoTransform.idt(); isoTransform.translate(0.0f, 0.25f, 0.0f); isoTransform.scale((float)(Math.sqrt(2.0) / 2.0), (float)(Math.sqrt(2.0) / 4.0), 1.0f); isoTransform.rotate(0.0f, 0.0f, 1.0f, -45.0f); //... and the inverse matrix invIsotransform = new Matrix4(isoTransform); invIsotransform.inv(); } public Vector2 worldToCell(float x, float y) { float halfTileWidth = TILE_WIDTH * 0.5f; float halfTileHeight = TILE_HEIGHT * 0.5f; float row = (1.0f/2) * (x/halfTileWidth + y/halfTileHeight); float col = (1.0f/2) * (x/halfTileWidth - y/halfTileHeight); return new Vector2((int)col,(int)row); } public Vector2 screenToWorld(float x, float y){ Vector3 touch = new Vector3(x,y,0); camera.unproject(touch); touch.mul(invIsotransform); touch.mul(isoTransform); return new Vector2(touch.x,touch.y); } public Vector2 screenToCell(float x, float y) { Vector2 world = screenToWorld(x,y); world.y -= TILE_HEIGHT *0.5f; return worldToCell(world.x,world.y); } Does anyone have an idea how to write worldToCell to get the proper coordinates?
I found the issue, i was missing one step. This is what touchDown should look like: #Override public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) { Vector2 newCoords = stage.stageToScreenCoordinates(new Vector2(x, y)); System.out.println(screenToCell(newCoords.x, newCoords.y)); return true; } I need to convert stage coordinates to screen coordinates, now this code is working.
BitmapFont animation in Libgdx scene2d Actor
I am trying to animate an actor which contain a simple message written with bitmapfont using fadeIn action but it doesn't work. Here is my code. public class Message extends Actor{ BitmapFont font; String message; float x, y; public Message(BitmapFont font, String message, float x, float y) { this.font = font; this.message = message; this.x = x; this.y = y; } #Override public void draw(Batch batch, float parentAlpha) { Color color = getColor(); batch.setColor(color.r, color.g, color.b, color.a * parentAlpha); font.setColor(color.r, color.g, color.b, color.a * parentAlpha); font.draw(batch, message, x, y); } } Then when clicking a button the message should appear but it is only appear without animation. button.addListener(new ClickListener() { #Override public void clicked(InputEvent event, float x, float y) { BitmapFont font = new BitmapFont(Gdx.files.internal("myfont.font")); Message message = new Message(font, "Tap Here", 100, 20); stage.addActor(message); message.addAction(Actions.fadeIn(5)); }}); Other actors animations on the same stage work fine as I'm calling: stage.act(delta); stage.draw();
You should set message actor's alpha to zero before adding the fadeIn action: button.addListener(new ClickListener() { #Override public void clicked(InputEvent event, float x, float y) { BitmapFont font = new BitmapFont(Gdx.files.internal("myfont.font")); Message message = new Message(font, "Tap Here", 100, 20); message.getColor().a = 0f; // add this line stage.addActor(message); message.addAction(Actions.fadeIn(5)); }});
LibGDX - Distorted rendered 3d model
I have a 3d model exported from 3ds Max and converted to g3db format. The rendered model looks different on two different phones. When i run the app on Micromax Unite 2, the rendered view is sharp and exact. But for Nexus 5 or Emulator it is distorted.. Snapshot of Nexus 5 and Micromax Unite 2: http://i.stack.imgur.com/Fyy7j.jpg I have followed and implemented the code from: http://xoppa.github.io/blog/interacting-with-3d-objects/ MyCode AndroidLauncher.java public class AndroidLauncher extends AndroidApplication { #Override protected void onCreate (Bundle savedInstanceState) { super.onCreate(savedInstanceState); AndroidApplicationConfiguration config = new AndroidApplicationConfiguration(); config.useGLSurfaceView20API18 = true; config.numSamples = 4; initialize(new Viewer3D); } } Viewer3d.java #Override public void create () { stage = new Stage(); font = new BitmapFont(); label = new Label(" ", new Label.LabelStyle(font, Color.WHITE)); stage.addActor(label); stringBuilder = new StringBuilder(); modelBatch = new ModelBatch(); environment = new Environment(); environment.set(new ColorAttribute(ColorAttribute.AmbientLight, 0.4f, 0.4f, 0.4f, 1f)); environment.add(new DirectionalLight().set(0.8f, 0.8f, 0.8f, 100,1000,300)); cam = new PerspectiveCamera(67, Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); cam.position.set((float) (maxX/2f+radius * Math.sin(finalAngle)), finalY, (float) (maxZ/2f+radius * Math.cos(finalAngle))); cam.lookAt(centerX, 1500, centerY); cam.up.set(Vector3.Y); cam.near = 1; cam.far = 7000; cam.update(); camController = new CameraInputController(cam); Gdx.input.setInputProcessor(new InputMultiplexer(this, camController)); Arrays.fill(usedIndexes, false); assets = new AssetManager(); assets.load("models" + "/final.g3db", Model.class);// loading = true; } #Override public void render () { if (loading && assets.update()) doneLoading(); camController.update(); Gdx.gl.glViewport(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT); modelBatch.begin(cam); visibleCount = 0; for (final GameObject instance : instances) { if (isVisible(cam, instance)) { modelBatch.render(instance, environment); visibleCount++; } } if (Sky != null) modelBatch.render(Sky); modelBatch.end(); stage.draw(); } /* updaing my camera position such that it moves in a circle in XZ-plane */ private void updateCamera() { float x = (float) (centerX + radius * Math.sin(finalAngle)); float z = (float) (centerY + radius * Math.cos(finalAngle)); // Constrained camera posX > 0 posZ > 0 cam.position.set(x < 0 ? 0 : x , 3000, z < 0 ? 0 : z); cam.lookAt(centerX, 1500, centerY); cam.up.set(Vector3.Y); cam.update(); camController = new CameraInputController(cam); Gdx.input.setInputProcessor(new InputMultiplexer(this, camController)); } I have tried: 1. config.numSamples = 8; 2. config.immersiveMode = true But nothing worked ! :( Kindly help.
Libgdx collision using box2d working for desktop but failed to collide in Android emulator
My code for collision of a dynamic body to a static body works perfectly for desktop , but when running in Android emulator it can not able to detect collision ,Dynamic body goes down without collision. I used Libgdx version : 1.5.0 . Code : package com.kg.game; import java.util.Random; import aurelienribon.tweenengine.BaseTween; import aurelienribon.tweenengine.Tween; import aurelienribon.tweenengine.TweenCallback; import aurelienribon.tweenengine.TweenManager; import com.badlogic.gdx.ApplicationAdapter; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.InputAdapter; import com.badlogic.gdx.InputProcessor; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.OrthographicCamera; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.Texture.TextureFilter; import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.Sprite; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.physics.box2d.Body; import com.badlogic.gdx.physics.box2d.BodyDef; import com.badlogic.gdx.physics.box2d.BodyDef.BodyType; import com.badlogic.gdx.physics.box2d.Box2DDebugRenderer; import com.badlogic.gdx.physics.box2d.CircleShape; import com.badlogic.gdx.physics.box2d.FixtureDef; import com.badlogic.gdx.physics.box2d.PolygonShape; import com.badlogic.gdx.physics.box2d.World; public class MyGdxGame extends ApplicationAdapter implements InputProcessor { private static final float VIEWPORT_WIDTH = 10; private static final float BALL_RADIUS = 0.15f; private static final int MAX_BALLS = 200; private World world; private Body[] ballModels; private Texture bottleTexture; private Texture ballTexture; private Sprite[] ballSprites; private Texture whiteTexture; private Sprite groundSprite; private SpriteBatch batch; private BitmapFont font; private OrthographicCamera camera; private final Random rand = new Random(); float w; float h; Box2DDebugRenderer debugRenderer; private final TweenManager tweenManager = new TweenManager(); #Override public void create() { debugRenderer = new Box2DDebugRenderer(); world = new World(new Vector2(0, -10), true); createGround(); createBalls(); batch = new SpriteBatch(); font = new BitmapFont(); font.setColor(Color.BLACK); w = Gdx.graphics.getWidth(); h = Gdx.graphics.getHeight(); camera = new OrthographicCamera(VIEWPORT_WIDTH, VIEWPORT_WIDTH * h / w); camera.position.set(0, (VIEWPORT_WIDTH * h / w) / 2, 0); camera.update(); createSprites(); Gdx.input.setInputProcessor(new InputAdapter() { #Override public boolean touchDown(int x, int y, int pointer, int button) { restart(); return true; } }); restart(); } private void createGround() { BodyDef bd = new BodyDef(); bd.position.set(0, 0); bd.type = BodyType.StaticBody; PolygonShape shape = new PolygonShape(); shape.setAsBox(100, 4); FixtureDef fd = new FixtureDef(); fd.density = 1; fd.friction = 0.5f; fd.restitution = 0.5f; fd.shape = shape; world.createBody(bd).createFixture(fd); shape.dispose(); } private void createBalls() { BodyDef ballBodyDef = new BodyDef(); ballBodyDef.type = BodyType.DynamicBody; CircleShape shape = new CircleShape(); shape.setRadius(BALL_RADIUS); FixtureDef fd = new FixtureDef(); fd.density = 1; fd.friction = 0.5f; fd.restitution = 0.5f; fd.shape = shape; ballModels = new Body[MAX_BALLS]; for (int i = 0; i < MAX_BALLS; i++) { ballModels[i] = world.createBody(ballBodyDef); ballModels[i].createFixture(fd); } shape.dispose(); } private void createSprites() { ballTexture = new Texture(Gdx.files.internal("ball.png")); ballTexture.setFilter(TextureFilter.Linear, TextureFilter.Linear); ballSprites = new Sprite[MAX_BALLS]; for (int i = 0; i < MAX_BALLS; i++) { ballSprites[i] = new Sprite(ballTexture); ballSprites[i].setSize(BALL_RADIUS * 2, BALL_RADIUS * 2); ballSprites[i].setOrigin(BALL_RADIUS, BALL_RADIUS); } whiteTexture = new Texture(Gdx.files.internal("ground.png")); groundSprite = new Sprite(whiteTexture); groundSprite.setSize(100, 4); groundSprite.setPosition(-VIEWPORT_WIDTH / 2, 0); groundSprite.setColor(Color.BLACK); } private float elapsed = 0; #Override public void render() { tweenManager.update(1 / 60f); world.step(1 / 60f, 10, 10); debugRenderer.render(world, camera.combined); float w = Gdx.graphics.getWidth(); float h = Gdx.graphics.getHeight(); GL20 gl = Gdx.gl20; gl.glClearColor(1, 1, 1, 1); gl.glClear(GL20.GL_COLOR_BUFFER_BIT); // Update for (int i = 0; i < MAX_BALLS; i++) { Vector2 ballPos = ballModels[i].getPosition(); ballSprites[i].setPosition(ballPos.x - ballSprites[i].getWidth() / 2, ballPos.y - ballSprites[i].getHeight() / 2); ballSprites[i].setRotation(ballModels[i].getAngle() * MathUtils.radiansToDegrees); } // Render batch.setProjectionMatrix(camera.combined); batch.begin(); groundSprite.draw(batch); for (int i = 0; i < MAX_BALLS; i++) ballSprites[i].draw(batch); batch.end(); // batch.getProjectionMatrix().setToOrtho2D(0, 0, w, h); batch.begin(); font.draw(batch, "Touch the screen to restart", 5, h - 5); batch.end(); } #Override public void dispose() { bottleTexture.dispose(); ballTexture.dispose(); batch.dispose(); font.dispose(); world.dispose(); } #Override public boolean keyDown(int keycode) { return false; } #Override public boolean keyUp(int keycode) { return true; } #Override public boolean keyTyped(char character) { return false; } #Override public boolean touchDown(int screenX, int screenY, int pointer, int button) { return true; } #Override public boolean touchUp(int screenX, int screenY, int pointer, int button) { return false; } #Override public boolean touchDragged(int screenX, int screenY, int pointer) { return false; } #Override public boolean mouseMoved(int screenX, int screenY) { return false; } #Override public boolean scrolled(int amount) { return false; } private void restart() { Vector2 vec = new Vector2(); for (int i = 0; i < MAX_BALLS; i++) { float tx = rand.nextFloat() * 1.0f - 0.5f; float ty = VIEWPORT_WIDTH * h / w; float angle = rand.nextFloat() * MathUtils.PI * 2; ballModels[i].setActive(false); ballModels[i].setLinearVelocity(vec.set(0, 0)); ballModels[i].setAngularVelocity(0); ballModels[i].setTransform(vec.set(tx, ty), angle); } tweenManager.killAll(); Tween.call(new TweenCallback() { private int idx = 0; #Override public void onEvent(int type, BaseTween<?> source) { if (idx < ballModels.length) { ballModels[idx].setAwake(true); ballModels[idx].setActive(true); idx += 1; } } }).repeat(-1, 0.1f).start(tweenManager); } } Screensots :
this is an idea, looking at the proportions of your images, I think the error may be out there, I would look at these lines: groundSprite.setSize(100, 4); //<--especially here groundSprite.setPosition(-VIEWPORT_WIDTH / 2, 0); //<--especially here . private void createGround() { BodyDef bd = new BodyDef(); bd.position.set(0, 0); //<--especially here bd.type = BodyType.StaticBody; PolygonShape shape = new PolygonShape(); shape.setAsBox(100, 4); //<--especially here especially the position and size, if it is standing in the same place, with the change of screen "device". You tried to render debug? I see out there, if so, you can see your object in the emulated device? I hope I understand, but not if that be the error
Trying To Render Scene To FBO Returns White
So this is what I'm trying to do. I'm trying to make a 2d game using LWJGL that has a dynamically generated terrain using images as tiles. It works fine, but when I try to do a zoom, the tiles get a dark shade on the edges and I get an extra pixel in-between tiles. Here's a screenshot of before and after zoom: Before and After Zoom Screenshot: http://postimage.org/image/rhuc9744/ I've looked and looked, and from what little I've gathered on the internet, I think the gist is that zoom is causing pixel-precision issues which gets worse after blending. I saw a blog saying I can overlap some pixels between tiles, but it seems too complicated. I've tried all blending options, and none of them worked. So I figured, I should just render all the tiles into a buffer, an FBO, and then apply it as one big texture, so even when I zoom, I won't see dark edges because it would all be just one giant picture. So I've read a lot of tutorials on FBO, but most if not all of them are meant for 3D, which I actually got working. The problem is when I apply it to 2d ortho. The code breaks and all I get is a white box. I've been stuck on this problem for days, and I can't seem to find an answer using google. Here's a sample code I'm t trying to get working: package com.helgravis; import static org.lwjgl.opengl.EXTFramebufferObject.GL_COLOR_ATTACHMENT0_EXT; import static org.lwjgl.opengl.EXTFramebufferObject.GL_DEPTH_ATTACHMENT_EXT; import static org.lwjgl.opengl.EXTFramebufferObject.GL_FRAMEBUFFER_EXT; import static org.lwjgl.opengl.EXTFramebufferObject.GL_RENDERBUFFER_EXT; import static org.lwjgl.opengl.EXTFramebufferObject.glBindFramebufferEXT; import static org.lwjgl.opengl.EXTFramebufferObject.glBindRenderbufferEXT; import static org.lwjgl.opengl.EXTFramebufferObject.glFramebufferRenderbufferEXT; import static org.lwjgl.opengl.EXTFramebufferObject.glFramebufferTexture2DEXT; import static org.lwjgl.opengl.EXTFramebufferObject.glGenFramebuffersEXT; import static org.lwjgl.opengl.EXTFramebufferObject.glGenRenderbuffersEXT; import static org.lwjgl.opengl.EXTFramebufferObject.glRenderbufferStorageEXT; import static org.lwjgl.opengl.GL11.GL_INT; import static org.lwjgl.opengl.GL11.GL_RGBA; import static org.lwjgl.opengl.GL11.GL_RGBA8; import static org.lwjgl.opengl.GL11.GL_TEXTURE_2D; import static org.lwjgl.opengl.GL11.glBindTexture; import static org.lwjgl.opengl.GL11.glGenTextures; import static org.lwjgl.opengl.GL11.glTexImage2D; import java.awt.Color; import java.awt.Component; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.MediaTracker; import java.awt.color.ColorSpace; import java.awt.image.BufferedImage; import java.awt.image.ComponentColorModel; import java.awt.image.DataBuffer; import java.awt.image.DataBufferByte; import java.awt.image.Raster; import java.awt.image.WritableRaster; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.IntBuffer; import java.util.Hashtable; import javax.swing.JPanel; import org.lwjgl.LWJGLException; import org.lwjgl.input.Keyboard; import org.lwjgl.opengl.Display; import org.lwjgl.opengl.DisplayMode; import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL14; public class AnFBOExample { private int width, height, canvasWidth = 800, canvasHeight = 600; private String title; private boolean bFullscreen; public int FRAMERATE = 60; public int framebufferID; public int framebufferTextureID, spriteTextureID; public int depthRenderBufferID; public int fboWidth = 100, fboHeight = 100; public AnFBOExample() { bFullscreen = false; } public AnFBOExample(boolean bFullscreen) { this.bFullscreen = bFullscreen; } public void setTitle(String title) { this.title = title; if(Display.isCreated()) Display.setTitle(title); } public String getTitle() { return title; } public void setResolution(int x, int y) { width = x; height = y; } public void setCanvasSize(int x, int y) { canvasWidth = x; canvasHeight = y; } private boolean initDisplayMode() throws Exception { if(bFullscreen) Display.setFullscreen(true); try { DisplayMode[] dm = org.lwjgl.util.Display.getAvailableDisplayModes(width, height, -1, -1, -1, -1, 60, 60); org.lwjgl.util.Display.setDisplayMode(dm, new String[] { "width=" + width, "height=" + height, "freq=" + FRAMERATE, "bpp="+ org.lwjgl.opengl.Display.getDisplayMode().getBitsPerPixel() } ); return true; } catch(Exception e) { e.printStackTrace(); System.out.println("Unable to enter fullscreen, continuing in windowed mode"); } return false; } public void init() throws Exception { try { initDisplayMode(); Display.create(); GL11.glEnable(GL11.GL_BLEND); GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); GL11.glEnable(GL11.GL_ALPHA_TEST); GL11.glAlphaFunc(GL11.GL_GREATER, 0.5f); GL11.glDisable(GL11.GL_DEPTH_TEST); GL11.glMatrixMode(GL11.GL_PROJECTION); GL11.glLoadIdentity(); GL11.glOrtho(0, canvasWidth, canvasHeight, 0, -1, 1); int framebufferID = glGenFramebuffersEXT(); int colorTextureID = glGenTextures(); int depthRenderBufferID = glGenRenderbuffersEXT(); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, framebufferID); glBindTexture(GL_TEXTURE_2D, colorTextureID); GL11.glTexParameteri(GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, fboWidth, fboHeight, 0, GL_RGBA, GL_INT, (java.nio.ByteBuffer) null); glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,GL_COLOR_ATTACHMENT0_EXT,GL_TEXTURE_2D, colorTextureID, 0); glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, depthRenderBufferID); glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL14.GL_DEPTH_COMPONENT24, fboWidth, fboHeight); glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT,GL_DEPTH_ATTACHMENT_EXT,GL_RENDERBUFFER_EXT, depthRenderBufferID); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); BufferedImage image = loadImage("resources/lamp.png"); spriteTextureID = getTexture(image); } catch(LWJGLException le) { le.printStackTrace(); } } public void draw() { glBindTexture(GL_TEXTURE_2D, 0); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, framebufferID); GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT); GL11.glPushMatrix(); GL11.glEnable(GL11.GL_TEXTURE_2D); GL11.glBindTexture(GL11.GL_TEXTURE_2D, spriteTextureID); GL11.glBegin(GL11.GL_QUADS); { GL11.glTexCoord2f(0, 0); GL11.glVertex2f(0, 0); GL11.glTexCoord2f(0, 1f); GL11.glVertex2f(0, 50f); GL11.glTexCoord2f(1f, 1f); GL11.glVertex2f(50f, 50f); GL11.glTexCoord2f(1f, 0); GL11.glVertex2f(50f, 0); } GL11.glEnd(); GL11.glPopMatrix(); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT); GL11.glPushMatrix(); GL11.glEnable(GL11.GL_TEXTURE_2D); GL11.glBindTexture(GL11.GL_TEXTURE_2D, framebufferTextureID); GL11.glTranslatef(100f, 100f, 0); GL11.glBegin(GL11.GL_QUADS); { GL11.glTexCoord2f(0, 0); GL11.glVertex2f(0, 0); GL11.glTexCoord2f(0, 1f); GL11.glVertex2f(0, 100f); GL11.glTexCoord2f(1f, 1f); GL11.glVertex2f(100f, 100f); GL11.glTexCoord2f(1f, 0); GL11.glVertex2f(100f, 0); } GL11.glEnd(); GL11.glPopMatrix(); Display.update(); Display.sync(FRAMERATE); } public void draw2() { // glBindTexture(GL_TEXTURE_2D, 0); // glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, framebufferID); // glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT); GL11.glPushMatrix(); GL11.glEnable(GL11.GL_TEXTURE_2D); GL11.glBindTexture(GL11.GL_TEXTURE_2D, spriteTextureID); GL11.glBegin(GL11.GL_QUADS); { GL11.glTexCoord2f(0, 0); GL11.glVertex2f(0, 0); GL11.glTexCoord2f(0, 1f); GL11.glVertex2f(0, 50f); GL11.glTexCoord2f(1f, 1f); GL11.glVertex2f(50f, 50f); GL11.glTexCoord2f(1f, 0); GL11.glVertex2f(50f, 0); } GL11.glEnd(); GL11.glPopMatrix(); // glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); // GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT); // GL11.glPushMatrix(); // GL11.glEnable(GL11.GL_TEXTURE_2D); // GL11.glBindTexture(GL11.GL_TEXTURE_2D, framebufferTextureID); // GL11.glTranslatef(100f, 100f, 0); // GL11.glBegin(GL11.GL_QUADS); // { // GL11.glTexCoord2f(0, 0); // GL11.glVertex2f(0, 0); // GL11.glTexCoord2f(0, 1f); // GL11.glVertex2f(0, 100f); // GL11.glTexCoord2f(1f, 1f); // GL11.glVertex2f(100f, 100f); // GL11.glTexCoord2f(1f, 0); // GL11.glVertex2f(100f, 0); // } // GL11.glEnd(); // GL11.glPopMatrix(); // // Display.update(); // Display.sync(FRAMERATE); } public void cleanup() { Display.destroy(); } public void run() { while(!Thread.interrupted()) { progress(); handleEvents(); draw(); //draw2(); } } public void handleEvents() { while(Keyboard.next()) if(Keyboard.getEventKey() == Keyboard.KEY_ESCAPE) quit(); if(Display.isCloseRequested()) quit(); } public void quit() { System.exit(0); } public void progress() { //Add game logic here } protected static boolean waitForImage(Image image, Component c) { Image[] images = new Image[1]; images[0] = image; return waitForImages(images, c); } protected static boolean waitForImages(Image[] images, Component c) { MediaTracker tracker = new MediaTracker(c); for(int i=0; i<images.length; i++) tracker.addImage(images[i], 0); try { tracker.waitForAll(); } catch(InterruptedException ie) {} return !tracker.isErrorAny(); } public static BufferedImage loadImage(String imageFile) throws Exception { Image image = null; JPanel buffer = new JPanel(); image = buffer.getToolkit().getImage(imageFile); waitForImage(image, buffer); int width = image.getWidth(null); int height = image.getHeight(null); BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); Graphics2D g2d = bufferedImage.createGraphics(); g2d.drawImage(image, 0, 0, width, height, buffer); g2d.dispose(); return bufferedImage; } public int getTexture(BufferedImage image) throws Exception { return getTexture(image, GL11.GL_TEXTURE_2D, GL11.GL_RGBA, GL11.GL_LINEAR, GL11.GL_LINEAR); } public int getTexture(String resourceName, int target, int dstPixelFormat, int minFilter, int magFilter) throws Exception { BufferedImage bufferedImage = loadImage(resourceName); return getTexture(bufferedImage, target, dstPixelFormat, minFilter, magFilter); } private int createTextureID() { ByteBuffer temp = ByteBuffer.allocateDirect(4 * 1); temp.order(ByteOrder.nativeOrder()); IntBuffer tmp = temp.asIntBuffer(); GL11.glGenTextures(tmp); return tmp.get(0); } public int getTexture(BufferedImage bufferedImage, int target, int dstPixelFormat, int minFilter, int magFilter) throws Exception { int srcPixelFormat = 0; int textureID = createTextureID(); GL11.glBindTexture(target, textureID); if(bufferedImage.getColorModel().hasAlpha()) srcPixelFormat = GL11.GL_RGBA; else srcPixelFormat = GL11.GL_RGB; ByteBuffer textureBuffer = convertImageData(bufferedImage); if(target == GL11.GL_TEXTURE_2D) { GL11.glTexParameteri(target, GL11.GL_TEXTURE_MIN_FILTER, minFilter); GL11.glTexParameteri(target, GL11.GL_TEXTURE_MAG_FILTER, magFilter); } GL11.glTexImage2D(target, 0, dstPixelFormat, get2Fold(bufferedImage.getWidth()), get2Fold(bufferedImage.getHeight()), 0, srcPixelFormat, GL11.GL_UNSIGNED_BYTE, textureBuffer); return textureID; } private int get2Fold(int fold) { int ret = 2; while (ret < fold) ret *= 2; return ret; } #SuppressWarnings("rawtypes") private ByteBuffer convertImageData(BufferedImage bufferedImage) { ComponentColorModel glAlphaColorModel = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[] {8,8,8,8}, true, false, ComponentColorModel.TRANSLUCENT, DataBuffer.TYPE_BYTE); ComponentColorModel glColorModel = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[] {8,8,8,0}, false, false, ComponentColorModel.OPAQUE, DataBuffer.TYPE_BYTE); ByteBuffer imageBuffer = null; WritableRaster raster; BufferedImage texImage; int texWidth = 2; int texHeight = 2; while (texWidth < bufferedImage.getWidth()) texWidth *= 2; while (texHeight < bufferedImage.getHeight()) texHeight *= 2; if(bufferedImage.getColorModel().hasAlpha()) { raster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, texWidth, texHeight, 4, null); texImage = new BufferedImage(glAlphaColorModel, raster, false, new Hashtable()); } else { raster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, texWidth, texHeight, 3, null); texImage = new BufferedImage(glColorModel, raster, false, new Hashtable()); } Graphics g = texImage.getGraphics(); g.setColor(new Color(0f,0f,0f,0f)); g.fillRect(0,0,texWidth,texHeight); g.drawImage(bufferedImage,0,0,null); byte[] data = ((DataBufferByte) texImage.getRaster().getDataBuffer()).getData(); imageBuffer = ByteBuffer.allocateDirect(data.length); imageBuffer.order(ByteOrder.nativeOrder()); imageBuffer.put(data, 0, data.length); imageBuffer.flip(); return imageBuffer; } public static void main(String args[]) throws Exception { AnFBOExample window = new AnFBOExample(false); window.setResolution(800, 600); window.setCanvasSize(800, 600); window.init(); window.setTitle("FBO Test"); window.run(); } } The code loads an image in resource/lamp.png, which can be any .png file, and tries to render it to an FBO, then applies it as a texture in a 2d quad. For some reason, I only get a white blank quad when I try to bind the FBO as a texture. I'm not sure if I'm not rendering to the FBO properly, or I'm not binding it correctly. You can check the method draw() so you know what I'm talking about. Like I said, I've been stuck in this problem for day, so any help would be very much appreciated. resources/lamp.png: http://s3.postimage.org/rhpdn5ms/lamp.png?noCache=1315464566
framebufferTextureID, shouldn't it be colourTextureID?