【问题标题】:Cell-Shading Outlines: edge mesh writer does not define all desired edges单元着色轮廓:边缘网格编写器未定义所有所需的边缘
【发布时间】:2014-03-19 07:23:13
【问题描述】:

我正在编写的程序接收 3D 网格的顶点数据,执行一系列计算(请原谅含糊不清,稍后我将尝试更详细地解释),并输出一个二进制文件,该文件定义了边缘在网格上。然后我的程序在边缘所在的位置绘制一条彩色线。如果没有适当的顶点着色器,这看起来就像一个常规的三角网格,但是一旦应用了适当的顶点着色器,只有“锐利”的边缘(它们的法线的点积大于接近于零的值)才会绘制线条在它们上,以及图形外部的边缘。我对轮廓的实现是不正确的,因为我假设如果边缘不在边缘后面,并且没有定义锐边,它将是轮廓边缘。我还没有在其他地方找到令人满意的答案,我不想依赖于将网格重新绘制为纯色并将其渲染为比原始网格稍大的旧技巧。这种方法完全基于数学,仅依赖于网格的顶点数据。我正在编写一个使用以下顶点着色器的程序:

uniform mat4 worldMatrix;
uniform mat4 projMatrix;
uniform mat4 viewProjMatrix;
uniform vec4 eyepos;

attribute vec3 a;
attribute vec3 b;
attribute vec3 n1;
attribute vec3 n2;
attribute float w;

void main()
{
    float a_vertex = dot(eyepos.xyz - a, n1);
    float b_vertex = dot(eyepos.xyz - a, n2);

    if (a_vertex * b_vertex > 0.0) // signs are different, edge is behind the object
    {
        gl_Position = vec4(2.0,2.0,2.0,1.0);
    }

    else // the outline of the figure
    {
        if(w == 0.0)
        {
            vec4 p = vec4(a.x, a.y, a.z, 1.0);
            p = p * worldMatrix * viewProjMatrix;
            gl_Position = p;
        }

        else
        {
            vec4 p = vec4(b.x, b.y, b.z, 1.0);
            p = p * worldMatrix * viewProjMatrix;
            gl_Position = p;
        }
    }

    if(dot(n1, n2) <= 0.2) // there is a sharp edge
    {
        if(w == 0.0)
        {
            vec4 p = vec4(a.x, a.y, a.z, 1.0);
            p = p * worldMatrix * viewProjMatrix;
            gl_Position = p;
        }

        else
        {
            vec4 p = vec4(b.x, b.y, b.z, 1.0);
            p = p * worldMatrix * viewProjMatrix;
            gl_Position = p;
        }
    }
}

...从使用此程序在 C++ 中编写的二进制文件中获取信息:

#include <iostream>
#include "llgl.h"
#include <fstream>
#include <vector>
#include "SuperMesh.h"

using namespace std;
using namespace llgl;

struct Vertex
{
    float x,y,z,w;
    float s,t,p,q;
    float nx,ny,nz,nw;
};

bool isFileAlright(string fName)
{
    ifstream in(fName.c_str());
    if(!in.good())
        return false;
    return true;
}

