游戲渲染技術(shù):前向渲染 vs 延遲渲染 vs Forward+渲染(二)

2 前向渲染

前向渲染是三個(gè)光照技術(shù)中最簡(jiǎn)單的,也是游戲圖形渲染中最常見(jiàn)的技術(shù)焕蹄。出于這個(gè)原因逾雄,也是光照計(jì)算最昂貴的技術(shù),它不允許在場(chǎng)景中出現(xiàn)大量的動(dòng)態(tài)光源腻脏。

大部分使用前向渲染的圖形引擎會(huì)采用一些技術(shù)來(lái)模擬場(chǎng)景中大量的光源的情況鸦泳,例如,lightmap(光照貼圖)和lightProbe(light probe)都是采用從場(chǎng)景中放置的靜態(tài)光源預(yù)先計(jì)算光照貢獻(xiàn)的方法永品,并將這些光照貢獻(xiàn)存儲(chǔ)在紋理中做鹰,以便在運(yùn)行時(shí)加載。不幸的是鼎姐,lightmap和lightprobe不能模擬場(chǎng)景中的動(dòng)態(tài)光源钾麸,因?yàn)檫@些光源產(chǎn)生的光照貼圖常常在運(yùn)行時(shí)會(huì)被廢棄掉(discard)。

在這個(gè)實(shí)驗(yàn)中炕桨,前向渲染的結(jié)果被用作與另外兩個(gè)渲染技術(shù)進(jìn)行對(duì)比的基準(zhǔn)饭尝。前向渲染技術(shù)也被用來(lái)構(gòu)建與其它渲染技術(shù)進(jìn)行性能對(duì)比的基準(zhǔn)(baseline)。

很多在前向渲染中的方法會(huì)在延遲和forward+中被復(fù)用献宫,例如芋肠,前向渲染中的頂點(diǎn)著色器也會(huì)被用在延遲渲染和forward+渲染中。同樣遵蚜,計(jì)算最終光照和材質(zhì)著色的方法也被用于所有的渲染技術(shù)帖池。

在下一部分奈惑,我會(huì)描述前向渲染的實(shí)現(xiàn)細(xì)節(jié)。

2.1 頂點(diǎn)著色器(vertex shader)

vertex shader對(duì)所有的渲染技術(shù)是通用的睡汹,在這個(gè)實(shí)驗(yàn)中肴甸,只支持靜態(tài)幾何體,沒(méi)有骨骼動(dòng)畫(huà)和地表囚巴,這些需要不同的vertex shader原在。 vertex shader盡可能簡(jiǎn)單到可以支持pixel shader中的一些功能,如法線映射(normal mapping)彤叉。

在展示vertex shader的代碼之前庶柿,我會(huì)描述一下vertex shader使用的數(shù)據(jù)結(jié)構(gòu)。

// CommonInclude.hlsl
140 struct AppData
141 {
142     float3 position : POSITION;
143     float3 tangent  : TANGENT;
144     float3 binormal : BINORMAL;
145     float3 normal   : NORMAL;
146     float2 texCoord : TEXCOORD0;
147 };

AppData這個(gè)結(jié)構(gòu)定義的需要被應(yīng)用程序代碼發(fā)送到GPU端的數(shù)據(jù)秽浇。除了用于法線映射的normal向量浮庐,我們也需要發(fā)送切線(tagent)向量,副法線(或副切線)向量是可選的柬焕。切線和副法線即可以由3D美術(shù)師創(chuàng)建模型時(shí)生成审残,也可以由模型加載器進(jìn)行生成。在本例中斑举,如果它們沒(méi)有被3D美術(shù)師生成搅轿,我則使用Open Asset Import Library[7]來(lái)生成切線和副切線。

在vertex shader中富玷,我們也需要知道如何將模型空間向量變換成Pixel shader中需要的視圖空間(view space)向量璧坟,為了實(shí)現(xiàn)這個(gè)變換,我們需要發(fā)送world, view以及投影(projection)矩陣到vertex shader赎懦。為了存儲(chǔ)這些vertex shader中需要的每個(gè)模型的變量沸柔,我會(huì)創(chuàng)建一個(gè)常量緩沖區(qū)(constant buffer)。

// CommonInclude.hlsl
149 cbuffer PerObject : register( b0 )
150 {
151     float4x4 ModelViewProjection;
152     float4x4 ModelView;
153 };

因?yàn)槲也恍枰獑为?dú)存儲(chǔ)世界矩陣铲敛,因此我在應(yīng)用程序中預(yù)計(jì)算組合的model和view矩陣褐澎,以及組合的model、view和projection矩陣伐蒋,為vertex shader將它們發(fā)送到一個(gè)單獨(dú)的常量緩沖區(qū)中工三。

vertex shader的輸出(也即是pixel shader的輸入)看起來(lái)是這樣的:

CommonInclude.hlsl
181  struct VertexShaderOutput
182  {
183      float3 positionVS   : TEXCOORD0;    // View space position.
184      float2 texCoord     : TEXCOORD1;    // Texture coordinate
185      float3 tangentVS    : TANGENT;      // View space tangent.
186      float3 binormalVS   : BINORMAL;     // View space binormal.
187      float3 normalVS     : NORMAL;       // View space normal.
188      float4 position     : SV_POSITION;  // Clip space position.
189  };

VertexShaderOutput結(jié)構(gòu)用來(lái)傳遞變換過(guò)的頂點(diǎn)屬性(vertex attribute)到Pixel shader,vs后綴的成員表示該向量是view空間的先鱼。我選擇在view空間做所有的光照俭正,而不是在世界空間,這是因?yàn)樵趯?shí)現(xiàn)延遲渲染和Forward+渲染時(shí)在view空間坐標(biāo)下更容易焙畔。

vertex shader是很直接而簡(jiǎn)短的掸读,它唯一的目標(biāo)是將應(yīng)用程序傳來(lái)的模型空間向量變換成pxiel shader中使用的view空間向量。

vertex shader也必須要計(jì)算出光柵器(rasterizer)需要的在裁剪空間(clip space)中的position,用于vertex shader輸出的SV_POSITION被用于裁剪空間的位置儿惫,但該語(yǔ)義也可以作為pixel shader的輸入變量澡罚。當(dāng)SV_POSITION被用作pixel shader的輸入時(shí),該值表示屏幕空間(screen space)的位置[8]肾请,在延遲渲染和forward+的shader中留搔,我會(huì)使用該語(yǔ)義來(lái)獲取當(dāng)前像素在屏幕空間的位置。

