WebGL 计算点光源下的漫反射光颜色

news2024/12/26 23:29:50

目录

点光源光

逐顶点光照(插值)

示例程序(PointLightedCube.js)  

代码详解

示例效果

逐顶点处理点光源光照效果时出现的不自然现象

更逼真:逐片元光照

示例程序(PointLightedCube_perFragment.js)

代码详解


点光源光

与平行光相比,点光源光发出的光,在三维空间的不同位置上其方向也不同,如下图所示。所以,在对点光源光下的物体进行着色时,需要在每个入射点计算点光源光在该处的方向。

点光源光的方向随位置变化

平行光漫反射https://blog.csdn.net/dabaooooq/article/details/132942385?spm=1001.2014.3001.5502WebGL 计算平行光、环境光下的漫反射光颜色_山楂树の的博客-CSDN博客https://blog.csdn.net/dabaooooq/article/details/132942385?spm=1001.2014.3001.5502根据每个顶点的法向量和平行光入射方向来计算反射光的颜色。这一节还是采用该方法,只不过点光源光的方向不再是恒定不变的,而要根据每个顶点的位置逐一计算。着色器需要知道点光源光自身的所在位置,而不是光的方向。 

示例程序PointLightedCube是WebGL 计算平行光、环境光下的漫反射光颜色_山楂树の的博客-CSDN博客LightedCube_ambient示例程序的点光源光版本,显示了一个点光源下的红色立方体。立方体表面仍然是漫反射,环境光保持不变,程序的效果如下图所示。

 

逐顶点光照(插值)

示例程序(PointLightedCube.js)  

如下显示了示例程序的代码,与WebGL 计算平行光、环境光下的漫反射光颜色_山楂树の的博客-CSDN博客LightedCube_ambient相比,顶点着色器中新增加了u_ModelMatrix变量和u_LightPosition变量,前者表示模型矩阵,后者表示点光源的位置。本例中的光是点光源光而非平行光,所以我们需要用到点光源光的位置,而不是光线方向。为了让你看得更清楚,本例将立方体稍做放大。

var VSHADER_SOURCE =
  'attribute vec4 a_Position;\n' +
  'attribute vec4 a_Color;\n' +
  'attribute vec4 a_Normal;\n' +
  'uniform mat4 u_MvpMatrix;\n' +
  'uniform mat4 u_ModelMatrix;\n' +   // 模型矩阵
  'uniform mat4 u_NormalMatrix;\n' +  // 用来变换法向量的矩阵
  'uniform vec3 u_LightColor;\n' +    // 光的颜色
  'uniform vec3 u_LightPosition;\n' + // 光源位置(世界坐标系)
  'uniform vec3 u_AmbientLight;\n' +  // 环境光颜色
  'varying vec4 v_Color;\n' +
  'void main() {\n' +
  '  gl_Position = u_MvpMatrix * a_Position;\n' +
     //计算变换后的法向量并归一化
  '  vec3 normal = normalize(vec3(u_NormalMatrix * a_Normal));\n' +
     // 计算顶点的世界坐标(模型矩阵变换后的世界坐标)
  '  vec4 vertexPosition = u_ModelMatrix * a_Position;\n' +
     // 计算点光源方向并归一化(用点光源的世界坐标 - 物体顶点坐标)
  '  vec3 lightDirection = normalize(u_LightPosition - vec3(vertexPosition));\n' +
     // 计算光线方向和法向量的点积 cosθ
  '  float nDotL = max(dot(lightDirection, normal), 0.0);\n' +
     // 计算点光源漫反射光的颜色
  '  vec3 diffuse = u_LightColor * a_Color.rgb * nDotL;\n' +
     // 计算环境光产生的反射光的颜色
  '  vec3 ambient = u_AmbientLight * a_Color.rgb;\n' +
     // 将以上两者相加作为最终的颜色(物体表面的反射光颜色 = 漫反射光颜色(这里是点光源) + 环境反射光颜色)
  '  v_Color = vec4(diffuse + ambient, a_Color.a);\n' + 
  '}\n';

