在GLES 2.0中定位灯光时混淆眼睛和世界空间

时间:2017-03-07 06:28:47

标签: android opengl-es lighting

我的GLES 2.0 Android应用程序中有关于移动点灯的问题:

我有一个人在大面积上走动的例子。这个人需要在头顶上方有一盏灯才能让他身边的一小块区域正常照亮。由于灯光实例将人物实例作为其父实例,因此它在世界空间中的位置完全按照人物移动的方式移动(y-Offset +4)。但每次我启动应用程序时,灯光都不会位于顶部,并且它不会完全按照人的移动方式移动(或者至少它看起来不是这样)。即使光和人共享相同的x值和z值,它似乎离开了人的面前。 Person是一个立方体(还没有复杂的模型)。

这是我的立方体绘制方法的代码:

public void draw(float[] pPMatrix, float[] pVMatrix)
{
    float[] MVPMatrix = new float[16];
    Matrix.setIdentityM(getParent().getModelMatrix(),0);
    Matrix.translateM(getParent().getModelMatrix(),0,mXLL, mYLL, mZLL);


    Matrix.multiplyMM(MVPMatrix, 0, pVMatrix, 0, getParent().getModelMatrix(), 0);
    Matrix.multiplyMM(MVPMatrix, 0, pPMatrix, 0, MVPMatrix, 0);

    // Add program to OpenGL ES environment
    GLES20.glUseProgram(mProgram);

    // ..... 

    GLES20.glUniformMatrix4fv(LightingProgram.getMVPMatrixHandle(), 1, false, MVPMatrix, 0);
    GLES20.glUniformMatrix4fv(LightingProgram.getMVMatrixHandle(), 1, false, pVMatrix, 0);

    LightObject lo = mParent.getWorld().getLightObjects().get(0);
    Matrix.multiplyMV(lo.getLightPosInEyeSpace(), 0, pVMatrix, 0, lo.getLightPosInWorldSpace(), 0 );
    GLES20.glUniform3f(LightingProgram.getLightPosHandle(), lo.getLightPosInEyeSpace()[0], lo.getLightPosInEyeSpace()[1], lo.getLightPosInEyeSpace()[2]);

    // Draw the triangle
    GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, mVertexCount);

}

顶点着色器代码是:

uniform mat4 u_MVPMatrix;
uniform mat4 u_MVMatrix;        

attribute vec4 a_Position;  
attribute vec4 a_Color;         
attribute vec3 a_Normal;
attribute vec2 a_TexCoordinate;

varying vec3 v_Position;
varying vec4 v_Color;   
varying vec3 v_Normal;      
varying vec2 v_TexCoordinate;


void main()
{
    // Transform the vertex into eye space.
    v_Position = vec3(u_MVMatrix * a_Position);

    // Pass through the color.
    v_Color = a_Color;

    // Pass through the texture coordinate.
    v_TexCoordinate = a_TexCoordinate;

    // Transform the normal's orientation into eye space.
    v_Normal = vec3(u_MVMatrix * vec4(a_Normal, 0.0));

    // gl_Position is a special variable used to store the final position.
    // Multiply the vertex by the matrix to get the final point in normalized screen coordinates.
    gl_Position = u_MVPMatrix * a_Position;
}

Fragment Shader代码是:

precision mediump float;
uniform vec3 u_LightPos;   
uniform sampler2D u_Texture; 

varying vec3 v_Position;
varying vec4 v_Color;
varying vec3 v_Normal
varying vec2 v_TexCoordinate;

void main()
{

    float distance = length(u_LightPos - v_Position);

    // Get a lighting direction vector from the light to the vertex.
    vec3 lightVector = normalize(u_LightPos - v_Position);

    float diffuse = max(dot(v_Normal, lightVector), 0.0);
    diffuse = diffuse * (1.0 / (1.0 + (0.25 * distance)));

    // Add ambient lighting
    diffuse = diffuse + 0.25;

    gl_FragColor = (diffuse * v_Color * texture2D(u_Texture, v_TexCoordinate));
}

