Диффузный шейдер для OpenGL ES 2.0: свет меняется с движением камеры (Vuforia на Android)

В качестве отправной точки я использую образец Vuforia (версия 4) под названием MultiTargets, который отслеживает трехмерный физический "куб" в канале камеры и дополняет его желтыми линиями сетки по краям куба. Чего я хочу добиться, так это удалить текстуры и использовать рассеянное освещение на гранях куба, установив собственную позицию освещения.

Я хочу сделать это на родном Android, и я не хочу использовать Unity.

Это был тяжелый путь нескольких дней работы и учебы. Я впервые работаю с OpenGL любого рода, и OpenGL ES 2.0 не совсем облегчает работу новичка.

Таким образом, у меня есть источник света, расположенный немного выше верхней грани моего куба. Я обнаружил, что могу получить эффект рассеивания правильно, если вычислю коэффициент Ламберта в пространстве модели, все останется на месте, независимо от моей камеры, и только верхняя грань будет получать свет.

Но когда я перехожу к использованию глазного пространства, это становится странным, и свет, кажется, следует за моей камерой. Другие лица становятся светлыми, а не только верхнее лицо. Я не понимаю, почему это так. Для тестирования я удостоверился, что положение света соответствует ожидаемому, используя только расстояние до источника света для рендеринга яркости пикселей в фрагментном шейдере. Поэтому я довольно уверен в правильности моего "lightDirectionEyespace", и мое единственное объяснение состоит в том, что что-то с нормалями должно быть не так. Но я думаю, что следовал объяснениям для создания нормальной матрицы правильно...

Помогите, пожалуйста!

Тогда, конечно, возникает вопрос, СЛЕДУЕТ ли выполнять эти диффузные вычисления в глазном пространстве? Будут ли недостатки, если я просто сделаю это в модельном пространстве? Я подозреваю, что, вероятно, когда я позже использую больше моделей и источников света и добавлю зеркальность и прозрачность, это больше не будет работать, хотя я пока не понимаю, почему.

Мой метод renderFrame: (некоторые имена переменных по-прежнему содержат "бутылку", то есть объект, который я хочу осветить следующим после того, как я правильно понял куб)

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

2 ответа

Я наконец-то решил все проблемы. Было 2 вопроса, которые могли бы заинтересовать будущих читателей.

  1. Класс Vuforia CubeObject из официального образца (текущая версия Vuforia 4) имеет неправильные нормали. Они не все соответствуют порядку определения вершин. Если вы используете CubeObject из образца, убедитесь, что нормальные определения правильно соответствуют граням. Вуфория не удалась...

  2. Как и предполагалось, моя нормальная Matrix была неправильно построена. Мы не можем просто инвертировать-транспонировать 4x4 modelViewMatrix, нам нужно сначала извлечь из него верхнюю левую подматрицу 3x3, а затем инвертировать-транспонировать ее.

Вот код, который работает для меня:

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

Этот код сам по себе не так полезен, потому что он опирается на пользовательский класс Mat3, который я случайно импортировал из этого парня, потому что ни Android, ни Vuforia, похоже, не предлагают ни одного класса матриц, который может инвертировать / транспонировать матрицы 3x3. Это действительно заставляет меня задуматься - единственный код, который работает для такой основной проблемы, должен полагаться на собственный класс матрицы? Может быть, я просто делаю это неправильно, я не знаю...

Недурно за не использовать фиксированные функции на этом! Я нашел ваш пример весьма полезным для понимания того, что нужно также перевести свет в положение в глазном пространстве. Все вопросы, которые я нашел, просто рекомендую использовать glLight.

Хотя это помогло мне решить проблему с использованием статического источника света, то, чего не хватает в вашем коде, если вы хотите также выполнить преобразования в вашей модели (моделях) при сохранении статического источника света (например, вращение объекта), - это отслеживать оригинал матрица вида модели, пока вид не изменится, или пока вы не рисуете другой объект, который имеет другую модель. Так что-то вроде:

vLightPositionEyespace = fixedModelView * uLightPosition;

где fixedModelView может быть обновлено в вашем методе renderFrame().

Эта ветка на форумах opengl помогла:)

Другие вопросы по тегам