无法让 OpenGL 中的 Gouraud 着色工作

Posted

技术标签:

【中文标题】无法让 OpenGL 中的 Gouraud 着色工作【英文标题】:Can't get Gouraud Shading in OpenGL to work 【发布时间】:2013-02-10 09:26:47 【问题描述】:

我正在尝试使形状由于光源而具有一些阴影,但我希望该形状都是一种颜色。

我的问题是,无论我多么努力,我似乎​​都无法在单一颜色模型上获得任何阴影。我已将我的模型简化为单个三角形,以使此示例更清晰:

#include <GL/glut.h>
#include <math.h>
#include <iostream>

#include<map>
#include<vector>

using namespace std;

/* Verticies for simplified demo */
float vertices[][3] = 
            0.1, 0.1, 0.1,
            0.2, 0.8, 0.3,
            0.3, 0.5, 0.5,
            0.8, 0.2, 0.1,
           ;
const int VERTICES_SIZE = 4;
/* Polygons for simplified demo */
int polygon[][3] = 
                0, 1, 3,
                0, 2, 1,
                0, 3, 2,
                1, 2, 3,
            ;
const int POLYGON_SIZE = 4;
/* Average point for looking at */
float av_point[3];

/*
 * Holds the normal for each vertex calculated by averaging the
 * planar normals that each vertex is connected to.
 * It holds index_of_vertex_in_vertices : normal
 */
map<int, float*> vertex_normals;

/*
 * Calculates average point in list of vertices
 * Stores in result
 */
void averagePoint(float vertices[][3], int length, float result[3]) 
  for(int i = 0; i < length; i++) 
    result[0] += vertices[i][0];
    result[1] += vertices[i][1];
    result[2] += vertices[i][2];
  

  result[0] /= length;
  result[1] /= length;
  result[2] /= length;


/*
 * Performs inplace normalisation of vector v
 */
void normalise(float v[3]) 
  GLfloat length = sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
  v[0] /= length;
  v[1] /= length;
  v[2] /= length;


/*
 * Performs cross product of vectors u and v and stores
 * result in result
 * Normalises result.
 */
void crossProduct(float u[], float v[], float result[]) 
  result[0] = u[1] * v[2] - u[2] * v[1];
  result[1] = u[2] * v[0] - u[0] * v[2];
  result[2] = u[0] * v[1] - u[1] * v[0];


/*
 * Calculates normal for plane
 */
void calculate_normal(int polygon[3], float vertices[][3], float normal[3]) 
  GLfloat u[3], v[3];
  for (int i = 0; i < 3; i++) 
    u[i] = vertices[polygon[0]][i] - vertices[polygon[1]][i];
    v[i] = vertices[polygon[2]][i] - vertices[polygon[1]][i];
  

  crossProduct(u, v, normal);
  normalise(normal);


/*
 * Populates vertex_normal with it's averaged face normal
 */
void calculate_vertex_normals (map<int, float*> &vertex_normal)
  map<int, vector<int> > vertex_to_faces;
  map<int, float*> faces_to_normal;
  // Loop over faces
  for (int i = 0; i < POLYGON_SIZE; i++) 
    float* normal = new float[3];
    calculate_normal(polygon[i], vertices, normal);
    for (int j = 0; j < 3; j++) 
     vertex_to_faces[polygon[i][j]].push_back(i);
    
    faces_to_normal[i] = normal;
  


  vertex_normal.clear();
  // Loop over vertices
  for (int v = 0; v < VERTICES_SIZE; v++) 
    vector<int> faces = vertex_to_faces[v];
    int faces_count = 0;
    float* normal = new float[3];
    for (vector<int>::iterator it = faces.begin(); it != faces.end(); ++it)
      normal[0] += faces_to_normal[*it][0];
      normal[1] += faces_to_normal[*it][1];
      normal[2] += faces_to_normal[*it][2];
      faces_count++;
    
    normal[0] /= faces_count;
    normal[1] /= faces_count;
    normal[2] /= faces_count;
    vertex_normal[v] = normal;
  

  // Delete normal declared in first loop
  for (int i = 0; i < POLYGON_SIZE; i++) 
    delete faces_to_normal[i];
  


/*
 * Draws polygons in polygon array.
 */