我认为这与我传递轻物体位置的方式有关...但我无法弄清楚正确的方法是什么。

提前致谢......: - )

==========================================

!! EDIT !!我上传了一个问题视频: https://dl.dropboxusercontent.com/u/17038392/opengl_lighting_test.mp4(2MB)

此场景中的每个形状都是一个立方体。当人站在房间中间时,灯光对地板没有影响。如果此人移动到上角,则灯光移动到房间的中间。 现在这里有一件非常奇怪的事情:由于灯光位于人的上方,当人处于房间中间时,它会照亮黄色的板条箱。地球怎么可能发生这种情况? ;-)

==========================================

编辑2: 好的,所以我试着做你说的话。但是,作为一名新手,我无法正确地做到这一点:

我对任何多维数据集实例的绘制方法:

public void draw(float[] pPMatrix, float[] pVMatrix)
{
    float[] MVPMatrix = new float[16];
    float[] normalVMatrix = new float[16];
    float[] normalTransposed = new float[16];

    // Move object
    Matrix.setIdentityM(getParent().getModelMatrix(),0);
    Matrix.translateM(getParent().getModelMatrix(),0,mXLL, mYLL, mZLL);
    Matrix.multiplyMM(MVPMatrix, 0, pVMatrix, 0, getParent().getModelMatrix(), 0);
    Matrix.multiplyMM(MVPMatrix, 0, pPMatrix, 0, MVPMatrix, 0);

    // create normal matrix by inverting and transposing the modelmatrix
    Matrix.invertM(normalVMatrix, 0, getParent().getModelMatrix(), 0);
    Matrix.transposeM(normalTransposed, 0, normalVMatrix, 0);

    // Add program to OpenGL ES environment
    GLES20.glUseProgram(mProgram);

    // ============================
    // POSITION
    // ============================
    getVertexBuffer().position(0);
    GLES20.glVertexAttribPointer(LightingProgram.getPositionHandle(), COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, vertexStride, getVertexBuffer());
    GLES20.glEnableVertexAttribArray(LightingProgram.getPositionHandle());

    // ============================
    // COLOR
    // ============================
    getColorBuffer().position(0);
    GLES20.glVertexAttribPointer(LightingProgram.getColorHandle(), COLOR_DATA_SIZE, GLES20.GL_FLOAT, false, 0, getColorBuffer());
    GLES20.glEnableVertexAttribArray(LightingProgram.getColorHandle());

    // ============================
    // NORMALS
    // ============================
    // Pass in the normal information
    if(LightingProgram.getNormalHandle() != -1)
    {
        getNormalBuffer().position(0);
        GLES20.glVertexAttribPointer(LightingProgram.getNormalHandle(), NORMAL_DATA_SIZE, GLES20.GL_FLOAT, false, 0, getNormalBuffer());
        GLES20.glEnableVertexAttribArray(LightingProgram.getNormalHandle());
        checkGLError("normals");
    }

    // ============================
    // TEXTURE
    // ============================
    // Set the active texture unit to texture unit 0.
    GLES20.glActiveTexture(GLES20.GL_TEXTURE0);

    // Bind the texture to this unit.
    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, getTextureHandle());

    // Tell the texture uniform sampler to use this texture in the shader by binding to texture unit 0.
    //GLES20.glUniform1i(mTextureUniformHandle, 0);
    GLES20.glUniform1i(LightingProgram.getTextureUniformHandle(), 0);


    getTextureBuffer().position(0);
    GLES20.glVertexAttribPointer(LightingProgram.getTextureCoordinateHandle(), TEXTURE_DATA_SIZE, GLES20.GL_FLOAT, false, 0, getTextureBuffer());
    GLES20.glEnableVertexAttribArray(LightingProgram.getTextureCoordinateHandle());

    // Pass the projection and view transformation to the shader
    GLES20.glUniformMatrix4fv(LightingProgram.getMVPMatrixHandle(), 1, false, MVPMatrix, 0);
    GLES20.glUniformMatrix4fv(LightingProgram.getMVMatrixHandle(), 1, false, pVMatrix, 0);
    GLES20.glUniformMatrix4fv(LightingProgram.getNormalHandle(), 1, false, normalTransposed, 0);

    LightObject lo = mParent.getWorld().getLightObjects().get(0);
    Matrix.multiplyMV(lo.getLightPosInEyeSpace(), 0, pVMatrix, 0, lo.getLightPosInWorldSpace(), 0 );
    GLES20.glUniform3f(LightingProgram.getLightPosHandle(), lo.getLightPosInEyeSpace()[0], lo.getLightPosInEyeSpace()[1], lo.getLightPosInEyeSpace()[2]);

    // Draw the triangle
    GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, mVertexCount);

    // Disable vertex array
  GLES20.glDisableVertexAttribArray(LightingProgram.getPositionHandle());
    GLES20.glDisableVertexAttribArray(LightingProgram.getTextureCoordinateHandle());
    if(LightingProgram.getNormalHandle() != -1)
        GLES20.glDisableVertexAttribArray(LightingProgram.getNormalHandle());
    GLES20.glDisableVertexAttribArray(LightingProgram.getColorHandle());
    checkGLError("end");
}

