|
|
May 11 看了两篇关于卡通阴影构造的教程, 觉得有必要写下自己的理解 原理教程: http://www.gamedev.net/reference/programming/features/celshading/ 实例教程: http://nehe.gamedev.net/data/lessons/lesson.asp?lesson=37 其实实例教程是作者写的第二个版本, 以解他人求代码之苦, 呵呵 本文并非翻译, 如有谬误之处, 请指出 卡通阴影主要是指那些漫画手绘绘出的阴影效果, 不用光照实现的虚拟效果. 首先构造一个一维纹理, 其像素值只有白色, 不同程度的灰色, 黑色. 如下图所示
 上图为16个像素的一维纹理数据. 然后根据顶点的法线与光线方向的角度设置纹理坐标.其中顶点法线与光线成0度角, 则取纹理坐标1(cos0).如果顶点法线与光线方向垂直,则取纹理坐标0(cos(∏/2)). 本实例的一维纹理有32个像素,如下图所示
 用三角形来实现效果, 假设顶点1和顶点3的法线平行于光线方向. 而顶点2的法线则垂直于光线方向. 则效果如下:
代码为: glBegin(GL_TRIANGLES); glTexCoord1f(0.0); glVertex3f(-0.5, -0.5, 0.0); glTexCoord1f(1.0); glVertex3f(0.5, -0.5, 0.0); glTexCoord1f(1.0); glVertex3f(0, 0.5, 0.0); glEnd(); 结论: 如果绘制一个模型, 已知顶点法线向量N, 光线向量L, 顶点坐标V. 则 V*N = |V|*|N|cos(V和N的夹角) (其中*为点积) 所以 V*N / (|V|*|N|) 就等于其纹理坐标向量, 标记为T. 然后 绘制的时候,设置顶点和设置纹理坐标的语句如下: glTexCoord1f(T); glVertex3fv(V); 在使用纹理过程中注意要禁止光照,禁止混合, 允许一维纹理. 在实例教程中, 还涉及了法线向量旋转的问题. 假设原法线向量在单元矩阵中为N, 旋转后的矩阵为 M = {x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16}; 取子矩阵 M1 = {x1, x2, x3, x5, x6, x7, x9, x10, x11}. N*M1 就可以得到旋转后的法线向量N1. 本例实现图像, 模型数据来自于实例教程.
May 01 第11章 分格化和二次方程表面 11.1 多边形分格化 简单凸多边形---多边形的边只在顶点处相交, 没有重复的顶点, 并且任何顶点只有两条边相遇. 多边形分格化---将多边形分解成简单的凸多边形. 步骤: 1. 用gluNewTess()函数创建一个新的分格化对象 2. 调用几次gluTessCallback()函数, 注册一些回调函数, 在分格化时执行必要的操作. 最复杂的情况--分格化算法检测到多边形存在相交并且必须调用在GLU_TESS_COMBINE回调函数中所注册的函数的时候. 3. 调用gluTessProperty()函数指定分格化属性. 最重要的属性是环绕规则.用于确定哪些区域应该被填充,哪些区域不应该着色. 4. 通过指定一个或多个闭合多边形的轮廓线来渲染经过分格化的多边形. 如果物体的数据是静态的, 可以把经过分格化的多边形放在显示列表中 5. 如果需要对其他物体进行分格化, 可以复用原来的分格化对象. 如果完成了对分格化对象的操作, 可以使用gluDeleteTess()函数来删除它. gluGetString(GLU_VERSION)函数得到GLU版本 11.1.1 创建分格化对象 GLUtesselator* gluNewTess(void); 创建一个新的分格化对象. 11.1.2 分格化回调函数 void gluTessCallback(GLUtesselator *tessobj, GLenum type, void (*fn)()); 1. 更改回调函数只需要调用gluTessCallback()即可. 2. combine回调函数用于在边相交的地方创建新的顶点. 3. error回调函数则是在分格化过程中遇到错误的时候调用的. 4. 每个分格化对象被创建之后, GLU_TESS_BEGIN回调函数就会被调用, 并使用下面这四个参数值之一: GL_TRIANGLE_FAN, GL_TRIANGLE_STRIP, GL_TRIANGLES或GL_LINE_LOOP.(如果启用了GLU_TESS_BOUNDARY_ONLY属性, 那么就使用GL_LINE_LOOP进行渲染) 5. 如果与GLU_TESS_EDGE_FLAG相关联的回调函数启用了边界标志, 那么GL_TESS_BEGI回调函数只能用与GL_TRIANGLES. 6. 可以使用gluErrorString()函数获得一个描述错误信息的字符串. 声明了回调函数的程序需要在声明函数时加上符号CALLBACK. #ifndef CALLBACK #define CALLBACK #endif GL_TESS_COMBINE回调函数的用法: 1. 当分格化算法检查输入轮廓线的相交情况, 以决定是否创建新顶点时, 该回调函数会被调用. 2. 当分格化对象决定把两个非常靠近的顶点合并为一个顶点时,该回调函数会被调用. 新创建的顶点是最多可达4个原有顶点的线性组合 每种类型的回调函数都有一个具有用户指定数据的版本和非用户指定数据的版本 用户指定的数据是由应用程序向gluTessBeginPolygon()函数提供的, 然后不经修改地传递给每个*DATA回调函数 使用GLU_TESS_BEGIN_DATA, 用户指定的数据可以用于表示”每个多边形”的数据 如果指定了一个特定回调函数的全部两个版本, 具有user_Data的那个版本就会被实际调用, 另一个被忽略. 11.1.3 分格化属性 void gluTessProperty(GLUtesselator *tessobj, Glenum property, Gldouble value); 设置分格化属性 GLU_TESS_WINDING_ODD和GLU_TESS_WINDING_NONZERO是最为常用的环绕规则. 环绕规则的CSG用法: 假设每条轮廓线已经进行了定义, 每个外部区域的环绕数为0, 每个内部区域环绕数为1. CSG操作: 并集,交集,差集 void gluGetTessProperty(GLUtesselator *tessobj, Glenum property, Gldouble* value ); 对于分格化对象tessobj,property的当前值被返回value void gluTessNormal(GLUtesselator *tessobj, GLdouble x, GLdouble y, GLdouble z); 对于分格化对象tessobj,定义了一条法线向量. 用于控制生成多边形的环绕方向. 11.1.4 多边形定义 void gluTessBeginPolygon(GLUtesselator *tessobj, void* user_data); void gluTessEndPolygon(GLUtesselator *tessobj); 开始和结束对需要进行分格化的多边形的指定. void gluTessBeginCountour(GLUtesselator *tessobj); void gluTessEndCountour(GLUtesslator *tessobj); 开始和结束一条闭合的轮廓线指定. void gluTessVertex(GLUtesselator *tessobj, GLdouble coords[3], void *vertex_data); 指定分格化对象的当前轮廓线的一个顶点 11.1.5 删除分格化对象 void gluDeleteTess(GLUtesselator *tessobj); 删除指定的分格化对象tessobj, 并释放与它相关联的所有内容.
11.2 二次方程表面: 渲染球体, 圆柱体和圆盘 11.2.1 管理二次方程对象 GLUquadricObj* gluNewQuadric(); 创建一个新的二次方程对象 void gluDeleteQuadric(GLUquadricObj* qobj); 销毁二次方程对象qobj, 并释放它所使用的内存. void gluQuadricCallback(GLUquadricObj* qobj, GLenum which, void(*fn)()); 定义了一个函数fn, 以便在特定情况下调用 11.2.2 控制二次方程对象的属性 void gluQuadricDrawStyle(GLUquadricObj* qobj, GLenum drawStyle); 对于二次方程对象qobj, drawStyle参数用于控制它的绘图风格. void gluQuadricOrientation(GLUquadricObj* qobj, GLenum orientation); 对于二次方程对象qobj, orientation是GL_OUTSIDE(默认值)或GL_INDISE, 这个参数用于控制法线向量的方向. void gluQuadricNormals(GLUquadricObj* qobj, GLenum normals); 设置法线的模式 void gluQuadricTexture(GLUquadricObj* qobj, GLUboolean textureCoords); 设置是否生成纹理坐标. 11.2.3 二次方程图元 void gluSphere(GLUquadricObj* qobj, GLdouble radius, GLint slices, (GLint)stacks);; 绘制一个半径为radius的球体. void gluCylinder(GLUquadricObj* qobj, GLdouble baseRadius, GLdouble topRadius, GLdouble height, GLdouble slices, GLdouble stacks); 绘制一个沿z轴的圆柱体 void gluDisk(GLUquadricObj* qobj, GLdouble innerRadius, GLdouble outerRadius, GLint slices, GLint rings); 绘制一个位于z=0平面的圆盘. void gluPartialDisk(GLUquadricObj *qobj,GLdouble innerRadius,GLdouble outerRadius,GLint slices, GLint loops,GLdouble startAngle,GLdouble sweepAngle); 绘制一个位于z=0平面的不完整圆盘.  April 30 第10章 帧缓存 在OpenGL窗口中, 左下角的像素为(0, 0). 一般而言, 像素(x, y)占据的矩形区域左下角为(x, y), 右上角为(x+1, y+1).
10.1 缓存及其用途 1) 颜色缓存, 左前,右前,左后,右后和任意数量的辅助颜色缓存; 2) 深度缓存 3) 模板缓存 4) 累积缓存
注意:X窗口系统,RGBA模式至少保证1个颜色缓冲区,模板缓冲区,深度缓冲区,累计缓冲区 颜色索引模式至少保证1个眼色缓冲区,深度缓冲区,模板缓冲区. 可以使用glXGetConfig{}函数查询. 用glGetIntegerv()查询每个像素占据的缓存空间的参数 GL_RED_BITS, GL_GREEN_BITS, GL_BLUE_BITS, GL_ALPHA_BITS --- 颜色缓存中R, G, B, A分量的位数 GL_INDEX_BITS --- 颜色缓存中每个颜色索引的位数 GL_DEPTH_BITS --- 深度缓存中每个像素的位数 GL_STENCIL_BITS --- 模板缓存中每个像素的位数 GL_ACCUM_RED_BITS, GL_ACCUM_GREEN_BITS, GL_ACCUM_BLUE_BITS, GL_ACCUM_ALPHA_BITS --- 累积缓存中R, G, B, A分量的位数. 10.1.1 颜色缓存 1) 颜色缓存存储了颜色索引或RGB颜色数据, 还可能存储了alpha值. 2) 支持立体观察(stereoscopic viewing)的OpenGL实现有左颜色缓存和右颜色缓存. 它们分别用于左立体图像和右立体图像. 3) 如不支持立体观察, 则只使用左颜色缓存. 4) 双颜色缓存有前缓存和后缓存, 单缓存系统只有前缓存. 5) 支持不可显示的辅助颜色缓存 6) 函数glGetBooleanv()查询是否支持立体观察和双缓存: GL_STEREO和GL_DOUBLE_BUFFER. 函数glGetIntegerv()查询多少个辅助缓存可用: GL_AUX_BUFFERES.
10.1.2 深度缓存 深度缓存 --- 存储了每个像素的深度值. 通常是离视点的距离, 因此深度值大的像素会被深度值小的像素覆盖.
10.1.3 模板缓存 用途之一: 绘图范围限制在屏幕的特定区域内.
10.1.4 累积缓存 累积缓存也存储RGBA颜色数据, 将一系列的图像合成一幅图像. 可以对图像进行超量采样, 然后对样本进行平均, 并将结果写入颜色缓存中, 从而实现场景反走样. 不能将数据直接写入累积缓存. 累加操作总是针对一个矩形块的, 通常是将数据移入或移出颜色缓存.
10.1.5 清空缓存 void glClearColor(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); void glClearIndex(GLfloat index); void glClearDepth(GLclampd depth); void glClearStencil(GLint s); void glClearAccum(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); 功能: 指定用于清除颜色缓存(RGBA模式或颜色索引模式), 深度缓存, 模板缓存和累积缓存的值. 类型为GLclampf和GLclampd的参数值被截取到[0.0, 1.0]. 深度缓存的默认清除值为1.0, 其他缓存为0.0.
设置清除值后, 便可以调用函数glClear()来清除缓存. void glClear(GLbitfield mask); 功能: 清除指定的缓存. mask:GL_COLOR_BUFFER_BIT, GL_DEPTH_BUFFER_BIT, GL_STENCIL_BUFFER_BIT, GL_ACCUM_BUFFER_BIT的逻辑或(OR). 清除颜色缓存时, 如果启用了像素所有权测试, 裁剪测试和抖动操作, 它们都会在清除操作中执行. 屏蔽操作(glColorMask()和glIndexMask())也会生效.alpha测试, 模版测试, 深度测试并不会影响glClear()函数的操作. 10.1.6 指定要读写的颜色缓存 指定要写入的缓存 glDrawBuffer(); 指定要读取的缓存 glReadBuffer();
使用双缓存, 通常只绘制后缓存, 并在绘制完成后交换缓存. 你可能想将双缓存窗口视为单缓存窗口: 通过调用函数glDrawBuffer()使得可以同时绘制前缓存和后缓存. void glDrawBuffer(GLenum mode); 功能: 指定要写入或消除的颜色缓存以及禁用之前被启用的颜色缓存. 可以一次性启用多个缓存. GL_FRONT: 单缓存的默认值 GL_FRONT_RIGHT: GL_NONE: GL_FRONT_LEFT: GL_FRONT_AND_BACK: GL_RIGHT: GL_AUXi: i表示第几个辅助缓存. GL_LEFT: GL_BACK_RIGHT: GL_BACK: 双缓存的默认值 GL_BACK_LEFT: 注意: 启用多个缓存用于写操作时, 只要其中一个缓存存在, 就不会发生错误. 如果指定的缓存都不存在, 就发生错误. void glReadBuffer(GLenum mode); 功能: 选择接下来的函数调用glReadPixels(), glCopyPixels(), glCopyTexImage*(), glCopyTexSubImage*() 和 glCopyConvolutionFilter*()将读取的缓存. 并启用以前被函数glReadBuffer()启用的缓存. 参数mode取值: GL_FRONT: 单缓存默认 GL_FRONT_RIGHT: GL_BACK_RIGHT: GL_FRONT_LEFT: GL_LEFT: GL_AUX: GL_BACK_LEFT: GL_BACK: 双缓存默认 GL_RIGHT: 注意: 启用缓存用于读取操作时, 指定的缓存必须存在, 否则将发生错误. 10.1.7 屏蔽缓存 void glIndexMask(GLuint mask); void glColorMask(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); void glDepthMask(GLboolean flag); void glStencilMask(GLuint mask); 功能: 设置掩码, 用于控制写入到指定缓存中的数据, glIndexMask: 只用于颜色索引模式中, 掩码中1对应的数据位被写入颜色索引缓存中. 0对应的位不写入. glColorMask: 只影响RGBA模式下的写入, red, green, blue, alpha决定是否写入相应的分量, GL_TRUE时写入. glDepthMask(): 如果参数flag的值为GL_TRUE, 则启用深度缓存用于写入, 否则禁用深度缓存. glStencilMask(): 参数mask的含义与函数glIndexMask()中相同. 所有GLboolean参数的默认值是GL_TRUE, 两个GLuint参数的默认值都是1.
禁用深度缓存: 如果背景很复杂, 则在背景绘制之后, 禁用深度缓存. 绘制新增的物体, 只要不相互重叠.. 下一帧时, 只需恢复树木图像, 无需恢复深度缓存中的值. 这种方法很有效. 模板缓存的屏蔽操作让你能够使用一个多位模板缓存来存储多个模板(每位一个) 函数glStencilMask()指定的掩码用于控制哪些模板位面可写, 与函数glStencileFunc()的第三个参数指定的掩码无关, 后者指定模板函数将考虑哪些位面. 10.2 片段测试和操作 测试顺序: 1. 裁剪测试 2. alpha测试 3. 模版测试 4. 深度测试 5. 混合 6. 抖动 7. 逻辑操作
10.2.1 裁剪测试 void glScissor(GLint x, GLint y, GLsizei width, GLsizei height); 设置裁剪矩形的位置和大小. 需要启用GL_SCISSOR_TEST.
10.2.2 alpha测试 需要启用GL_ALPHA_TEST void glAlphaFunc(GLenum func, GLclampf ref); 设置用于alpha测试的参考值和比较函数. alpha测试可用于透明算法和贴花.
10.2.3 模板测试 void glStencilFunc(GLenum func, GLint ref, GLuint mask); void glStencilFuncSeparate(GLenum face, GLenum func, GLint ref, GLuint mask); 设置模板测试所使用的比较函数(func),参考值(ref),掩码(mask)
void glStencilOp(GLenum fail, GLenum zfail, GLenum zpass); void glStencilOpSeparate(GLenum face, GLenum fail, GLenum zfail, GLenum zpass); 指定当一个片段通过或未通过模板测试时, 模板缓冲区中的数据如何进行修改. 下图设置模板为中间的方块
模板查询glGetInteger()可使用的参数: GL_STENCIL_FUNC GL_STENCIL_REF GL_STENCIL_VALUE_MASK GL_STENCIL_FAIL GL_STENCIL_PASS_DEPTH_FAIL GL_STENCIL_PASS_DEPTH_PASS 10.2.4 深度测试 void glDepthFunc(GLenum func); 为深度测试设置比较函数.
10.2.5 遮挡测试 遮挡测试允许我们判断一组几何图形在进行深度测试之后是否可见. 步骤: 1. 为每个所需的遮挡查询生成一个查询ID 2. 调用glBeginQuery(), 表示开始一个遮挡查询 3. 渲染需要进行遮挡查询的几何图形 4. 调用glEndQuery(), 表示已经完成了遮挡查询 5. 提取通过遮挡查询的样本数量.
生成查询对象 void glGenQueries(GLsizei n, GLuint* ids); 对遮挡查询对象进行初始化 void glBeginQuery(GLenum target, GLuint id); void glEndQuery(GLenum target); target必须为GL_SAMPLES_PASSED. 判断遮挡查询的结果 void glGetQueryObjectiv(GLenum id, GLenum pname, GLint *params); void glGetQueryObjectuiv(GLenum id, GLenum pname, GLuint *params); 清除遮挡查询对象 void glDeleteQueries(GLsizei n, const GLuint *ids); 10.2.6 混合,抖动和逻辑操作 void glLogicOp(GLenum opcode); 选择需要执行的逻辑操作.
10.3 累计缓冲区 void glAccum(GLenum op, GLfloat value); 控制累积缓冲区 op参数: GL_ACCUM--用glReadBuffer()所读取的当前缓冲区中读取每个像素.把R,G,B,A值乘以value.而后结果累加到累积缓冲区. GL_LOAD--同上,只是用结果替换累积缓冲区中的值. GL_RETURN--累积缓冲区中的值乘以value, 结果放在颜色缓冲区中. GL_ADD和AL_MULT--累积缓冲区中的值与value相加或者相乘,结果写回累积缓冲区. 另GL_MULT的结果截取[-1.0. 1.0].GL_ADD的结果不截取.
10.3.1 场景抗锯齿 首先清除累积缓冲区, 并启用前缓冲区用于读取和写入. 然后循环执行几次(例如n次)代码, 对图像进行微移和绘制. 对数据进行累积的方法: glAccum(GL_ACCUM, 1.0/n); // 绘制到不会显示的颜色缓冲区中, 避免显示中间图像. 并最终调用 glAccum((GL_RETURN, 1.0); // 绘制到可显示的颜色缓冲区中(或即将交换的后缓冲区).
可提供一用户接口, 来显示每次图像累积之后所获得的改善, 如图像足够满意, 可随时终止累积. 本例主要是逐步在窗口累积各颜色分量. 一共累积八次,且其中每次都用j8数组的数据微移场景, 使用glFrustum函数可以是的我们场景不必对称. 正交投影的偏移只需要用glTranslatef()移动一个像素内的偏移即可. 下图为没有反锯齿没有用累积缓存的图像 下图为使用了累积缓存反锯齿的图像
累积缓存我分步骤显示,看看效果 10.3.2 运动模糊 按照相同的方式设置累积缓冲区, 但不是对图像进行空间上的微移, 而是进行时间上的微移. glAccum(GL_MULT, decayFactor); 这样随着场景绘制到累积缓冲区中,整个场景越来越模糊. 其中decayFactor是个0.0到1.0之间的数字, 其值越小, 运动速度越快. 然后使用 glAccum(GL_RETURN, 1.0); 转移到眼色缓冲区中.
10.3.3 景深 距离聚焦平面越远,物体就越模糊.
accPerspective函数 第五个和第六个参数表示在x和y方向上微移, 实现场景抗锯齿 第九个参数设定聚焦平面. 模糊程度有第七个和第八个参数决定, 由这两个参数的乘积决定.
10.3.4 柔和阴影 多个光源所产生的柔和阴影-- 可以多次渲染场景,每次只打开一个光源, 然后将渲染结果累积起来.
10.3.5 微移 样本的微移值 见表10-6
March 25 近日来, 这件事情颇为热闹. 不少理智人士颇为惊讶莫名. 将廉价爱国, 冲动, 无理智等等大帽盖在这轰赶和服母女的学生头上.
不可
否认, 和服是属于日本的民族服装. 但在中国的土地上穿上和服, 毕竟还是很难让人接受的事情. 且不说中日战争, 就那对母女穿上和服,
我是一点美感都没看出来. 一个中国人, 以穿外国传统服装为荣, 真的很难揣测这对母女之心态. 而对于任何参加武大樱花盛典的人, 看到这对母女,
至少心里是绝对不喜的, 只是很少有人直接出面反对而已. 一些学生出于义愤轰赶这对母女. 怎么就导致一些人惊讶莫名了呢?
虽然中国有
很多人借爱国敛财, 借爱国做一些丑陋的事情. 但是如果整个国家的青年对于历史没有了羞耻感, 对国家一点感情都没了, 那才是整个中国莫大的悲哀.
就好比法国的青年, 如果看到法国人穿纳粹服装在凯旋门耀武扬威之时,不义愤,不作为, 反而大喊这是人家的个人爱好和权利一样.
我对中国有些文人那出奇冷酷的理智表示无语...
|
|
|
|