不解

《深入了解OpenGL》第四讲 :抗犬齿

继《深入了解OpenGL:颜色混合》、《深入了解OpenGL:顶点线性变换》之后,CocoaChina 会员“zenny_chen”为我们带来了 OpenGL 系列教程的第三讲:抗犬齿。原帖地址 http://www.cocoachina.com/bbs/read.php?tid-28590.html,欢迎参与讨论。下面是教程正文

    我们从第一讲到现在所绘制出的图形的边缘都会存在比较明显的犬齿印,如果边缘不处于水平、垂直或斜45度角的话。这一讲,我们将介绍如何摆脱这大煞风景的犬齿。
    抗犬齿的第一种做法是比较直观、易懂的。我们通过将线段边缘所覆盖到的像素面积进行判断,如果覆盖到了大部分的像素,那么使用线段颜色的比例就高,否则线段颜色的比例就低。也就是说,我们通过淡化边缘覆盖到一个面积小的像素来达成抗犬齿效果。我们可以看到红宝石书的第149页,图6-3。像素A、B、M以及N所覆盖到的像素面积很小,因此这些像素基本上就用背景色。这种方式的算法是:将OpenGL片断的覆盖比例与alpha值相乘,并且将这个值作为新的alpha把片断与帧缓存中对应的像素进行混合。
    我们下面将介绍一下如何多线段进行抗犬齿。
    首先,我们要用glEnable()开启GL_LINE_SMOOTH,这个用于启动直线的抗犬齿功能。然后我们可以调用glHint()函数来指定,暗示OpenGL做何种质量的抗犬齿。选项一般有:GL_FASTEST,这个是速度最快,但质量最差的;GL_NICEST,这个是速度最慢,但效果最好的;GL_DONT_CARE,这个是默认方式,没有偏向。


下面贴代码:

//
// MyView.m
// OpenGLTest
//
// Created by Zenny Chen on 4/25/10.
// Copyright 2010 GreenGames Studio. All rights reserved.
// P44
 
#import "MyView.h"
 
#include <OpenGL/OpenGL.h>
#include <math.h>
 
 
@implementation MyView
 
- (id)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self) {
// Initialization code here.
}
return self;
}
 
static const struct VertexInfo
{
GLfloat vertices[3];
}vertexList[] = {
// line 1
{-0.8f, 0.0f, -2.0f},
{-0.2f, 0.0f, -2.0f},
 
// line2
{0.2f, 0.0f, -2.0f},
{0.8f, 0.0f, -2.0f},
};
 
- (void)prepareOpenGL
{
glEnable(GL_CULL_FACE);
glEnable(GL_DEPTH_TEST);
 
glEnableClientState(GL_VERTEX_ARRAY);
 
glVertexPointer(3, GL_FLOAT, 0, vertexList);
 
glFrontFace(GL_CCW);
glCullFace(GL_BACK);
 
glShadeModel(GL_SMOOTH);
 
glClearColor(0.4, 0.4, 0.4, 1.0);
 
glViewport(0, 0, 320, 320);
 
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(-1.0, 1.0, -1.0, 1.0, 1.0, 5.0);
 
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glRotatef(10.0f, 0.0f, 0.0f, 1.0f);
 
glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glLineWidth(1.5f);
}
 
- (void)drawRect:(NSRect)dirtyRect {
 
// Drawing code here.
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 
glEnable(GL_BLEND);
glEnable(GL_LINE_SMOOTH);
 
glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
glDrawArrays(GL_LINES, 0, 2);
 
glDisable(GL_BLEND);
glDisable(GL_LINE_SMOOTH);
 
glColor4f(0.0f, 1.0f, 0.0f, 1.0f);
glDrawArrays(GL_LINES, 2, 2); 
  
glFlush();
}
 
@end

    上述代码完成的功能是画两条线段。其中左侧的红色线段用了抗犬齿效果;右侧的绿色线段则没用。我们可以对比一下效果。
    这边需要深入再讨论的是红宝石书上没进一步讲的东西。我们找到glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)这行代码。
    我们在前一章节讲了颜色混合,用的就是这个函数。第一个参数指定你绘制对象,即当前片断的混合系数,这里用的是源的alpha值;而目的,即当前在帧缓存中对应像素的混合系数,采用的是1减源的alpha值。
    我这里要强调一下,在真正计算时,alpha并不是我们用glColor4f所设定的那个alpha值。就如同我前面所说的,源的alpha值是先通过片断 覆盖到的一个相应像素的面积的比例,与源alpha值相乘得到的一个新的alpha值。因此,这个线段各个像素所对应的alpha值不是完全相同的。如同 红宝石书中图6-3中描述的,像素C、H的alpha值就比较高;而像素A、B的alpha值就小得多。而目的的alpha值就是计算得到的新的alpha值,然后用1去减。
    此外,我们还可以将GL_ONE_MINUS_SRC_ALPHA修改为GL_ONE,看看效果。我们会发现红线的犬齿印比原来要明显些,因为背景色的比重被加大了。
原来目的系数是1-alpha,现在变成了1。

    对于多边形的抗犬齿,我们可以采用多重采样来实现。多重采样技巧需要使用额外的颜色、深度和模板(stencil)信息对OpenGL图元进行抗锯齿处理。这样,每个片断就不只一种颜色,1个深度值和1组纹理坐标,而是根据子像素样本的数量,具有多种颜色、多个深度值和多组纹理坐标。这些信息的计算并不是在每个像素的中心进行的,而是分布于多个采样位置。

    在Mac OS X的OpenGL应用中,我们可以通过Interface Builder中对OpenGL视图进行属性上的设置来指明它可以有多少样本,然后采取多重采样。一般,4个样本进行多重采样对边缘抗犬齿的效果就已经非常明显了。尽管多重采样的计算比较复杂,但是我们对此不必有任何的焦虑,这些都由GPU自己去完成的。而对于iPhone开发而言,并没有提供如此的设置。由于多重采样的计算量很大,因此这也可能是iPhone没引入的原因。但是OpenGL ES确实有glEnable(GL_MULTISAMPLE)这个功能。所以,在iPhone App开发上,我们可以使用前一种方法,通过颜色混合进行抗犬齿。

评论