所以,我更新的顶点着色器代码现在是:

uniform mat4 u_MVPMatrix;       // A constant representing the combined model/view/projection matrix.
uniform mat4 u_MVMatrix;        // A constant representing the combined model/view matrix.
uniform mat4 u_NMatrix;         // combined normal/view matrix ???

attribute vec4 a_Position;      // Per-vertex position information we will pass in.
attribute vec4 a_Color;         // Per-vertex color information we will pass in.
attribute vec3 a_Normal;        // Per-vertex normal information we will pass in.
attribute vec2 a_TexCoordinate; // Per-vertex texture coordinate information we will pass in.

varying vec3 v_Position;        // This will be passed into the fragment shader.
varying vec4 v_Color;           // This will be passed into the fragment shader.
varying vec3 v_Normal;          // This will be passed into the fragment shader.
varying vec2 v_TexCoordinate;   // This will be passed into the fragment shader.

// The entry point for our vertex shader.
void main()
{
    // Transform the vertex into eye space.
    v_Position = vec3(u_MVMatrix * a_Position);

    // Pass through the color.
    v_Color = a_Color;

    // Pass through the texture coordinate.
    v_TexCoordinate = a_TexCoordinate;

    // Transform the normal's orientation into eye space.
    v_Normal = vec3(u_NMatrix * vec4(a_Normal, 0.0)); // THIS does not look right...
    //v_Normal = vec3(u_MVMatrix * vec4(a_Normal, 0.0));

    // gl_Position is a special variable used to store the final position.
    // Multiply the vertex by the matrix to get the final point in normalized screen coordinates.
    gl_Position = u_MVPMatrix * a_Position;
}

我想如果我将模型矩阵反转并转换为普通矩阵,可能已经解决了问题。 但我认为我完全错了..

1 个答案:

答案 0 :(得分:1)

看起来有点乱。缺少某些代码,您需要更频繁地开始评论代码。即使只针对SO问题。

我不确定绘制输入参数是什么,但我可以假设pPMatrix是投影矩阵,pVMatrix是视图矩阵。

然后在代码中有一个奇怪的行:Matrix.translateM(getParent().getModelMatrix(),0,mXLL, mYLL, mZLL);我假设将人移动到当前位置。如果你从形象角度看,我会期望这实际上是视图矩阵的一部分。在任何情况下,此值都不包含在用于灯光的组件中。那么getLightPosInWorldSpace又回来了什么?

如果我们尝试将其分解一下,你会得到一个character,其位置由他的模型矩阵定义。这描述了它在场景中的位置和方向。投影矩阵由视图大小和视野定义。然后,视图矩阵是根据人物方向或从您查看场景的任何位置计算出来的(lookAt过程最常见)。

无论您如何定义所有这些,灯光位置仅取决于人物模型矩阵。因此,您需要将灯光位置(0, 4, 0)乘以角色的模型矩阵。所以这可能是你想要在Matrix.multiplyMV(lo.getLightPosInEyeSpace(), 0, pVMatrix, 0, lo.getLightPosInWorldSpace(), 0 );中做的。