var FSHADER_SOURCE =
  '#ifdef GL_ES\n' +
  'precision mediump float;\n' +
  '#endif\n' +
  'varying vec4 v_Color;\n' +
  'void main() {\n' +
  '  gl_FragColor = v_Color;\n' +
  '}\n';

function main() {
  var canvas = document.getElementById('webgl');
  var gl = getWebGLContext(canvas);
  if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) return
  var n = initVertexBuffers(gl);
  gl.clearColor(0.0, 0.0, 0.0, 1.0);
  gl.enable(gl.DEPTH_TEST);

  // 获取统一变量的存储位置等等
  var u_ModelMatrix = gl.getUniformLocation(gl.program, 'u_ModelMatrix');
  var u_MvpMatrix = gl.getUniformLocation(gl.program, 'u_MvpMatrix');
  var u_NormalMatrix = gl.getUniformLocation(gl.program, 'u_NormalMatrix');
  var u_LightColor = gl.getUniformLocation(gl.program, 'u_LightColor');
  var u_LightPosition = gl.getUniformLocation(gl.program, 'u_LightPosition');
  var u_AmbientLight = gl.getUniformLocation(gl.program, 'u_AmbientLight');

  // 设置点光源的颜色为白色
  gl.uniform3f(u_LightColor, 1.0, 1.0, 1.0);
  // 设置点光源的位置(世界坐标下)
  gl.uniform3f(u_LightPosition, 2.3, 4.0, 3.5);
  // 设置环境光的颜色
  gl.uniform3f(u_AmbientLight, 0.2, 0.2, 0.2);

  var modelMatrix = new Matrix4();  // 模型矩阵
  var mvpMatrix = new Matrix4();    // 模型视图投影矩阵
  var normalMatrix = new Matrix4(); // 用于计算变换后的法向量的矩阵

  // 旋转90度,计算模型矩阵
  modelMatrix.setRotate(90, 0, 1, 0);
  // 将模型矩阵传递给u_ModelMatrix
  gl.uniformMatrix4fv(u_ModelMatrix, false, modelMatrix.elements);

  // 计算模型视图投影矩阵
  mvpMatrix.setPerspective(30, canvas.width/canvas.height, 1, 100); 
  mvpMatrix.lookAt(6, 6, 14, 0, 0, 0, 0, 1, 0);
  mvpMatrix.multiply(modelMatrix); // 模型 视图投影 相乘得到最终矩阵
  gl.uniformMatrix4fv(u_MvpMatrix, false, mvpMatrix.elements);  // 将模型视图投影矩阵传给u_MvpMatrix变量

  /* 根据模型矩阵计算逆转置矩阵以变换法线 */ 
  normalMatrix.setInverseOf(modelMatrix); // 求原矩阵的逆矩阵
  normalMatrix.transpose(); // 将上一步求得的逆矩阵进行转置,并将自己设为转置后的结果
  gl.uniformMatrix4fv(u_NormalMatrix, false, normalMatrix.elements);   // 将用来变换法向量的矩阵传给u_NormalMatrix变量

  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
  gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0);
}

