Point : GPU编程的艺术!一切的历史!

Point: 渲染渲染,神奇的渲染!!
————————————————
只要你走的足够远,你肯定能到达某个地方。
1"GPU编程" History
—————————
//由于笔记我是由印象里面转移过来的,排版上请见谅
想要实现自己的光线?想要渲染出自己的正方体!?那么没错了。
我们需要的东西不是C语言,而是英伟达所提出的 Cg 语言了。
GPU 概念于20世纪70年代末80年代初被提出,采用单片集成电路作为图形芯片。【具有高并行结构,更多的ALU】
它能够很快的进行几张图片的合成和渲染【最初仅限于此】
它的结构图如下↓
在CPU上的C++所采用循环逻辑来遍历一个像素,而在GPU上我们只需要一条语句则足够了。
—————————
2"GPU图形绘制管线" 
—————————
图形绘制管线描述GPU渲染流程,即给定【点、三维物体、光源、照明模式、纹理元素等】如何绘制一幅二维图像。
我们将图形绘制管线分为三个主要阶段: 应用程序阶段,几何阶段,光栅阶段。
【应用阶段】: [采用高级编程语言进行开发,主要和内存|CPU打交道],诸如碰撞检测,场景图建立,空间八叉树更新,视锥裁剪等等算法都在此执行。该阶段末节,几何体数据(顶点坐标,法向量,纹理坐标,纹理等)通过数据总线传送到图形硬件。
【几何阶段】: [主要负责顶点坐标变换、光照、裁剪、投影以及屏幕映射] 此阶段一般在GPU中进行运算,该阶段末尾得到了经过变换和投影之后的顶点坐标、颜色、以及纹理坐标。
【光栅阶段】: [基于几何阶段的输出数据],为像素正确配色,以便绘制完整图像,该阶段进行的都是单个像素的操作,每个像素的信息存储在颜色缓冲器(color buffer 或者 frame buffer)中。
 
"几何阶段" 
几何阶段的主要工作是 "变换三维顶点坐标"和"光照计算"。
显卡信息中通常有一个标识为 "T&L" 硬件部分。
我们从计算机中得到一系列三维坐标顶点,但我们最终输出的地方却是一块二位屏幕。
一般情况下GPU帮我们自动完成这个转换。
根据顶点坐标变换的先后顺序,主要有几个坐标空间,或者说坐标类型:
Object space 模型坐标空间
World space 世界坐标空间
Eye space 观察坐标空间
Clip and Project space 屏幕坐标空间
【Object Space -> World Space】
object space coordinate 就是模型文件中的顶点值,这些值在模型建模时得到的。
例如我们用 3dsMax 生成一个 球体 且输出为 .max 文件。
这个文件中包含的数据就是  object space coordinate
而且我们的 object space coordinate 和其他他物体没有任何参照关系。
在计算机世界中,我们的物体需要一个稳定的【坐标原点】才能确认自己所在的位置。
显然,我们将一个模型导入计算机后,就应该给它一个相对于坐标原点的位置。
那么这个位置就是 world space coordinate.
从 object space coordinate 到 world space coordinate 的变换我们由一个 四阶矩阵控制。
我们称之为 :  world matrix [世界矩阵]
光照计算也通常在 世界坐标系 中进行的,当然可以在 视野坐标系 中得到同样的效果,因为在同一观察空间中物体之间相对关系是不变的。
   Point : 模型文件中的顶点法向量需要转换为world space坐标时才能在GPU中进行使用。
法向量从模型坐标到世界坐标中的转换是 world matrix 的转置矩阵的逆矩阵。[这里可以参阅 潘李亮的3D变换中法向量变换矩阵的推导一文 or 《计算机图形学》11章]
 
【World Space -> Eye Space】
每个人都是从各自的视点出发观察这个世界的,无论是主观世界还是客观世界。
同样,在计算机中每次只能从唯一的视觉出发渲染物体。
在游戏程序中,都会提供视点漫游功能,屏幕显示内容随着视点的变化而变化,这是因为GPU将物体顶点坐标从 world space 转到了 eye space。
所谓的eye sapce 即以相机为原点,由视线方向,视觉和远近平面,共同组成一个梯形的三维空间 -> 视锥。
视锥内的顶点保留渲染,视锥外的顶点去除渲染。
 