// ForwardRendering.hlsl
3  VertexShaderOutput VS_main( AppData IN )
4  {
5      VertexShaderOutput OUT;
6
7      OUT.position = mul( ModelViewProjection, float4( IN.position, 1.0f ) );
8   
9      OUT.positionVS = mul( ModelView, float4( IN.position, 1.0f ) ).xyz;
10     OUT.tangentVS = mul( ( float3x3 )ModelView, IN.tangent );
11     OUT.binormalVS = mul( ( float3x3 )ModelView, IN.binormal );
12     OUT.normalVS = mul( ( float3x3 )ModelView, IN.normal );
13  
14     OUT.texCoord = IN.texCoord;
15  
16     return OUT;
17 }

你會(huì)注意到我會(huì)使用矩陣乘以輸入向量(矩陣在前铛铁,向量在后)隔显,這意味著矩陣是以主列(column-major)順序進(jìn)行存儲(chǔ)的。DirectX 10之前饵逐,HLSL中的矩陣是以主行(row-major)的順序進(jìn)行加載的括眠,輸入的向量是后乘矩陣的(向量在前,矩陣在后)倍权,DirectX 10之后掷豺,矩陣默認(rèn)加載的是主列順序。你可以通過(guò)在矩陣的聲明處指定主行修飾符來(lái)改變默認(rèn)順序[9]账锹。

2.2 像素著色器(Pixel Shader)

pixel shader會(huì)計(jì)算所有的光照和著色萌业,用于決定一個(gè)屏幕像素的最終顏色坷襟。在Pixel shader中采用的光照方程參考DirectX 11中的紋理和光照奸柬,如果你對(duì)光照方程不熟悉的話,在繼續(xù)之前需要去閱讀這篇文章婴程。

pixel shader使用幾個(gè)結(jié)構(gòu)來(lái)做這項(xiàng)工作廓奕,Material結(jié)構(gòu)存儲(chǔ)了描述被著色對(duì)象表面材質(zhì)的所有信息,Light struct包含了描述場(chǎng)景燈光的所有參數(shù)档叔。

2.2.1 材質(zhì)(Material)

Material定義了用于描述當(dāng)前著色對(duì)象表面的所有屬性桌粉,因?yàn)橐恍┎馁|(zhì)屬性可能還有相關(guān)的紋理(如,diffuse紋理衙四,specular紋理铃肯,或者法線貼圖),我們也會(huì)使用材質(zhì)來(lái)指明這個(gè)紋理是否呈現(xiàn)在這個(gè)對(duì)象上传蹈。

// CommonInclude.hlsl
10 struct Material
11 {
12     float4  GlobalAmbient;
13     //-------------------------- ( 16 bytes )
14     float4  AmbientColor;
15     //-------------------------- ( 16 bytes )
16     float4  EmissiveColor;
17     //-------------------------- ( 16 bytes )
18     float4  DiffuseColor;
19     //-------------------------- ( 16 bytes )
20     float4  SpecularColor;
21     //-------------------------- ( 16 bytes )
22     // Reflective value.
23     float4  Reflectance;
24     //-------------------------- ( 16 bytes )
25     float   Opacity;
26     float   SpecularPower;
27     // For transparent materials, IOR > 0.
28     float   IndexOfRefraction;
29     bool    HasAmbientTexture;
30     //-------------------------- ( 16 bytes )
31     bool    HasEmissiveTexture;
32     bool    HasDiffuseTexture;
33     bool    HasSpecularTexture;
34     bool    HasSpecularPowerTexture;
35     //-------------------------- ( 16 bytes )
36     bool    HasNormalTexture;
37     bool    HasBumpTexture;
38     bool    HasOpacityTexture;
39     float   BumpIntensity;
40     //-------------------------- ( 16 bytes )
41     float   SpecularScale;
42     float   AlphaThreshold;
43     float2  Padding;
44     //--------------------------- ( 16 bytes )
45 };  //--------------------------- ( 16 * 10 = 160 bytes )

GlobalAmbient用來(lái)描述全局地作用于所有對(duì)象上的環(huán)境光屬性押逼,從技術(shù)上而言,該變量應(yīng)當(dāng)是一個(gè)全局變量(不指定到單一對(duì)象)惦界,但因?yàn)樵谝粋€(gè)pixel shader一次只有一個(gè)材質(zhì)挑格,因此我認(rèn)為這是一個(gè)比較好的位置來(lái)存儲(chǔ)它。

ambient, emissive, diffusespecular顏色與在DirectX 11中的紋理和光照中具有相同的意義沾歪,所以這里不再進(jìn)一步解釋漂彤。

Reflectance用來(lái)表示應(yīng)當(dāng)與diffuse顏色混合的反射顏色的數(shù)量,這需要環(huán)境貼圖(cube texture)來(lái)實(shí)現(xiàn),在該實(shí)驗(yàn)中不會(huì)用到挫望。

Opacity用來(lái)決定一個(gè)對(duì)象的總的不透明度立润,這個(gè)值可以用來(lái)讓物體顯示透明,該屬性用來(lái)在透明pass中渲染半透明物體士骤,如果該值小于1(1表示完全不透明范删,0表示完全透明),該物體會(huì)被認(rèn)為是透明的拷肌,將會(huì)在透明Pass中渲染這個(gè)物體到旦,而不是在opaque pass中。

變量SpecularPower用來(lái)決定對(duì)象看起來(lái)有多閃亮巨缘,在DirectX 11中的紋理和光照中有對(duì)該變量的詳細(xì)解釋添忘。

在29-38行定義的變量HasTexture指明該對(duì)象是否使用相關(guān)的紋理進(jìn)行渲染,如果該參數(shù)為true若锁,相應(yīng)的紋理會(huì)被采樣搁骑,采樣得到的紋素(texel, 與pixel進(jìn)行區(qū)分)會(huì)與相應(yīng)的材質(zhì)顏色進(jìn)行混合。

BumpIntensity被用來(lái)縮放從bump貼圖中得到的高度值(不要與法線映射混淆又固,法線不會(huì)進(jìn)行縮放)仲器,以此來(lái)平滑(soften)或強(qiáng)化物體表面的起伏。大多數(shù)情況下仰冠,模型會(huì)使用法線貼圖來(lái)增加沒(méi)有細(xì)分(tessellation)的物體表面的細(xì)節(jié)乏冀,但也可以使用高度圖(heightmap)來(lái)做同樣的事情。如果模型使用了bump貼圖洋只,材質(zhì)的HasBumpTexture屬性會(huì)被設(shè)置為true辆沦,這種情況下模型使用被bump映射而不是法線映射。

SpecularScale用來(lái)縮放從高光強(qiáng)度紋理中讀取的高光強(qiáng)度值(specular power value)识虚。因?yàn)榧y理通常保存無(wú)符號(hào)的歸一化(normalized)的值肢扯,從紋理中采樣的值被讀取為[0..1]范圍的浮點(diǎn)數(shù)。1.0的高光強(qiáng)度沒(méi)有意義担锤,所以從紋理中讀取的高光強(qiáng)度在參與最終的光照計(jì)算之前會(huì)被SpecularScale進(jìn)行縮放蔚晨。