function initVertexBuffers(gl) {
  // Create a cube
  //    v6----- v5
  //   /|      /|
  //  v1------v0|
  //  | |     | |
  //  | |v7---|-|v4
  //  |/      |/
  //  v2------v3
  // Coordinates
  var vertices = new Float32Array([
     2.0, 2.0, 2.0,  -2.0, 2.0, 2.0,  -2.0,-2.0, 2.0,   2.0,-2.0, 2.0, // v0-v1-v2-v3 front
     2.0, 2.0, 2.0,   2.0,-2.0, 2.0,   2.0,-2.0,-2.0,   2.0, 2.0,-2.0, // v0-v3-v4-v5 right
     2.0, 2.0, 2.0,   2.0, 2.0,-2.0,  -2.0, 2.0,-2.0,  -2.0, 2.0, 2.0, // v0-v5-v6-v1 up
    -2.0, 2.0, 2.0,  -2.0, 2.0,-2.0,  -2.0,-2.0,-2.0,  -2.0,-2.0, 2.0, // v1-v6-v7-v2 left
    -2.0,-2.0,-2.0,   2.0,-2.0,-2.0,   2.0,-2.0, 2.0,  -2.0,-2.0, 2.0, // v7-v4-v3-v2 down
     2.0,-2.0,-2.0,  -2.0,-2.0,-2.0,  -2.0, 2.0,-2.0,   2.0, 2.0,-2.0  // v4-v7-v6-v5 back
  ]);

  // Colors
  var colors = new Float32Array([
    1, 0, 0,   1, 0, 0,   1, 0, 0,  1, 0, 0,     // v0-v1-v2-v3 front
    1, 0, 0,   1, 0, 0,   1, 0, 0,  1, 0, 0,     // v0-v3-v4-v5 right
    1, 0, 0,   1, 0, 0,   1, 0, 0,  1, 0, 0,     // v0-v5-v6-v1 up
    1, 0, 0,   1, 0, 0,   1, 0, 0,  1, 0, 0,     // v1-v6-v7-v2 left
    1, 0, 0,   1, 0, 0,   1, 0, 0,  1, 0, 0,     // v7-v4-v3-v2 down
    1, 0, 0,   1, 0, 0,   1, 0, 0,  1, 0, 0     // v4-v7-v6-v5 back
 ]);

  // Normal
  var normals = new Float32Array([
    0.0, 0.0, 1.0,   0.0, 0.0, 1.0,   0.0, 0.0, 1.0,   0.0, 0.0, 1.0,  // v0-v1-v2-v3 front
    1.0, 0.0, 0.0,   1.0, 0.0, 0.0,   1.0, 0.0, 0.0,   1.0, 0.0, 0.0,  // v0-v3-v4-v5 right
    0.0, 1.0, 0.0,   0.0, 1.0, 0.0,   0.0, 1.0, 0.0,   0.0, 1.0, 0.0,  // v0-v5-v6-v1 up
   -1.0, 0.0, 0.0,  -1.0, 0.0, 0.0,  -1.0, 0.0, 0.0,  -1.0, 0.0, 0.0,  // v1-v6-v7-v2 left
    0.0,-1.0, 0.0,   0.0,-1.0, 0.0,   0.0,-1.0, 0.0,   0.0,-1.0, 0.0,  // v7-v4-v3-v2 down
    0.0, 0.0,-1.0,   0.0, 0.0,-1.0,   0.0, 0.0,-1.0,   0.0, 0.0,-1.0   // v4-v7-v6-v5 back
  ]);

  // Indices of the vertices
  var indices = new Uint8Array([
     0, 1, 2,   0, 2, 3,    // front
     4, 5, 6,   4, 6, 7,    // right
     8, 9,10,   8,10,11,    // up
    12,13,14,  12,14,15,    // left
    16,17,18,  16,18,19,    // down
    20,21,22,  20,22,23     // back
 ]);

  // 将顶点属性写入缓冲区(坐标、颜色和法线)
  if (!initArrayBuffer(gl, 'a_Position', vertices, 3, gl.FLOAT)) return -1;
  if (!initArrayBuffer(gl, 'a_Color', colors, 3, gl.FLOAT)) return -1;
  if (!initArrayBuffer(gl, 'a_Normal', normals, 3, gl.FLOAT)) return -1;
  gl.bindBuffer(gl.ARRAY_BUFFER, null);
  var indexBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
  gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
  return indices.length;
}

function initArrayBuffer(gl, attribute, data, num, type) {
  var buffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
  gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
  var a_attribute = gl.getAttribLocation(gl.program, attribute);
  gl.vertexAttribPointer(a_attribute, num, type, false, 0, 0);
  gl.enableVertexAttribArray(a_attribute);
  return true;
}

代码详解

