对DX11来说,纹理占了它的半边天空,除了其赋予的色彩外,更由于其易于为数据的载体。
如上图,我们仅仅使用了一个球、一个矩形,一个立方体纹理,一个3D纹理、一个无关紧要的水面颜色纹理。
球形与矩形的创建,本文不打算提及,参考博文:https://blog.****.net/BonChoix/article/details/8394916
立方体纹理和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环境就出来了:
cube纹理网站:http://www.humus.name/index.php?page=Textures
cube纹理创建:https://blog.****.net/chenjinxian_3D/article/details/51866112
体积云:
体积云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混合并不打算在此节讲述。
反射基础知识:https://blog.****.net/BonChoix/article/details/8582123
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通道,这也给我们一个实现水面
的方式,就是将水面的波动做成灰度图(精细一点:法线贴图),然后循环读取移动的纹理坐标。
本例还是很粗糙的水面实现,未考虑透明、净蚀、观看角度不同带来的折射与反射的变化、
波动方向、波形、海浪等等。
exe:https://pan.baidu.com/s/1D8VCfLGrA_hwa0lMG4tl5w
没有源代码,就是没有源代码。