You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

实例化绘制球体绕Y轴旋转时法向量导致光照异常问题

实例化绘制球体旋转后光照异常问题

我采用实例化绘制(instanced drawing)方式渲染球体,同时将模型视图矩阵绕Y轴旋转。
初始状态下显示正常:
初始显示效果
但旋转到另一角度时显示效果异常:
异常显示效果

初步判断是法向量相关问题,当前我从模型视图矩阵计算得到法向量矩阵后传入着色器,实现类Phong光照,相关代码如下:

顶点着色器代码

attribute vec4 a_position;
attribute vec3 a_normal;
attribute vec4 a_color;
attribute vec2 a_coord;
attribute mat4 a_matrix;

uniform mat4 u_mv_matrix;
uniform mat4 u_projection_matrix;
uniform mat3 u_normal_matrix;

varying vec4 v_position;
varying vec3 v_normal;
varying vec4 v_color;
varying vec2 v_coord;

void main() {
  vec4 transformedPosition = u_mv_matrix * a_matrix * a_position;

  v_position = transformedPosition;
  v_normal = u_normal_matrix * a_normal;
  v_color = a_color;
  v_coord = a_coord;

  gl_Position = u_projection_matrix * transformedPosition;
}

片元着色器代码

uniform sampler2D u_sampler;

varying vec4 v_position;
varying vec3 v_normal;
varying vec4 v_color;
varying vec2 v_coord;

void main() {
  vec3 lightPosition = vec3(0.0); // XXX

  // set diffuse and specular colors
  vec3 cDiffuse = (v_color * texture2D(u_sampler, v_coord)).rgb;
  vec3 cSpecular = vec3(0.3);

  // lighting calculations
  vec3 N = normalize(v_normal);
  vec3 L = normalize(lightPosition - v_position.xyz);
  vec3 E = normalize(-v_position.xyz);
  vec3 H = normalize(L + E);

  // Calculate coefficients.
  float phong = max(dot(N, L), 0.0);

  const float kMaterialShininess = 20.0;
  const float kNormalization = (kMaterialShininess + 8.0) / (3.14159265 * 8.0);
  float blinn = pow(max(dot(N, H), 0.0), kMaterialShininess) * kNormalization;

  // diffuse coefficient
  vec3 diffuse = phong * cDiffuse;

  // specular coefficient
  vec3 specular = blinn * cSpecular;

  gl_FragColor = vec4(diffuse + specular, 1);
}

基础信息

  • 运行环境:桌面端OpenGL 2.1、浏览器端WebGL
  • 光源位置:在片元着色器中定义为vec3(0)
  • 上层逻辑使用自研框架实现,网格构建、变换处理等能力经过验证运行正常

补充实现细节

网格构建逻辑

构建时传入单位矩阵,代码如下:

void Sphere::append(IndexedVertexBatch<XYZ.N.UV> &batch, const Matrix &matrix) const {
  float sectorStep = TWO_PI / sectorCount;
  float stackStep = PI / stackCount;

  for(int i = 0; i <= stackCount; ++i) {
    float stackAngle = HALF_PI - i * stackStep;
    float xy = radius * cosf(stackAngle);
    float z = radius * sinf(stackAngle);

    for(int j = 0; j <= sectorCount; ++j) {
      float sectorAngle = j * sectorStep;

      float x = xy * cosf(sectorAngle);
      float y = xy * sinf(sectorAngle);

      float nx = x / radius;
      float ny = y / radius;
      float nz = z / radius;

      float s = (float)j / sectorCount;
      float t = (float)i / stackCount;

      batch.addVertex(matrix.transformPoint(x, y, z), matrix.transformNormal(nx, ny, nz), glm::vec2(s, t));
    }
  }

  for(int i = 0; i < stackCount; ++i) {
    float k1 = i * (sectorCount + 1);
    float k2 = k1 + sectorCount + 1;

    for(int j = 0; j < sectorCount; ++j, ++k1, ++k2) {
      if (i != 0) {
        if (frontFace == CCW) {
          batch.addIndices(k1, k1 + 1, k2);
        } else {
          batch.addIndices(k1, k2, k1 + 1);
        }
      }

      if (i != (stackCount - 1)) {
        if (frontFace == CCW) {
          batch.addIndices(k1 + 1, k2 + 1, k2);
        } else {
          batch.addIndices(k1 + 1, k2, k2 + 1);
        }
      }
    }
  }
}

变换矩阵处理逻辑

camera.getMVMatrix()
  .setIdentity()
  .translate(0, -150, -600)
  .rotateY(clock()->getTime() * 0.5f);

State()
  .setShader(shader)
  .setShaderMatrix<MV>(camera.getMVMatrix())
  .setShaderMatrix<PROJECTION>(camera.getProjectionMatrix())
  .setShaderMatrix<NORMAL>(camera.getNormalMatrix())
  .apply();

问题更新

原使用的着色器光照部分逻辑存在错误,已更换为其他实现方式。最终的解决方法可在避免非均匀缩放的前提下,解决实例化绘制场景下的法向量问题。

内容的提问来源于stack exchange,提问作者Ariel Malka

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.25 08:24:01