void draw_polygon() 
  for(int i = 0; i < POLYGON_SIZE; i++) 
    glBegin(GL_POLYGON);
    for(int j = 0; j < 3; j++) 
      glNormal3fv(vertex_normals[polygon[i][j]]);
      glVertex3fv(vertices[polygon[i][j]]);
    
    glEnd();
  



/*
 * Sets up lighting and material properties
 */
void init()

  // Calculate average point for looking at
  averagePoint(vertices, VERTICES_SIZE, av_point);

  // Calculate vertices average normals
  calculate_vertex_normals(vertex_normals);

  glClearColor (0.0, 0.0, 0.0, 0.0);
  cout << "init" << endl;

  // Intialise and set lighting parameters
  GLfloat light_pos[] = 1.0, 1.0, 1.0, 0.0;
  GLfloat light_ka[] = 0.2, 0.2, 0.2, 1.0;
  GLfloat light_kd[] = 1.0, 1.0, 1.0, 1.0;
  GLfloat light_ks[] = 1.0, 1.0, 1.0, 1.0;

  glLightfv(GL_LIGHT0, GL_POSITION, light_pos);
  glLightfv(GL_LIGHT0, GL_AMBIENT,  light_ka);
  glLightfv(GL_LIGHT0, GL_DIFFUSE,  light_kd);
  glLightfv(GL_LIGHT0, GL_SPECULAR, light_ks);

  // Initialise and set material parameters
  GLfloat material_ka[] = 1.0, 1.0, 1.0, 1.0;
  GLfloat material_kd[] = 0.43, 0.47, 0.54, 1.0;
  GLfloat material_ks[] = 0.33, 0.33, 0.52, 1.0;
  GLfloat material_ke[] = 0.0, 0.0, 0.0, 0.0;
  GLfloat material_se[] = 10.0;

  glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT,  material_ka);
  glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE,  material_kd);
  glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR,  material_ks);
  glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION,  material_ke);
  glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, material_se);

  // Smooth shading
  glShadeModel(GL_SMOOTH);

  // Enable lighting
  glEnable (GL_LIGHTING);
  glEnable (GL_LIGHT0);

  // Enable Z-buffering
  glEnable(GL_DEPTH_TEST);


/*
 * Free's resources
 */
void destroy() 
  for (int i = 0; i < VERTICES_SIZE; i++) 
    delete vertex_normals[i];
  


/*
 * Display simple polygon
 */
void display ()
  glClear  (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  draw_polygon();
  glutSwapBuffers();


/*
 * Sets up camera perspective and view point
 * Looks at average point in model.
 */
void reshape (int w, int h)

  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(70, 1.0, 0.1, 1000);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  gluLookAt(0, 0, 1, av_point[0], av_point[1], av_point[2], 0, 0.5, 0);


int main (int argc, char **argv)


  // Initialize graphics window
  glutInit(&argc, argv);
  glutInitWindowSize(256, 256);
  glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE);

  // Initialize OpenGL
  init();

  glutCreateWindow("Rendering");
  glutDisplayFunc(display);
  glutReshapeFunc(reshape);

  glutMainLoop   ();

  destroy();

  return 1;

我对 OpenGL 真的很陌生,所以我希望它很简单。因为我记得设置了我的法线,所以我不确定还有什么问题。

最终目标是为我的课程使用 Gouraud 着色(然后是纹理)渲染一张脸,但是我们几乎只能为自己找出 OpenGL(1.4 - 课程要求),我们不允许使用着色器。 我正在尝试创建类似于这张图片的东西(取自谷歌):

用我的三角形。

【问题讨论】:

为什么人们仍然声称 FFP 比现代 OpenGL 更更容易,这让我无法理解。如果您不尝试重新实现 GLM 并放弃 FFP,您的代码可能会受益匪浅。 OpenGL 1.4 太旧了!为什么会有人这样教?太可怕了…… @SarahTattersall:请告诉您的老师/教授/助教他们正在介绍过时的方法,只有在您要学习或维护旧代码时才会感兴趣。 3D 图形领域的每位雇主都希望应聘者知道如何使用可编程图形管道。 10 多年前,第一批可编程 GPU 进入市场。 @LightnessRacesinOrbit:我从未见过你参加 OpenGL 问答。 OpenGL 标签通常只由标签的***用户***.com/tags/opengl/topusers 中提到的人漫游——我们之间的共识是,每当有人询问如何在新项目中使用 FFP 时,我们会尝试推动他们使用着色器。 FFP 之于图形就像 BASIC 之于编程语言。 @datenwolf:所有这些,在各个方面。是时候结束这次谈话了! 【参考方案1】:

由于光源造成的阴影,但我希望形状都是一种颜色。

这两个要求不是相互排斥的吗?你想要的结果到底是什么。你能画出你想象中的样子吗?在实现方面,使用着色器很多比处理大量的 OpenGL 状态机开关更容易。

更新

无论如何,这是我的 OPs 代码修订版,它绘制了一个受 Gourad 照明影响的三角形。这段代码编译并绘制了一个带有镜面反射提示的三角形。

让我们来看看我做了什么。首先是三角形的原始设置。这里没有什么特别的也没有任何改变(除了一些包含) (编辑)第二次看我做了改变。 std::map 的使用完全无法解释。我们知道顶点的数量并且可以预先分配法线的内存。

#include <GL/glut.h>
#include <math.h>

// for memcpy
#include <string.h>

#include <map>
#include <vector>
#include <iostream>

using namespace::std;

/* Verticies for simplified demo */
const int VERTICES_SIZE = 4;
float vertices[VERTICES_SIZE][3] = 
            0.1, 0.1, 0.1,
            0.2, 0.8, 0.3,
            0.3, 0.5, 0.5,
            0.8, 0.2, 0.1,
           ;

// this is now a plain array
float vertex_normals[VERTICES_SIZE][3];

/* Polygons for simplified demo */
const int POLYGON_SIZE = 4;
int polygon[POLYGON_SIZE][3] = 
                0, 1, 3,
                0, 2, 1,
                0, 3, 2,
                1, 2, 3,
;

/* Average point for looking at */
float av_point[3];
    
/*
 * Calculates average point in list of vertices
 * Stores in result
 */
void averagePoint(float vertices[][3], int length, float result[3]) 
  for(int i = 0; i < length; i++) 
    result[0] += vertices[i][0];
    result[1] += vertices[i][1];
    result[2] += vertices[i][2];
  

  result[0] /= length;
  result[1] /= length;
  result[2] /= length;


/*
 * Performs inplace normalisation of vector v
 */
