1. 程式人生 > >DX11 遊戲開發筆記 (六) 體積雲 水面

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通道,這也給我們一個實現水面

的方式,就是將水面的波動做成灰度圖(精細一點:法線貼圖),然後迴圈讀取移動的紋理座標。

本例還是很粗糙的水面實現,未考慮透明、淨蝕、觀看角度不同帶來的折射與反射的變化、

波動方向、波形、海浪等等。

沒有原始碼,就是沒有原始碼。