[计算机图形学]着色,布林-冯着色模型,着色频率(前瞻预习/复习回顾)

一、前言

到目前为止的前几篇文章已经向大家介绍了从MVP变换屏幕映射光栅化的过程,但是仅仅这些还不能让我们很好的模拟真实的世界,究其原因是上面的所有过程都没有涉及光线的运算,而缺少了光也就缺少了明暗的变化,导致渲染出来的结果并不真实。这就需要我们本篇所讲的内容:着色:引入明暗颜色的不同。而闫令琪老师的GAMES101中把着色定义为引入不同的材质材质本质上就是和光线的作用结果,也就是说不同的材质对光线有不同的作用结果,也就导致了我们看每种不同材质的物体的感觉并不相同。

二、Blinn-Phong模型

1.总览

Blinn-Phong 反射模型最早由 Phong 提出,后由 Blinn 对其做出改进。该模型不但计算效率高并且和物理事实足够接近,可以得到不错的效果。Blinn-Phong和Phong的区别我们会在待一会介绍给大家。无论是Phong还是Blinn-Phong都有一个基础的思想,即任何光照都分为三个部分漫反射部分,高光部分,环境光部分。由此三种光照来模拟任何物体表面的光照。

 Photo credit: Jessica Andrews, flickr

定义:

着色点始终在物体的局部规定为平面,

观测方向v,法线方向n,光线方向l,以单位向量表示

物体表面相关属性:color,shininess(这里不是亮度)

同时只考虑着色点的局部作用,不考虑阴影(shading ≠ shadow)

2.漫反射部分

 在漫反射中,光线会被均匀的反射到各个方向

但是实际上,这只是理想情况,实际中,光线在传播过程中会有各种各样的能量损失,包括角度和其他因素。

 上图揭示了,当光照和平面呈一定角度时候,光线的所有能量并不能都打到平面上,而是会损失一部分,而我们之前已经定义出了法线和光线的单位向量,不难发现,两者夹角越小时,损失的能量越少,反之损失能量越多。我们定义的单位向量的点积刚好可以表示出这个关系,也就是说l·n越接近1,光线越接近于与表面垂直,那么这个表面应该越亮,反之则越暗。

当然光线能量的损失不仅仅有角度这一因素,我们再来看下面这张图:

 这张图表示,一个点光源散发出的光每一段距离的传播,他的能量都集中在一个球壳上,而随着半径r,也就是传播距离越大,那么球壳的表面积增大,如果总能量不变的话,分布于每块单位面积的能量就会减少,而表面积中有r的平方,那么每层的单位面积上的能量应该和r的平方呈反比。这个过程称为衰减。也就说接受光线的点离光源越远,那么接受的能量越少。

那么综上,我们可以得出漫反射的表示方法:

 其中kd为定义的漫反射系数,为1表示不吸收任何能量,反射出白光(如果光源是白色),为0则吸收所有能量,显示出黑色,如果把它定义为RGB,那么就可以定义对不同颜色的吸收率,也就可以显示出不同的颜色。

其中max把cos的值从[-1,1]截断到了[0,1],因为负数在这里没有意义。(鄙人的补充:这里其实是兰伯特模型和半兰伯特模型的区别,这里采用半兰伯特在美术上显得更通透而不是兰伯特的那种背光面完全死黑)

3.高光部分

接下来我们说高光部分,通过生活经验和一些物理常识不难得知,当我们的视角方向和镜面反射光的方向越接近时,我们看到的高光会更强一些,这给了我们启发。

 也就说,我们用可以计算 l 的反射方向 R 和我们视角 v 的cos大小来得到我们高光的大小,而这也就是我们Phong模型与Blinn-Phong模型的区别,这套方案是Phong模型中计算高光的办法,而在Blinn-Phong中我们对高光计算进行了优化如下图:

 我们引入半程向量h:视角方向v和光照方向l的角平分线方向的单位向量。

ks为镜面反射系数,通常认为是白色。

不难看出,当h法线n越接近时,我们的v也就和R(l的反射方向)越接近。我们用这种近似方法代替Phong中的R·v,而这就是Blinn-Phong的高光计算方法它减少了计算时候的性能损耗。

那么为什么要在余弦上加一个p次方呢?因为余弦函数本身的变化率不够剧烈甚至接近于线性,导致如果真的单独用余弦来描述的话,我们会看到一个很大范围的高光,所以加上p次方之后,可以根据实际情况进行调节高光范围的大小,p越大,高光越集中,高光范围越小。下图揭示了这一规律:

4.环境光部分

