着色

着色 shading

对于每一个shading point
赋予每个像素点:明暗或者颜色
对一个物体添加材质的过程
着色过程是只考虑单独物体,不考虑其他物体造成的光线遮挡,所以着色完成后之后有明暗,颜色,不会有阴影
考虑局部
image
shading point所在的平面的法线与光线的夹角(法线向量与光线向量的夹角)导致了这个表面的明暗变化

Blinn-Phone反射模型:漫反射+高光+环境光照

漫反射

光在漫反射的平面上反射后是均匀的,观察效果与观察位置无关
光源到反射平面的能量在平面上的作用后
Lambertian(Diffuse)Shading

Ld=kdIr2max(0,nl)

Ld:人眼接受的漫反射光 diffusely reflected light
kd:diffuse coefficient(color) 着色点的吸收率
max(0,n·l):着色点能接受到的能量
I/r²:光到达着色点的能量,
点光源能量守恒,在一个球体内,每个球面的能量相同
image

高光 specular

image
高光产生原因:对于一个shading point 镜面反射方向与观察方向足够接近的时候会表现出高光。

为了求解高光的数学表达,引入h向量,该向量为入射向量和观测向量的角平分线,其与shading point的法线方向的夹角,就是镜面反射向量与观测向量的夹角。由于其数学表达式易求。
image

h=l+v||l+v||Ls=ks(Ir2)max(0,nh)p

注意到这个角度有一个指数,添加这个指数的原因是为了使得高光更加贴合实际,比如如果角度为45°,应该没有什么高光效果,但其余弦仍然是一个较大的值。
image

环境光照#

假设任何一个点从各处环境接受的光照是相同的

La=kaIa

与入射方向和观测方向无关,一般为一个常数

Blinn Phone模型

L=La+Ld+Ls=kaIa+kdIr2max(0,nl)+ks(Ir2)max(0,nh)p

image
观测点对shading point的距离是不会影响效果,而光源对shading point的距离会影响效果

着色频率

对所有shading point 进行着色
三种处理方式

  • Flat shading
    将物体分为多个平面,如果将这个平面视为shading point进行shading得到的着色效果
    image

  • Gouraud shading
    对每个平面的顶点当作shading point进行着色,然后对平面内的点进行插值计算颜色
    image

  • Phone shading
    对平面内每一个像素当作shading point进行着色,其法线方向主要是通过平面顶点的法线插值得到,从而进行着色
    image

三者随着平面的划分越多,差别越小
image

求取顶点的法线#

  • 当目标模型是一个圆或者球时,顶点的法线就是圆心(球心)与该点的连线的单位向量
  • 对于一般的目标模型,一个顶点一般为多个三角形(划分的平面)的顶点,可以求出每个三角形平面的法线方向,然后加权平均得到该顶点的法线

Nv=iNi||iNi||

image

每个像素的法线#

已知顶点求取两点中间的各点法线,利用重心坐标

渲染(实时渲染)管线

管线pipeline:从场景到图片的过程
输入空间的点->投影->点形成三角形->光栅化形成像素->对像素着色->最终屏幕上的图片颜色
image

Shader->控制顶点或者像素是如何着色的代码

  • 一些管线步骤下的具体操作:
    vetex processing: MVP变换,gouraud shading,纹理(贴图Texture mapping)
    rasterization: 采样,反走样
    fragment processing: Z-buffer,phone shading,纹理(贴图Texture mapping)

Shader Programs#

GPU允许自己编程控制顶点或者像素如何着色,这个shader应该是通用的,不需要调用for循环。下面以一个opengl的简洁版对像素点只用漫反射的着色举例

//全局变量
uniform sampler2D myTexture;
uniform vec3 lightDir;
//定义法线
varing vec3 norm;
//uv为纹理上的坐标
varing vec2 uv;
//每个像素如何着色
void diffuseShader(){
  //计算漫反射系数
  vec3 kd;

  //与纹理相关
  kd = texture2d(myTextur,uv);
  //与入射光线和法线的夹角余弦相乘,其余两个变量均为1
  kd *= clamp(dot(-lightDir, norm),0.0,1.0);
  //返回到opengl的变量中输出该像素的颜色
  gl_FragColor = vec4(kd,1.0);
}

在线shader编程

纹理Texture Mapping

作用:定义物体上任何一个点的特有的属性
纹理为一张二维图,贴图。
将一个三维物体展开为二维物体,并且使得每一个划分的三角形与二维贴图上的三角形一一对应,这样就获得了三维图形的每一个三角形的纹理就是它的属性。
二维贴图一般的范围是1×1的正方形大小。每一个三角形在纹理上对应了一个坐标uv。良好的纹理是可以复用的,并且前后左右相互可以完美拼接。

插值

求得三角形顶点值后,如何求取平滑的内部值,使用重心坐标,计算三角形内的点与顶点关系,然后将这个关系应用到其他情况(比如纹理,深度等)
一个与三角形同平面的点可以这样表示

(x,y)=αA+βB+γCα+β+γ=1

如果α,β,γ都是非负的,那么这个点就在三角形内部。
有两种方法来计算α,β,γ的值,第一种是利用面积比
image

第二种是利用坐标,其实就是将叉积面积求解展开

α=(xxB)(yCyB)+(yyB)(xCxB)(xAxB)(yCyB)+(yAyB)(xCxB)β=(xxC)(yAyC)+(yyC)(xAxC)(xBxC)(yAyC)+(yByC)(xAxC)γ=1αβ