最关键的变化发生在顶点着色器中。首先使用模型矩阵变换顶点坐标,获得顶点在世界坐标系中的坐标(即变换后的坐标),以便计算点光源光在顶点处的方向。点光源向四周放射光线,所以顶点处的光线方向是由点光源光坐标减去顶点坐标而得到的矢量。点光源在世界坐标系中的坐标已经传给了着色器中的u_LightPosition(第9行),而前面也已经算出了顶点在世界坐标系中的坐标,这样就计算出了光线方向矢量lightDirection(第19行)。注意,需要使用normalize()函数进行归一化,以保证光线方向矢量的长度为1.0。最后,计算光线方向矢量与法向量的点积(第21行),从而算出每个顶点的颜色。

运行程序,你会发现效果更加逼真了,如下图所示。但是,如果仔细观察还是能发现一个问题:立方体表面上有不自然的线条

示例效果

逐顶点处理点光源光照效果时出现的不自然现象

出现该现象的原因:你应该还记得,WebGL系统会根据顶点的颜色,内插出表面上每个片元的颜色。实际上,点光源光照射到一个表面上,所产生的效果(即每个片元获得的颜色)与简单使用4个顶点颜色(虽然这4个顶点的颜色也是由点光源产生)内插出的效果并不完全相同(在某些极端情况下甚至很不一样),所以为了使效果更加逼真,我们需要对表面的每一点(而不仅仅是4个顶点)计算光照效果。如果使用一个球体,二者的差异可能会更明显,如下图所示。

点光源下的球体

如你所见,左图中球体暗部与亮部的分界不是很自然,而右侧的就自然多了。

更逼真:逐片元光照

乍一听,要在表面的每一点上计算光照产生的颜色,似乎是个不可能完成的任务。但实际上,我们只需要逐片元地进行计算。片元着色器总算要派上用场了。

示例程序(PointLightedCube_perFragment.js)

如下显示了示例程序的代码,与PointLightedCube.js相比,只有着色器部分被修改了,计算光照效果的逻辑从顶点着色器移到了片元着色器中。

var VSHADER_SOURCE = // 309
  'attribute vec4 a_Position;\n' +
  'attribute vec4 a_Color;\n' +
  'attribute vec4 a_Normal;\n' +
  'uniform mat4 u_MvpMatrix;\n' +
  'uniform mat4 u_ModelMatrix;\n' +    // 模型矩阵
  'uniform mat4 u_NormalMatrix;\n' +   // 用来变换法向量的矩阵
  'varying vec4 v_Color;\n' +
  'varying vec3 v_Normal;\n' +
  'varying vec3 v_Position;\n' +
  'void main() {\n' +
  '  gl_Position = u_MvpMatrix * a_Position;\n' +
     // 计算顶点的世界坐标
  '  v_Position = vec3(u_ModelMatrix * a_Position);\n' +
  '  v_Normal = normalize(vec3(u_NormalMatrix * a_Normal));\n' +
  '  v_Color = a_Color;\n' + 
  '}\n';

var FSHADER_SOURCE =
  '#ifdef GL_ES\n' +
  'precision mediump float;\n' +
  '#endif\n' +
  'uniform vec3 u_LightColor;\n' +     // 点光源颜色
  'uniform vec3 u_LightPosition;\n' +  // 点光源位置
  'uniform vec3 u_AmbientLight;\n' +   // 环境光颜色
  'varying vec3 v_Normal;\n' +
  'varying vec3 v_Position;\n' +
  'varying vec4 v_Color;\n' +
  'void main() {\n' +
     // 对法线进行归一化,因为其内插之后长度不一定是1.0
  '  vec3 normal = normalize(v_Normal);\n' +
     // 计算点光源光线方向并归一化
  '  vec3 lightDirection = normalize(u_LightPosition - v_Position);\n' +
     // 计算光线方向和法向量的点积(余弦角度值)
  '  float nDotL = max(dot(lightDirection, normal), 0.0);\n' +
     // 计算diffuse,ambient以及最终的颜色
  '  vec3 diffuse = u_LightColor * v_Color.rgb * nDotL;\n' +
  '  vec3 ambient = u_AmbientLight * v_Color.rgb;\n' +
  '  gl_FragColor = vec4(diffuse + ambient, v_Color.a);\n' +
  '}\n';