通过执行此操作,您可以在CPU上实际测试灯光位置的结果是否正确,具体取决于角色位置的位置。

现在你需要在着色器中传递(看你使用的是什么)实际上是MVP矩阵和计算光源旁边的模型矩阵。此处不应使用MV矩阵,因为眼睛位置不会影响您的光照效果。

现在v_Position必须是在场景坐标中计算的片段,因此它必须仅与模型矩阵相乘。这基本上会为您提供场景中片段(像素)的坐标,而不是视图。现在使用此位置来获取与灯光的距离,并继续进行计算。

然后你的法线似乎有问题。计算法线不是通过将它们与模型矩阵或模型 - 视图矩阵相乘来完成的。想象一下,您有一个正常(0,1,0)的情况,并将其与一个具有翻译(10, 0, 0)的矩阵相乘;然后得到的法线为(10, 1, 0),即使归一化也没有意义,结果必须仍为(0,1,0),因为没有应用旋转。请研究如何生成矩阵以转换法线,包括所有可能的边缘情况。但是请注意,您可以使用(模型)矩阵的左上角3x3部分来转换它们,包括大多数情况的归一化(对于不应该进行归一化的情况以及模型矩阵未按比例缩放的情况,这会失败)每个轴)。

修改

从理论上来看,你正在处理的是我们通常使用3个矩阵,模型,视图和投影。

投影矩阵定义了屏幕上形状的投影。在您的情况下,它应取决于您的视图比率和您想要显示的视野。它不应该影响照明,形状定位或超出所有这些都映射到屏幕上的任何东西。

视图矩阵通常用于定义您如何观察场景。在你的情况下,你从哪个方向看你的场景。你可能应该使用一个查看程序。此矩阵不会影响对象的光照或任何位置,只会影响对象的外观。

然后,模型矩阵是仅用于定位场景中特定对象的矩阵。我们使用它的原因是你可能只有1个顶点缓冲区用于绘制对象的所有实例。因此,在您的情况下,您有3个立方体,它们应该共享相同的顶点缓冲区,但是在3个不同的位置绘制,因为它们的模型矩阵不同。

现在,您的角色与场景中的任何其他对象没有区别。它有一个顶点缓冲区,它有一个模型矩阵。如果你想从你所拥有的东西换到第一人称视角,你只需要将自然基矢量与字符模型矩阵相乘,然后在观察方法中使用这些矢量来构造一个新的视图矩阵。基本向量可能是location(0,0,0)forward(0,0,1)up(0,1,0)。一旦转换了这些,你就可以构建"中心"为location+forward。通过这样做,你仍然没有区别照明的工作方式或物体的照明方式,你对场景的看法应该没有影响。

因此,您的灯光会通过某个矢量offset(0,4,0)附加到字符偏移上。这意味着场景中的灯光位置是与您的角色模型矩阵相乘的相同矢量,因为该矩阵是定义场景中角色位置的矩阵。它甚至可以被解释为光位置在(0,0,0)并移动到一个字符位置,该位置将其与模型矩阵相乘,然后由offset翻译,因此再次乘以用此创建的平移矩阵向量。这很重要,因为您可以构建此转换矩阵T和旋转矩阵R,它围绕X轴旋转(例如),然后将它们乘以modelMatrix*T*R会使光线围绕你的角色旋转。

因此,假设您拥有所有对象的所有矩阵并且您有一个轻的位置,那么您可以开始查看着色器。您需要构建整个MVP矩阵,因为它是将对象映射到屏幕上的矩阵。所以这个矩阵只用于gl_Position。至于场景中的实际像素,您需要将其与模型矩阵相乘。

然后第一个问题是您还需要转换法线。您需要通过反转然后转置模型矩阵来为它们构造矩阵。 The source。因此,将您的法线乘以此矩阵而不是模型矩阵。