AlphaThreshold用來(lái)丟棄不透明度低于某個(gè)值的像素,通常在pixel shader中使用"discard"肛循。這可以被用于"cut-out"材質(zhì)铭腕,使用該材質(zhì)的物體不需要alpha進(jìn)行blend,但在物體上卻有洞(例如鏈接的柵欄)育拨。

Padding用來(lái)顯式的增加8個(gè)字節(jié)來(lái)填充material結(jié)構(gòu)谨履。盡管HLSL會(huì)隱式上增加這個(gè)填充(8個(gè)字節(jié))到該結(jié)構(gòu),以確保該結(jié)構(gòu)是16字節(jié)的倍數(shù)熬丧,顯式的增加填充會(huì)更加明確該結(jié)構(gòu)的尺寸和對(duì)齊方式與相應(yīng)的C++副本一致笋粟。

材質(zhì)屬性通過(guò)一個(gè)常量緩沖區(qū)傳遞給pixel shader怀挠。

// CommonInclude.hlsl
155 cbuffer Material : register( b2 )
156 {
157     Material Mat;
158 };

常量緩沖區(qū)與buffer寄存器的slot分配被用于該本文的所有pixel shader。

2.2.2 紋理

材質(zhì)已經(jīng)支持了8種不同類型的紋理

  1. 環(huán)境貼圖-Ambient
  2. 自發(fā)光貼圖-Emissive
  3. 漫反射貼圖-Diffuse
  4. 高光貼圖-Specular
  5. 高光強(qiáng)度貼圖-SpecularPower
  6. 法線貼圖-Normals
  7. 凹凸貼圖-Bump
  8. 不透明度貼圖-Opacity

并非所有的場(chǎng)景對(duì)象會(huì)用到所有的紋理插槽(slot)(法線貼圖和bump貼圖是互斥的害捕,所以它們可能可以復(fù)用同一個(gè)紋理插槽)绿淋,這取決于3D美術(shù)師讓場(chǎng)景中的模型使用哪些紋理。應(yīng)用程序會(huì)加載一個(gè)材質(zhì)相關(guān)的紋理尝盼,一個(gè)紋理參數(shù)和一個(gè)相關(guān)的紋理插槽為每個(gè)這些材質(zhì)屬性而聲明吞滞。

// CommonInclude.hlsl
167 Texture2D AmbientTexture        : register( t0 );
168 Texture2D EmissiveTexture       : register( t1 );
169 Texture2D DiffuseTexture        : register( t2 );
170 Texture2D SpecularTexture       : register( t3 );
171 Texture2D SpecularPowerTexture  : register( t4 );
172 Texture2D NormalTexture         : register( t5 );
173 Texture2D BumpTexture           : register( t6 );
174 Texture2D OpacityTexture        : register( t7 );

在本文的每個(gè)pixel shader中,紋理插槽0-7為這些紋理而保留盾沫。

2.2.3 燈光

Light結(jié)構(gòu)存儲(chǔ)了場(chǎng)景中定義一個(gè)燈光所需的所有信息裁赠。聚光燈,點(diǎn)光源和方向光沒(méi)有分開(kāi)到不同的結(jié)構(gòu)中赴精,定義任意一種類型的燈光所有必須的屬性都存儲(chǔ)在一個(gè)結(jié)構(gòu)中佩捞。

CommonInclude.hlsl
47  struct Light
48  {
49      /**
50      * Position for point and spot lights (World space). 
51      */ 
52      float4   PositionWS;
53      //--------------------------------------------------------------( 16 bytes )  
54      /** 
55      * Direction for spot and directional lights (World space).  
56      */ 
57      float4   DirectionWS; 
58      //--------------------------------------------------------------( 16 bytes )  
59      /**  
60      * Position for point and spot lights (View space).  
61      */  
62      float4   PositionVS;  
63      //--------------------------------------------------------------( 16 bytes )  
64      /**  
65      * Direction for spot and directional lights (View space).  
66      */  
67      float4   DirectionVS;  
68      //--------------------------------------------------------------( 16 bytes )  
69      /**  
70      * Color of the light. Diffuse and specular colors are not seperated.  
71      */  
72      float4   Color;  
73      //--------------------------------------------------------------( 16 bytes )  
74      /**  
75      * The half angle of the spotlight cone.  
76      */  
77      float    SpotlightAngle;  
78      /**  
79      * The range of the light.  
80      */  
81      float    Range;  
82     
83      /**  
84       * The intensity of the light.  
85       */  
86      float    Intensity;  
87     
88      /**  
89      * Disable or enable the light.  
90      */  
91      bool    Enabled;  
92      //--------------------------------------------------------------( 16 bytes )  
93     
94      /**  
95       * Is the light selected in the editor?  
96       */
97      bool    Selected;  
98     
99      /**  
100     * The type of the light.
101     */
102     uint    Type;
103     float2  Padding;
104     //--------------------------------------------------------------( 16 bytes )
105     //--------------------------------------------------------------( 16 * 7 = 112 bytes )
106 };

PositionDirection同時(shí)存儲(chǔ)了世界空間(WS后綴)和視圖空間(VS后綴)中的位置和方向。當(dāng)然蕾哟,位置屬性只應(yīng)用于點(diǎn)光源和聚光燈一忱,方向?qū)傩灾蛔饔糜诰酃鉄艉头较蚬狻V煌瑫r(shí)存儲(chǔ)了兩個(gè)不同空間谭确,是因?yàn)樵趹?yīng)用程序階段世界空間更易于使用帘营,然后在傳遞給GPU之前將世界空間轉(zhuǎn)換成視圖空間,使用這種方式可以不再需要多余的GPU存儲(chǔ)空間來(lái)管理多個(gè)燈光列表逐哈。因?yàn)?0,000燈光才只需要1.12MB的GPU內(nèi)存芬迄,所以這是一個(gè)合理的犧牲。但是最小化燈光結(jié)構(gòu)對(duì)GPU緩存有積極的一面鞠眉,并能提高渲染性能薯鼠。

在一些光照模型中择诈,漫反射和高光光照貢獻(xiàn)是分開(kāi)的械蹋,因?yàn)檫@種差異很小,這里選擇不分開(kāi)兩者的貢獻(xiàn)羞芍,而是將兩者存儲(chǔ)在Color變量中哗戈。

SpotlightAngle是以角度來(lái)表示的聚光燈圓椎體的半角,使用角度比弧度(radian)更加直觀荷科。當(dāng)然聚光燈的角度會(huì)在shader中計(jì)算余弦(consine)時(shí)被轉(zhuǎn)換成弧度唯咬。

