【问题标题】:Make light independent from the view in a Phong model使光独立于 Phong 模型中的视图
【发布时间】:2020-01-04 10:40:50
【问题描述】:

我正在尝试实现 Phong 着色模型,但遇到了一些很奇怪的事情。当我改变观察位置时,看起来灯光表现不同,就好像它依赖于视图一样。比如,如果我靠近物体,我只会看到环境光的效果,而如果我远离它,我会开始看到漫反射的影响。

这些是我的着色器:

//Vertex Shader

attribute vec4 vPosition;
attribute vec4 vNormal;
varying vec3 N, L, E;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
uniform vec4 lightPosition;

void main()
{
    vec3 pos = -(modelViewMatrix * vPosition).xyz;
    vec3 light = lightPosition.xyz;
    L = normalize(light - pos);
    E = -pos;
    N = normalize((modelViewMatrix * vNormal).xyz);
    gl_Position = projectionMatrix * modelViewMatrix * vPosition;
}
//Fragment Shader

uniform vec4 ambientProduct;
uniform vec4 diffuseProduct;
uniform vec4 specularProduct;
uniform float shininess;
varying vec3 N, L, E;

void main()
{
    vec4 fColor;
    vec3 H = normalize(L + E);
    vec4 ambient = ambientProduct;
    float Kd = max(dot(L, N), 0.0);
    vec4 diffuse = Kd * diffuseProduct;
    float Ks = pow(max(dot(N, H), 0.0), shininess);
    vec4 specular = Ks * specularProduct;
    if (dot(L, N) < 0.0) {
        specular = vec4(0.0, 0.0, 0.0, 1.0);
    }
    fColor = ambient + diffuse + specular;
    fColor.a = 1.0;
    gl_FragColor = fColor;
}

我做错了什么?如何使灯光的行为独立于观察者的位置?

更新 1:

在@Rabbid76 的回答之后,我通过添加这些行来编辑顶点着色器(以及传递单独的模型和视图矩阵,但为了简洁起见,我将省略它):

    vec3 pos = (modelViewMatrix * vPosition).xyz;
    vec3 light = (viewMatrix * lightPosition).xyz;

我还更新了 N 向量的计算,因为以前的做法似乎实际上不允许每个片段着色:

    N = normalize(mat3(modelViewMatrix) * vNormal.xyz);

不过,阴影似乎随着相机的旋转而移动。这可能与我猜的光乘以 viewMatrix 的事实有关?

【问题讨论】:

标签: glsl webgl lighting phong


【解决方案1】:

光矢量计算错误。

L = normalize(light - pos);

pos 是视图空间中的位置,light 是世界空间中的位置。 light 是光在世界上的位置。所以light - pos 根本没有任何意义。两个向量必须与相同的参考系统相关。

通过视图矩阵变换光源的位置,然后将其设置为统一的lightPosition,以解决问题。

当然也可以在着色器代码中进行转换:

uniform mat4 viewMatrix;
uniform mat4 modelViewMatrix;
uniform vec4 lightPosition;

void main()
{
    vec3 pos   = (modelViewMatrix * vPosition).xyz;
    vec3 light = (viewMatrix * lightPosition).xyz;
    L = normalize(light - pos);

    // ...
}

进一步注意,视图空间中的位置不能倒置。必须是

vec3 pos = (modelViewMatrix * vPosition).xyz; 

而不是

vec3 pos = -(modelViewMatrix * vPosition).xyz;

【讨论】:

  • 我还是有一些奇怪的效果,灯光似乎仍然随着视图旋转
  • 注意,镜面高光取决于观察方向。
  • 感谢您指出这一点,但奇怪的是,随着镜面反射高光,阴影也会随着视图的方向移动!
  • 我已经用这些修复和另一个我制作的问题编辑了这个问题,因为没有它我无法获得逐片段着色
  • @memememe 分别见 Lambertian diffuse Phong reflection。可能您对更复杂的light model 感兴趣
【解决方案2】:

在您的问题中工作的snippet 总是有帮助的!

问题

  1. 灯光和位置需要在同一个空间。

    它们可以是世界空间或视图空间,但它们必须是相同的空间。

    代码在视图空间中的位置为E,但在世界空间中的位置为lightPosition

  2. 您不能将法线乘以 modelViewMatrix

    您需要删除翻译。您还可能需要处理 缩放问题。见this article

  3. 代码正在计算顶点着色器中的值,因此当它们被传递到片段着色器时将对其进行插值。这意味着它们将不再是单位向量,因此您需要重新规范化它们。

  4. 在计算半向量时,您需要添加它们的方向

    代码将 L(从表面到灯光的方向)添加到表面的视图位置,而不是从表面到视图的方向。

  5. 在计算表面到光的方向时,该方向将是light - pos,但代码否定了pos。当然,您还需要 pos 为负值才能使表面查看方向 E

const gl = document.querySelector('canvas').getContext('webgl');
const m4 = twgl.m4;

const vs = `
attribute vec4 vPosition;
attribute vec4 vNormal;
varying vec3 N, L, E;
uniform mat4 viewMatrix;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
uniform vec4 lightPosition;

void main()
{
    vec3 pos = (modelViewMatrix * vPosition).xyz;
    vec3 light = (viewMatrix * lightPosition).xyz;
    L = light - pos;
    E = -pos;
    N = mat3(modelViewMatrix) * vNormal.xyz;
    gl_Position = projectionMatrix * modelViewMatrix * vPosition;
}
`;