接下来就要利用α,β,γ来插值三角形内部的属性,比如着色,深度,纹理等V=αVA+βVB+γVC
注意:重心坐标在投影变换后会发生改变,对于深度的插值,所使用的重心坐标应当是投影之前在三维空间的重心坐标。

理想情况下,对于每一个像素点恰好有一个对应的纹理,我们只需要把纹理坐标(u,v)的值赋值给像素坐标(x,y)就可以了,但实际情况比较复杂,主要有两种情况:第一种为纹理图案的尺寸比整个屏幕小,第二种情况就是纹理图案的尺寸比整个屏幕大

为了方便处理,我们将纹理也分成块状称为texel

纹理图案小的情况

不经过处理,可能会导致多个像素pixel使用同一个texel,图像锯齿状严重。
一种简单的处理方式为Bilinear,双线性插值,对于下方这样一个pixel中心点(红点),在texel上的位置,如果不处理,那么这个texel所包含的所用pixel中心点都会被赋以相同的颜色,而为了求取相对准确的pixel,利用将它围住的4个texel做双线性插值。
image

定义相关的texel和一些间距如下图
image

插值相关公式lerp(x,v0,v1)=v0+x(v1v0),所以先求取纵坐标方向上的插值

u0=lerp(s,u00,u01)u1=lerp(s,u01,u11)

最终坐标就是(x,y)=lerp(t,u0,u1),这个(x,y)就是pixel相对准确的纹理值

纹理图案较大的情况

具体表现为在近处,因为一个像素无法包含一个纹理,导致锯齿状,在远处一个像素包含了多个纹理,而最终表现仅仅是区域内中间纹理的值,导致摩尔纹的出现
image

可以发现实际上就是采样问题,只不过之前是物体投影在pixel上被采样,现在是pixel在texel上被采样,其实也是物体投影,因为物体投影经过光栅化后,就通过pixels来组成了。
如果利用超分辨率,是可以解决的,但是消耗过大,超分辨其实就是利用将一个pixel分成多个子Pixel,然后子pixel去寻找合适的texel然后平均。那么有没有一种方法可以直接获得一个区域的平均值呢?
那就是范围查询,范围查询和点查询都是属于查询类算法,点查询就如同之前的双线性插值,根据多个点求解一个点的值。而范围查询就是查找这个范围的平均值,最大值,最小值等。
我们现在主要利用mipmap来求解平均值--fast approx.(not accurate) only square

mipmap#

利用一张图生成一系列的图,每一张图都是上一张图的一半。
image

除去原始的第一张,剩余缩小的图加起来所需的多余容量为1/3,然后mimap相当于一个散列表,将每次的图片存储起来,索引值为层数。原始图片为0.
image

如何使用mipmap在纹理映射中#

image

上图左边为光栅化图形,uv为纹理坐标,右边为像素点映射到纹理坐标上的结果,我们利用Mipmap求取的就是图中红点和蓝点的纹理值。那么如何确定该像素点在纹理图上的覆盖区域呢。
使用红点举例,左下角的红点,在光栅化图上有上和右两个红点,投影在纹理图上也是相似位置,然后求取在纹理图上上点与该点的距离,右点与该点的距离,选择最大值,成为该点的square边长
image
求取这个正方形边长后,就需要根据这个正方形的边长L,在mipmap中查询平均值,平均值就是当Mipmap的square变为1×1时。而什么时候变成1×1呢,层数D=log2L
例如,当L是包含了4×4个texel时,通过Mipmap,第一次之后变为2×2,第二次变为1×1,此时的mipmap图像就是该边长为L的square的平均值。

回到实际中,当物体离我们比较近,一个square的包含的可能就是1×1的texel不需要平均,我们看到也比较清晰,而当离我们比较远,一个square包含的可能就是n×n的texel通过mipmap,就显得模糊。那么随之而来的又有两个问题,第一个是变换不连续,第二个是远处会非常模糊。

  • 解决不连续问题:利用插值
    不连续问题的原因:我们的层数是整数分散的,0,1,2,...但图片是连续的。
    解决:比如求解1.8层,首先在光栅化上每个点在每一层纹理投影上都有自己的位置,我们对代求的光栅化上的一个点它的实际mipmap层数是1.8层,那么我们通过双线性插值,求得他在第1层和第2层上的值,然后再利用这两个值,在层与层之间插值
    image

该图为光栅化图,不是纹理uv图,图中黑点表示pixel的中心点,红点表示任意非pixel中心点的点

  • 远处模糊问题 overblur
    image

主要原因:远处一个像素实际投影在uv图上的为一个细长的斜的长方形,导致其所需要的square的L较大,使得其模糊过度。
image

解决方法:

  1. 部分解决:各向异性过滤,就是增加按长 ,宽分别缩小而不是等比例缩小
    image

mipmap的作用实际是对角线,相当于增加分析情况(pixel),来避免模糊,可查询的内容更多
内存开销是原本的3倍。
2. EWA :对于斜着的长方形
将每个图形分割为多个圆形,然后每次查询都是多次查询

作者:XTG111

出处:https://www.cnblogs.com/XTG111/p/17738862.html

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   XTG111  阅读(27)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
more_horiz
keyboard_arrow_up light_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示