I've ported a desktop OpenGL application to Android NDK (under OpenGL ES 2), and there seems to be a random deformation of my mesh. On most application runs, it looks 100% perfect, but sometimes it looks as follows:
The inconsistency of the problem is the most concerning to me. I don't know if it's because of my Android simulator, or if it's something else. Through my testing, I can establish that it's either:
An OpenGL setting that doesn't play nice on Android, but does on everything else
A bug in the Open Asset Import Library (Assimp) which I've compiled by hand to work on Android
A bug in the Android simulator
My model process looks as follows:
On every draw:
- bind the program
- change the uniforms
- if (has vao support)
- bind vao
- enable all vertex attribute arrays
- for every mesh
- bind array buffer
- set the attribute pointer for each vertex array
- bind element buffer
- bind texture & set uniform of texture location
- glDrawElements
- disable all vertex attribute arrays
And this is the actual code:
glUseProgram(program_);
if (loaded_vao_)
{
#if !defined(TARGET_OS_IPHONE) && !defined(__ANDROID__)
glBindVertexArray(vao_);
#else
glBindVertexArrayOES(vao_);
#endif
}
glEnableVertexAttribArray(vPosition_);
glEnableVertexAttribArray(vTexCoord_);
glEnableVertexAttribArray(boneids_);
glEnableVertexAttribArray(weights_);
for (unsigned int i = 0; i < vbo_.size(); i++)
{
glBindBuffer(GL_ARRAY_BUFFER, vbo_[i]);
glVertexAttribPointer(vPosition_, 3, GL_FLOAT, GL_FALSE, 0, 0);
glVertexAttribPointer(vTexCoord_, 2, GL_FLOAT, GL_FALSE, 0, reinterpret_cast<void*>(texcoord_locations_[i]));
#if !defined(TARGET_OS_IPHONE) && !defined(__ANDROID__)
glVertexAttribIPointer(boneids_, 4, GL_INT, 0, reinterpret_cast<void*>(bone_id_locations_[i]));
#else // APPLE OR ANDROID
glVertexAttribPointer(boneids_, 4, GL_FLOAT, GL_FALSE, 0, reinterpret_cast<void*>(bone_id_locations_[i]));
#endif
glVertexAttribPointer(weights_, 4, GL_FLOAT, GL_FALSE, 0, reinterpret_cast<void*>(bone_weight_locations_[i]));
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo_[i]);
// Textures
if (!textures_.empty())
{
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, textures_[texture_numbers_[i]]);
glUniform1i(textureSample_, 0);
}
glDrawElements(GL_TRIANGLES, ind_size_[i], GL_UNSIGNED_SHORT, 0);
}
glDisableVertexAttribArray(vPosition_);
glDisableVertexAttribArray(vTexCoord_);
glDisableVertexAttribArray(boneids_);
glDisableVertexAttribArray(weights_);
As well, my vertex shader looks as follows:
precision mediump float;
attribute vec3 vPosition;
attribute vec2 vTexCoord;
attribute vec4 boneids;
attribute vec4 weights;
uniform mat4 pos;
uniform mat4 view;
uniform mat4 scale;
uniform mat4 rotate;
uniform mat4 proj;
uniform mat4 bones[50];
uniform int has_bones;
varying vec4 color;
varying vec2 texcoord;
void main()
{
color = vec4(1.0f);
texcoord = vTexCoord;
vec4 newPos = vec4(vPosition,1.0);
if (has_bones == 1)
{
mat4 bone_transform = bones[int(boneids[0])]*weights[0];
bone_transform += bones[int(boneids[1])]*weights[1];
bone_transform += bones[int(boneids[2])]*weights[2];
bone_transform += bones[int(boneids[3])]*weights[3];
newPos = bone_transform * newPos;
}
gl_Position = proj * view * pos * scale * rotate * newPos;
}
Do note that I've tried commenting out the bone_transform in the vertex shader, and the problem still persists.
EDIT:
It seems that I was able to recreate some deformations on my Linux OpenGL 3.3 version by removing any assimp optimization post process flags:
scene = importer.ReadFile(file_path.c_str(), aiProcess_Triangulate | aiProcess_FlipUVs | aiProcess_LimitBoneWeights | aiProcess_ValidateDataStructure);
Based on the output of the Assimp::DefaultLogger, there's no errors or vertex warnings.
The issue seemed to be a part of Blender's COLLADA export, or Assimp's COLLADA reader.
By exporting to FBX and using Autodesk's free FBX to DAE tool, the deformation was fixed.
Blender was version 2.71
Assimp was version 3.1.1
Assimp, even with all logging and data integrity flags on, did not post any errors about corruption, so I don't know which component to blame. Regardless, I'm happy I've found a workaround.
Related
Working on AR app and trying to replace plane texture.
I'm trying to render texture on vertical and horizontal planes. It's working fine for horizontal planes, but doesn't work well on vertical.
i found that something wrong with texture_coord calculations, but can't figure it out (new to OpenGL).
Here is my vertex shader
void main()
{
vec4 local_pos = vec4(a_position, 1.0);
vec4 world_pos = u_model * local_pos;
texture_coord = world_pos.sp * u_scale;
gl_Position = u_mvp * local_pos;
}
fragment shader
out vec4 outColor;
void main()
{
vec4 control = texture(u_texture, diffuse_coord);
float dotScale = 1.0;
float lineFade = 0.5;
vec3 newColor = (control.r * dotScale > u_gridControl.x) ? u_dotColor.rgb : control.g > u_gridControl.y ? u_lineColor.rgb * lineFade: u_lineColor.rgb * 0.25 * lineFade;
outColor = vec4(newColor, 1.0);
}
The important bit is texture_coord = world_pos.sp in your vertex shader.
There are 3 ways to refer to the components of a vector in GLSL. xyzw (the most common), rgba (more natural for colours), stpq (more natural for texture coordinates).
The line texture_coord = world_pos.sp would be clearer if it were written as texture_coord = world_pos.xz.
Once you realize that you're generating texture coordinates by ignoring the y-component it's obvious why vertical planes are not textured how you would like.
Unfortunately there's no simple one line fix. Perhaps tri-planar texturing might be an appropriate solution for you - this seems to be a good explanation of the technique.
I'm using OpenGL ES 2.0 on Android to add effects to a video. Everything works fine until I add another texture.
What I've tried:
Using a Texture2D instead of external (EOS) -> Impossible for an external source (video file)
Changed texture to a power of two after using the method that checks if the GPU supports non pow of 2 -> No change
Played with the chronological order of GLES20 methods -> No visible change in execution behaviour
Binded textures using different index logic -> No change
Not tried yet:
Using another bitmap format, but the GLUtils method to load bitmap is supposed to handle the format
Using the 'legacy' method to load a texture from a bitmap (GLES instead of GLUtils)
Using a array of pixels instead of a bitmap.
But at this point I would just surrender about bitmap and send an Uniform Buffer containing my pixels to the shader...
When my surface is created I do this:
var vertexShader = _videoView.Filtering.Shaders.Item1;
var fragmentShader = _videoView.Filtering.Shaders.Item2;
var progExists = glPrograms.ContainsKey (vertexShader + fragmentShader);
if (!progExists) {
var prog = createProgram (vertexShader, fragmentShader);
glPrograms.Add (vertexShader + fragmentShader, prog);
}
_glProgram = glPrograms [vertexShader + fragmentShader];
if (_glProgram == 0) {
// Should compile with default shaders from GlFilter.
throw new System.Exception ("Can't create GL Program. There is something wrong with EGL on this device.");
}
I also load my textures:
_aPositionHandle = GLES20.GlGetAttribLocation(_glProgram, "aPosition");
checkGlError("glGetAttribLocation aPosition");
if (_aPositionHandle == -1) {
throw new RuntimeException(
"Could not get attrib location for aPosition");
}
_aTextureCoord = GLES20.GlGetAttribLocation(_glProgram,
"aTextureCoord");
checkGlError("glGetAttribLocation aTextureCoord");
if (_aTextureCoord == -1) {
throw new RuntimeException (
"Could not get attrib location for aTextureCoord");
}
_uMVPMatrixHandle = GLES20.GlGetUniformLocation(_glProgram,
"uMVPMatrix");
checkGlError("glGetUniformLocation uMVPMatrix");
if (_uMVPMatrixHandle == -1) {
throw new RuntimeException(
"Could not get attrib location for uMVPMatrix");
}
_uSTMatrixHandle = GLES20.GlGetUniformLocation(_glProgram,
"uSTMatrix");
checkGlError("glGetUniformLocation uSTMatrix");
if (_uSTMatrixHandle == -1) {
throw new RuntimeException(
"Could not get attrib location for uSTMatrix");
}
_texelWidthOffsetUniform = GLES20.GlGetUniformLocation(_glProgram,
"texelWidthOffset");
checkGlError("glGetUniformLocation texelWidthOffset");
if (_texelWidthOffsetUniform == -1) {
throw new RuntimeException(
"Could not get attrib location for texelWidthOffset");
}
_texelHeightOffsetUniform = GLES20.GlGetUniformLocation(_glProgram,
"texelHeightOffset");
checkGlError("glGetUniformLocation texelHeightOffset");
if (_texelHeightOffsetUniform == -1) {
throw new RuntimeException(
"Could not get attrib location for texelHeightOffset");
}
_sTexture2Uniform = GLES20.GlGetUniformLocation(_glProgram,
"sTexture2");
checkGlError("glGetUniformLocation sTexture2Uniform");
if (_sTexture2Uniform == -1) {
throw new RuntimeException(
"Could not get attrib location for sTexture2");
}
int[] textures = new int[1];
GLES20.GlGenTextures(1, textures, 0);
_textureID = textures[0];
GLES20.GlBindTexture(GL_TEXTURE_EXTERNAL_OES, _textureID);
checkGlError("glBindTexture _textureID");
GLES20.GlTexParameterf(GL_TEXTURE_EXTERNAL_OES, GLES20.GlTextureMinFilter, GLES20.GlNearest);
GLES20.GlTexParameterf(GL_TEXTURE_EXTERNAL_OES, GLES20.GlTextureMagFilter, GLES20.GlLinear);
Still in Surface Created event, I add a texture by uploading a Bitmap
source = Bitmap.CreateBitmap(256, 256, Bitmap.Config.Argb8888);
source.EraseColor(Color.Pink.ToArgb());
for (int x = 0; x < 256; x++)
for (int y = 0; y < 256; y++) {
var cof = (int)(System.Math.Cos ((double)x / (double)256) * 255);
var sif = (int)(System.Math.Sin ((double)y / (double)256) * 255);
var p = (double)x / (double)256 * 360d;
var q = (double)y / (double)256 * 360d;
var c = (int)(((System.Math.Sin (p * q) / 2d) + 0.5d) * 255d);
source.SetPixel (x, y, Color.Argb (255, c, (c + cof) / 2, (c + sif) / 2));
}
int[] textures = new int[1];
GLES20.GlGenTextures(1, textures, 0);
_texture2Id = textures[0];
GLUtils.TexImage2D(GLES20.GlTexture2d, 0, source, 0);
source.Recycle ();
GLES20.GlBindTexture(GLES20.GlTexture2d, _texture2Id);
GLES20.GlTexParameterf(GLES20.GlTexture2d, GLES20.GlTextureWrapS, GLES20.GlClampToEdge);
GLES20.GlTexParameterf(GLES20.GlTexture2d, GLES20.GlTextureWrapT, GLES20.GlClampToEdge);
GLES20.GlTexParameterf(GLES20.GlTexture2d, GLES20.GlTextureMagFilter, GLES20.GlLinear);
GLES20.GlTexParameterf(GLES20.GlTexture2d, GLES20.GlTextureMinFilter, GLES20.GlLinear);
_surfaceTexture = new SurfaceTexture(_textureID);
_surfaceTexture.FrameAvailable += _surfaceTexture_FrameAvailable;
_mediaPlayer = new MediaPlayer();
Then in OnDraw:
var fragment = _videoView.Filtering.Shaders.Item2;
var vertex = _videoView.Filtering.Shaders.Item1;
var progExists = glPrograms.ContainsKey (vertex + fragment);
if (!progExists) {
var prog = createProgram (vertex, fragment);
glPrograms.Add (vertex + fragment, prog);
}
var progToUse = glPrograms [vertex + fragment];
GLES20.GlClearColor(0.0f, 0.0f, 0.0f, 0.0f);
GLES20.GlClear(GLES20.GlColorBufferBit);
lock (syncLock) {
_surfaceTexture.UpdateTexImage ();
_surfaceTexture.GetTransformMatrix (_STMatrix);
updateSurface = false;
}
GLES20.GlViewport(0, 0, _surfaceWidth, _surfaceHeight);
if (progToUse != _glProgram) {
GLES20.GlUseProgram (progToUse);
}
_glProgram = progToUse;
GLES20.GlUniform1i(_sTexture2Uniform, 1);
GLES20.GlActiveTexture (GLES20.GlTexture0);
GLES20.GlBindTexture (GL_TEXTURE_EXTERNAL_OES, _textureID);
GLES20.GlActiveTexture (GLES20.GlTexture1);
GLES20.GlBindTexture (GLES20.GlTexture2d, _texture2Id);
_triangleVertices.Position (TRIANGLE_VERTICES_DATA_POS_OFFSET);
GLES20.GlVertexAttribPointer (_aPositionHandle, 3, GLES20.GlFloat, false, TRIANGLE_VERTICES_DATA_STRIDE_BYTES, _triangleVertices);
GLES20.GlEnableVertexAttribArray (_aPositionHandle);
_textureVertices.Position (TRIANGLE_VERTICES_DATA_UV_OFFSET);
GLES20.GlVertexAttribPointer (_aTextureCoord, 2, GLES20.GlFloat, false, TEXTURE_VERTICES_DATA_STRIDE_BYTES, _textureVertices);
GLES20.GlEnableVertexAttribArray (_aTextureCoord);
Android.Opengl.Matrix.SetIdentityM (_MVPMatrix, 0);
GLES20.GlUniformMatrix4fv (_uMVPMatrixHandle, 1, false, _MVPMatrix, 0);
GLES20.GlUniformMatrix4fv (_uSTMatrixHandle, 1, false, _STMatrix, 0);
GLES20.GlDrawArrays(GLES20.GlTriangleStrip, 0, 4);
The problem is when I hit this line `GLES20.GlUniform1i(_sTexture2Uniform, 1);`
I keep getting this error then setting my bitmap texture uniform in OnDraw.
[Adreno200-ES20] <__load_uniform_float:539>: GL_INVALID_OPERATION
The two errors I get are:
[SurfaceTexture] [unnamed-22096-0] updateTexImage: clearing GL error: 0x502
[Adreno200-ES20] <__load_uniform_int:305>: GL_INVALID_OPERATION
Vertex looks like it:
uniform mat4 uMVPMatrix;
uniform mat4 uSTMatrix;
uniform highp float texelWidthOffset;
uniform highp float texelHeightOffset;
attribute vec4 aPosition;
attribute vec4 aTextureCoord;
varying vec2 vTextureCoord;
void main() {
highp float useTX = texelWidthOffset;
highp float useTY = texelHeightOffset;
gl_Position = uMVPMatrix * aPosition;
vTextureCoord = (uSTMatrix * aTextureCoord).xy;
}
Fragment looks like it:
#version 150
#extension GL_OES_EGL_image_external : require
precision mediump float;
uniform sampler2D sTexture2;
uniform samplerExternalOES sTexture;
varying vec2 vTextureCoord;
varying float texelWidthOffset;
varying float texelHeightOffset;
void main() {
highp float useTX = texelWidthOffset;
highp float useTY = texelHeightOffset;
lowp vec4 inputColor0 = texture2D(sTexture,vTextureCoord);
lowp vec4 someColor = mix(inputColor0, texture2D(sTexture2,vTextureCoord), useTY);
lowp vec4 outputColor0 = mix(inputColor0, texture2D(sTexture,vTextureCoord), useTX);
gl_FragColor=outputColor0;
}
I don't know what to say except "Help!", since I've maybe tried to follow 100+ tutorials to fix this GL_INVALID_OPERATION error.
None of the attempts I've made ever result in any colored pixel when mixing OES external source (a movie file) with a bitmap texture.
[EDIT]
Ok. In the code above I was setting _glProgram to the proper created program id, then before using the program, I was saying: "Is _glProgram equals to the proper created program id? If yes, then don't come here to call useProgram." So I was never using the proper program. So there is not a single OpenGL error now, but the screen is black like in zero. This is a good news. I know the pixels from my bitmap texture are correct. (Coded an a trigo algo randomly, happened to form cute polka four holes buttons.) So I'm revising my OpenGL ES code.
[EDIT 2]
I feel like I'm stuck with this problem.
There's one very clear problem in your code. In this call sequence:
GLES20.GlGenTextures(1, textures, 0);
_texture2Id = textures[0];
GLUtils.TexImage2D(GLES20.GlTexture2d, 0, source, 0);
source.Recycle ();
GLES20.GlBindTexture(GLES20.GlTexture2d, _texture2Id);
You need to bind the texture before calling GLUtils.texImage2D(). That method will load the texture data into the currently bound texture. The correct order is:
GLES20.GlGenTextures(1, textures, 0);
_texture2Id = textures[0];
GLES20.GlBindTexture(GLES20.GlTexture2d, _texture2Id);
GLUtils.TexImage2D(GLES20.GlTexture2d, 0, source, 0);
source.Recycle ();
This doesn't explain the errors from the glUniform1i() call, though. It can give a GL_INVALID_OPERATION error for the following reasons:
No program is bound.
The location is not valid for the currently bound program.
The type/size used for the call does not match the type/size of the uniform in the shader.
We can probably exclude options 1, or at the very least you should be able to check quickly that the program you just bound is valid.
This leaves the options that the location is invalid, or the location of the wrong uniform. Unless I missed something in the relatively lengthy code you posted, the only logical explanation is that the program used to retrieve the shader locations is not the same as the one you bind for setting the uniforms in onDraw().
I notice that you're using some kind of shader cache to reuse shaders. While that looks fairly straightforward, I would double (and triple, and quadruple) check that it really works as expected. Compare the program id used during setup where you get the uniform locations with the one used when preparing to draw. And make sure that no shaders were destroyed and recreated. And that everything happened in the same thread/context.
I have a shader that dynamically indexes uniform data, based on a vertex stream. It uses the following shader:
#version 300 es
uniform mat4 data[24];
attribute vec4 pos;
attribute float index;
void main()
{
gl_Position = (pos) * ( data[int(index * 1.01)]);
}
When run on any Adreno 300 series Android GPU, the data is not indexed correctly. It is sometimes correct, but frequently, the geometry seems to be accessing bogus uniform data, causing missing geometry, or corrupted rendering. The exact same code run on other Android devices (even Adreno 200 series) produces correct results. When captured with the Adreno profiler, the rendered result also shows correctly. Further, a shader which is essentially equivalent:
#version 300 es
uniform vec4 data[96];
attribute vec4 pos;
attribute float index;
void main()
{
gl_Position.x = dot(pos, data[int(index * 4.01 + 0.0)]);
gl_Position.y = dot(pos, data[int(index * 4.01 + 1.0)]);
gl_Position.z = dot(pos, data[int(index * 4.01 + 2.0)]);
gl_Position.w = dot(pos, data[int(index * 4.01 + 3.0)]);
}
Produces correct results (with modified glUniform code). Is this a bug in the Adreno 300 series driver dynamically indexing mat4 data, is there something incorrect about my shader code, or is there something non-standard/incorrect here that the Adreno 300 doesn't support?
As a starting point I use the Vuforia (version 4) sample called MultiTargets which tracks a 3d physical "cube" in the camera feed and augments it with yellow grid lines along the cube edges.
What I want to achieve is remove the textures and use diffuse lighting on the cube faces instead, by setting my own light position.
I want to do this on native Android and I do NOT want to use Unity.
It's been a hard journey of several days of work and learning. This is my first time working with OpenGL of any kind, and OpenGL ES 2.0 doesn't exactly make it easy for the beginner.
So I have a light source positioned slightly above the top face of my cube. I found that I can get the diffuse effect right if I compute the lambert factor in model space, everything remains in place regardless of my camera, and only the top face gets any light.
But when I move to using eye space, it becomes weird and the light seems to follow my camera around. Other faces get light, not only the top face. I don't understand why that is. For testing I have made sure that the light position is as expected by only using distance to lightsource for rendering pixel brightness in the fragment shader. Therefore, I'm fairly confident in the correctness of my "lightDirectionEyespace", and my only explanation is that something with the normals must be wrong. But I think I followed the explanations for creating the normal matrix correctly...
Help please!
Then there is of course the question whether those diffuse calculations SHOULD be performed in eye space? Will there be any disadvantages if I just do it in model space? I suspect that probably when I later use more models and lights and add specular and transparency, it will not work anymore, even though I don't see yet why.
My renderFrame method: (some variable names still contain "bottle", which is the object I want to light next after I get the cube right)
private void renderFrame()
{
ShaderFactory.checkGLError("Check gl errors prior render Frame");
// Clear color and depth buffer
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
// Get the state from Vuforia and mark the beginning of a rendering section
final State state=Renderer.getInstance().begin();
// Explicitly render the Video Background
Renderer.getInstance().drawVideoBackground();
GLES20.glEnable(GLES20.GL_DEPTH_TEST);
GLES20.glEnable(GLES20.GL_BLEND);
GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
// Did we find any trackables this frame?
if(0 != state.getNumTrackableResults())
{
// Get the trackable:
TrackableResult result=null;
final int numResults=state.getNumTrackableResults();
// Browse results searching for the MultiTarget
for(int j=0; j < numResults; j++)
{
result=state.getTrackableResult(j);
if(result.isOfType(MultiTargetResult.getClassType()))
break;
result=null;
}
// If it was not found exit
if(null == result)
{
// Clean up and leave
GLES20.glDisable(GLES20.GL_BLEND);
GLES20.glDisable(GLES20.GL_DEPTH_TEST);
Renderer.getInstance().end();
return;
}
final Matrix44F modelViewMatrix_Vuforia=Tool.convertPose2GLMatrix(result.getPose());
final float[] modelViewMatrix=modelViewMatrix_Vuforia.getData();
final float[] modelViewProjection=new float[16];
Matrix.scaleM(modelViewMatrix, 0, CUBE_SCALE_X, CUBE_SCALE_Y, CUBE_SCALE_Z);
Matrix.multiplyMM(modelViewProjection, 0, vuforiaAppSession
.getProjectionMatrix().getData(), 0, modelViewMatrix, 0);
GLES20.glUseProgram(bottleShaderProgramID);
// Draw the cube:
GLES20.glEnable(GLES20.GL_CULL_FACE);
GLES20.glCullFace(GLES20.GL_BACK);
GLES20.glVertexAttribPointer(vertexHandleBottle, 3, GLES20.GL_FLOAT, false, 0, cubeObject.getVertices());
GLES20.glVertexAttribPointer(normalHandleBottle, 3, GLES20.GL_FLOAT, false, 0, cubeObject.getNormals());
GLES20.glEnableVertexAttribArray(vertexHandleBottle);
GLES20.glEnableVertexAttribArray(normalHandleBottle);
// add light position and color
final float[] lightPositionInModelSpace=new float[] {0.0f, 1.1f, 0.0f, 1.0f};
GLES20.glUniform4f(lightPositionHandleBottle, lightPositionInModelSpace[0], lightPositionInModelSpace[1],
lightPositionInModelSpace[2], lightPositionInModelSpace[3]);
GLES20.glUniform3f(lightColorHandleBottle, 0.9f, 0.9f, 0.9f);
// create the normalMatrix for lighting calculations
final float[] normalMatrix=new float[16];
Matrix.invertM(normalMatrix, 0, modelViewMatrix, 0);
Matrix.transposeM(normalMatrix, 0, normalMatrix, 0);
// pass the normalMatrix to the shader
GLES20.glUniformMatrix4fv(normalMatrixHandleBottle, 1, false, normalMatrix, 0);
// extract the camera position for lighting calculations (last column of matrix)
// GLES20.glUniform3f(cameraPositionHandleBottle, normalMatrix[12], normalMatrix[13], normalMatrix[14]);
// set material properties
GLES20.glUniform3f(matAmbientHandleBottle, 0.0f, 0.0f, 0.0f);
GLES20.glUniform3f(matDiffuseHandleBottle, 0.1f, 0.9f, 0.1f);
// pass the model view matrix to the shader
GLES20.glUniformMatrix4fv(modelViewMatrixHandleBottle, 1, false, modelViewMatrix, 0);
// pass the model view projection matrix to the shader
// the "transpose" parameter must be "false" according to the spec, anything else is an error
GLES20.glUniformMatrix4fv(mvpMatrixHandleBottle, 1, false, modelViewProjection, 0);
GLES20.glDrawElements(GLES20.GL_TRIANGLES,
cubeObject.getNumObjectIndex(), GLES20.GL_UNSIGNED_SHORT, cubeObject.getIndices());
GLES20.glDisable(GLES20.GL_CULL_FACE);
// disable the enabled arrays after everything has been rendered
GLES20.glDisableVertexAttribArray(vertexHandleBottle);
GLES20.glDisableVertexAttribArray(normalHandleBottle);
ShaderFactory.checkGLError("MultiTargets renderFrame");
}
GLES20.glDisable(GLES20.GL_BLEND);
GLES20.glDisable(GLES20.GL_DEPTH_TEST);
Renderer.getInstance().end();
}
My vertex shader:
attribute vec4 vertexPosition;
attribute vec3 vertexNormal;
uniform mat4 modelViewProjectionMatrix;
uniform mat4 modelViewMatrix;
uniform mat4 normalMatrix;
// lighting
uniform vec4 uLightPosition;
uniform vec3 uLightColor;
// material
uniform vec3 uMatAmbient;
uniform vec3 uMatDiffuse;
// pass to fragment shader
varying vec3 vNormalEyespace;
varying vec3 vVertexEyespace;
varying vec4 vLightPositionEyespace;
varying vec3 vNormal;
varying vec4 vVertex;
void main()
{
// we can just take vec3() of a vec4 and it will take the first 3 entries
vNormalEyespace = vec3(normalMatrix * vec4(vertexNormal, 1.0));
vNormal = vertexNormal;
vVertexEyespace = vec3(modelViewMatrix * vertexPosition);
vVertex = vertexPosition;
// light position
vLightPositionEyespace = modelViewMatrix * uLightPosition;
gl_Position = modelViewProjectionMatrix * vertexPosition;
}
And my fragment shader:
precision highp float; //apparently necessary to force same precision as in vertex shader
//lighting
uniform vec4 uLightPosition;
uniform vec3 uLightColor;
//material
uniform vec3 uMatAmbient;
uniform vec3 uMatDiffuse;
//from vertex shader
varying vec3 vNormalEyespace;
varying vec3 vVertexEyespace;
varying vec4 vLightPositionEyespace;
varying vec3 vNormal;
varying vec4 vVertex;
void main()
{
vec3 normalModel = normalize(vNormal);
vec3 normalEyespace = normalize(vNormalEyespace);
vec3 lightDirectionModel = normalize(uLightPosition.xyz - vVertex.xyz);
vec3 lightDirectionEyespace = normalize(vLightPositionEyespace.xyz - vVertexEyespace.xyz);
vec3 ambientTerm = uMatAmbient;
vec3 diffuseTerm = uMatDiffuse * uLightColor;
// calculate the lambert factor via cosine law
float diffuseLambert = max(dot(normalEyespace, lightDirectionEyespace), 0.0);
// Attenuate the light based on distance.
float distance = length(vLightPositionEyespace.xyz - vVertexEyespace.xyz);
float diffuseLambertAttenuated = diffuseLambert * (1.0 / (1.0 + (0.01 * distance * distance)));
diffuseTerm = diffuseLambertAttenuated * diffuseTerm;
gl_FragColor = vec4(ambientTerm + diffuseTerm, 1.0);
}
I finally solved all problems.
There were 2 issues that might be of interest for future readers.
Vuforia CubeObject class from the official sample (current Vuforia version 4) has wrong normals. They do not all correspond with the vertex definition order. If you're using the CubeObject from the sample, make sure that the normal definitions are correctly corresponding with the faces. Vuforia fail...
As suspected, my normalMatrix was wrongly built. We cannot just invert-transpose the 4x4 modelViewMatrix, we need to first extract the top left 3x3 submatrix from it and then invert-transpose that.
Here is the code that works for me:
final Mat3 normalMatrixCube=new Mat3();
normalMatrixCube.SetFrom4X4(modelViewMatrix);
normalMatrixCube.invert();
normalMatrixCube.transpose();
This code by itself is not that useful though, because it relies on a custom class Mat3 which I randomly imported from this guy because neither Android nor Vuforia seem to offer any matrix class that can invert/transpose 3x3 matrices. This really makes me question my sanity - the only code that works for such a basic problem has to rely on a custom matrix class? Maybe I'm just doing it wrong, I don't know...
thumbs up for not using the fixed functions on this! I found your example quite useful for understanding that one needs to also translate the light to a position in eyespace. All the questions i've found just recommend using glLight.
While this helped me solve using a static light source, something which is missing from your code if you wish to also make transformations on your model(s) while keeping the light source static(e.g rotating the object) is to keep track of the original modelview matrix until the view is changed, or until you're drawing another object which has a different model. So something like:
vLightPositionEyespace = fixedModelView * uLightPosition;
where fixedModelView can be updated in your renderFrame() method.
This thread on opengl discussion boards helped :)
I am having some problems uploading a small vector of vec4s to the GPU. I have boiled this problem down to the bare minimum code to throw an error.
Here is my Fragment shader:
precision mediump float;
uniform vec4 test[5];
void main() {
gl_FragColor = test[0]+test[1]+test[2]+test[3]+test[4];
}
And the vertex shader is trivial:
attribute vec4 vPosition;
void main(){
gl_Position = vPosition;
}
Here is the code that tries to upload the vec4 vector:
float[] testBuffer = new float[4*5];
// Fill with 1/5s for now
Arrays.fill(testBuffer, 0.2f);
// Get the location
int testLoc = GLES20.glGetUniformLocation(mProgram, "test");
checkGlError("glGetUniformLocation test");
// Upload the buffer
GLES20.glUniform4fv(testLoc, 5, testBuffer, 0);
checkGlError("glUniform4fv testBuffer");
The error is found on the second call to checkGlError(), and the error code is GL_INVALID_OPERATION.
I've read the documentation on glUniform and all of the sizes and types appear to be correct. testLoc is a valid location handle, and I have no errors when uploading the fragment and vertex shader code.
I just can't see what I'm doing wrong! Any ideas?
--UPDATED
See glUniform documentation:
GL_INVALID_OPERATION is generated if there is no current program
object
Make sure your shader is currently bound/used when calling glUniform (glUseProgram has been called with the corresponding shader program handle). The uniform keeps its value when unbinding the shader (e.g. glUseProgram(0)), but the program has to be active when setting the uniform value.