Spotlight.png

  • 圖3,聚光燈的角度

Range決定了燈光到達(dá)表面的距離畏浆,同時(shí)也決定了燈光達(dá)表面的貢獻(xiàn)胆胰。雖然在物理上不完全正確(真實(shí)的燈光有一個(gè)衰減,實(shí)際上不會(huì)是0)刻获,燈光需要有一個(gè)有限的范圍來(lái)實(shí)現(xiàn)延遲著色和Forward+渲染技術(shù)蜀涨。這個(gè)范圍的單位是場(chǎng)景特定的,但這里會(huì)使用1單位是1米的規(guī)格。對(duì)于點(diǎn)光源厚柳,范圍是代表光的球體的半徑氧枣,對(duì)于聚光燈,范圍是代表光的圓錐體的長(zhǎng)度别垮。方向光不使用范圍便监,因?yàn)樗鼈儽徽J(rèn)為是無(wú)限遠(yuǎn)的,且指向同一個(gè)方向碳想。

Intensity用于調(diào)節(jié)計(jì)算出的光貢獻(xiàn)烧董。默認(rèn)情況下,這個(gè)值是1胧奔,它可以用來(lái)調(diào)節(jié)燈的亮度解藻。

Enabled標(biāo)志可以控制場(chǎng)景中燈光的開(kāi)啟或關(guān)閉,Enabled為false的燈會(huì)在shader中被跳過(guò)葡盗。

在本demo中螟左,燈光是可以被編輯的,可以通過(guò)在demo中點(diǎn)擊一個(gè)燈來(lái)選中它觅够,它的屬性也可以被修改胶背,為了表明一個(gè)燈被選中,Selected標(biāo)記會(huì)被設(shè)置為true喘先。當(dāng)一個(gè)燈在場(chǎng)景中被選中時(shí)钳吟,它會(huì)表現(xiàn)的暗一些,以表明它被中了窘拯。

Type用來(lái)指定該燈光的類型红且,可以是下面其中之一:

// CommonInclude.hlsl
6 #define POINT_LIGHT 0
7 #define SPOT_LIGHT 1
8 #define DIRECTIONAL_LIGHT 2

再一次給Light結(jié)構(gòu)顯式地添加8個(gè)字節(jié)的填充,以匹配C++中的struct布局涤姊,并使用該結(jié)構(gòu)滿足HLSL需要的16字節(jié)對(duì)齊暇番。

燈光數(shù)組通過(guò)StructuredBuffer進(jìn)行訪問(wèn),大部分光照Shader的實(shí)現(xiàn)都會(huì)使用常量緩沖區(qū)(constant buffer)進(jìn)行存儲(chǔ)思喊,但是常量緩沖區(qū)限制64KB的大小壁酬,這也意味著在耗盡GPU上的常量?jī)?nèi)存之前最多可以使用570個(gè)動(dòng)態(tài)光源臭脓。結(jié)構(gòu)化的緩沖區(qū)(structured buffer)存儲(chǔ)在紋理內(nèi)存上爵嗅,它受限于GPU提供的可用紋理內(nèi)存數(shù)量(在桌面GPU上通常按GB來(lái)算)误墓。在大部分GPU上紋理內(nèi)存是很快的浅浮,所以使用紋理內(nèi)存存儲(chǔ)燈光不會(huì)有性能上的影響米同,事實(shí)上惫恼,在一些特定的GPU上(NVIDIA GeForce GTX 680)棚贾,將數(shù)據(jù)放在結(jié)構(gòu)化的緩沖區(qū)上反而有一定的性能提升酥宴。

// CommonInclude.hlsl
176 StructuredBuffer<Light> Lights : register( t8 );

2.3 Pixel Shader Continued

相比于vertex shader纲辽,前向渲染的Pixel shader相對(duì)會(huì)更加復(fù)雜一點(diǎn)颜武,這里會(huì)詳細(xì)解釋該pixel shader贫母,因?yàn)樗潜疚闹兴袖秩舅惴ǖ幕A(chǔ)。

2.3.1 材質(zhì)

首先盒刚,我們需要收到材質(zhì)的所有材質(zhì)屬性腺劣,如果一個(gè)材質(zhì)包含紋理和相應(yīng)的組件(component),這些紋理會(huì)在光照計(jì)算之前被采樣因块。在所有的材質(zhì)屬性被初始化后橘原,場(chǎng)景中所有的燈光會(huì)被遍歷,光照貢獻(xiàn)會(huì)隨著材質(zhì)屬性的積累和調(diào)整而產(chǎn)生最終的像素顏色涡上。

ForwardRendering.hlsl
19 [earlydepthstencil]
20 float4 PS_main( VertexShaderOutput IN ) : SV_TARGET
21 {
22     // Everything is in view space.
23     float4 eyePos = { 0, 0, 0, 1 };
24     Material mat = Mat;

函數(shù)之前的[earlydepthstencil]屬性表明GPU應(yīng)該先做早期深度和模板剔除( early depth and stencil culling)[10]趾断,這會(huì)讓depth/stencil測(cè)試在pixel shader之前執(zhí)行。這個(gè)屬性不能用于使用SV_Depth語(yǔ)義來(lái)修改深度的shader吩愧。因?yàn)檫@個(gè)pixel shader只使用了SV_TARGET語(yǔ)義來(lái)輸出顏色芋酌,因此當(dāng)一個(gè)像素被reject時(shí)可以利用早期深度和模板測(cè)試(early depth/stencil test)來(lái)提升性能。大部分的GPU都會(huì)執(zhí)行early depth/stencil test雁佳,甚至在沒(méi)有[earlydepthstencil]屬性的情況下脐帝,雖然添加這個(gè)屬性不會(huì)有一個(gè)明顯的性能影響,但我還是保留這個(gè)屬性糖权。

因?yàn)樗械墓庹沼?jì)算都在視圖空間堵腹,所以眼睛的位置(相機(jī)的位置)總是(0, 0, 0),這是使用視圖空間積極的一面星澳,因此相機(jī)的位置不需要另一個(gè)參數(shù)傳遞給shader疚顷。

第24行拷貝了一個(gè)材質(zhì),這是因?yàn)槿绻嘘P(guān)聯(lián)的紋理到材質(zhì)屬性禁偎,材質(zhì)的屬性在shader中將會(huì)發(fā)生改變(會(huì)從紋理中加載相應(yīng)的屬性)腿堤。因?yàn)椴馁|(zhì)屬性存儲(chǔ)在一個(gè)常量緩沖區(qū),沒(méi)有辦法直接更新一個(gè)常量緩沖區(qū)中的uniform變量如暖,所以使用了一個(gè)臨時(shí)變量笆檀。

2.3.1.1 漫反射(Diffuse)