function main() {
  var canvas = document.getElementById('webgl');
  var gl = getWebGLContext(canvas);
  if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) return
  var n = initVertexBuffers(gl);
  gl.clearColor(0.0, 0.0, 0.0, 1.0);
  gl.enable(gl.DEPTH_TEST);
  // Get the storage locations of uniform variables
  var u_ModelMatrix = gl.getUniformLocation(gl.program, 'u_ModelMatrix');
  var u_MvpMatrix = gl.getUniformLocation(gl.program, 'u_MvpMatrix');
  var u_NormalMatrix = gl.getUniformLocation(gl.program, 'u_NormalMatrix');
  var u_LightColor = gl.getUniformLocation(gl.program, 'u_LightColor');
  var u_LightPosition = gl.getUniformLocation(gl.program, 'u_LightPosition');
  var u_AmbientLight = gl.getUniformLocation(gl.program, 'u_AmbientLight');

  // Set the light color (white)
  gl.uniform3f(u_LightColor, 1.0, 1.0, 1.0);
  // Set the light direction (in the world coordinate)
  gl.uniform3f(u_LightPosition, 2.3, 4.0, 3.5);
  // Set the ambient light
  gl.uniform3f(u_AmbientLight, 0.2, 0.2, 0.2);

  var modelMatrix = new Matrix4();  // Model matrix
  var mvpMatrix = new Matrix4();    // Model view projection matrix
  var normalMatrix = new Matrix4(); // Transformation matrix for normals

  // Calculate the model matrix
  modelMatrix.setRotate(90, 0, 1, 0); // Rotate around the y-axis
  // Calculate the view projection matrix
  mvpMatrix.setPerspective(30, canvas.width/canvas.height, 1, 100);
  mvpMatrix.lookAt(6, 6, 14, 0, 0, 0, 0, 1, 0);
  mvpMatrix.multiply(modelMatrix);
  // Calculate the matrix to transform the normal based on the model matrix
  normalMatrix.setInverseOf(modelMatrix);
  normalMatrix.transpose();

  // Pass the model matrix to u_ModelMatrix
  gl.uniformMatrix4fv(u_ModelMatrix, false, modelMatrix.elements);

  // Pass the model view projection matrix to u_mvpMatrix
  gl.uniformMatrix4fv(u_MvpMatrix, false, mvpMatrix.elements);

  // Pass the transformation matrix for normals to u_NormalMatrix
  gl.uniformMatrix4fv(u_NormalMatrix, false, normalMatrix.elements);

  // Clear color and depth buffer
  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

  // Draw the cube
  gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0);
}

