1

我正在尝试实现 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 的事实有关?

4

2 回答 2

1

光矢量的计算是错误的。

L = normalize(light - pos);

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

通过视图矩阵变换光源的位置,然后将其设置为 uniform 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;
于 2019-08-31T15:45:36.253 回答
1

您问题中的工作片段总是有帮助的!

问题

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

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

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

  2. 您不能将法线乘以modelViewMatrix

    您需要删除翻译。您还可能需要处理扩展问题。看这篇文章

  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>

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

于 2019-09-01T02:14:49.277 回答