【Eye Space -> project and clip space】
只要顶点坐标转换到 视野坐标中,就需要判断哪些点是可见的,位于viewing frustum 梯形内的顶点被视为可见的。
位于 视锥 外的顶点视为不可见,这一步称为 "clip"(裁剪),识别指定区域内或区域外的图形部分称之为裁剪算法。
我们并非是先裁剪再投影,裁剪被安排到一个单位立方体中进行。该立方体的对角顶点分别为(-1,-1,-1)和(1,1,1)。
这个立方体我们称为 = CVV 规范立方体
CVV的近平面(梯形体比较小的矩形面)的X,Y坐标对应屏幕像素坐标(左下角是0,0),Z坐标则是代表画面像素深度。
  1.用透视变换矩阵把顶点从视锥体中变换到裁剪空间的CVV中。
  2.在CVV进行图元裁剪。
  3.屏幕映射,将前过程得到的坐标映射到屏幕坐标上。
把顶点从 视锥 变换到 CVV 中才是我们常说的“投影”
主要的投影方法:  平行投影   And   透视投影
【透视投影推导: 潘宏 (网名: Twinsen) 的 "透视投影变换推导" 算法部分可以越读:<计算机图形学>12章第三节】
    Point : 使用C++进行视锥裁剪的算法可以参阅OGRE引擎的源码
 
【Primitive Assembly 图元装配】
这一步将顶点根据primitive(原始连接关系)还原出网格结构,网格由顶点和索引组成,在之前的流水线是对顶点的处理,而这一阶段是将顶点链接在一起,组成线,线组成面单元。之后就是对超出屏幕外的三角形进行裁剪。
想象一下,一个顶点在画面外,另两个顶点在画面内,我们在屏幕上看到的就是一个四边形。然后将该四边形切割成两个三角形。
裁剪算法主要有: 视域剔除 ||  背面剔除  ||  遮挡剔除 || 视口裁剪  等等。
 
【Rasterization 光栅化】
光栅化:  决定哪些像素被集合图元覆盖的过程。经过上面多个坐标转换后,现在我们得到了每个顶点的屏幕坐标值,也知道了我们需要绘制的图元。
但同时我们遗留两个问题:  
1. 点屏幕坐标是浮点数,但是像素都是由整数点来表示的,如何确定屏幕坐标值所对应的像素?
2.在屏幕上需要绘制的有点,线,面,如何根据两点绘制一线?如何根据确定位置的3个像素点绘制一个三角面片?
问题解答同时给出:
1.绘制的位置只能接近两指定端点的实际线段位置。
2.画线算法: DDA算法,Bresenham画线算法。 区域图元填充算法: 扫描多边形填充算法,边界填充算法。
 
【Pixel 像素处理】
我们需要在更新缓冲前计算出每个像素的颜色值,此阶段被遮挡面通过一个被称为深度测试的过程而消除。
》包含的步骤;  消除遮挡面 -> 纹理操作,根据像素纹理坐标查询对应纹理值 -> Blending 混色,根据透明度混合两色。称为alpha混合技术,每个像素都有一个 RGB颜色值 + Z缓存器深度值 + alpha值。
得到一个 RGBA,使用 over 操作符将该值与原像素颜色混合。
  
  a是透明度(alpha)  ca 表示透明物体颜色  cs表示混合前像素颜色
Over操作可用于照片混合,OGRE中也有一项技术称为compositor(合成器)
关于 透明度、合成的知识 可以在 《实时计算机图形学》第四章 4.5节获取
———————————————
3"Shader Language" 着色器语言
———————————————
上面说到的管线器渲染果然还是太老了,英伟达为我们准备了新的东西。
那就是着色器语言,着色器语言被定位为高级语言。
Cg语言全称为 "C for Graphic" 另一种  GLSL "High Level Shading Language"
这两种语言的语法接近C语言,Shader language目前主要有3种语言
基于 OpenGL 的 GLSL,  基于 Direct3D 的 HLSL, 还有 NVIDIA 的 Cg 语言。
有关它的原理,这里暂留,在后续的学习中再对其研究。
【Vertex Shader Program && Fragment shader program】
Vertex Shader Program(顶点着色程序)被 Programmable Vertex Processor(可编程顶点处理器)执行。
Fragment shader program(片段着色程序)被 Programmable Fragment Processo(可编程片段处理器)执行。
顶点着色程序从GPU前端模块{寄存器} 中提取图元信息(顶点位置,法向量,纹理坐标等),并完成顶点坐标空间转换,法向量空间转换,光照计算等操作,最后将计算好的数据传送到指定的寄存器中。
然后片段着色程序从中获取需要的数据,通常为【纹理坐标,光照信息等】,并根据这些信息以及从应用程序传递的纹理信息进行每个片段的颜色计算,最后将处理后的数据光栅化。
【什么是片段?】
片段和像素有什么不一样?所谓片段就是所有的三维顶点在光栅化之后的数据集合,这些数据还没有经过深度值比较,而屏幕内显示的像素是经过深度比较的。
 