环境光是间接光照例如一个物体靠在一个红色的墙边,那么红色的墙反射出的红光也会照到该物体,但是这并不是光源的直接照射,所以被称为间接光照。想要精确计算这么复杂的光是很难的,在Blinn-Phong模型中对环境光进行了理想化的简化假设即:任何一点接受的环境光都是相同的,且环境光来自四面八方,强度Ia。也就是我们把环境光假设成了一个常数,与我们定义的各种单位向量没有任何关系。

5.总结

 把我们上面说到的三部分都加在一块,就构成了Blinn-Phong模型,如上图。

三、着色频率

上图的三种球的模型是相同的,但是着色频率不同,第一幅图对面着色,第二幅图对每个顶点着色,而第三幅图对每一个像素着色。这说明了,我们的着色频率越高,效果越好。

 上面给出了三种不同的着色频率:Flat Shading,Gouraud Shading, Phong Shading。也就分别对应了我们说的:

对面着色,对顶点着色中间进行插值,对顶点像素中间的像素插值法线进而对每个像素着色。

但是需要注意的是,并不是说Flat这种低频率的着色频率就无法达到比较好的效果如下图所示:

 当球的模型足够光滑,也就是面数足够多的时候,即使是对每个面着色,也可以达到比较好的效果。所以要根据实际情况选择合适的着色频率才能有效的减少性能消耗和达到比较好的着色效果。(例如若面数很多,场景很复杂,那么Flat Shading会造成巨大的性能消耗,而Phong Shading基于像素的着色频率则会节省很多性能)

逐顶点的法线怎么求呢?

这里分为两种情况:知道的基础模型/其它情况

如果知道了模型的样子,比如一个球,那么法线自然就是从球心出发向各个方向,但是实际情况往往复杂的多,于是人们发明了一种方法,用临近该点的各个三角面的法线进行加权平均,权重的大小即为该面所占各个面积之和的大小。如下图所示:

 那么如果知道了两个顶点的法线,顶点之间的逐像素的法线怎么求呢?

这里需要用到重心坐标进行插值(求出的法线都要做归一化,因为它们都是单位向量)

这里因为篇幅有限,重心坐标我们下一篇进行讲解。

 参考:

Lecture 07 Shading 1 (Illumination, Shading and 
Graphics Pipeline)_哔哩哔哩_bilibili