int main(int argc, char* argv[])
{
    // INPUT FILE NAME //
    string fName;
    cout << "Enter the path to your spec.mesh file here: ";
    cin >> fName;
    while(!isFileAlright(fName))
    {
        cout << "Enter the path to your spec.mesh file here: ";
        cin >> fName;
    }
    SuperMesh* Model = new SuperMesh(fName.c_str());
    // END INPUT //

    Model->load();
    Model->draw();

    string fname = Model->fname;
    string FileName = fname.substr(0, fname.size() - 10); // supposed to slash the last 10 characters off of the string, removing ".spec.mesh"...
    FileName = FileName + ".bin"; //... and then we make it a .bin file*/
    cout << FileName << endl;

    ofstream out(FileName.c_str(), ios::binary);

    for (unsigned w = 0; w < Model->m.size(); w++)
    {
        vector<float> &vdata = Model->m[w]->vdata;
        vector<char> &idata = Model->m[w]->idata;


        //Create a vertex and index variable, a map for Edge Mesh, perform two loops to analyze all triangles on a mesh and write out their vertex values to a file.//
        Vertex* V = (Vertex*)(&vdata[0]);

        unsigned short* I16 = (unsigned short*)(&idata[0]);
        unsigned char* I8 = (unsigned char*)(&idata[0]);
        unsigned int* I32 = (unsigned int*)(&idata[0]);

        map<set<int>, vector<vec3> > EM;

        for(unsigned i = 0; i < Model->m[w]->ic; i += 3) // 3 because we're looking at triangles //
        {
            Mesh* foo = Model->m[w];
            int i1;
            int i2;
            int i3;
            if( Model->m[w]->ise == GL_UNSIGNED_BYTE)
            {
                i1 = I8[i];
                i2 = I8[i + 1];
                i3 = I8[i + 2];
            }
            else if( Model->m[w]->ise == GL_UNSIGNED_SHORT)
            {
                i1 = I16[i];
                i2 = I16[i + 1];
                i3 = I16[i + 2];
            }
            else
            {
                i1 = I32[i];
                i2 = I32[i + 1];
                i3 = I32[i + 2];
            }
            vec3 p = vec3(V[i1].x, V[i1].y, V[i1].z); // to represent the point in 3D space of each vertex on every triangle on the mesh
            vec3 q = vec3(V[i2].x, V[i2].y, V[i2].z);
            vec3 r = vec3(V[i3].x, V[i3].y, V[i3].z);
            vec3 v1 = p - q;
            vec3 v2 = r - q;
            vec3 n = cross(v2,v1); //important to make sure the order is correct here, do VERTEX TWO dot VERTEX ONE//
            set<int> tmp;
            tmp.insert(i1); tmp.insert(i2);
            EM[tmp].push_back(n);
            set<int> tmp2;
            tmp2.insert(i2); tmp2.insert(i3);
            EM[tmp2].push_back(n);
            set<int> tmp3;
            tmp3.insert(i3); tmp3.insert(i1);
            EM[tmp3].push_back(n);
            //we have now pushed every needed point into our edge map

        }
        int edgeNumber = 0;
        cout << "There should be 12 edges on a lousy cube." << endl;
        for(map<set<int>, vector<vec3> >::iterator it = EM.begin(); it != EM.end(); ++it)
        {
            //Now we will take our edge map and write its data to the file!//
            /* Information is written to the file in this form:
            Vertex One, Vertex Two, Normal One, Normal Two, r (where r, depending on its value, determines whether one edge is on top of the other in the case
                                                                                    where two edges are aligned with one another)
            */
            set<int>::iterator tmp = it->first.begin();
            int pi = *tmp;
            tmp++;
            int qi = *tmp;
            Vertex One = V[pi];
            Vertex Two = V[qi];
            vec3 norm1 = it->second[0];
            vec3 norm2;
            if(it->second.size() == 1)
                norm2 = -1 * norm1;
            else
                norm2 = it->second[1];
            out.write((char*) &One, 12);
            out.write((char*) &Two, 12);
            out.write((char*) &norm1, 12);
            out.write((char*) &norm2, 12);
            float r = 0;
            out.write((char*) &r, 4);
            out.write((char*) &One, 12);
            out.write((char*) &Two, 12);
            out.write((char*) &norm1, 12);
            out.write((char*) &norm2, 12);
            r = 1;
            out.write((char*) &r, 4);
            edgeNumber++;
            cout << "Wrote edge #" << edgeNumber << endl;
        }
    }

    return 0;
}

这个程序的问题是它在测试用例中没有做这两个基本的事情,我用它来绘制一个带有轮廓的简单框:

  1. 它不绘制轮廓。顶点着色器不足以确定物体边缘的位置。实现这一点的二进制文件使用上面发布的第二个 sn-p 中的代码在单独的程序中预先计算,然后将其与所属的网格资产一起保存为 .bin 文件。然而,原始顶点数据只能让我走这么远,我想找一种方法在网格外部画一条线,而不使用更传统的方法。

  2. 它不会绘制我需要的所有边缘。在我的测试用例中,缺少两条边,我终其一生都无法弄清楚原因。我想我一定是在写边缘图时做错了。

关于上述代码的几点说明:

  • llgl 是一个 OpenGL 包装器,我用它来简化 OpenGL 的许多元素。它在这里没有广泛使用,而是用于创建网格,在其他地方完成。

  • 像 Mesh 和 SuperMesh(将网格集合到一个刚体中)之类的东西在我的场景中是 3D 对象。在我的测试用例中,我的场景中只有一个 Mesh,定义单个 Mesh 的 SuperMesh 本质上只是创建单个 Mesh。

  • 第二个 sn-p 中的“draw”调用预先计算了 Mesh 的边缘图,实际上并没有绘制任何东西。有必要访问 Mesh 的顶点数据。

  • 变量“ise”取自 SuperMesh 中的各个网格,是从原始 Blender .OBJ 文件中读取的变量。它与应该使用多少内存来存储重要的顶点数据有关。使用 Blender 的朋友和导师告诉我,分配超出这些值所需的空间通常不是一个好主意。

没有很好的评论,因为我不是唯一一个处理过这段代码的人,不幸的是,我对第二个 sn-p 如何遍历所有三角形的理解有限一个网格,不知何故错过了最后两条边。一旦我更好地理解了这段代码在正确编写时应该做什么,我计划大量评论它并在未来的应用程序中使用它。