void normalise(float v[3]) 
  GLfloat length = sqrtf(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
  v[0] /= length;
  v[1] /= length;
  v[2] /= length;


/*
 * Performs cross product of vectors u and v and stores
 * result in result
 * Normalises result.
 */
void crossProduct(float u[], float v[], float result[]) 
  result[0] = u[1] * v[2] - u[2] * v[1];
  result[1] = u[2] * v[0] - u[0] * v[2];
  result[2] = u[0] * v[1] - u[1] * v[0];


/*
 * Calculates normal for plane
 */
void calculate_normal(int polygon[3], float vertices[][3], float normal[3]) 
  GLfloat u[3], v[3];
  for (int i = 0; i < 3; i++) 
    u[i] = vertices[polygon[0]][i] - vertices[polygon[1]][i];
    v[i] = vertices[polygon[2]][i] - vertices[polygon[1]][i];
  

  crossProduct(u, v, normal);
  normalise(normal);

编辑:我的下一个变化是在这里。见评论

/*
 * Populates normals with it's averaged face normal
 *
 * Passing the normal output buffer as a parameter was a bit
 * pointless, as this procedure accesses global variables anyway.
 * Either pass everything as parameters or noting at all,
 * be consequent. And doing it mixed is pure evil.
 */
void calculate_vertex_normals()

  // We love RAII, no need for new and delete!
  vector< vector<int> > vertex_to_faces(POLYGON_SIZE);
  vector< vector<float> > faces_to_normal(POLYGON_SIZE);

  // Loop over faces
  for (int i = 0; i < POLYGON_SIZE; i++) 
    vector<float> normal(3);
    calculate_normal(polygon[i], vertices, &normal[0]);
    for (int j = 0; j < 3; j++) 
     vertex_to_faces[polygon[i][j]].push_back(i);
    
    faces_to_normal[i] = normal;
  

  // Loop over vertices
  for (int v = 0; v < VERTICES_SIZE; v++) 
    // avoid a copy here by using a reference
    vector<int> &faces = vertex_to_faces[v];
    int faces_count = 0;
    float normal[3];
    for (vector<int>::iterator it = faces.begin(); it != faces.end(); ++it)
      normal[0] += faces_to_normal[*it][0];
      normal[1] += faces_to_normal[*it][1];
      normal[2] += faces_to_normal[*it][2];
      faces_count++;
    
    // dividing a vector obtained by a number of unit length vectors
    // summed by the number of unit vectors summed does not normalize
    // it. You need to normalize it properly!
    normalise(normal);

    // memcpy is really be best choice here
    memcpy(vertex_normals[v], normal, sizeof(normal));
  

draw_polygon 是这个函数的一个相当不愉快的名字。它绘制一个三角网格。 *编辑:也可以通过使用顶点数组来写得更好(自 1994 年起使用 OpenGL-1.1)。

/* 
 * Draws polygons in polygon array.
 */
void draw_polygon() 
  glEnableClientState(GL_VERTEX_ARRAY);
  glEnableClientState(GL_NORMAL_ARRAY);

  glVertexPointer(3, GL_FLOAT, 0, &vertices[0][0]);
  glNormalPointer(GL_FLOAT, 0, &vertex_normals[0][0]);

  glDrawElements(GL_TRIANGLES, POLYGON_SIZE*3, GL_UNSIGNED_INT, polygon);

这里变得有趣了。一个常见的误解是,人们认为 OpenGL 已“初始化”。事实并非如此。你初始化的是数据。在您的情况下,您的几何数据

/*
 * Sets up lighting and material properties
 */
void init_geometry()

  // Calculate average point for looking at
  averagePoint(vertices, VERTICES_SIZE, av_point);

  // Calculate vertices average normals
  calculate_vertex_normals(vertex_normals);

棘手的部分来了:OpenGL 固定函数照明是一种状态,就像其他一切一样。当您调用 glLightfv 时,它会根据调用时的状态设置内部参数。调用此位置时,模型视图会转换位置。但是如果没有设置正确的模型视图,您将无法设置照明。因此我把它放到了自己的函数中,我们在绘图函数中设置好模型视图后立即调用它。

void setup_illumination()

  // Intialise and set lighting parameters
  GLfloat light_pos[] = 1.0, 1.0, 1.0, 0.0;
  GLfloat light_ka[] = 0.2, 0.2, 0.2, 1.0;
  GLfloat light_kd[] = 1.0, 1.0, 1.0, 1.0;
  GLfloat light_ks[] = 1.0, 1.0, 1.0, 1.0;

  glLightfv(GL_LIGHT0, GL_POSITION, light_pos);
  glLightfv(GL_LIGHT0, GL_AMBIENT,  light_ka);
  glLightfv(GL_LIGHT0, GL_DIFFUSE,  light_kd);
  glLightfv(GL_LIGHT0, GL_SPECULAR, light_ks);

  // Initialise and set material parameters
  GLfloat material_ka[] = 1.0, 1.0, 1.0, 1.0;
  GLfloat material_kd[] = 0.43, 0.47, 0.54, 1.0;
  GLfloat material_ks[] = 0.33, 0.33, 0.52, 1.0;
  GLfloat material_ke[] = 0.0, 0.0, 0.0, 0.0;
  GLfloat material_se[] = 10.0;

  glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT,  material_ka);
  glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE,  material_kd);
  glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR,  material_ks);
  glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION,  material_ke);
  glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, material_se);

  // Smooth shading
  glShadeModel(GL_SMOOTH);

  // Enable lighting
  glEnable (GL_LIGHTING);
  glEnable (GL_LIGHT0);

对于绘图功能进行了一些更改。查看代码中的cmets

/*
 * Display simple polygon
 */