Diffuse顏色是讀取到的第一個(gè)材質(zhì)屬性。

// ForwardRendering.hlsl
26 float4 diffuse = mat.DiffuseColor;
27 if ( mat.HasDiffuseTexture )
28 {
29     float4 diffuseTex = DiffuseTexture.Sample( LinearRepeatSampler, IN.texCoord );
30     if ( any( diffuse.rgb ) )
31     {
32         diffuse *= diffuseTex;
33     }
34     else
35     {
36         diffuse = diffuseTex;
37     }
38 }

默認(rèn)的diffuse顏色是材質(zhì)中的DiffuseColor装处,如果該材質(zhì)有一個(gè)關(guān)聯(lián)的diffuse紋理误债,該顏色會(huì)與diffuse紋理中加載的顏色進(jìn)行混合浸船。如果材質(zhì)中的顏色是黑色(0, 0, 0)妄迁,會(huì)直接使用diffuse紋理加載的顏色。HLSL內(nèi)置的any函數(shù)可以用來(lái)判斷是否有一個(gè)顏色通道不為0李命。

2.3.1.2 不透明度(Opacity)

決定了像素的alpha值登淘。

ForwardRendering.hlsl
41 float alpha = diffuse.a;
42 if ( mat.HasOpacityTexture )
43 {
44     // If the material has an opacity texture, use that to override the diffuse alpha.
45     alpha = OpacityTexture.Sample( LinearRepeatSampler, IN.texCoord ).r;
46 }

默認(rèn)情況下,片元(fragment)的透明值(也即alpha值)由diffuse顏色的alpha決定封字。如果材質(zhì)有關(guān)聯(lián)的opacity紋理黔州,opacity紋理的紅色通道(r通道)會(huì)代替diffuse紋理中的alpha值耍鬓,來(lái)作為diffuse顏色的alpha值。大多數(shù)情況下流妻,opacity紋理只存儲(chǔ)一個(gè)通道在顏色的第一個(gè)component牲蜀,被采樣時(shí)也會(huì)返回到第一個(gè)component。為了從單通道紋理中讀取值绅这,我們必須從紅色(r)通道中讀涣达,而不是alpha通道,因?yàn)閱瓮ǖ兰y理中的alpha值始終為1.

2.3.1.3 環(huán)境光和自發(fā)光(Ambient和Emissive)

環(huán)境光(Ambient)和自發(fā)光(Emissive)顏色的讀取與diffuse顏色類似证薇,環(huán)境光顏色也需要與材質(zhì)中的GlobalAmbient變量進(jìn)行混合度苔。

// ForwardRendering.hlsl
48 float4 ambient = mat.AmbientColor;
49 if ( mat.HasAmbientTexture )
50 {
51     float4 ambientTex = AmbientTexture.Sample( LinearRepeatSampler, IN.texCoord );
52     if ( any( ambient.rgb ) )
53     {
54         ambient *= ambientTex;
55     }
56     else
57     {
58         ambient = ambientTex;
59     }
60 }
61 // Combine the global ambient term.
62 ambient *= mat.GlobalAmbient;
63
64 float4 emissive = mat.EmissiveColor;
65 if ( mat.HasEmissiveTexture )
66 {
67     float4 emissiveTex = EmissiveTexture.Sample( LinearRepeatSampler, IN.texCoord );
68     if ( any( emissive.rgb ) )
69     {
70         emissive *= emissiveTex;
71     }
72     else
73     {
74         emissive = emissiveTex;
75     }
76 }

2.3.1.4 Specular Power

接下來(lái)會(huì)計(jì)算高光強(qiáng)度。

// ForwardRendering.hlsl
78 if ( mat.HasSpecularPowerTexture )
79 {
80     mat.SpecularPower = SpecularPowerTexture.Sample( LinearRepeatSampler, IN.texCoord ).r \
81                         * mat.SpecularScale;
82 }

如果材質(zhì)有關(guān)聯(lián)的SpecularPower紋理浑度,該紋理的紅色component會(huì)被采樣寇窑,然后使用縮放材質(zhì)中的SpecularScale對(duì)其進(jìn)行縮放。在本例中箩张,材質(zhì)中的SpecularPower會(huì)被紋理中縮放過(guò)的值所取代甩骏。

2.3.1.5 法線(Normal)

If the material has either an associated normal map or a bump map, normal mapping or bump mapping will be performed to compute the normal vector. If neither a normal map nor a bump map texture is associated with the material, the input normal is used as-is.

如果紋理中有關(guān)聯(lián)的法線貼圖(normal map)或凹凸貼圖(bump map),會(huì)執(zhí)行法線映射或凹凸映射來(lái)計(jì)算法線向量先慷,如果兩者都沒(méi)有横漏,則使用輸入的法線(從vertex shader中輸出)。

// ForwardRendering.hlsl
85  // Normal mapping
86  if ( mat.HasNormalTexture )
87  {
88      // For scenes with normal mapping, I don't have to invert the binormal.
89      float3x3 TBN = float3x3( normalize( IN.tangentVS ),
90                               normalize( IN.binormalVS ),
91                               normalize( IN.normalVS ) );
92
93      N = DoNormalMapping( TBN, NormalTexture, LinearRepeatSampler, IN.texCoord );
94  }
95  // Bump mapping
96  else if ( mat.HasBumpTexture )
97  {
98      // For most scenes using bump mapping, I have to invert the binormal.
99      float3x3 TBN = float3x3( normalize( IN.tangentVS ),
100                              normalize( -IN.binormalVS ), 
101                              normalize( IN.normalVS ) );
102  
103     N = DoBumpMapping( TBN, BumpTexture, LinearRepeatSampler, IN.texCoord, mat.BumpIntensity );
104 }
105 // Just use the normal from the model.
106 else
107 {
108     N = normalize( float4( IN.normalVS, 0 ) );
109 }

2.3.1.6 法線映射(Normal Mapping)

函數(shù)DoNormalMapping會(huì)使用TBN(切線(tangent)熟掂,副切線/副法線(bitangent/binormal)缎浇,法線(normal))矩陣和法線貼圖計(jì)算法線映射(Normal Mapping)。

lion2_ddn.jpg

  • 一個(gè)獅子頭的法線貼圖示例. [11]
CommonInclude.hlsl
323 float3 ExpandNormal( float3 n )
324 {
325     return n * 2.0f - 1.0f;
326 }
327
328 float4 DoNormalMapping( float3x3 TBN, Texture2D tex, sampler s, float2 uv )
329 {
330     float3 normal = tex.Sample( s, uv ).xyz;
331     normal = ExpandNormal( normal );
332  
333     // Transform normal from tangent space to view space.
334     normal = mul( normal, TBN );
335     return normalize( float4( normal, 0 ) );
336 }

