You can’t, to the best of my knowledge, render to a non-rectangular texture, but you can render to rectangular textures then render those textures on triangular meshes.
I put together a few things to get you going here. First we have the Geometry
/Mesh
which creates a plane comprised of 4 triangles with different vertex colors assigned to each triangle so they can be distinguished between one another in the accompanying shader.
import com.jme3.math.ColorRGBA;
import com.jme3.scene.Mesh;
import com.jme3.scene.VertexBuffer;
import com.jme3.scene.Geometry;
import com.jme3.util.BufferUtils;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;
/**
*
* @author Adam T. Ryder http://1337atr.weebly.com
*/
public class TriDisplay extends Geometry {
public TriDisplay(String name, final float width, final float height) {
super(name);
setMesh(new TriMesh(width, height));
}
private class TriMesh extends Mesh {
private TriMesh(final float width, final float height) {
updateGeometry(width, height);
}
public void updateGeometry(final float width, final float height) {
FloatBuffer verts = BufferUtils.createVector3Buffer(12);
FloatBuffer tex = BufferUtils.createVector2Buffer(12);
ByteBuffer col = BufferUtils.createByteBuffer(48);
ShortBuffer indices = BufferUtils.createShortBuffer(12);
int blue = new ColorRGBA(0, 0, 1, 0).asIntABGR();
int green = new ColorRGBA(0, 1, 0, 0).asIntABGR();
int red = new ColorRGBA(1, 0, 0, 0).asIntABGR();
int alp = new ColorRGBA(0, 0, 0, 1).asIntABGR();
//Top triangle
verts.put(-width / 2f);
verts.put(height / 2f);
verts.put(0);
tex.put(0);
tex.put(0);
col.putInt(blue);
verts.put(width / 2f);
verts.put(height / 2f);
verts.put(0);
tex.put(1);
tex.put(0);
col.putInt(blue);
verts.put(0);
verts.put(0);
verts.put(0);
tex.put(0.5f);
tex.put(1);
col.putInt(blue);
indices.put((short)0);
indices.put((short)2);
indices.put((short)1);
//Right triangle
verts.put(width / 2f);
verts.put(height / 2f);
verts.put(0);
tex.put(1);
tex.put(0);
col.putInt(green);
verts.put(width / 2f);
verts.put(-height / 2f);
verts.put(0);
tex.put(1);
tex.put(1);
col.putInt(green);
verts.put(0);
verts.put(0);
verts.put(0);
tex.put(0);
tex.put(0.5f);
col.putInt(green);
indices.put((short)3);
indices.put((short)5);
indices.put((short)4);
//Bottom triangle
verts.put(-width / 2f);
verts.put(-height / 2f);
verts.put(0);
tex.put(0);
tex.put(1);
col.putInt(red);
verts.put(width / 2f);
verts.put(-height / 2f);
verts.put(0);
tex.put(1);
tex.put(1);
col.putInt(red);
verts.put(0);
verts.put(0);
verts.put(0);
tex.put(0.5f);
tex.put(0);
col.putInt(red);
indices.put((short)6);
indices.put((short)7);
indices.put((short)8);
//Left triangle
verts.put(-width / 2f);
verts.put(height / 2f);
verts.put(0);
tex.put(0);
tex.put(0);
col.putInt(alp);
verts.put(-width / 2f);
verts.put(-height / 2f);
verts.put(0);
tex.put(0);
tex.put(1);
col.putInt(alp);
verts.put(0);
verts.put(0);
verts.put(0);
tex.put(1);
tex.put(0.5f);
col.putInt(alp);
indices.put((short)9);
indices.put((short)10);
indices.put((short)11);
verts.flip();
VertexBuffer vb = new VertexBuffer(VertexBuffer.Type.Position);
vb.setupData(VertexBuffer.Usage.Stream, 3, VertexBuffer.Format.Float, verts);
setBuffer(vb);
tex.flip();
vb = new VertexBuffer(VertexBuffer.Type.TexCoord);
vb.setupData(VertexBuffer.Usage.Static, 2, VertexBuffer.Format.Float, tex);
setBuffer(vb);
col.flip();
vb = new VertexBuffer(VertexBuffer.Type.Color);
vb.setupData(VertexBuffer.Usage.Stream, 4, VertexBuffer.Format.UnsignedByte, col);
vb.setNormalized(true);
setBuffer(vb);
indices.flip();
vb = new VertexBuffer(VertexBuffer.Type.Index);
vb.setupData(VertexBuffer.Usage.Static, 3, VertexBuffer.Format.UnsignedShort, indices);
setBuffer(vb);
updateBound();
}
}
}
In the constructor the name
can be anything you like, it doesn’t have to be unique. The width
and height
values should be set to the width and height of the view in world coordinates. For example if you have an orthographic camera with a frustum ranging from -0.5 on the left to 0.5 on the right and 0.25 on the top and -0.25 on the bottom then you’d enter a width and height of 1 and 0.5.
Now let’s take a look at the shader starting with the vertex shader:
uniform mat4 g_WorldViewProjectionMatrix;
attribute vec3 inPosition;
attribute vec2 inTexCoord;
attribute vec4 inColor;
uniform float m_yscale;
varying vec2 texCoord;
varying vec4 vertCol;
void main() {
vertCol = inColor;
if (vertCol.b > 0.01) {
texCoord = vec2(inTexCoord.x, inTexCoord.y * m_yscale);
} else if (vertCol.g > 0.01) {
texCoord = vec2((inTexCoord.x * m_yscale) + m_yscale, inTexCoord.y);
} else if (vertCol.r > 0.01) {
texCoord = vec2(inTexCoord.x, (inTexCoord.y * m_yscale) + m_yscale);
} else {
texCoord = vec2(inTexCoord.x * m_yscale, inTexCoord.y);
}
gl_Position = g_WorldViewProjectionMatrix * vec4(inPosition, 1.0);
}
The fragment shader:
uniform sampler2D m_tex1;
uniform sampler2D m_tex2;
uniform sampler2D m_tex3;
uniform sampler2D m_tex4;
varying vec2 texCoord;
varying vec4 vertCol;
void main() {
vec4 col = vertCol;
if (vertCol.b > 0.01) {
col = texture2D(m_tex1, texCoord);
} else if (vertCol.g > 0.01) {
col = texture2D(m_tex2, texCoord);
} else if (vertCol.r > 0.01) {
col = texture2D(m_tex3, texCoord);
} else {
col = texture2D(m_tex4, texCoord);
}
gl_FragColor = col;
}
And finally the material definition:
MaterialDef trishade {
MaterialParameters {
Texture2D tex1
Texture2D tex2
Texture2D tex3
Texture2D tex4
Float yscale : 1.0
}
Technique {
VertexShader GLSL120: Shaders/trishade.vert
FragmentShader GLSL120: Shaders/trishade.frag
WorldParameters {
WorldViewProjectionMatrix
}
RenderState {
Blend Alpha
}
}
}
What this does is use the vertex colors assigned to each vertex to determine which texture to display, but also allows you to stretch the textures to help you match the aspect ratio of the texture’s you’re using. For instance if your top and bottom textures are rendered with a height of screenHeight / 2
and your left/right textures are rendered with a width of screenWidth / 2
everything should match up fine. If you’re rendering each of your textures at full screen resolution then you’ll probably want to set the material’s yscale
value to 0.5.
Let’s setup the default camera so it can easily render our TriDisplay
Geometry
.
cam.setParallelProjection(true);
cam.setLocation(new Vector3f(0, 0, 4f));
float ratio = (float)settings.getHeight() / settings.getWidth();
cam.setFrustum(1f, 5f, -0.5f, 0.5f, ratio / 2, -ratio / 2);
The above will setup an orthographic camera that can see everything from -0.5 to 0.5 on the x axis and everything from negative half the ratio of the height to the width to positive half the ratio of the height to the width.
Now all you need to do is instantiate a TriDisplay
, add it to the rootNode
and assign the custom material to it:
Geometry view = new TriDisplay("TriDisplay", 1, ratio);
rootNode.attachChild(view);
Material mat = new Material(assetManager, "MatDefs/trishade.j3md");
mat.setTexture("tex1", myTexture1); //Top
mat.setTexture("tex2", myTexture2); //Right
mat.setTexture("tex3", myTexture3); //Bottom
mat.setTexture("tex4", myTexture4); //Left
mat.setFloat("yscale", 0.5f); //Only if your textures are rendered at full screen resolution or otherwise need to be stretched or squished to fit the triangles properly.
view.setMaterial(mat);