【问题讨论】:

    标签: c++ opengl 3d


    【解决方案1】:

    矩阵和向量的乘法顺序不可交换,所以 您的顶点着色器必须输出 Projection * Model * Vertex 而不是相反。

    【讨论】:

      【解决方案2】:

      我通过分配更多空间在代码的不同部分写入顶点数据来解决未绘制线的谜团。至于我的其他问题,虽然在我的顶点着色器中执行的乘法顺序实际上没问题,但我搞砸了向量数学的另一个基本概念。当法线形成钝角时,两个面法线的点积将为负数......就像我模型上的尖点一样。此外,上面的错误逻辑基本上是说如果面部可见,则在其上绘制所有线条。我重新编写了我的着色器以首先测试一张脸是否可见,然后在同一个条件块中我对锐边进行了测试。现在,如果一个面可见但它没有创建一个锐利的边缘,着色器将忽略该边缘。此外,现在出现了轮廓,但并不完美。这是上述顶点着色器的修改版本:

      uniform mat4 worldMatrix; /* the matrix that defines how to project a point from 
      object space to world space.*/
      uniform mat4 viewProjMatrix; // the view (pertaining to screen size) matrix times the projection (how to project points to 3D) matrix.
      uniform vec4 eyepos; // the position of the eye, given by the program.
      
      attribute vec3 a; // one vertex on an edge, having an x,y,z, and w coordinate.
      attribute vec3 b; // the other edge vertex.
      attribute vec3 n1; // the normal of the face the edge is on.
      attribute vec3 n2; // another normal in the case that an edge shares two faces... otherwise, this is the same as n1.
      attribute float w; // an attribute given to make a binary choice between two edges when they draw on top of one another.
      
      void main()
      {
          // WORLD SPACE ATTRIBUTES //
          vec4 eye_world = eyepos * worldMatrix;
          vec4 a_world = vec4(a.x, a.y,a.z,1.0) * worldMatrix;
          vec4 b_world = vec4(b.x, b.y,b.z,1.0) * worldMatrix;
          vec4 n1_world = normalize(vec4(n1.x, n1.y,n1.z,0.0) * worldMatrix);
          vec4 n2_world = normalize(vec4(n2.x, n2.y,n2.z,0.0) * worldMatrix);
          // END WORLD SPACE ATTRIBUTES //
      
          // TEST CASE ATTRIBUTES //
          float a_vertex = dot(eye_world - a_world, n1_world);
          float b_vertex = dot(eye_world - b_world, n2_world);
          float normalDot = dot(n1_world.xyz, n2_world.xyz);
          float vertProduct = a_vertex * b_vertex;
          float hardness = 0.0; // this would be the value for an object made of sharp angles, like a box. Take a look at its use below.
          // END TEST CASE ATTRIBUTES //
      
          gl_Position = vec4(2.0,2.0,2.0,1.0); // if all else fails, keeping this here will discard unwanted data.
      
          if (vertProduct >= 0.1) // NOTE: face is behind the viewable portion of the object, normally uses 0.0 when not checking for silhouette
          {
                  gl_Position = vec4(2.0,2.0,2.0,1.0);
          }
      
          else if(vertProduct < 0.1 && vertProduct >= -0.1) // NOTE: face makes almost a right angle with the eye vector
          {
              if(w == 0.0)
              {
                  vec4 p = vec4(a_world.x, a_world.y, a_world.z, 1.0);
                  p = p  * viewProjMatrix;
                  gl_Position = p;
              }
              else
              {
                  vec4 p = vec4(b_world.x, b_world.y, b_world.z, 1.0);
                  p = p * viewProjMatrix;
                  gl_Position = p;
              }
          }
      
          else // NOTE: this is the case where you can very clearly see a face.
          { // NOTE: the number that normalDot compares to should be its "hardness" value. The more negative the value, the smoother the surface.
             // a.k.a. the less we care about hard edges (when the normals of the faces make an obtuse angle) on the object, the more negative
             // hardness becomes on a scale of 0.0 to -1.0.
              if(normalDot <= hardness) // NOTE: the dot product of the two normals is obtuse, so we are looking at a sharp edge.
              {
                  if(w == 0.0)
                  {
                      vec4 p = vec4(a_world.x, a_world.y, a_world.z, 1.0);
                      p = p * viewProjMatrix;
                      gl_Position = p;
                  }
                  else
                  {
                      vec4 p = vec4(b_world.x, b_world.y, b_world.z, 1.0);
                      p = p * viewProjMatrix;
                      gl_Position = p;
                  }
              }
              else // NOTE: not sharp enough, just throw the vertex away
              {
                  gl_Position = vec4(2.0,2.0,2.0,1.0);
              }
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-06-20
        • 2013-08-07
        • 2017-07-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-11-28
        • 2013-12-29
        相关资源
        最近更新 更多