实时渲染 -- 着色(Shading)

着色是一个很重要的环节,它负责计算出颜色(光栅化只是填充像素格,换句话说是负责转移颜色到屏幕),着色计算要考虑的因素通常有:光照、纹理、着色频率(着色单位)等。

当然,现在着色的概念已经扩张到不是单纯的输出颜色,有时还可能需要输出别的属性(例如顶点着色器vertex shader可以输出修改后的顶点属性)。

Blinn-Phong 光照模型

光的能量基本规律

朗伯余弦定律认为,当光源照射方向与表面法线夹角越小,物体表面接受的光的能量就会越多:

 其次,能量也存在衰减现象,其能量和与光源距离的平方成反比:

能量时刻向外扩散,因此随着距离的平方越大,球表面面积也越大,每单位面积能量强度便越小

 

现实世界的光是非常复杂,这些基本规律其实还远远不够,而前人基于这些简单原理,就提出了Blinn-Phong 光照模型。

Blinn-Phong 是一个经典的经验光照模型(非现实物理),它使用三种类型的光叠加而成得到光照结果。

 环境光(Ambient)

现实世界存在大量间接光照(即多次反射后最终经过目标物体并最终反射到眼睛的光),但是计算这些间接光照过于复杂,Blinn-Phong模型直接将这类间接光的总和表示为环境光 I_a ,并且每个着色材质以一个系数 k_a来控制其接受环境光的程度。

L_a = k_aI_a

漫反射光(Diffuse)

漫反射光则是常见的一种物理现象(粗糙的表面会将入射光均匀反射),因此漫反射光只通过计算入射光线与着色表面法线的点乘(朗伯余弦定律),并用max函数避免夹角为负数(0意味着摄像机看不到着色点),然后乘上光的能量(能量衰减后的),最后同理每个着色材质以一个系数 k_d 来控制其接受漫反射光的程度。

L_d = k_d(I/r^2)max(0,n\cdot l)

高光(Specular)

高光则基于镜面反射现象(当眼睛观测方向与光反射方向相向时会看到最亮的颜色),也就是说眼睛观测方向 v 与反射方向 R 夹角越接近,则接受的光能量也越多:

而Blinn-Phong假设,一个半程向量(观测方向v和入射方向l的中间方向)与表面法线的角度α,近似等于观测方向v 与反射方向 R 的夹角(因为V接近R等价于h接近n,而且偏差也可以通过系数调整)。使用半程向量计算夹角的好处是相比后者计算量大大减少。使用p次方是为了体现高光特性(亮者更亮,暗者更暗,从而突出亮光),最后同理每个着色材质以一个系数 k_s 来控制其接受高光的程度。

半程向量: h = bisector(v,l) = \frac{v+l}{||v+l||}

L_s = k_s(I/r^2)max(0,cos\alpha )^p = k_s(I/r^2)max(0,n\cdot h)^p

Blinn-Phong 光照总结

BlinnPhong(光照结果) = Ambien(环境光)+ Diffuse(漫反射光) + Specular(高光)

L = L_a+L_d+L_s = k_aI_a + k_d(I/r^2)max(0,n\cdot l) + k_s(I/r^2)max(0,n\cdot h)^p

这样我们可以通过以下三个参数来表示一个着色物体的材质:

  • k_a 调整一个着色材质的基础光照影响程度
  • k_d 调整一个着色材质的漫反射接受程度
  • k_s 调整一个着色材质的高光接受程度

光源模型 

除了光照模型,我们还应该提供光源模型以供模拟完整的光照场景;假设我们当前有1个物体,n 个光源,在对该物体每个像素点着色(主要指前向渲染)的时候,我们需要进行 O(n) 次光照计算(根据每个光源模型特性计算一次)。

环境光(Ambient Light)

影响因素:强度、颜色

这种类型的光源来自空间中的任何地方,并以相同的方式照亮所有物体,可以理解成所有物体的基础光照颜色。

点光源(Point Light)

影响因素:强度、颜色、位置、衰减系数

点光源是从一个具体的点位置向外散发光线。

聚光灯(Spot Light)

影响因素:强度、颜色、位置、衰减系数、方向、夹角

聚光灯就是在点光源的基础之上加上了方向和夹角的概念,使其只在某些方向向外散发光线

方向光(Directional Light)

影响因素:颜色、方向、强度

可以理解成无数个平行光照射过来(类似太阳照射地面上的物体),无论在哪个地方收到的入射光方向都是一样的。

着色频率(着色单位)

给物体着色的一个问题是:我们该给物体每个面进行一次着色还是每个点进行一次着色。容易知道,着色频率越高,其效果更真实,但是代价也就越高。

基于面的着色(Flat shading)

对每个三角面进行一次着色计算,因此根据光照模型的着色得到的结果将会是同个三角面都是同一颜色:

计算三角形法线:简单地将两条边叉乘得出三角形的法线,并且还需要进行标准化

N_t= (V_1-V_0)\times (V_2-V_1)

基于顶点的着色(Gouraud shading)

对每个顶点进行一次着色计算,然后三角形内部像素点则通过在三角形的重心坐标插值(Barycentric Interpolation)得到颜色:

计算顶点近似法线:简单地将与顶点衔接的面法线平均起来。

N_v = \frac{\sum _iN_i}{\left \| \sum _iN_i \right \|}

基于像素的着色(Phong shading)

基于像素的着色是实际上最常用的着色频率,它对每个像素点进行一次着色计算,得到更加真实的效果:

计算像素近似法线:三角形内部像素点通过在三角形的重心坐标插值得到向量,最后这个插值出来的向量还需要标准化:

在现在的渲染管线中,往往提供了可编程的vertex shader和pixel/fragment shader来让开发者决定顶点着色器和像素着色器分别进行什么着色计算。由于现代GPU性能的强大,我们都习惯在pixel/fragment shader进行光照计算以获得更好的视觉效果。

  • 25
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 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
发出的红包

打赏作者

全栈游戏开发

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值