【Cg语言】
Cg语言可以被OpenGL和Direct3D使用支持,Cg程序是运行在OpenGL和DirectX标准顶点和像素着色的基础上的。
Cg编译器首先将Cg程序翻译成可被图形API所接受的形式,然后应用程序使用适当的OpenGL和Direct3D命令将翻译后的Cg程序传递给图形处理器。
 
我们安装了 CgTool Kit 后在cmd中测试是否安装好了Cg编译器
Cg程序的编译命令形式为: 
==================================
 cgc [options]      file
      可选配置 |  Cg程序文件名
===========典型编译方式============
 cgc -profile glslv -entry main_v test.cg
==========将cg转换为GLSL和HLSL的编译==========
 cgc -profile glslv -o direct.glsl -entry main_v test.cg
-profile是profile配置项名: glslv 是当前所使用的profile名称。【OpenGL: (Vetex)vp40 (pixel)fp40 DirectX:(Vetex) vs_2_x (pixel)ps_2_x】
-entry着色器程序的入口函数名称配置: main_v是顶点着色程序的入口函数名。
通常为了区分顶点\片段着色器程序入口函数名区别开,不使用默认名称
【Cg语言基本数据类型】
》float ; 32 位浮点数据    》 half ; 16位浮点数据    》 int ; 32位整数
》fixed ; 12 位顶点数       》 bool ; 布尔值    
常量类型后缀   f  -> float ;  h-> half;   x->fixed;
> sampler* ; 纹理对象句柄
分为6类 :  [sampler] [sampler1D] [sampler2D] [sampler3D] [samplerCUBE] [samplerRECT<!DirectX>]
》向量  float4 array = float(1.0, 2.0 , 3.0 , 4.0);
向量的一些使用:  float2 a = float2(1.0,1.0);  float3 c=float3(a,0.0);
》矩阵  float1x1 matrixl;  // 等价于float martirl,最大不能大于4*4阶矩阵
矩阵的初始化:  float2x3 martrix5 = {1.0,2.0,3.0,4.0,5.0,6.0}
【Cg数组类型】
着色器程序中的数组的目的: 作为从外部应用程序传入大量参数到 Cg 的顶点程序中的形参接口。
例如: 皮肤形变相关的 矩阵数组 或者 光照参数数组等
====数组的声明=====
float a[10];
float4 b[10];
====数组的初始化=====
float a[4]={1.0,2.0,3.0,4.0}
====数组长度获取=====
int length = a.length;
===数组的数组===
1 float b[2][3] = {{0.0,0.0,0.0},{1.0,1.0,1.0}};
2 int length1= b.length; // 2
3 int length2= b[0].length; //3
===矩阵的复数使用===
float4x4 M[4];  // 表示包含4个4阶矩阵数据
【Cg结构体类型】
Cg结构体更加贴近于C++语言中的类,但主要展现它的封装性。
1 struct myAdd{
2     float val;
3     float add(float x)
4     {
5         return val+x;
6     }
7 };
8 myAdd s;
//成员函数是否可以重载依赖于使用的profile版本
一般来说,我们的Cg源代码都会在文件首部定义两个结构体,分别用于定义输入和输出的类型。
这两个结构体定于与普通的C结构定义不同,除了定义结构体成员的数据类型以外,还定义了该成员的绑定语义类型,所谓绑定语义类型是为了与宿主环境进行数据交换的时候识别不同数据类型的。
Cg支持的绑定语义类型包括:  POSTION(位置),COLOR(颜色),NORMAL(法向量),Texcoord(纹理坐标)等类型。
当顶点着色程序向片段着色程序传递的数据类型较多的情况下,使用结构体可以大大的方便代码的编写和维护。
总而言之,使用结构体是一个好习惯。
【Cg接口(Interfaces)类型】
Cg语言提供接口类型,实际上使用得并不多。
【Cg表达式与控制语句】
Cg语言表达式由操作符关联一个或多个操作数构成,Cg中的操作符与C语言中相似,但是多了一个Swizzle操作符,它可以取向量的分量。
===Cg语言关系操作符的使用===
1 float3 a=float3(0.5,0.0,1.0);
2 float3 b=float3(0.6,-0.1,0.9);
3 bool3 c = a<b;
===Swizzle操作符===
可以使用swizzle操作符(.)将一个向量的成员取出组成一个新的向量。
swizzle操作符后接 x,y,z,w 分别表示原始向量的第一个,第二个,第三个,第四个元素。
swizzle操作符后接 r、g、b、a的含义与前者相同。
float4(a,b,c,d).xyz 等价于float3(a,b,c);
float4(a,b,c,d).wxyz 等价于float4(d,a,b,c);
posted @ 2018-09-16 16:07  CodeLoser  阅读(435)  评论(0编辑  收藏  举报