所以现在事情变得非常简单。您已计算出CPU上的灯光位置并将其作为制服发送。你有片段现场位置,你有它的正常。因此,使用这些可以计算片段着色器中已有的光照。

现在,我确实对视图矩阵撒了一点,对照明没有影响。它确实有效,但不是在你的情况下。您没有实现任何光泽,因此不添加此照明组件。但是当(如果)你将添加它时,更容易简单地将你的位置作为另一个制服传递,然后使用视图矩阵来获得相同的结果。

很难从你发布的代码中看出所有内容都存在冲突,但至少看起来光位置转换不正确并且法线转换不正确。

编辑:关于调试

使用openGL时,在调试方面需要具有创造性。看到你的结果仍然有很多可能是错误的。很难在CPU上检查它们或者有一些日志,最好的方法通常是修改着色器,以获得结果,为您提供额外的信息。

调试场景中的片段位置:

如前所述,场景位置中的片段不受您所看到的透视影响。所以它不应该取决于视图或投影矩阵。在片段着色器中,这是存储在v_Position中的值。

您需要设置要测试的边界,这取决于您的场景大小(放置墙壁和立方体的位置......)。

你说你的墙被偏移25,所以可以安全地假设你的场景将在[-30,30]的范围内。你需要分别调试每个轴,例如让我们在-25处取红色值,在25处取绿色值。要测试Y坐标(通常是高度),你只需使用gl_FragColor = vec4(1.0-(v_Position.y+30)/60, (v_Position.y+30)/60), 0.0, 1.0)。这应该在所有对象上显示一个漂亮的渐变,其中Y值越低颜色越红。然后你对其他2个组件做同样的事情,对于每个组件,你应该在每个方向都得到这些漂亮的渐变。渐变应该在整个场景中同样可见,而不仅仅是每个对象。

调试场景中的法线:

法线必须在对象之间保持一致,具体取决于它们面对的方式。由于您的案例中的所有对象都与轴平行并进行了规范化,因此这应该非常简单。您期望只有6个可能的值,因此2次通过应该完成工作(正面和负面)。

gl_FragColor = vec4(max(v_Normal.x, 0.0), max(v_Normal.y, 0.0), max(v_Normal.z, 0.0), 1.0)用于正常法线。这会将所有面临正面X的面部显示为红色,所有面临正面Y绿色并且所有面临正面Z蓝色。

第二个测试是gl_FragColor = vec4(max(-v_Normal.x, 0.0), max(-v_Normal.y, 0.0), max(-v_Normal.z, 0.0), 1.0),对于那些面对负坐标的人来说,这个测试完全相同。

调试灯光位置:

当第一次测试通过时,可以通过仅照亮近处的物体来测试光位置。所以length(u_LightPos - v_Position.xyz)会显示光线距离。您必须将其标准化,以便在场景中使用highp float scale = 1.0 - length(u_LightPos - v_Position.xyz)/50.0,然后在颜色中使用它作为gl_FragColor = vec4(比例,比例,比例,1.0)。这将使所有物体靠近浅白色,而那些非常远的黑色物体。应该显示一个漂亮的渐变。

这些测试的重点:

您这样做是因为您当前的结果取决于多个值,其中所有值都可能被窃听。通过隔离问题,您可以轻松找到问题所在。

第一个测试会丢弃法线和灯光位置,因此如果它不正确,则只表示您将位置乘以错误的矩阵。您需要确保在计算v_Position时使用模型矩阵乘以位置。

第二个测试丢弃场景中的位置和灯光位置。您仍然可以看到您的场景,但颜色将仅由您的法线定义。如果结果不正确,则要么在开始时使用了错误的法线,要么在使用普通矩阵时它们被错误地转换。你甚至可以禁用正常的矩阵乘法,看看哪两个是不正确的。如果禁用不能解决问题,则顶点缓冲区中的法线不正确。

第三个测试会丢弃您的法线和计算光照效果的逻辑。第一次测试必须通过,因为我们仍然需要模型的位置。因此,如果此测试失败,您很可能无法正确定位灯光。其他可能性是法线不正确(你有测试),第三个是你不正确地计算光效。