function initVertexBuffers(gl) {
  // Create a cube
  //    v6----- v5
  //   /|      /|
  //  v1------v0|
  //  | |     | |
  //  | |v7---|-|v4
  //  |/      |/
  //  v2------v3
  // Coordinates
  var vertices = new Float32Array([
     2.0, 2.0, 2.0,  -2.0, 2.0, 2.0,  -2.0,-2.0, 2.0,   2.0,-2.0, 2.0, // v0-v1-v2-v3 front
     2.0, 2.0, 2.0,   2.0,-2.0, 2.0,   2.0,-2.0,-2.0,   2.0, 2.0,-2.0, // v0-v3-v4-v5 right
     2.0, 2.0, 2.0,   2.0, 2.0,-2.0,  -2.0, 2.0,-2.0,  -2.0, 2.0, 2.0, // v0-v5-v6-v1 up
    -2.0, 2.0, 2.0,  -2.0, 2.0,-2.0,  -2.0,-2.0,-2.0,  -2.0,-2.0, 2.0, // v1-v6-v7-v2 left
    -2.0,-2.0,-2.0,   2.0,-2.0,-2.0,   2.0,-2.0, 2.0,  -2.0,-2.0, 2.0, // v7-v4-v3-v2 down
     2.0,-2.0,-2.0,  -2.0,-2.0,-2.0,  -2.0, 2.0,-2.0,   2.0, 2.0,-2.0  // v4-v7-v6-v5 back
  ]);

  // Colors
  var colors = new Float32Array([
    1, 0, 0,   1, 0, 0,   1, 0, 0,  1, 0, 0,     // v0-v1-v2-v3 front
    1, 0, 0,   1, 0, 0,   1, 0, 0,  1, 0, 0,     // v0-v3-v4-v5 right
    1, 0, 0,   1, 0, 0,   1, 0, 0,  1, 0, 0,     // v0-v5-v6-v1 up
    1, 0, 0,   1, 0, 0,   1, 0, 0,  1, 0, 0,     // v1-v6-v7-v2 left
    1, 0, 0,   1, 0, 0,   1, 0, 0,  1, 0, 0,     // v7-v4-v3-v2 down
    1, 0, 0,   1, 0, 0,   1, 0, 0,  1, 0, 0     // v4-v7-v6-v5 back
 ]);

  // Normal
  var normals = new Float32Array([
    0.0, 0.0, 1.0,   0.0, 0.0, 1.0,   0.0, 0.0, 1.0,   0.0, 0.0, 1.0,  // v0-v1-v2-v3 front
    1.0, 0.0, 0.0,   1.0, 0.0, 0.0,   1.0, 0.0, 0.0,   1.0, 0.0, 0.0,  // v0-v3-v4-v5 right
    0.0, 1.0, 0.0,   0.0, 1.0, 0.0,   0.0, 1.0, 0.0,   0.0, 1.0, 0.0,  // v0-v5-v6-v1 up
   -1.0, 0.0, 0.0,  -1.0, 0.0, 0.0,  -1.0, 0.0, 0.0,  -1.0, 0.0, 0.0,  // v1-v6-v7-v2 left
    0.0,-1.0, 0.0,   0.0,-1.0, 0.0,   0.0,-1.0, 0.0,   0.0,-1.0, 0.0,  // v7-v4-v3-v2 down
    0.0, 0.0,-1.0,   0.0, 0.0,-1.0,   0.0, 0.0,-1.0,   0.0, 0.0,-1.0   // v4-v7-v6-v5 back
  ]);

  // Indices of the vertices
  var indices = new Uint8Array([
     0, 1, 2,   0, 2, 3,    // front
     4, 5, 6,   4, 6, 7,    // right
     8, 9,10,   8,10,11,    // up
    12,13,14,  12,14,15,    // left
    16,17,18,  16,18,19,    // down
    20,21,22,  20,22,23     // back
 ]);

  // Write the vertex property to buffers (coordinates, colors and normals)
  if (!initArrayBuffer(gl, 'a_Position', vertices, 3)) return -1;
  if (!initArrayBuffer(gl, 'a_Color', colors, 3)) return -1;
  if (!initArrayBuffer(gl, 'a_Normal', normals, 3)) return -1;
  gl.bindBuffer(gl.ARRAY_BUFFER, null);
  var indexBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
  gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
  return indices.length;
}

function initArrayBuffer(gl, attribute, data, num) {
  var buffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
  gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
  var a_attribute = gl.getAttribLocation(gl.program, attribute);
  gl.vertexAttribPointer(a_attribute, num, gl.FLOAT, false, 0, 0);
  gl.enableVertexAttribArray(a_attribute);
  return true;
}

代码详解

为了逐片元地计算光照,你需要知道:(1)片元在世界坐标系下的坐标,(2)片元处表面的法向量。可以在顶点着色器中,将顶点的世界坐标和法向量以varying变量的形式传入片元着色器,片元着色器中的同名变量就已经是内插后的逐片元值了。

顶点着色器使用模型矩阵乘以顶点坐标计算出顶点的世界坐标(第14行),将其赋值给v_Position变量。经过内插过程后,片元着色器就获得了逐片元的v_Position变量,也就是片元的世界坐标。类似地,顶点着色器将顶点的法向量赋值给v_Normal变量(第15行),经过内插,片元着色器就获得了逐片元的v_Normal变量,即片元的法向量。

片元着色器计算光照效果的方法与PointLightedCube.js相同。首先对法向量v_Normal进行归一化(第31行),因为内插之后法向量可能不再是1.0了;然后,计算片元处的光线方向并对其归一化(第33行);接着计算法向量与光线方向的点积(第35行);最后分别计算点光源光和环境光产生的反射光颜色,并将两个结果加起来,赋值给gl_FragColor,片元就会显示为这个颜色。