法線映射很簡(jiǎn)單赴肚,這文章法線映射中有詳細(xì)的解釋素跺。簡(jiǎn)單來(lái)說(shuō)我們只需要從法線貼圖中采樣法線,展開(kāi)法線到[-1..1]范圍誉券,然后通過(guò)后乘TBN矩陣將其從切線空間變換到視圖空間指厌。

2.3.1.7 凹凸映射(Bump Mapping)

凹凸映射原理類似,除了bump紋理中不是直接存儲(chǔ)的法線踊跟,而是[0..1]范圍的高度值踩验。法線可以通過(guò)計(jì)算bump紋理在U和V坐標(biāo)方向上高度的梯度(gradient)來(lái)生成,通過(guò)兩個(gè)方向上梯度的叉積(cross product)來(lái)得到紋理空間的法線商玫,然后通過(guò)后乘TBN矩陣將其從切線空間變換到視圖空間箕憾。可以通過(guò)縮放從bump貼圖中讀取的高度值來(lái)產(chǎn)生更大(更小)的凹凸拳昌。


Bumpmapping-Head.jpg
  • 凹凸紋理(左)和相應(yīng)的人頭模型(右)[12]
CommonInclude.hlsl
333 float4 DoBumpMapping( float3x3 TBN, Texture2D tex, sampler s, float2 uv, float bumpScale )
334 {
335     // Sample the heightmap at the current texture coordinate.
336     float height = tex.Sample( s, uv ).r * bumpScale;
337     // Sample the heightmap in the U texture coordinate direction.
338     float heightU = tex.Sample( s, uv, int2( 1, 0 ) ).r * bumpScale;
339     // Sample the heightmap in the V texture coordinate direction.
340     float heightV = tex.Sample( s, uv, int2( 0, 1 ) ).r * bumpScale;
341
342     float3 p = { 0, 0, height };
343     float3 pU = { 1, 0, heightU };
344     float3 pV = { 0, 1, heightV };
345  
346     // normal = tangent x bitangent
347     float3 normal = cross( normalize(pU - p), normalize(pV - p) );
348  
349     // Transform normal from tangent space to view space.
350     normal = mul( normal, TBN );
351  
352     return float4( normal, 0 );
353 }

這里并不能保證bump映射算法100%正確袭异,沒(méi)有找到相關(guān)資源說(shuō)如何正確進(jìn)行bump映射,如果有更好的方來(lái)執(zhí)行bump映射炬藤,請(qǐng)留言討論御铃。

如果材質(zhì)沒(méi)有關(guān)聯(lián)的法線貼圖或凹凸貼圖碴里,直接使用vertex shader中輸出的法線向量。

現(xiàn)在我們用了計(jì)算光照所需要的所有數(shù)據(jù)上真。

2.3.2 光照(Lighting)

The lighting calculations for the forward rendering technique are performed in the DoLighting function. This function accepts the following arguments:

前向渲染技術(shù)的光照計(jì)算在函數(shù)DoLighting執(zhí)行咬腋,該函數(shù)接受如下的參數(shù):

  • lights: 光源的數(shù)組(structured buffer)。
  • mat: 我們前面計(jì)算的材質(zhì)屬性睡互。
  • eyePos: 視圖空間的相機(jī)坐標(biāo)(總是(0, 0, 0))帝火。
  • P: 被著色點(diǎn)在視圖空間中的位置。
  • N: 被著色點(diǎn)在視圖空間中的法線湃缎。

函數(shù)DoLighting返回一個(gè)包含場(chǎng)景中所有燈光的diffuse和高光光照貢獻(xiàn)的DoLighting結(jié)構(gòu)犀填。

// ForwardRendering.hlsl
425 // This lighting result is returned by the
426 // lighting functions for each light type.
427 struct LightingResult
428 {
429     float4 Diffuse;
430     float4 Specular;
431 };
432
433 LightingResult DoLighting( StructuredBuffer<Light> lights, Material mat, float4 eyePos, float4 P, float4 N )
434 {
435     float4 V = normalize( eyePos - P );
436  
437     LightingResult totalResult = (LightingResult)0;
438  
439     for ( int i = 0; i < NUM_LIGHTS; ++I )
440     {
441         LightingResult result = (LightingResult)0;
442  
443         // Skip lights that are not enabled.
444         if ( !lights[i].Enabled ) continue;
445         // Skip point and spot lights that are out of range of the point being shaded.
446         if ( lights[i].Type != DIRECTIONAL_LIGHT &&
447              length( lights[i].PositionVS - P ) > lights[i].Range ) continue;
448  
449         switch ( lights[i].Type )
450         {
451         case DIRECTIONAL_LIGHT:
452         {
453             result = DoDirectionalLight( lights[i], mat, V, P, N );
454         }
455         break;
456         case POINT_LIGHT:
457         {
458             result = DoPointLight( lights[i], mat, V, P, N );
459         }
460         break;
461         case SPOT_LIGHT:
462         {
463             result = DoSpotLight( lights[i], mat, V, P, N );
464         }
465         break;
466         }
467         totalResult.Diffuse += result.Diffuse;
468         totalResult.Specular += result.Specular;
469     }
470  
471     return totalResult;
472 }

視線向量(V)通過(guò)眼睛位置和被著色像素點(diǎn)在視圖空間的位置計(jì)算而來(lái)。

燈光緩沖區(qū)的迭代在439行嗓违,因?yàn)楸唤玫墓庠春统龇秶墓庠床粫?huì)貢獻(xiàn)任何光照九巡,所以可以跳過(guò)這些光源,否則會(huì)根據(jù)光源類型來(lái)調(diào)用相應(yīng)的光照函數(shù)蹂季。

每個(gè)不同類型的光源會(huì)計(jì)算他們的diffuse和specular光照貢獻(xiàn)冕广,因?yàn)閷?duì)不同類型光源,計(jì)算diffuse和specular的方式相同偿洁,所以我會(huì)定義不依賴于光源類型的函數(shù)來(lái)計(jì)算diffuse和specular光照貢獻(xiàn)撒汉。

2.3.2.1 漫反射光照(Diffuse Lighting)

函數(shù)DoDiffuse非常簡(jiǎn)單,并且只需要知道光向量(L)和表面法線(N)涕滋。

Diffuse-Lighting.png

  • 漫反射光照
// CommonInclude.hlsl
355 float4 DoDiffuse( Light light, float4 L, float4 N )
356 {
357     float NdotL = max( dot( N, L ), 0 ); 
358     return light.Color * NdotL; 
359 } 

漫反射光照的計(jì)算采用光向量(L)和表面法線(N)的點(diǎn)積(dot product)睬辐,兩個(gè)向量需要是歸一化的(normalized),通過(guò)將點(diǎn)積的結(jié)果與燈光的顏色相乘來(lái)得到該燈光的光照貢獻(xiàn)宾肺。