void display (void)

  // float window sizes are usefull for view volume calculations
  //
  // requesting the window dimensions for each drawing iteration
  // is just two function calls. Compare this to the number of function
  // calls a typical application will do for the actual rendering
  // Trying to optimize away those two calls is a fruitless microoptimization
  float const window_width  = glutGet(GLUT_WINDOW_WIDTH);
  float const window_height = glutGet(GLUT_WINDOW_HEIGHT);
  float const window_aspect = window_width / window_height;

  // glViewport operates independent of the projection --
  // another reason to put it into the drawing code
  glViewport(0, 0, window_width, window_height);

  glClearDepth(1.);
  glClearColor (0.0, 0.0, 0.0, 0.0);
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  // It's a often made mistake to setup projection in the window resize
  // handler. Projection is a drawing state, hence should be set in
  // the drawing code. Also in most programs you will have multiple
  // projections mixed throughout rendering a single frame so there you
  // actually **must** set projection in drawing code, otherwise it
  // wouldn't work.
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(70, window_aspect, 1, 100);

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  gluLookAt(0, 0, -3, av_point[0], av_point[1], av_point[2], 0, 1, 0);
 
  // Fixed function pipeline light position setup calls operate on the current
  // modelview matrix, so we must setup the illumination parameters with the
  // modelview matrix at least after the view transformation (look-at) applied.
  setup_illumination();
 
  // Enable depth testing (z buffering would be enabled/disabled with glDepthMask)
  glEnable(GL_DEPTH_TEST);

  draw_polygon();

  glutSwapBuffers();

   
int main (int argc, char **argv)
    
  // Initialize graphics window
  glutInit(&argc, argv);
  glutInitWindowSize(256, 256);
  glutInitDisplayMode    (GLUT_DEPTH | GLUT_DOUBLE);

  // we actually have to create a window
  glutCreateWindow("illuination");

  // Initialize geometry
  init_geometry();

  glutDisplayFunc(display);

  glutMainLoop();

  return 0;

【讨论】:

您的其他答案已被删除,因为它已被标记为审核之前您宣布要更新它。很高兴您已经修复了 OP 代码,但仅仅发布新版本也不是答案。你真的应该解释如何你已经修复它了。 @Alnitak:给你,添加解释。 @LightnessRacesinOrbit:嗯,在 OpenGL 标记中以这种方式完成是相当普遍的。图形是一件复杂的事情,通常最初的问题并不清楚。所以OpenGL标签中的既定方法是与OP一起迭代地朝着解决方案工作。 OP 想要用单一颜色对形状进行着色,根据定义,这根本不是着色。所以我要求回去完善它。我为解决这个问题而努力的方式就像我经常在 opengl 标记中处理所有其他不清楚的问题一样,并且到目前为止效果很好。 @datenwolf:听起来 OpenGL 标记对已建立的堆栈溢出机制有一定程度的漠视。 SO 规则不适用于“除 OpenGL 标记之外的所有 SO”。 SO 不是“聊天”或“讨论论坛”;它不是迭代工作的地方。 @LightnessRacesinOrbit:SO 答案的目标应该是正确、简洁且最重要的是对其他人有用的东西。您在图形编程方面的经验如何?很多时候,当涉及到图形时,您无法立即给出正确或有意义的答案,因为它是一个如此微妙的主题,您必须在进行过程中进行一些调整。我认为这里没有问题,只要 quenstioner 和 answerer 合作的最终产品是正确且对未来读者有用的东西。【参考方案2】:

您似乎有一个名为vertices 的数组(这是正确的拼写),还有一个名为verticies 的数组,在几个地方(calculate_normal 是最明显的例子)。这是一个错误吗?如果您从第一个数组中获取一个坐标,但从另一个不相关的数组中获取第二个坐标,这可能会打乱您的正常计算。

【讨论】:

错误对不起!我尝试更正示例中的拼写,但遗漏了一些地方。 @Sarah:请确保您的测试用例/示例是您在发布之前实际运行的代码。现在我想知道您可能遗漏了哪些其他错别字,这些错别字会分散我们的注意力,让我们无法找到真正的问题。 @SarahTattersall 他是对的 - 我只是尝试构建您的代码,但其中仍有许多编译错误。即使我修正了明显的拼写错误,添加了必需的#include 等,仍然调用了未定义的set_normal 函数。

以上是关于无法让 OpenGL 中的 Gouraud 着色工作的主要内容,如果未能解决你的问题,请参考以下文章

在 gouraud shading 中,啥是 T-junction 问题以及如何用 OpenGL 演示它

WPF 中的逐像素光照

flat 和 phong 着色问题

无法让 openGL 的 glDrawElements 与几何着色器一起使用

Phong 和 Gouraud 着色 WebGL

使用 gouraud 着色处理不输出任何内容