DX11 遊戲開發筆記 (六) 體積雲 水面
對DX11來說,紋理佔了它的半邊天空,除了其賦予的色彩外,更由於其易於為資料的載體。
如上圖,我們僅僅使用了一個球、一個矩形,一個立方體紋理,一個3D紋理、一個無關緊要的水面顏色紋理。
立方體紋理和3d紋理使用方式跟2D紋理很像。
建立c++紋理資源:
ID3D11ShaderResourceView *m_texView;
D3DX11CreateShaderResourceViewFromFile(device, TextureLoadFileName, 0, 0, &m_texView, 0);
基本上使用這個函式就能應對大部分問題;
ID3DX11EffectShaderResourceVariable *m_fxTex; m_fxTex =effect->GetVariableByName("2DTexture")->AsShaderResource(); HR(m_fxTex->SetResource(m_texView));
對應的shader:
2D紋理:
Texture2D 2DTexture;
3D紋理:
Texture3D 3DTexture;
立方體紋理:
TextureCube CubeTexture;
取樣方式:
float2 fpos; float4 color=2DTexture.Sample(samTriLinear, fpos); float3 fpos; float4 color=3DTexture.Sample(samTriLinear, fpos); float3 xyz=3DTexture.Sample(samTriLinear, fpos).xyz; float3 fpos; float4 color=CubeTexture.Sample(samTriLinear, fpos); float3 rgb=CubeTexture.Sample(samTriLinear, fpos).rgb;
2D紋理從uv(xz)取樣,3d紋理從xyz取樣,立方體紋理從中心點發出的射線向量(vector.xyz)取樣。
首先我們先看天空盒的HLSL:
TextureCube CubeTexture; //紋理資源 cbuffer cbPerObject { row_major float4x4 g_worldViewProj : WORLDVIEWPROJECTION; } RasterizerState DisableCulling { CullMode = NONE; //FrontCounterClockwise = true; }; DepthStencilState LessEqualDSS { DepthFunc = LESS_EQUAL; }; SamplerState samTriLinear { Filter = MIN_MAG_MIP_LINEAR; AddressU = WRAP; AddressV = WRAP; }; struct VertexIn { float3 Pos:POSITION; }; struct VertexOut { float4 PosH :SV_POSITION; float3 Texc :TEXCOORD; }; VertexOut VS(VertexIn In) { VertexOut Out = (VertexOut)0; Out.Texc = In.Pos; Out.PosH = mul(float4(In.Pos, 1.0f), g_worldViewProj)/*.xyww*/; return Out; } float4 PS(VertexOut In) :SV_Target { return CubeTexture.Sample(samTriLinear, In.Texc); } technique11 Draw { Pass p0 { SetVertexShader(CompileShader(vs_5_0, VS())); SetPixelShader(CompileShader(ps_5_0, PS())); SetRasterizerState(DisableCulling); SetDepthStencilState(LessEqualDSS, 0); } }
兩個簡單的狀態設定,稍微提及一下:
RasterizerState DisableCulling
{
CullMode = NONE;
//FrontCounterClockwise = true;
};
DepthStencilState LessEqualDSS
{
DepthFunc = LESS_EQUAL;
};
CullMode = NONE; 三角形背面的圖形也渲染。
FrontCounterClockwise = true; 將三角形背面設為正面。(渲染逆時針的三角形)
DepthFunc = LESS_EQUAL;深度比較函式,(畫素組成的片元)我在你前面(less:<),所以我顯示。
SetDepthStencilState(LessEqualDSS, 0); 0是模板參考值,我們在這裡初始化過:
m_deviceContext->ClearDepthStencilView(m_depthStencilView, D3D11_CLEAR_DEPTH, 1.0f, 0);
因為模板跟深度共屬一個32位float的暫存器,所以模板可以看成深度的小弟,實現特效什麼的。
Out.PosH = mul(float4(In.Pos, 1.0f), g_worldViewProj).xyww;
這個也很簡單,就是z=w,然後z/w=1,這樣就是無窮遠。
在(平頭截體轉化成的)齊次剪裁空間裁剪之後,(硬體會自動完成所有的裁剪工作)
硬體會自動執行透視除法,(xyzw/w)。
將頂點從齊次裁剪空間變換到規範化裝置空間。(-1<=x<=1, -1<=y<=1, 0<=z<=1)
OpenGL:(-1<=x<=1, -1<=y<=1, -1<=z<=1)
構成 2D 影象的 2D x、y 座標就會被變換到後臺緩衝區中的一個稱為視口的矩形區域內 ,
然後進行拉伸。
把C++的相關變數設定好,一個3D環境就出來了:
體積雲:
體積雲hlsl跟立方體大部分類似,只不過我們從紋理中取的rgb不再是作為顏色輸入,
而是參與一個偽隨機演算法去生成一個顏色。
Texture3D g_VolumeTex;
cbuffer cbPerObject
{
row_major float4x4 g_worldViewProj : WORLDVIEWPROJECTION;
}
cbuffer cbPerFrame
{
float g_ftime : TIME;
}
RasterizerState DisableCulling
{
CullMode = NONE;
};
SamplerState samTriLinear
{
Filter = MIN_MAG_MIP_LINEAR;
AddressU = WRAP;
AddressV = WRAP;
};
struct VertexIn
{
float3 Pos:POSITION;
};
struct VertexOut
{
float4 PosH :SV_POSITION;
float3 PosW :POSITION;
};
VertexOut VS(VertexIn In)
{
VertexOut Out = (VertexOut)0;
Out.PosW = In.Pos;
Out.PosH = mul(float4(In.Pos, 1.0f), g_worldViewProj);
return Out;
}
float4 PS(VertexOut In) :SV_Target
{
float4 sky = float4(0.0f, 0.0f, 1.0f, 0.8f);
float4 cloud = float4(1.0f, 1.0f, 1.0f, 1.0f);
In.PosW.xy += g_ftime*3.7;
In.PosW.z += g_ftime*4.8;
float noisy = g_VolumeTex.Sample(samTriLinear, In.PosW.xyz/100.0).r;
float lrp = noisy * 3 - 1.0f;
return lerp(cloud, sky, saturate(lrp));
}
technique11 Draw
{
Pass p0
{
SetVertexShader(CompileShader(vs_5_0, VS()));
SetPixelShader(CompileShader(ps_5_0, PS()));
SetRasterizerState(DisableCulling);
}
}
說白了就是按照一個向量(In.Pos)去取3D紋理中的值,本次體積紋理實際上還是灰度紋理,
rgb都是一個灰度值,(灰度紋理可以做alpha通道,地形,鏡面高光等,看你心情,)
取到不同的灰度值(0<x<1),然後按這個值在白色和藍色中插值取樣。
In.PosW.xy += g_ftime*3.7;
In.PosW.z += g_ftime*4.8;
作用僅僅只在於移動紋理取樣點。
g_ftime:從遊戲開始計時的時間(就一個自增變數)。
noisy * 3: 這個值可以改變雲的密集度。
在c++中設定好紋理、時間、球形。雲就成形了:
波動的水面:
上面我們回顧了立方體紋理、體積紋理,你會發現,紋理妙處無窮,而我們使用起來也是
得心應手,就簡簡單單幾個步驟。
那麼水面波動怎麼實現了:很簡單,我們在光照那節知道,我們所謂的燈光其實就是簡單的
條件限制,光學反射,其中比較重要的就是平面的法向量。無獨有偶,我們這次波動也是在法
向量上做文章。
水面顏色大致可分為三種顏色混合:
1.水本身的顏色;
2.反射周邊物體的顏色;
3.水下物體的顏色。
本次並沒有考慮3的情況,因為blending混合並不打算在此節講述。
hlsl:
TextureCube g_CubeTexture; //紋理資源
Texture2D ShaderTexture;
Texture3D g_BumpTexture;
cbuffer cbPerObject
{
row_major float4x4 g_worldViewProj : WORLDVIEWPROJECTION;
}
cbuffer cbPerFrame
{
float3 g_CameraPos:CAMERAPOS;
float g_ftime : TIME;
}
SamplerState samTriLinear
{
Filter = MIN_MAG_MIP_LINEAR;
AddressU = WRAP;
AddressV = WRAP;
};
struct VertexIn
{
float3 Pos:POSITION;
float3 Normal:NORMAL;
float2 Texture:TEXCOORD0;
};
struct VertexOut
{
float4 PosH :SV_POSITION;
float3 Normal :NORMAL;
float2 Texture :TEXCOORD0;
float3 Texc :TEXCOORD1;
};
VertexOut VS(VertexIn In)
{
VertexOut Out = (VertexOut)0;
Out.Texc = In.Pos;
Out.PosH = mul(float4(In.Pos, 1.0f), g_worldViewProj);
Out.Normal = In.Normal;
Out.Texture = In.Texture;
return Out;
}
float4 PS(VertexOut In) :SV_Target
{
float4 color = (float4)0;
float3 fpos = (float3)0;
fpos.x = g_ftime*0.2 + In.Texture.x*2.3;
fpos.z = g_ftime*0.1 + In.Texture.y*2.4;
fpos.y = 0.0f;
float3 v3BumpVector = g_BumpTexture.Sample(samTriLinear, fpos).xyz;
float3 v3Bump = 2 * v3BumpVector - 1;
v3Bump.xy *= 0.135;
v3Bump.z = 0.3 * abs(v3Bump.z) + 0.2;
float3 incident = In.Texc - g_CameraPos;
float3 normal = In.Normal + v3Bump * 0.10f;
float3 refW = reflect(incident, normal);
float4 reflectedColor = g_CubeTexture.Sample(samTriLinear, refW);
float2 Tex = In.Texture + refW.xy*0.1f;
color = 0.8*reflectedColor + 0.2*ShaderTexture.Sample(samTriLinear, Tex);
return color;
}
technique11 Draw
{
Pass p0
{
SetVertexShader(CompileShader(vs_5_0, VS()));
SetPixelShader(CompileShader(ps_5_0, PS()));
}
}
這裡我單獨講一下ps的程式碼:
float4 PS(VertexOut In) :SV_Target
{
float4 color = (float4)0;
float3 fpos = (float3)0;
fpos.x = g_ftime*0.2 + In.Texture.x*2.3;
fpos.z = g_ftime*0.1 + In.Texture.y*2.4;
fpos.y = 0.0f;
float3 v3BumpVector = g_BumpTexture.Sample(samTriLinear, fpos).xyz;
float3 v3Bump = 2 * v3BumpVector - 1;
v3Bump.xy *= 0.135;
v3Bump.z = 0.3 * abs(v3Bump.z) + 0.2;
float3 incident = In.Texc - g_CameraPos;
float3 normal = In.Normal + v3Bump * 0.10f;
float3 refW = reflect(incident, normal);
float4 reflectedColor = g_CubeTexture.Sample(samTriLinear, refW);
float2 Tex = In.Texture + refW.xy*0.1f;
color = 0.8*reflectedColor + 0.2*ShaderTexture.Sample(samTriLinear, Tex);
return color;
}
我們首先跟製作體積雲一樣,先從體積紋理(g_BumpTexture)中取得y=0平面
的一個灰度值v3BumpVector (x=y=z),然後對其v3Bump一頓搗鼓(借鑑意義不大)。
去影響normal,改變我們需要的反射向量,
從而改變立方體取值座標。我還對水的紋理座標也稍稍做了改動。
最後加顏色累加就行。
本例水面只是簡單的波動,但已經能達到欺騙效果,我們並未改變頂點座標,
只是簡單的畫素級擾動,需要注意的是:本例的3d紋理其實使用的資料很少,完全可以
用2d紋理代替,就水面波動而言,我們甚至可以將其做成alpha通道,這也給我們一個實現水面
的方式,就是將水面的波動做成灰度圖(精細一點:法線貼圖),然後迴圈讀取移動的紋理座標。
本例還是很粗糙的水面實現,未考慮透明、淨蝕、觀看角度不同帶來的折射與反射的變化、
波動方向、波形、海浪等等。
沒有原始碼,就是沒有原始碼。