4

作为起点,我使用了名为 MultiTargets 的 Vuforia(版本 4)示例,它跟踪摄像机源中的 3d 物理“立方体”,并沿立方体边缘使用黄色网格线对其进行扩充。我想要实现的是通过设置我自己的光照位置来移除纹理并在立方体面上使用漫反射光照。

我想在原生 Android 上执行此操作,我不想使用 Unity。

这是几天的工作和学习的艰难旅程。这是我第一次使用任何类型的 OpenGL,而 OpenGL ES 2.0 对初学者来说并不容易。

所以我有一个位于立方体顶面上方的光源。我发现如果我在模型空间中计算朗伯因子,我可以得到正确的漫反射效果,无论我的相机如何,一切都保持在原位,只有顶面有任何光线。

但是当我开始使用眼睛空间时,它变得很奇怪,光线似乎跟着我的相机走来走去。其他面变亮,不仅是顶面。我不明白为什么会这样。为了测试,我通过仅使用到光源的距离在片段着色器中渲染像素亮度来确保灯光位置符合预期。因此,我对我的“lightDirectionEyespace”的正确性相当有信心,我唯一的解释是法线一定是错误的。但我认为我遵循了正确创建法线矩阵的解释......

请帮忙!

然后当然还有一个问题,这些漫反射计算是否应该在眼睛空间中执行?如果我只是在模型空间中这样做会有什么缺点吗?我怀疑当我以后使用更多模型和灯光并添加镜面反射和透明度时,它可能不再起作用,即使我还不明白为什么。

我的 renderFrame 方法:(一些变量名仍然包含“bottle”,这是我在获得正确的立方体后接下来要点亮的对象)

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();
}

我的顶点着色器:

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;
}

还有我的片段着色器:

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);
}
4

2 回答 2

2

我终于解决了所有问题。有 2 个问题可能对未来的读者感兴趣。

  1. 来自官方示例(当前 Vuforia 版本 4)的 Vuforia CubeObject 类具有错误的法线。它们并不都与顶点定义顺序相对应。如果您使用示例中的 CubeObject,请确保法线定义与面正确对应。Vuforia 失败...

  2. 正如怀疑的那样,我的 normalMatrix 构建错误。我们不能只反转转置 4x4 modelViewMatrix,我们需要首先从中提取左上角的 3x3 子矩阵,然后反转转置它。

这是对我有用的代码:

  final Mat3 normalMatrixCube=new Mat3();
  normalMatrixCube.SetFrom4X4(modelViewMatrix);
  normalMatrixCube.invert();
  normalMatrixCube.transpose();

不过,这段代码本身并没有那么有用,因为它依赖于我从这个人那里随机导入的自定义类 Mat3,因为 Android 和 Vuforia 似乎都没有提供任何可以反转/转置 3x3 矩阵的矩阵类。这真的让我质疑我的理智——唯一适用于这种基本问题的代码必须依赖于自定义矩阵类?也许我只是做错了,我不知道......

于 2015-04-09T08:31:54.567 回答
0

为不使用此固定功能竖起大拇指!我发现您的示例对于理解还需要将光转换到眼空间中的位置非常有用。我发现的所有问题都建议使用glLight

虽然这帮助我解决了使用静态光源的问题,但如果您希望在保持光源静态(例如旋转对象)的同时对模型进行转换,那么您的代码中缺少的东西是跟踪原始modelview 矩阵,直到视图被更改,或者直到您绘制另一个具有不同模型的对象。所以像:

vLightPositionEyespace = fixedModelView * uLightPosition;

哪里fixedModelView可以在你的 renderFrame() 方法中更新。

opengl讨论板上的这个帖子有帮助:)

于 2015-11-14T22:45:14.347 回答