const fs = `
precision highp float;

uniform vec4 ambientProduct;
uniform vec4 diffuseProduct;
uniform vec4 specularProduct;
uniform float shininess;
varying vec3 N, L, E;

void main()
{
    vec4 fColor;
    vec3 normal = normalize(N);
    vec3 surfaceToLightDir = normalize(L);
    vec3 surfaceToViewDir = normalize(E);
    vec3 H = normalize(surfaceToLightDir + surfaceToViewDir);
    vec4 ambient = ambientProduct;
    float Kd = max(dot(surfaceToLightDir, normal), 0.0);
    vec4 diffuse = Kd * diffuseProduct;
    float Ks = pow(max(dot(normal, H), 0.0), shininess);
    vec4 specular = Ks * specularProduct;
    if (dot(surfaceToLightDir, normal) < 0.0) {
        specular = vec4(0.0, 0.0, 0.0, 1.0);
    }
    fColor = ambient + diffuse + specular;
    fColor.a = 1.0;
    gl_FragColor = fColor;
}
`;

// compiles shaders, links program, looks up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
const vertices = twgl.primitives.createSphereVertices(
    2, // radius
    8, // subdivision around
    6, // subdivisions down
);
// calls gl.createBuffer, gl.bindBuffer, gl.bufferData
const bufferInfo = twgl.createBufferInfoFromArrays(gl, {
  vPosition: vertices.position,
  vNormal: vertices.normal,
  indices: vertices.indices,
});

function render(time) {
  time *= 0.001;  // convert to seconds
  
  gl.enable(gl.DEPTH_TEST);
  gl.enable(gl.CULL_FACE);
  
  gl.useProgram(programInfo.program);

  // calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
  twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
  
  const projectionMatrix = m4.perspective(
      60 * Math.PI / 180,  // field of view
      gl.canvas.clientWidth / gl.canvas.clientHeight,  // aspect
      0.1,  // znear
      100,  // zfar
  );
  
  const eye = [
    Math.sin(time) * 5, 
    3, 
    3 + Math.cos(time) * 5,
  ];
  const target = [0, 2, 3];
  const up = [0, 1, 0];
  const cameraMatrix = m4.lookAt(eye, target, up);
  const viewMatrix = m4.inverse(cameraMatrix);
  
  const worldMatrix = m4.translation([0, 2, 3]);
  
  const modelViewMatrix = m4.multiply(viewMatrix, worldMatrix);

  const uniforms = {
    viewMatrix,
    modelViewMatrix,
    projectionMatrix,
    lightPosition: [4, 3, 1, 1],
    ambientProduct: [0, 0, 0, 1], 
    diffuseProduct: [1, 1, 1, 1],
    specularProduct: [1, 1, 1, 1],
    shininess: 50,
  };

  // calls gl.uniformXXX
  twgl.setUniforms(programInfo, uniforms);

  // calls gl.drawArrays or gl.drawElements
  twgl.drawBufferInfo(gl, bufferInfo);
  
  // -- not important to answer --
  drawLightAndGrid(uniforms)  

  requestAnimationFrame(render);
}
requestAnimationFrame(render);

// -- ignore below this line. The only point is to give a frame
// of reference.
let gridBufferInfo;
function drawLightAndGrid(sphereUniforms) {
  if (!gridBufferInfo) {
    const vPosition = [];
    const s = 100;
    for (let x = -s; x <= s; x += 2) {
      vPosition.push(x, 0, -s);
      vPosition.push(x, 0,  s);
      vPosition.push(-s, 0, x);
      vPosition.push( s, 0, x);
    }
    gridBufferInfo = twgl.createBufferInfoFromArrays(gl, {
      vPosition,
      vNormal: { value: [0, 0, 0], }
    });
  }
  
  const worldMatrix = m4.translation(sphereUniforms.lightPosition);
  m4.scale(worldMatrix, [0.1, 0.1, 0.1], worldMatrix);
  const uniforms = Object.assign({}, sphereUniforms, {
    modelViewMatrix: m4.multiply(sphereUniforms.viewMatrix, worldMatrix),
    ambientProduct: [1, 0, 0, 1],
    diffuseProduct: [0, 0, 0, 0],
    specularProduct: [0, 0, 0, 0],
  });
  
  twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
  twgl.setUniforms(programInfo, uniforms);
  twgl.drawBufferInfo(gl, bufferInfo);
  twgl.setBuffersAndAttributes(gl, programInfo, gridBufferInfo);
  twgl.setUniforms(programInfo, {
    modelViewMatrix: sphereUniforms.viewMatrix,
    ambientProduct: [0, 0, 1, 1],
  });
  twgl.drawBufferInfo(gl, gridBufferInfo, gl.LINES);
}
canvas { border: 1px solid black }
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>

就我个人而言,我发现很难理解简短的神秘变量名称,但这是个人喜好。

【讨论】:

  • 您不能将法线乘以模型视图矩阵。您需要删除翻译。 请问为什么?我们必须以某种方式将法线与对象一起移动,不是吗?
  • 不,法线只是一个方向。如果您在 X 中将立方体平移 15 个单位,则法线本身仍然指向相同的方向。如果您为每个法线添加 +15,则归一化那些面向左侧的法线 (-1,0,0) 现在将面向 normalize(-1 + 15, 0, 0) = 1,0,0。删除平移的一种方法是采用 modelViewMatrix 的上部 3x3 子矩阵。另一种是在乘法之前将normal.w 设置为0。这仍然留下了缩放问题。见this article
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-01-11
  • 1970-01-01
  • 2013-06-07
  • 1970-01-01
  • 2014-07-30
  • 1970-01-01
  • 2019-03-17
相关资源
最近更新 更多