如果场景中有超过一个点光源,那么就需要在片元着色器中计算每一个点光源(当然还有环境光)对片元的颜色贡献,并将它们全部加起来。换句话说,有几个点光源,就得按照表面反射光颜色(漫反射+环境反射)公式计算几次。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1020765.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

paddlespeech asr脚本demo

概述 paddlespeech是百度飞桨平台的开源工具包,主要用于语音和音频的分析处理,其中包含多个可选模型,提供语音识别、语音合成、说话人验证、关键词识别、音频分类和语音翻译等功能。 本文介绍利用ps中的asr功能实现批量处理音频文件的demo。…

回溯算法 解题思路

文章目录 算法介绍回溯算法能解决的问题解题模板1. 组合问题2. N皇后问题 算法介绍 回溯法(Back Tracking Method)(探索与回溯法)是一种选优搜索法,又称为试探法,按选优条件向前搜索,以达到目标…

URL 管理器

基本介绍 对外接口 对外提供两个接口:一个可以提取URL,一个可以增加URL,分别对应图上的1和2。 当要爬取某个网页时,则可以从1接口提取出该网页的URL进行爬取。 有时候爬取的网页内容中会包含别的网页链接,即包含有U…

java版Spring Cloud+Mybatis+Oauth2+分布式+微服务+实现工程管理系统

鸿鹄工程项目管理系统 Spring CloudSpring BootMybatisVueElementUI前后端分离构建工程项目管理系统 1. 项目背景 一、随着公司的快速发展,企业人员和经营规模不断壮大。为了提高工程管理效率、减轻劳动强度、提高信息处理速度和准确性,公司对内部工程管…

Sui zkLogin让真正链接10亿用户成为可能

近日,Sui宣布推出zkLogin,这是将用户引入链上的最简单方式。zkLogin是Sui的一种原生功能,允许用户使用来自Google和Twitch等现有的Web2身份验证登录Web3应用程序,消除了用户需要记住或记录私钥的流程。 创建钱包通常被认为是区块…

使用vite创建vue3项目及项目的配置 | 环境准备 ESLint配置 prettier配置 husky配置 项目继承

文章目录 使用vite创建vue3项目及项目的配置1.环境准备2.项目配置ESLint校验代码工具配置 - js代码检测工具1.安装ESLint到开发环境 devDependencies2.生成配置文件:.eslint.cjs**3.安装vue3环境代码校验插件**4. 修改.eslintrc.cjs配置文件5.生成ESLint忽略文件6.在package.js…

K8S pod资源、探针

目录 一.pod资源限制 1.pod资源限制方式 2.pod资源限制指定时指定的参数 (1)request 资源 (2) limit 资源 (3)两种资源匹配方式 3.资源限制的示例 (1)官网示例 2&#xff0…

张勇时代落幕 蔡崇信能否让阿里变得更好

这两年,互联网行业似乎迎来了组织变革潮,只是谁也没想到,阿里的来得这么快,这么彻底。 9月10日晚,阿里巴巴董事会主席蔡崇信发布全员信,宣布已按计划完成集团管理职务交接,由他接任集团董事会主…

【JavaScript】对象类似数组那种数据结构 搜索一组匹配的数据