下面溯饵,我們來(lái)計(jì)算燈光的specular貢獻(xiàn)。

2.3.2.2 高光光照(Specular Lighting)

函數(shù)DoSpecular用來(lái)計(jì)算燈光的specular貢獻(xiàn)锨用,除了光向量(L)和表面法線(N)丰刊,該函數(shù)也需要視線向量(V來(lái)計(jì)算該燈光的specular貢獻(xiàn)。

Specular-Lighting.png

  • Specular Lighting
// CommonInclude.hlsl
361 float4 DoSpecular( Light light, Material material, float4 V, float4 L, float4 N )
362 {
363     float4 R = normalize( reflect( -L, N ) );
364     float RdotV = max( dot( R, V ), 0 );
365
366     return light.Color * pow( RdotV, material.SpecularPower );
367 }

因?yàn)楣饩€向量L是從被著色點(diǎn)到光源的向量增拥,所以在計(jì)算反射向量(R)之前需要將L取負(fù)啄巧,以使向量從光源指向被著色點(diǎn)。反射向量(R)和視線向量(V)的點(diǎn)積被用來(lái)計(jì)算的高光強(qiáng)度值的冪掌栅,然后使用光線顏色進(jìn)行調(diào)制秩仆,切記范圍是(0..1)高光強(qiáng)度的是無(wú)意義的。

2.3.2.3 衰減(Attenuation)

衰減(Attenuation)是光的強(qiáng)度下降渣玲,因?yàn)楣怆x被著色的點(diǎn)更遠(yuǎn)逗概。在傳統(tǒng)的光照模型中,衰減被計(jì)算為三個(gè)衰減因子的和乘以到光源的距離的倒數(shù)(如衰減中所解釋的):

  1. 常量衰減
  2. 線性衰減
  3. 二次方衰減

然而忘衍,這個(gè)方法計(jì)算的衰減是假設(shè)光永遠(yuǎn)不會(huì)衰減到0的(光具有無(wú)限的范圍)逾苫。對(duì)于延遲渲染和forward+,我們必須得能表示場(chǎng)景中的燈光具有有限的范圍枚钓,所以我們以一種差分的方法來(lái)計(jì)算光的衰減铅搓。

一種可行的方法的是做一個(gè)0到1的線性插值來(lái)計(jì)算光的衰減,其中1表示靠近光源搀捷,0表示點(diǎn)到光源的距離超過(guò)光的范圍星掰,然而線性衰減看起來(lái)不是很真實(shí),事實(shí)上衰減更像是二次方函數(shù)的倒數(shù)嫩舟。

我打算使用HLSL內(nèi)置的smoothstep函數(shù)氢烘,該函數(shù)返回一個(gè)在最小和最大值之間平滑的插值。


Smoothstep-Function2.png
  • 圖 HLSL內(nèi)置的smoothstep函數(shù)
// CommonInclude.hlsl
396 // Compute the attenuation based on the range of the light.
397 float DoAttenuation( Light light, float d )
398 {
399     return 1.0f - smoothstep( light.Range * 0.75f, light.Range, d );
400 }

如果到光源的距離(d)/小于光范圍的?家厌,函數(shù)smoothstep返回0播玖,如果距離大于光的范圍則返回1,通過(guò)從1中減去這個(gè)值就可以得到我們需要的衰減饭于。

或者蜀踏,我們可以通過(guò)在上面的方程中參數(shù)化0.75f來(lái)調(diào)整光的衰減的平滑度。平滑系數(shù)0.0應(yīng)該導(dǎo)致光的強(qiáng)度保持1.0掰吕,直到光的最大范圍果覆,而平滑系數(shù)1.0應(yīng)該導(dǎo)致光的強(qiáng)度內(nèi)插通過(guò)整個(gè)光的范圍。


Attenuation-Smoothness.jpg
  • 圖 可變的衰減平滑

現(xiàn)在殖熟,讓我們將diffuse局待,specular和衰減因子組合在一起為不同的燈光類型計(jì)算光光照貢獻(xiàn)。

2.3.2.4 點(diǎn)光源(Point Light)

點(diǎn)光源組合衰減菱属,diffuse和specular來(lái)決定最終的光照貢獻(xiàn)燎猛。

// ForwardRendering.hlsl
390 LightingResult DoPointLight( Light light, Material mat, float4 V, float4 P, float4 N )
391 {
392     LightingResult result;
393
394     float4 L = light.PositionVS - P;
395     float distance = length( L );
396     L = L / distance;
397  
398     float attenuation = DoAttenuation( light, distance );
399  
400     result.Diffuse = DoDiffuse( light, L, N ) * 
401                       attenuation * light.Intensity;
402     result.Specular = DoSpecular( light, mat, V, L, N ) * 
403                        attenuation * light.Intensity;
404  
405     return result;
406 }

在400和401行,diffuse和specular的貢獻(xiàn)被衰減和光強(qiáng)度(Intensity)進(jìn)行縮放照皆。

2.3.2.5 聚光燈(Spot Light)

除了衰減因子重绷,聚光燈還有一個(gè)錐角。在這種情況下膜毁,光的強(qiáng)度是由光向量(L)和聚光燈方向之間的點(diǎn)積決定的昭卓。如果光向量與聚光方向之間的夾角小于聚光錐角,則點(diǎn)應(yīng)由聚光燈點(diǎn)亮瘟滨。否則聚光燈不應(yīng)該為被著色點(diǎn)的點(diǎn)提供任何光照候醒。DoSpotCone函數(shù)將根據(jù)聚光錐的角度計(jì)算光強(qiáng)。

// CommonInclude.hlsl
375 float DoSpotCone( Light light, float4 L )
376 {
377     // If the cosine angle of the light's direction
378     // vector and the vector from the light source to the point being
379     // shaded is less than minCos, then the spotlight contribution will be 0.
380     float minCos = cos( radians( light.SpotlightAngle ) );
381     // If the cosine angle of the light's direction vector
382     // and the vector from the light source to the point being shaded
383     // is greater than maxCos, then the spotlight contribution will be 1.
384     float maxCos = lerp( minCos, 1, 0.5f );
385     float cosAngle = dot( light.DirectionVS, -L );
386     // Blend between the minimum and maximum cosine angles.
387     return smoothstep( minCos, maxCos, cosAngle );
388 }

首先杂瘸,計(jì)算聚光燈錐的余弦倒淫,如果聚光燈的方向和光向量(L)之間的點(diǎn)積小于最小該余弦值,那么光的貢獻(xiàn)將是0败玉。如果點(diǎn)積大于最大余弦角敌土,那么聚光燈的貢獻(xiàn)將是1镜硕。


