使用 Stencil-buffer 勾勒出 3D 模型

Posted

技术标签:

【中文标题】使用 Stencil-buffer 勾勒出 3D 模型【英文标题】:Outlining a 3D model using Stencil-buffer 【发布时间】:2015-03-22 18:21:30 【问题描述】:

首先我想提一下,我发现我认为是完全相同的问题,不幸的是没有答案,在这里: Java Using OpenGL Stencil to create Outline

我将在下面发布我的代码,但首先是问题所在:从这个捕获**,您可以看到整个框架结构正在显示,而不是球体周围的单线。我想摆脱所有这些线! ** 显然我无法添加图片:请参阅此链接 - 想象一个球体,四边形的所有边缘在 3 像素大线中可见。http://srbwks36224-03.engin.umich.edu/kdi/images/gs_sphere_with_frame.jpg 这是给出该结果的代码:

// First render the sphere:
// inside "show" is all the code to display a textured sphere
// looking like earth
sphe->show();

// Now get ready for stencil buffer drawing pass:
// 1. Clear and initialize it
// 2. Activate stencil buffer
// 3. On the first rendering pass, we want to "SUCCEED ALWAYS"
//    and write a "1" into the stencil buffer accordingly
// 4. We don't need to actually render the object, hence disabling RGB mask
glClearStencil(0);   //Edit: swapped this line and below
glClear(GL_STENCIL_BUFFER_BIT);                 
glEnable(GL_STENCIL_TEST);                  
glStencilFunc(GL_NEVER, 0x1, 0x1);          //Edit: GL_ALWAYS
glStencilOp(GL_REPLACE, GL_KEEP, GL_KEEP);  //Edit: GL_KEEP, GL_KEEP, GL_REPLACE                    
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
glDepthMask(GL_FALSE);   // As per Andon's comment
sphe->show();

// At this point, I expect to have "1" on the entire
// area covered by the sphere, so...
// 1. Stencil test should fail for anything, but 0 value
// RM: commented is another option that should work too I believe                       
// 2. The stencil op instruction at the point is somewhat irrelevant 
//    (if my understanding is correct), because we won't do anything 
//    else with the stencil buffer after that.
// 3. Re-enable RGB mask, because we want to draw this time  
// 4. Switch to LINE drawing instead of FILL and 
// 5. set a bigger line width, so it will exceed the model boundaries. 
//    We do want this, otherwise the line would not show 
// 6. Don't mind the "uniform" setting instruction, this is so
//    that my shader knows it should draw in plain color
// 7. Draw the sphere's frame
// 8. The principle, as I understand it is that all the lines should
//    find themselves matched to a "1" in the stencil buffer and therefore
//    be ignored for rendering. Only lines on the edges of the model should
//    have half their width not failing the stencil test.
glStencilFunc(GL_EQUAL, 0x0, 0x1);
//glStencilFunc(GL_NOTEQUAL, 0x1, 0x1);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glDepthMask(GL_TRUE);
glLineWidth(3);
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);  
psa::shader::setUniform("outlining", 1);
sphe->show();
psa::shader::setUniform("outlining", 0);

现在只是为了证明一点,我厌倦了使用模板缓冲区做一些不同的事情 - 我只是想确保我的代码中的所有内容都到位,以便它工作。 ** 再次遗憾的是,我无法显示我得到的结果的屏幕截图:场景是这样的http://mathworld.wolfram.com/images/eps-gif/SphereSphereInterGraphic_700.gif 但是较小的球体是不可见的(RGB 蒙版已停用),并且可以通过孔看到世界背景(而不是较大球体的内部 - 面部剔除已停用)。

这就是代码...有趣的是,我可以更改很多东西,例如激活/停用 STENCIL_TEST,将操作更改为 GL_KEEP,甚至将第二个 stencilFunc 更改为“NOT EQUAL 0”...结果总是相同的!我想我在这里缺少一些基本的东西。

void testStencil()

    // 1. Write a 1 in the Stencil buffer for 
    // every pixels of the first sphere:
    // All colors disabled, we don't need to see that sphere
    glEnable(GL_STENCIL_TEST);
    glStencilFunc(GL_ALWAYS, 0x1, 0x1);
    glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
    glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
    glDepthMask(GL_FALSE);   // Edit: added this
    
        sphe->W = mat4::trans(psa::vec4(1.0, 1.0, 1.0)) * mat4::scale(0.9);
        sphe->show();
    

    // 2. Draw the second sphere with the following rule:
    // fail the stencil test for every pixels with a 1.
    // This means that  any pixel from first sphere will
    // not be draw as part of the second sphere.
    glStencilFunc(GL_EQUAL, 0x0, 0x1);
    glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
    glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
    glDepthMask(GL_TRUE);    // Edit: added this
    
        sphe->W = mat4::trans(psa::vec4(1.2, 1.2, 1.2)) * mat4::scale(1.1);
        sphe->show();
    

瞧!如果有人能指出我正确的方向,我将不胜感激。我还将确保将您的答案参考我找到的其他帖子。

【问题讨论】:

