在 OpenGL ES2 着色器中计算顶点法线

Computing vertex normal in OpenGL ES2 shader

我从 Google Poly 抓取的 obj 模型的光照有一些问题。在这个模型中,定义了一堆没有法线的面。因此,我用于漫反射颜色的计算返回 0。如果未提供,是否有任何方法可以估计法线,或者我是否需要解析 obj 并手动注入法线?

这是我的片段着色器:

precision mediump float;

uniform bool u_UseTexAmbient;
uniform bool u_UseTexDiffuse;
uniform bool u_UseTexSpecular;

uniform sampler2D u_TexAmbient;
uniform sampler2D u_TexDiffuse;
uniform sampler2D u_TexSpecular;

uniform vec3 u_MtlAmbient;
uniform vec3 u_MtlDiffuse;
uniform vec3 u_MtlSpecular;

uniform float u_D;
uniform float u_SpecularPower;

uniform vec4 u_LightingParameters;

varying vec3 v_ViewPosition;
varying vec3 v_ViewNormal;
varying vec2 v_TexCoord;

vec4 getColor(bool useTex, sampler2D tex, vec3 mtl);
vec3 computeDiffuse(vec3 viewNormal, vec3 rawDiffuseColor);
vec3 computeSpecular(vec3 viewNormal, vec3 viewFragmentDirection, vec3 rawSpecularColor);

void main() {
    const float kGamma = 0.4545454;
    vec3 viewFragmentDirection = normalize(v_ViewPosition);
    vec3 viewNormal = normalize(v_ViewNormal);

    vec4 rawAmbientColor = getColor(u_UseTexAmbient, u_TexAmbient, u_MtlAmbient);
    vec4 rawDiffuseColor = getColor(u_UseTexDiffuse, u_TexDiffuse, u_MtlDiffuse);
    vec4 rawSpecularColor = getColor(u_UseTexSpecular, u_TexSpecular, u_MtlSpecular);

    vec3 diffuse = computeDiffuse(viewNormal, rawDiffuseColor.rgb);
    vec3 specular = computeSpecular(viewNormal, viewFragmentDirection, rawSpecularColor.rgb);

    gl_FragColor.a = rawDiffuseColor.a * u_D;
    gl_FragColor.rgb = pow(rawAmbientColor.rgb + diffuse + specular, vec3(kGamma));
}

vec4 getColor(bool useTex, sampler2D tex, vec3 mtl) {
    const float kInverseGamma = 2.2;
    vec4 color;
    if (useTex) {
        color = texture2D(tex, vec2(v_TexCoord.x, 1.0 - v_TexCoord.y));
    } else {
        color = vec4(mtl.r, mtl.g, mtl.b, 1.0);
    }
    color.rgb = pow(color.rgb, vec3(kInverseGamma));
    return color;
}

vec3 computeDiffuse(vec3 viewNormal, vec3 rawDiffuseColor) {
    vec3 diffuse = u_LightingParameters.w * rawDiffuseColor
            * max(dot(u_LightingParameters.xyz, viewNormal), 0.0);
    return clamp(diffuse, 0.0, 1.0);
}

vec3 computeSpecular(vec3 viewNormal, vec3 viewFragmentDirection, vec3 rawSpecularColor) {
    vec3 reflectedLightDirection = reflect(u_LightingParameters.xyz, viewNormal);
    float specularStrength = max(0.0, dot(viewFragmentDirection, reflectedLightDirection));
    vec3 specular = u_LightingParameters.w * rawSpecularColor *
        pow(specularStrength, u_SpecularPower);
    return clamp(specular, 0.0, 1.0);
}

这个顶点着色器取自 Google 的 ARCore 示例:

uniform mat4 u_ModelView;
uniform mat4 u_ModelViewProjection;

attribute vec4 a_Position;
attribute vec3 a_Normal;
attribute vec2 a_TexCoord;

varying vec3 v_ViewPosition;
varying vec3 v_ViewNormal;
varying vec2 v_TexCoord;

void main() {
    v_ViewPosition = (u_ModelView * a_Position).xyz;
    v_ViewNormal = normalize((u_ModelView * vec4(a_Normal, 0.0)).xyz);
    v_TexCoord = a_TexCoord;
    gl_Position = u_ModelViewProjection * a_Position;
}

基本上我所看到的是某些模型无法为我提供 a_Normal,因此我希望能够在顶点着色器中以某种方式生成 v_ViewNormal 而无需它。这甚至可能吗?

这是完整模型的 link:https://poly.google.com/view/eydI4__jXpi

一种可能性是在片段着色器中通过使用偏导数函数计算每个片段的法向量dFdx, dFdy

请注意,在 OpenGL ES 2.0 中,需要 OES_standard_derivatives 扩展,因为自 OpenGL ES 3.0 以来,派生函数才成为标准。

OpenGL ES Shading Language 3.00 Specification; 8.9 Fragment Processing Functions; page 104:

genType dFdx (genType p)

Returns the derivative in x using local differencing for the input argument p.

genType dFdy (genType p)

Returns the derivative in y using local differencing for the input argument p.


代码可能看起来像这样:

#extension GL_OES_standard_derivatives : enable

varying vec3 view_pos;

void main
{
    vec3 dX     = dFdx(view_pos);
    vec3 dY     = dFdy(view_pos);
    vec3 normal = normalize(cross(dX, dY));

    ....
}


进一步查看 An introduction to shader derivative functions - Face normal computation (flat shader)