在 JavaScript 中,如果您想在类似数组的对象中进行关键字搜索并找到一组匹配的数据,可以使用filter()方法结合正则表达式来实现。 以下是一个示例代码,演示如何在类似数组的对象中进行关键字搜索并找到匹配的数据: const obj {…

APEX数据源加载实现Excel表数据导入及自定义存储过程

在APEX应用程序中会涉及到数据加载,说白了就是导入导出数据到数据库中,这里就以Excel导入数据到TEST_DATA_WXX表为例,来学习共享组件 数据源 数据加载定义 1 第一步先导出一个数据模板 进入《王小小鸭的学习demo》打开【用户管理】-【操作】…

c++ day 6

1、 将之前定义的栈类和队列类都实现成模板类 #include <iostream>using namespace std;#define MAX 128template<typename T>class Stack { public://构造函数Stack();//析构函数~Stack();//拷贝构造函数Stack(const Stack &other);//入栈int push(T e);//出…

【Redis7】--4.事务、管道、发布和订阅

文章目录 事务1.Redis事务2.Redis事务特性3.Redis事务命令3.1MULTI3.2EXEC3.3DISCARD3.4WATCH3.5UNWATCH 4.不保证原子性4.1"全体连坐"4.2"冤头债主" 5.事务执行流程 管道1.pipeline的使用2.pipeline小总结 发布和订阅1.常用命令1.1SUBSCRIBE1.2PUBLISH1.3…

小鹏、长城先后宣布智能计划,传统车企与新势力决战AI赛点?

点击关注 文&#xff5c;姚 悦&#xff0c;编&#xff5c;王一粟 “尽管我们已经造车30多年&#xff0c;但现在我们面临一个全新问题和挑战。”长城汽车AI Lab负责人杨继峰表示&#xff0c;“在AI时代里每个问题都是AI问题。” 杨继峰所负责的AI Lab&#xff0c;正是长城汽车…

线性代数的本质——几何角度理解

B站网课来自 3Blue1Brown的翻译版&#xff0c;看完醍醐灌顶&#xff0c;强烈推荐&#xff1a; 线性代数的本质 本课程从几何的角度翻译了线代中各种核心的概念及性质&#xff0c;对做题和练习效果有实质性的提高&#xff0c;下面博主来总结一下自己的理解 1.向量的本质 在物…

uniapp项目实践总结(十七)实现滚动触底加载

导语&#xff1a;在日测的开发过程中&#xff0c;经常会碰到页面需要渲染大量数据的情况&#xff0c;这时候就需要用到滚动加载功能&#xff0c;下面总结一下方法。 目录 原理分析实战演练案例展示 原理分析 使用scrolltolower事件来监听滚动到底部&#xff0c;然后加载下一…

RabbitMQ深入 —— 死信队列

前言 前面荔枝梳理了RabbitMQ中的普通队列、交换机以及相关的知识&#xff0c;在这篇文章中荔枝将会梳理RabbitMQ的一个重要的队列 —— 死信队列&#xff0c;主要了解消息流转到死信队列的三种的方式以及相应的实现demo。希望能帮助到有需要的小伙伴~~~ 文章目录 前言 死信队…

C++——构造函数

定义 构造函数是一个特殊的成员函数&#xff0c;名字和类名相同&#xff0c;创建类类型对象时由编译器自动调节&#xff0c;保证每个数据成员都有一个合适的初始值&#xff0c;并且在对象的声明周期内只调用一次。 特性 1.函数名和类名相同 2.无返回值 3.对象实例化时编译…

自动化测试工具slelnium的初体验

1.slelnium介绍 1.1 一个Web的自动化测试工具&#xff0c;最初是为网站自动化测试而开发的。 1.2 可以直接运行在浏览器上&#xff0c;它支持所有主流的浏览器&#xff08;包括PhantomJS这些无界面的浏览器&#xff09;&#xff0c;可以接收指令&#xff0c;让浏览器自动加载页…

锐捷交换机vlan隔离(wifi段仅能访问外网,和内网隔离)

因为公司的wifi段&#xff0c;未做隔离&#xff0c;无意间上了网&#xff0c;发现能访问内网网段&#xff0c;这里内网是10、20段&#xff0c;管理网段是100段&#xff0c;于是做了和内网的vlan隔离。 拓朴如下&#xff0c;所有vlan的网关都起在核心上&#xff0c;核心上起了DH…

23062QTday2

完善登录框 点击登录按钮后&#xff0c;判断账号&#xff08;admin&#xff09;和密码&#xff08;123456&#xff09;是否一致&#xff0c;如果匹配失败&#xff0c;则弹出错误对话框&#xff0c;文本内容“账号密码不匹配&#xff0c;是否重新登录”&#xff0c;给定两个按钮…