这与您的问题无关,但它本身就是一个问题...glClearStencil (0) 设置在您调用glClear (GL_STENCIL_BUFFER_BIT) 时应用的值。您以错误的顺序调用了这两个函数,但这可能并不重要,因为默认的清除值无论如何都是 0 我认为可能与之相关的一件事是您的深度缓冲区。您在模板通道上禁用了颜色写入,但深度缓冲区仍被写入。您所描述的内容:“可以通过孔看到世界背景(而不是更大的球体内部 - 面部剔除已停用)。” 似乎是由于对球体的深度测试没写颜色。尝试禁用深度写入以及颜色写入,并考虑在通道之间清除深度缓冲区;深度听起来像是罪魁祸首,但我现在还不清楚绘制顺序。 谢谢安东,我纠正了我在模板清除方面的错误,但你怀疑这不是问题。 您对深度缓冲区提出了一个非常好的观点:我添加了 glDepthMask(GL_FALSE / TRUE) 以伴随 glColorMask 指令。现在看起来我的“testStencil”函数实际上没有任何效果——它显示了一个完整的、未改变的球体……有没有机会我根本没有 Stencil 缓冲区功能?我在创建窗口时确实有一个“SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 1)”指令。 通常没有 1 位模板缓冲区这样的东西。我认为至少,为了使这项工作,你需要一个 8 的值。基于 GPU 的渲染器通常只支持 8 位模板并将其与深度缓冲区相结合。因此,通常需要使用 24 位深度和 8 位模板(组合为 32 位)。 DX10 GPU 支持 32 位深度 + 8 位模板 ,但你必须跳过箍才能获得它,它浪费了 24 位(32 位深度 + 8 位模板 + 24 -位未使用) ;) 【参考方案1】:

此问题中发布的 OpenGL 代码有效。问题的原因在于窗口初始化/创建:

下面分别是工作代码的 SDL1.2 和 SDL2 版本。请注意,在这两种情况下,SetAttribute 语句都放在窗口创建之前。主要问题是错误放置的语句不一定会在运行时失败,但也不会起作用。

SDL1.2:

if(SDL_Init(SDL_INIT_EVERYTHING) < 0) 

    throw "Video initialization failed";


SDL_GL_SetAttribute(SDL_GL_RED_SIZE,     5);
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE,   5);
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE,    5);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE,  24);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES,16);  
SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);

const SDL_VideoInfo * i;
if((i = SDL_GetVideoInfo()) == NULL)  

    throw "Video query failed";


int flag = (fs ? SDL_OPENGL | SDL_FULLSCREEN : SDL_OPENGL);
if(SDL_SetVideoMode(w, h, i->vfmt->BitsPerPixel, flag) == 0)

    throw "Video mode set failed";


glewExperimental = GL_TRUE;
if(glewInit() != GLEW_OK)

    throw "Could not initialize GLEW";


if(!glewIsSupported("GL_VERSION_3_3"))

    throw "OpenGL 3.3 not supported";

SDL2(本质上相同的代码,只是窗口创建函数发生了变化):

if(SDL_Init(SDL_INIT_EVERYTHING) < 0) 

    throw "Video initialization failed";


SDL_GL_SetAttribute(SDL_GL_RED_SIZE,     5);
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE,   5);
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE,    5);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE,  24);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES,16);  
SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);

int flag = SDL_WINDOW_OPENGL;
if((win = SDL_CreateWindow("engine", 100, 100, w, h, flag)) == NULL)

    throw "Create SDL Window failed";


context = SDL_GL_CreateContext(win);

glewExperimental = GL_TRUE;
if(glewInit() != GLEW_OK)

    throw "Could not initialize GLEW";


if(!glewIsSupported("GL_VERSION_3_3"))

    throw "OpenGL 3.3 not supported";

值得一提的几点: 1。尽管我知道不建议这样做,但 SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 1) 也可以 2。在 SDL2 中,SDL_GL_SetAttribute SDL_GL_MULTISAMPLESAMPLES 对我来说上升到 16,在 glewInit() 失败之后,但是,在窗口创建后移动多重采样设置,突然 glewInit 停止抱怨:我的猜测是它只是被忽略了。 3。在 SDL1.2 中,Multisampling 的任何值“似乎”都有效 4。考虑到模板缓冲区功能,只有下面的代码也有效,但我发布它本质上是为了提出一个问题:有多少属性设置实际上是有效的?以及如何知道,因为代码编译和运行没有明显问题?

// PROBABLY WRONG:
// ----
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);

const SDL_VideoInfo * i;
if((i = SDL_GetVideoInfo()) == NULL)  

        throw "Video query failed";


int flag = (fs ? SDL_OPENGL | SDL_FULLSCREEN : SDL_OPENGL);
if(SDL_SetVideoMode(w, h, i->vfmt->BitsPerPixel, flag) == 0)

    throw "Video mode set failed";


// No idea if the below is actually applied!
SDL_GL_SetAttribute(SDL_GL_RED_SIZE,     5);
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE,   5);
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE,    5);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE,  24);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 16); 
SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);

【讨论】:

老实说,这不是一个高质量的答案。您可能应该考虑编辑您的问题以显示您尝试使用 SDL 进行的初始化。我们将能够帮助您解决那个问题,让您找到一个真正的解决方案,而不涉及使用像 GLUT 这样的已弃用框架。 我只是认为就主题“用模板缓冲区勾勒 3D 模型”而言,答案就在那里。在我看来,转向 SDL 和/或过剩的考虑是无关紧要的。无论如何,我会在稍后发布我的发现。

以上是关于使用 Stencil-buffer 勾勒出 3D 模型的主要内容,如果未能解决你的问题,请参考以下文章

画一个阴影,勾勒出文本的轮廓。

太可怕,这项技术仅通过语音就能勾勒出你的长相

CRM软件,轻松勾勒出客户画像!

数智为线,经纬中国:新华三勾勒出的山河锦绣

是否有可能在三个js中勾勒出three.js形状的边缘?

《逐梦旅程 WINDOWS游戏编程之从零开始》笔记6——Direct3D中的顶点缓存和索引缓存