Spotlight-Min-Max-Cone-Angles2.png
  • 圖 聚光燈的最小和最大余弦角

最大余弦角比最小余弦角小,這似乎是違反直覺(jué)的返干,但是不要忘記0°的余弦是1,90°的余弦是0兴枯。

DoSpotLight函數(shù)將計(jì)算聚光燈的貢獻(xiàn),與計(jì)算點(diǎn)光源的貢獻(xiàn)類似矩欠,另外算上聚光燈的余弦角财剖。

// ForwardRendering.hlsl
418 LightingResult DoSpotLight( Light light, Material mat, float4 V, float4 P, float4 N )
419 {
420     LightingResult result;
421
422     float4 L = light.PositionVS - P;
423     float distance = length( L );
424     L = L / distance;
425  
426     float attenuation = DoAttenuation( light, distance );
427     float spotIntensity = DoSpotCone( light, L );
428  
429     result.Diffuse = DoDiffuse( light, L, N ) * 
430                        attenuation * spotIntensity * light.Intensity;
431     result.Specular = DoSpecular( light, mat, V, L, N ) * 
432                        attenuation * spotIntensity * light.Intensity;
433  
434     return result;
435 }

2.3.2.6 方向光(Directional Lights)

方向光是最簡(jiǎn)單的燈光類型,因?yàn)樗鼈冊(cè)诒恢c(diǎn)上不會(huì)衰減癌淮。

// ForwardRendering.hlsl
406 LightingResult DoDirectionalLight( Light light, Material mat, float4 V, float4 P, float4 N )
407 {
408     LightingResult result;
409
410     float4 L = normalize( -light.DirectionVS );
411  
412     result.Diffuse = DoDiffuse( light, L, N ) * light.Intensity;
413     result.Specular = DoSpecular( light, mat, V, L, N ) * light.Intensity;
414  
415     return result;
416 }

2.3.2.7 最終著色

現(xiàn)在我們有了材質(zhì)屬性和場(chǎng)景中所有燈光的疊加照明效果躺坟,我們可以將它們結(jié)合起來(lái)進(jìn)行最終的著色。

// ForwardRendering.hlsl
111     float4 P = float4( IN.positionVS, 1 );
112
113     LightingResult lit = DoLighting( Lights, mat, eyePos, P, N );
114  
115     diffuse *= float4( lit.Diffuse.rgb, 1.0f ); // Discard the alpha value from the lighting calculations.
116  
117     float4 specular = 0;
118     if ( mat.SpecularPower > 1.0f ) // If specular power is too low, don't use it.
119     {
120         specular = mat.SpecularColor;
121         if ( mat.HasSpecularTexture )
122         {
123             float4 specularTex = SpecularTexture.Sample( LinearRepeatSampler, IN.texCoord );
124             if ( any( specular.rgb ) )
125             {
126                 specular *= specularTex;
127             }
128             else
129             {
130                 specular = specularTex;
131             }
132         }
133         specular *= lit.Specular;
134     }
135  
136     return float4( ( ambient + emissive + diffuse + specular ).rgb, 
137                      alpha * mat.Opacity );
138  
139 }

在第113行乳蓄,光照貢獻(xiàn)是使用剛才描述的DoLighting函數(shù)計(jì)算的咪橙。

在第115行,材質(zhì)的漫反射顏色(diffuse color)是由光的diffuse貢獻(xiàn)調(diào)節(jié)的栓袖。

如果材質(zhì)的高光強(qiáng)度低于1.0匣摘,則不會(huì)考慮它參與最終著色。如果材質(zhì)沒(méi)有高光裹刮,一些美術(shù)師會(huì)指定一個(gè)小于1的高光強(qiáng)度音榜。在這種情況下,我們只是忽略了高光的貢獻(xiàn)和材質(zhì)被認(rèn)為是只有漫反射的(lambert反射)捧弃。否則赠叼,如果材質(zhì)有與之相關(guān)的高光紋理,它將被采樣违霞,并與材質(zhì)的高光顏色相結(jié)合嘴办,然后再用光的高光貢獻(xiàn)進(jìn)行調(diào)制。

最后的像素顏色是環(huán)境买鸽、自發(fā)光涧郊、漫反射和高光顏色的總和,像素的不透明度由pixel shader中先前確定的alpha值決定眼五。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末妆艘,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子看幼,更是在濱河造成了極大的恐慌批旺,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,718評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件诵姜,死亡現(xiàn)場(chǎng)離奇詭異汽煮,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)暇赤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)心例,“玉大人,你說(shuō)我怎么就攤上這事翎卓∑跹” “怎么了摆寄?”我有些...
    開(kāi)封第一講書(shū)人閱讀 158,207評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵失暴,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我微饥,道長(zhǎng)逗扒,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,755評(píng)論 1 284
  • 正文 為了忘掉前任欠橘,我火速辦了婚禮矩肩,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘肃续。我一直安慰自己黍檩,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,862評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布始锚。 她就那樣靜靜地躺著刽酱,像睡著了一般。 火紅的嫁衣襯著肌膚如雪瞧捌。 梳的紋絲不亂的頭發(fā)上棵里,一...
    開(kāi)封第一講書(shū)人閱讀 50,050評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音姐呐,去河邊找鬼殿怜。 笑死,一個(gè)胖子當(dāng)著我的面吹牛曙砂,可吹牛的內(nèi)容都是我干的头谜。 我是一名探鬼主播,決...
    沈念sama閱讀 39,136評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼鸠澈,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼柱告!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起款侵,我...
    開(kāi)封第一講書(shū)人閱讀 37,882評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤末荐,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后新锈,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體甲脏,經(jīng)...
    沈念sama閱讀 44,330評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,651評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了块请。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片娜氏。...
    茶點(diǎn)故事閱讀 38,789評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖墩新,靈堂內(nèi)的尸體忽然破棺而出贸弥,到底是詐尸還是另有隱情,我是刑警寧澤海渊,帶...
    沈念sama閱讀 34,477評(píng)論 4 333
  • 正文 年R本政府宣布绵疲,位于F島的核電站,受9級(jí)特大地震影響臣疑,放射性物質(zhì)發(fā)生泄漏盔憨。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,135評(píng)論 3 317
  • 文/蒙蒙 一讯沈、第九天 我趴在偏房一處隱蔽的房頂上張望郁岩。 院中可真熱鬧,春花似錦缺狠、人聲如沸问慎。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,864評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)如叼。三九已至,卻和暖如春驮樊,著一層夾襖步出監(jiān)牢的瞬間薇正,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,099評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工囚衔, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留挖腰,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,598評(píng)論 2 362
  • 正文 我出身青樓练湿,卻偏偏與公主長(zhǎng)得像猴仑,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子肥哎,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,697評(píng)論 2 351

推薦閱讀更多精彩內(nèi)容