Lecture 08 Shading 2 (Shading, Pipeline and Texture Mapping)_哔哩哔哩_bilibili

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
本文将介绍计算机图形学中的着色(Shading)技术,包括着色频率图形管线、纹理映射等知识点,并提供部分代码示例。 ## 着色频率 计算机图形学中的着色可以分为两种频率,分别是顶点着和像素着色。 顶点着(Vertex Shading)是在顶点级别对图形进行着色的过程,即在图形的每个顶点上计算颜值,然后通过插值计算出整个图形的颜。顶点着通常用于处理顶点属性,如位置、法向量和颜等。 像素着色(Pixel Shading)是在像素级别对图形进行着色的过程,即对图形的每个像素计算颜值。像素着色通常用于处理纹理映射、阴影效果、反射和折射等。 ## 图形管线 图形管线(Graphics Pipeline)是计算机图形学中的一个重要概念,它是将输入的几何形状转化为最终图像的过程,通常包括以下几个阶段: 1. 顶点输入:将输入的顶点数据传入图形管线。 2. 顶点着:在顶点级别对图形进行着色。 3. 图元装配:将顶点组装成图元,如点、线、三角形等。 4. 光栅化:将图元转化为像素,并计算像素在屏幕上的位置。 5. 像素着色:对图形的每个像素进行着色。 6. 输出合成:将所有像素合成成最终的图像。 以下是一个简单的图形管线示例: ```c++ // 顶点着器 void vertexShader(in vec3 position, out vec4 color) { // 计算顶点颜 color = vec4(1.0, 0.0, 0.0, 1.0); // 将顶点位置传递给下一个阶段 gl_Position = vec4(position, 1.0); } // 像素着色器 void pixelShader(in vec4 color, out vec4 fragmentColor) { // 直接输出顶点颜 fragmentColor = color; } // 主程序 int main() { // 顶点数据 vec3 vertices[] = { vec3(-1.0, -1.0, 0.0), vec3(0.0, 1.0, 0.0), vec3(1.0, -1.0, 0.0) }; // 图元数据 GLuint indices[] = {0, 1, 2}; // 创建着色器程序 GLuint program = createProgram(vertexShader, pixelShader); // 获取顶点着器输入位置的位置 GLuint positionLocation = glGetAttribLocation(program, "position"); // 创建顶点数组对象 GLuint vao; glGenVertexArrays(1, &vao); glBindVertexArray(vao); // 创建顶点缓冲区对象 GLuint vbo; glGenBuffers(1, &vbo); glBindBuffer(GL_ARRAY_BUFFER, vbo); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); // 将顶点数据传递给顶点着器 glEnableVertexAttribArray(positionLocation); glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, 0); // 创建索引缓冲区对象 GLuint ibo; glGenBuffers(1, &ibo); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); // 渲染图元 glUseProgram(program); glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, 0); // 销毁着色器程序、缓冲区对象和顶点数组对象 glDeleteProgram(program); glDeleteBuffers(1, &vbo); glDeleteBuffers(1, &ibo); glDeleteVertexArrays(1, &vao); return 0; } ``` ## 纹理映射 纹理映射(Texture Mapping)是一种基于图像的着色技术,它可以在三维模型表面上贴上图片,从而增强模型的真实感和细节。 纹理映射通常包括以下几个步骤: 1. 加载纹理图像:从文件中加载纹理图像,并将其存储在计算机内存中。 2. 创建纹理对象:将纹理图像传递给图形硬件,并创建一个纹理对象。 3. 纹理坐标计算:计算每个顶点在纹理图像中对应的位置,通常使用二维纹理坐标表示。 4. 纹理采样:在纹理图像中根据纹理坐标采样像素颜,并将其作为顶点颜。 5. 顶点着:使用顶点颜进行顶点着。 以下是一个简单的纹理映射示例: ```c++ // 顶点着器 void vertexShader(in vec3 position, in vec2 texCoord, out vec2 vTexCoord) { // 将纹理坐标传递给下一个阶段 vTexCoord = texCoord; // 将顶点位置传递给下一个阶段 gl_Position = vec4(position, 1.0); } // 像素着色器 uniform sampler2D texture; void pixelShader(in vec2 vTexCoord, out vec4 fragmentColor) { // 在纹理图像中采样像素颜 vec4 texel = texture2D(texture, vTexCoord); // 输出纹理像素颜 fragmentColor = texel; } // 主程序 int main() { // 顶点数据 vec3 vertices[] = { vec3(-1.0, -1.0, 0.0), vec3(0.0, 1.0, 0.0), vec3(1.0, -1.0, 0.0) }; // 纹理坐标数据 vec2 texCoords[] = { vec2(0.0, 0.0), vec2(0.5, 1.0), vec2(1.0, 0.0) }; // 图元数据 GLuint indices[] = {0, 1, 2}; // 加载纹理图像 GLuint texture = loadTexture("texture.png"); // 创建着色器程序 GLuint program = createProgram(vertexShader, pixelShader); // 获取顶点着器输入位置和纹理坐标的位置 GLuint positionLocation = glGetAttribLocation(program, "position"); GLuint texCoordLocation = glGetAttribLocation(program, "texCoord"); // 创建顶点数组对象 GLuint vao; glGenVertexArrays(1, &vao); glBindVertexArray(vao); // 创建顶点缓冲区对象 GLuint vbo; glGenBuffers(1, &vbo); glBindBuffer(GL_ARRAY_BUFFER, vbo); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); // 将顶点数据传递给顶点着器 glEnableVertexAttribArray(positionLocation); glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, 0); // 创建纹理坐标缓冲区对象 GLuint tbo; glGenBuffers(1, &tbo); glBindBuffer(GL_ARRAY_BUFFER, tbo); glBufferData(GL_ARRAY_BUFFER, sizeof(texCoords), texCoords, GL_STATIC_DRAW); // 将纹理坐标数据传递给顶点着器 glEnableVertexAttribArray(texCoordLocation); glVertexAttribPointer(texCoordLocation, 2, GL_FLOAT, GL_FALSE, 0, 0); // 创建索引缓冲区对象 GLuint ibo; glGenBuffers(1, &ibo); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); // 渲染图元 glUseProgram(program); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, texture); glUniform1i(glGetUniformLocation(program, "texture"), 0); glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, 0); // 销毁着色器程序、缓冲区对象、纹理对象和顶点数组对象 glDeleteProgram(program); glDeleteBuffers(1, &vbo); glDeleteBuffers(1, &tbo); glDeleteBuffers(1, &ibo); glDeleteTextures(1, &texture); glDeleteVertexArrays(1, &vao); return 0; } ``` 以上是计算机图形学着色技术的简单介绍,希望对您有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值