前言

这一章我们主要学习由6个纹理所构成的立方体映射,以及用它来实现一个静态天空盒。

但是在此之前先要消除两个误区:

  1. 认为这一章的天空盒就是简单的在一个超大立方体的六个面内部贴上天空盒纹理;
  2. 认为天空盒的顶点都是固定的,距离起始点的位置特别远。

我提出这两个误区,是因为看到有些人的作品直接贴了六个立方体,就说自己用到了天空盒技术,但是当你真正学这一章的话会发现此天空盒非彼天空盒,而且该篇教程除了天空盒的技术实现外,还有其余的一些干货值得学习,建议认真研读。

在此之前还需要回顾一下里面有关纹理子资源的部分:

章节回顾
深入理解与使用2D纹理资源(重点了解纹理子资源、纹理数组和纹理天空盒)

DirectX11 With Windows SDK完整目录

Github项目源码

欢迎加入QQ群: 727623616 可以一起探讨DX11,以及有什么问题也可以在这里汇报。

立方体映射(Cube Mapping)

一个立方体(通常是正方体)包含六个面,对于立方体映射来说,它的六个面对应的是六张纹理贴图,然后以该立方体建系,中心为原点,且三个坐标轴是轴对齐的。我们可以使用方向向量(±X,±Y,±Z),从原点开始,发射一条射线(取方向向量的方向)来与某个面产生交点,取得该纹理交点对应的颜色。

注意:

  1. 方向向量的大小并不重要,只要方向一致,那么不管长度是多少,最终选择的纹理和取样的像素都是一致的。
  2. 使用方向向量时要确保所处的坐标系和立方体映射所处的坐标系一致,如方向向量和立方体映射同时处在世界坐标系中。

Direct3D提供了枚举类型D3D11_TEXTURECUBE_FACE来标识立方体某一表面:

typedef enum D3D11_TEXTURECUBE_FACE {
D3D11_TEXTURECUBE_FACE_POSITIVE_X = 0,
D3D11_TEXTURECUBE_FACE_NEGATIVE_X = 1,
D3D11_TEXTURECUBE_FACE_POSITIVE_Y = 2,
D3D11_TEXTURECUBE_FACE_NEGATIVE_Y = 3,
D3D11_TEXTURECUBE_FACE_POSITIVE_Z = 4,
D3D11_TEXTURECUBE_FACE_NEGATIVE_Z = 5
} D3D11_TEXTURECUBE_FACE;

可以看出:

  1. 索引0指向+X表面;
  2. 索引1指向-X表面;
  3. 索引2指向+Y表面;
  4. 索引3指向-Y表面;
  5. 索引4指向+Z表面;
  6. 索引5指向-Z表面;

使用立方体映射意味着我们需要使用3D纹理坐标进行寻址。

在HLSL中,立方体纹理用TextureCube来表示。

环境映射(Environment Maps)

关于立方体映射,应用最广泛的就是环境映射了。为了获取一份环境映射,我们可以将摄像机绑定到一个物体的中心(或者摄像机本身视为一个物体),然后使用90°的垂直FOV和水平FOV(即宽高比1:1),再让摄像机朝着±X轴、±Y轴、±Z轴共6个轴的方向各拍摄一张不包括物体本身的场景照片。因为FOV的角度为90°,这六张图片已经包含了以物体中心进行的透视投影,所记录的完整的周遭环境。接下来就是将这六张图片保存在立方体纹理中,以构成环境映射。综上所述,环境映射就是在立方体表面的纹理中存储了周围环境的图像。

由于环境映射仅捕获了远景的信息,这样附近的许多物体都可以共用同一个环境映射。这种做法称之为静态立方体映射,它的优点是仅需要六张纹理就可以轻松实现,但缺陷是该环境映射并不会记录临近物体信息,在绘制反射时就看不到周围的物体了。

注意到环境映射所使用的六张图片不一定非得是从Direct3D程序中捕获的。因为立方体映射仅存储纹理数据,它们的内容通常可以是美术师预先生成的,或者是自己找到的。

一般来说,我们能找到的天空盒有如下三种:

  1. 已经创建好的.dds文件,可以直接通过DDSTextureLoader读取使用
  2. 6张天空盒的正方形贴图,格式不限。(暂不考虑只有5张的)
  3. 1张天空盒贴图,包含了6个面,格式不限,图片宽高比为4:3

对于第三种天空盒,其平面分布如下:

对于其余两种天空盒,这里也提供了3种方法读取。

使用DXTex构建天空盒

准备6张天空盒的正方形贴图,如果是属于上述第三种情况,可以用截屏工具来截取出6张贴图,但是要注意按原图的分辨率来进行截取。

打开放在Github项目中Utility文件夹内的DxTex.exe,新建纹理:

Texture Type要选择Cubemap Texture

Dimensions填写正方形纹理的像素宽度和高度

如果你需要自动生成mipmaps,则指定mipmap Level为1.如果你需要手工填充mipmaps,由于1024x1024的纹理mipmap最大数目为11,你可以指定mipmap Level为2-11的值。

对于Surface/Volume Format,通常情况下使用Unsigned 32-bit: A8R8G8B8格式,如果想要节省内存(但是会牺牲质量),可以选用Four CC 4-bit: DXT1格式,可以获得6:1甚至8:1的压缩比。

创建好后会变成这样:

可以看到当前默认的是+X纹理。

接下来就是将这六张图片塞进该立方体纹理中了,选择View-Cube map Face,并选择需要修改的纹理:

在当前项目的Texture文件夹内已经准备好了有6张贴图。

选择File-Open To This Cubemap Face来选择对应的贴图以加载进来即可。每完成当前的面就要切换到下一个面继续操作,直到六个面都填充完毕。此时填充的是Mipmap Level为0的子资源:

如果你需要自动生成纹理,则可以点击下面的选项生成,要求创建时MipMap Level为1:

最后就可以点击File-Save As来保存dds文件了。

这种做法需要比较长的前期准备时间,它不适合批量处理。但是在读取上是最方便的。

使用代码读取天空盒

对于创建好的DDS立方体纹理,我们只需要使用DDSTextureLoader就可以很方便地读取进来:

HR(CreateDDSTextureFromFile(
device.Get(),
cubemapFilename.c_str(),
nullptr,
textureCubeSRV.GetAddressOf()));

然而从网络上能够下到的天空盒资源经常要么是一张天空盒贴图,要么是六张天空盒的正方形贴图,用DXTex导入还是比较麻烦的一件事情。我们也可以自己编写代码来构造立方体纹理。

将一张天空盒贴图转化成立方体纹理需要经历以下4个步骤:

  1. 读取天空盒的贴图
  2. 创建包含6个纹理的数组
  3. 选取原天空盒纹理的6个子正方形区域,拷贝到该数组中
  4. 创建立方体纹理的SRV

而将六张天空盒的正方形贴图转换成立方体需要经历这4个步骤:

  1. 读取这六张正方形贴图
  2. 创建包含6个纹理的数组
  3. 将这六张贴图完整地拷贝到该数组中
  4. 创建立方体纹理的SRV

可以看到这两种类型的天空盒资源在处理上有很多相似的地方。

有关天空盒读取的代码实现如果你想了解,需要回到开头了解"深入理解与使用2D纹理资源"这章

绘制天空盒

尽管天空盒是一个立方体,但是实际上渲染的是一个很大的"球体"(由大量的三角形逼近)表面。使用方向向量来映射到立方体纹理对应的像素颜色,同时它也指向当前绘制的"球"面上对应点。另外,为了保证绘制的天空盒永远处在摄像机能看到的最远处,通常会将该球体的中心设置在摄像机所处的位置。这样无论摄像机如何移动,天空盒也跟随摄像机移动,用户将永远到不了天空盒的一端。可以说这和公告板一样,都是一种欺骗人眼的小技巧。如果不让天空盒跟随摄像机移动,这种假象立马就会被打破。

天空球体和纹理立方体的中心一致,不需要管它们的大小关系。

实际绘制的天空球体

绘制天空盒需要以下准备工作:

  1. 将天空盒载入HLSL的TextureCube中
  2. 在光栅化阶段关闭背面消隐(正面是球面向外,但摄像机在球内)
  3. 在输出合并阶段的深度/模板状态,设置深度比较函数为小于等于,以允许深度值为1的像素绘制

新的深度/模板状态

RenderStates.h引进了一个新的ID3D11DepthStencilState类型的成员DSSLessEqual,定义如下:

D3D11_DEPTH_STENCIL_DESC dsDesc;

// 允许使用深度值一致的像素进行替换的深度/模板状态
// 该状态用于绘制天空盒,因为深度值为1.0时默认无法通过深度测试
dsDesc.DepthEnable = true;
dsDesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL;
dsDesc.DepthFunc = D3D11_COMPARISON_LESS_EQUAL; dsDesc.StencilEnable = false; HR(device->CreateDepthStencilState(&dsDesc, DSSLessEqual.GetAddressOf()));

在绘制天空盒前就需要设置该深度/模板状态:

deviceContext->OMSetDepthStencilState(RenderStates::DSSLessEqual.Get(), 0);

HLSL代码

现在我们需要一组新的特效来绘制天空盒,其中与之相关的是Sky.hlsli, Sky_VS.hlslSky_PS.hlsl,当然在C++那边还有新的SkyEffect类来管理,需要了解自定义Effect的可以回看第13章。

// Sky.hlsli
TextureCube g_TexCube : register(t0);
SamplerState g_Sam : register(s0); cbuffer CBChangesEveryFrame : register(b0)
{
matrix g_WorldViewProj;
} struct VertexPos
{
float3 PosL : POSITION;
}; struct VertexPosHL
{
float4 PosH : SV_POSITION;
float3 PosL : POSITION;
};
// Sky_VS.hlsl
#include "Sky.hlsli" VertexPosHL VS(VertexPos vIn)
{
VertexPosHL vOut; // 设置z = w使得z/w = 1(天空盒保持在远平面)
float4 posH = mul(float4(vIn.PosL, 1.0f), g_WorldViewProj);
vOut.PosH = posH.xyww;
vOut.PosL = vIn.PosL;
return vOut;
}
// Sky_PS.hlsl
#include "Sky.hlsli" float4 PS(VertexPosHL pIn) : SV_Target
{
return g_TexCube.Sample(g_Sam, pIn.PosL);
}

注意: 在过去,应用程序首先绘制天空盒以取代渲染目标和深度/模板缓冲区的清空。然而“ATI Radeon HD 2000 Programming Gudie"(现在已经404了)建议我们不要这么做。首先,为了获得内部硬件深度优化的良好表现,深度/模板缓冲区需要被显式清空。这对渲染目标同样有效。其次,通常绝大多数的天空会被其它物体给遮挡。因此,如果我们先绘制天空,再绘制物体的话会导致二次绘制,还不如先绘制物体,然后让被遮挡的天空部分不通过深度测试。因此现在推荐的做法为:总是先清空渲染目标和深度/模板缓冲区,天空盒的绘制留到最后。

模型的反射

关于环境映射,另一个主要应用就是模型表面的反射(只有当天空盒记录了除当前反射物体外的其它物体时,才能在该物体看到其余物体的反射)。对于静态天空盒来说,通过模型看到的反射只能看到天空盒本身,因此还是显得不够真实。至于动态天空盒就还是留到下一章再讲。

下图说明了反射是如何通过环境映射运作的。法向量n对应的表面就像是一个镜面,摄像机在位置e,观察点p时可以看到经过反射得到的向量v所指向的天空盒纹理的采样像素点:

首先在之前的Basic.hlsli中加入TextureCube:

// Basic.hlsli
Texture2D g_DiffuseMap : register(t0);
TextureCube g_TexCube : register(t1);
SamplerState g_Sam : register(s0); // ...

然后只需要在Basic_PS.hlsl添加如下内容:

float4 litColor = texColor * (ambient + diffuse) + spec;

if (g_ReflectionEnabled)
{
float3 incident = -toEyeW;
float3 reflectionVector = reflect(incident, pIn.NormalW);
float4 reflectionColor = g_TexCube.Sample(g_Sam, reflectionVector); litColor += g_Material.Reflect * reflectionColor;
} litColor.a = texColor.a * g_Material.Diffuse.a;
return litColor;

在C++端,将采样器设置为各向异性过滤以获取更好的绘制效果:

// 在RenderStates.h/.cpp可以看到
ComPtr<ID3D11SamplerState> RenderStates::SSAnistropicWrap; D3D11_SAMPLER_DESC sampDesc;
ZeroMemory(&sampDesc, sizeof(sampDesc)); // 各向异性过滤模式
sampDesc.Filter = D3D11_FILTER_ANISOTROPIC;
sampDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
sampDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
sampDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
sampDesc.ComparisonFunc = D3D11_COMPARISON_NEVER;
sampDesc.MaxAnisotropy = 4;
sampDesc.MinLOD = 0;
sampDesc.MaxLOD = D3D11_FLOAT32_MAX;
HR(device->CreateSamplerState(&sampDesc, SSAnistropicWrap.GetAddressOf())); // 在BasicEffect.cpp可以看到
deviceContext->PSSetSamplers(0, 1, RenderStates::SSAnistropicWrap.GetAddressOf());

通常一个像素的颜色不完全是反射后的颜色(只有镜面才是100%反射)。因此,我们将原来的光照等式加上了材质反射的分量。当初MaterialReflect成员现在就派上了用场:

// 物体表面材质
struct Material
{
Material() { memset(this, 0, sizeof(Material)); } DirectX::XMFLOAT4 Ambient;
DirectX::XMFLOAT4 Diffuse;
DirectX::XMFLOAT4 Specular; // w = 镜面反射强度
DirectX::XMFLOAT4 Reflect;
};

我们可以指定该材质的反射颜色,如果该材质只反射完整的红光部分,则在C++指定Reflect = XMFLOAT4(1.0f, 0.0f, 0.0f, 0.0f)

使用带加法的反射容易引发一个问题:过度饱和。两个颜色的相加可能会存在RGB值超过1而变白,这会导致某些像素的颜色过于明亮。通常如果我们添加反射分量的颜色,就必须减小材质本身的环境分量和漫反射分量来实现平衡。另一种方式就是对反射分量和像素颜色s进行插值处理:

\[\mathbf{f} = t\mathbf{c}_{R} + (1 - t)\mathbf{s} (0 <= t <= 1)
\]

这样我们就可以通过调整系数t来控制反射程度,以达到自己想要的效果。

还有一个问题就是,在平面上进行环境映射并不会取得理想的效果。这是因为上面的HLSL代码关于反射的部分只使用了方向向量来进行采样,这会导致以相同的的倾斜角度看平面时,不同的位置看到的反射效果却是一模一样的。正确的效果应该是:摄像机在跟随平面镜做平移运动时,平面镜的映象应该保持不动。下面用两张图来说明这个问题:

这里给出龙书所提供相关论文,用以纠正环境映射出现的问题: Brennan02

本项目现在不考虑解决这个问题。

SkyRender类

SkyRender类支持之前所述的3种天空盒的加载,由于在构造的同时还会创建球体,建议使用unique_ptr来管理对象。

下面是SkyRender的完整实现:

class SkyRender
{
public:
template<class T>
using ComPtr = Microsoft::WRL::ComPtr<T>; SkyRender() = default;
~SkyRender() = default;
// 不允许拷贝,允许移动
SkyRender(const SkyRender&) = delete;
SkyRender& operator=(const SkyRender&) = delete;
SkyRender(SkyRender&&) = default;
SkyRender& operator=(SkyRender&&) = default; // 需要提供完整的天空盒贴图 或者 已经创建好的天空盒纹理.dds文件
HRESULT InitResource(ID3D11Device* device,
ID3D11DeviceContext* deviceContext,
const std::wstring& cubemapFilename,
float skySphereRadius, // 天空球半径
bool generateMips = false); // 默认不为静态天空盒生成mipmaps // 需要提供天空盒的六张正方形贴图
HRESULT InitResource(ID3D11Device* device,
ID3D11DeviceContext* deviceContext,
const std::vector<std::wstring>& cubemapFilenames,
float skySphereRadius, // 天空球半径
bool generateMips = false); // 默认不为静态天空盒生成mipmaps ID3D11ShaderResourceView* GetTextureCube(); void Draw(ID3D11DeviceContext* deviceContext, SkyEffect& skyEffect, const Camera& camera); // 设置调试对象名
void SetDebugObjectName(const std::string& name); private:
HRESULT InitResource(ID3D11Device* device, float skySphereRadius); private:
ComPtr<ID3D11Buffer> m_pVertexBuffer;
ComPtr<ID3D11Buffer> m_pIndexBuffer; UINT m_IndexCount; ComPtr<ID3D11ShaderResourceView> m_pTextureCubeSRV;
};
HRESULT SkyRender::InitResource(ID3D11Device* device, ID3D11DeviceContext* deviceContext, const std::wstring& cubemapFilename, float skySphereRadius, bool generateMips)
{
// 防止重复初始化造成内存泄漏
m_pIndexBuffer.Reset();
m_pVertexBuffer.Reset();
m_pTextureCubeSRV.Reset(); HRESULT hr;
// 天空盒纹理加载
if (cubemapFilename.substr(cubemapFilename.size() - 3) == L"dds")
{
hr = CreateDDSTextureFromFile(device,
generateMips ? deviceContext : nullptr,
cubemapFilename.c_str(),
nullptr,
m_pTextureCubeSRV.GetAddressOf());
}
else
{
hr = CreateWICTexture2DCubeFromFile(device,
deviceContext,
cubemapFilename,
nullptr,
m_pTextureCubeSRV.GetAddressOf(),
generateMips);
} if (hr != S_OK)
return hr; return InitResource(device, skySphereRadius);
} HRESULT SkyRender::InitResource(ID3D11Device* device, ID3D11DeviceContext* deviceContext, const std::vector<std::wstring>& cubemapFilenames, float skySphereRadius, bool generateMips)
{
// 防止重复初始化造成内存泄漏
m_pIndexBuffer.Reset();
m_pVertexBuffer.Reset();
m_pTextureCubeSRV.Reset(); HRESULT hr;
// 天空盒纹理加载
hr = CreateWICTexture2DCubeFromFile(device,
deviceContext,
cubemapFilenames,
nullptr,
m_pTextureCubeSRV.GetAddressOf(),
generateMips);
if (hr != S_OK)
return hr; return InitResource(device, skySphereRadius);
} ID3D11ShaderResourceView* SkyRender::GetTextureCube()
{
return m_pTextureCubeSRV.Get();
} void SkyRender::Draw(ID3D11DeviceContext* deviceContext, SkyEffect& skyEffect, const Camera& camera)
{
UINT strides[1] = { sizeof(XMFLOAT3) };
UINT offsets[1] = { 0 };
deviceContext->IASetVertexBuffers(0, 1, m_pVertexBuffer.GetAddressOf(), strides, offsets);
deviceContext->IASetIndexBuffer(m_pIndexBuffer.Get(), DXGI_FORMAT_R32_UINT, 0); XMFLOAT3 pos = camera.GetPosition();
skyEffect.SetWorldViewProjMatrix(XMMatrixTranslation(pos.x, pos.y, pos.z) * camera.GetViewProjXM());
skyEffect.SetTextureCube(m_pTextureCubeSRV.Get());
skyEffect.Apply(deviceContext);
deviceContext->DrawIndexed(m_IndexCount, 0, 0);
} void SkyRender::SetDebugObjectName(const std::string& name)
{
#if (defined(DEBUG) || defined(_DEBUG)) && (GRAPHICS_DEBUGGER_OBJECT_NAME)
// 先清空可能存在的名称
D3D11SetDebugObjectName(m_pTextureCubeSRV.Get(), nullptr); D3D11SetDebugObjectName(m_pTextureCubeSRV.Get(), name + ".CubeMapSRV");
D3D11SetDebugObjectName(m_pVertexBuffer.Get(), name + ".VertexBuffer");
D3D11SetDebugObjectName(m_pIndexBuffer.Get(), name + ".IndexBuffer");
#else
UNREFERENCED_PARAMETER(name);
#endif
} HRESULT SkyRender::InitResource(ID3D11Device * device, float skySphereRadius)
{
HRESULT hr;
auto sphere = Geometry::CreateSphere<VertexPos>(skySphereRadius); // 顶点缓冲区创建
D3D11_BUFFER_DESC vbd;
vbd.Usage = D3D11_USAGE_IMMUTABLE;
vbd.ByteWidth = sizeof(XMFLOAT3) * (UINT)sphere.vertexVec.size();
vbd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
vbd.CPUAccessFlags = 0;
vbd.MiscFlags = 0;
vbd.StructureByteStride = 0; D3D11_SUBRESOURCE_DATA InitData;
InitData.pSysMem = sphere.vertexVec.data(); hr = device->CreateBuffer(&vbd, &InitData, &m_pVertexBuffer);
if (hr != S_OK)
return hr; // 索引缓冲区创建
m_IndexCount = (UINT)sphere.indexVec.size(); D3D11_BUFFER_DESC ibd;
ibd.Usage = D3D11_USAGE_IMMUTABLE;
ibd.ByteWidth = sizeof(DWORD) * m_IndexCount;
ibd.BindFlags = D3D11_BIND_INDEX_BUFFER;
ibd.CPUAccessFlags = 0;
ibd.StructureByteStride = 0;
ibd.MiscFlags = 0; InitData.pSysMem = sphere.indexVec.data(); return device->CreateBuffer(&ibd, &InitData, &m_pIndexBuffer);
}

与其配套的SkyEffect可以在源码中观察到。

项目演示

说了那么多内容,是时候看一些动图了吧。

该项目加载了三种类型的天空盒,可以随时切换。

DirectX11 With Windows SDK完整目录

Github项目源码

欢迎加入QQ群: 727623616 可以一起探讨DX11,以及有什么问题也可以在这里汇报。

DirectX11 With Windows SDK--22 立方体映射:静态天空盒的读取与实现的更多相关文章

  1. DirectX11 With Windows SDK--38 级联阴影映射(CSM)

    前言 在31章我们曾经实现过阴影映射,但是受到阴影贴图精度的限制,只能在场景中相当有限的范围内投射阴影.本章我们将以微软提供的例子和博客作为切入点,学习如何解决阴影中出现的Atrifacts: 边缘闪 ...

  2. 粒子系统与雨的效果 (DirectX11 with Windows SDK)

    前言 最近在学粒子系统,看这之前的<<3D图形编程基础 基于DirectX 11 >>是基于Direct SDK的,而DXSDK微软已经很久没有更新过了并且我学的DX11是用W ...

  3. DirectX11 With Windows SDK--19 模型加载:obj格式的读取及使用二进制文件提升读取效率

    前言 一个模型通常是由三个部分组成:网格.纹理.材质.在一开始的时候,我们是通过Geometry类来生成简单几何体的网格.但现在我们需要寻找合适的方式去表述一个复杂的网格,而且包含网格的文件类型多种多 ...

  4. DirectX11 With Windows SDK--23 立方体映射:动态天空盒的实现

    前言 上一章的静态天空盒已经可以满足绝大部分日常使用了.但对于自带反射/折射属性的物体来说,它需要依赖天空盒进行绘制,但静态天空盒并不会记录周边的物体,更不用说正在其周围运动的物体了.因此我们需要在运 ...

  5. DirectX11 With Windows SDK--31 阴影映射

    前言 阴影既暗示着光源相对于观察者的位置关系,也从侧面传达了场景中各物体之间的相对位置.本章将起底最基础的阴影映射算法,而像复杂如级联阴影映射这样的技术,也是在阴影映射的基础上发展而来的. 学习目标: ...

  6. DirectX11 With Windows SDK--00 目录

    前言 (更新于 2019/4/10) 从第一次接触DirectX 11到现在已经有将近两年的时间了.还记得前年暑假被要求学习DirectX 11,在用龙书的源码配置项目运行环境的时候都花了好几天的时间 ...

  7. DirectX11 With Windows SDK--24 Render-To-Texture(RTT)技术的应用

    前言 尽管在上一章的动态天空盒中用到了Render-To-Texture技术,但那是针对纹理立方体的特化实现.考虑到该技术的应用层面非常广,在这里抽出独立的一章专门来讲有关它的通用实现以及各种应用. ...

  8. DirectX11 With Windows SDK--07 添加光照与常用几何模型

    前言 对于3D游戏来说,合理的光照可以让游戏显得更加真实.接下来会介绍光照的各种分量,以及常见的光照模型.除此之外,该项目还用到了多个常量缓冲区,因此还会提及HLSL的常量缓冲区打包规则以及如何设置多 ...

  9. DirectX11 With Windows SDK--07 添加光照与常用几何模型、光栅化状态

    原文:DirectX11 With Windows SDK--07 添加光照与常用几何模型.光栅化状态 前言 对于3D游戏来说,合理的光照可以让游戏显得更加真实.接下来会介绍光照的各种分量,以及常见的 ...

随机推荐

  1. python 完整项目开发流程

    1. 安装 python    2. 安装virtualenvwrapper    3. 虚拟环境相关操作    4. 进入虚拟环境, 安装django    5. 安装编辑器    6. 安装mys ...

  2. SQLServer之索引简介

    索引设计基础知识 索引是与表或视图关联的磁盘上结构,可以加快从表或视图中检索行的速度. 索引包含由表或视图中的一列或多列生成的键. 这些键存储在一个结构(B 树)中,使 SQL Server 可以快速 ...

  3. Django的认证系统

    Django自带的用户认证 我们在开发一个网站的时候,无可避免的需要设计实现网站的用户系统.此时我们需要实现包括用户注册.用户登录.用户认证.注销.修改密码等功能,这还真是个麻烦的事情呢. Djang ...

  4. 【Python 19】BMR计算器3.0(字符串分割与格式化输出)

    1.案例描述 基础代谢率(BMR):我们安静状态下(通常为静卧状态)消耗的最低热量,人的其他活动都建立在这个基础上. 计算公式: BMR(男) = (13.7*体重kg)+(5.0*身高cm)-(6. ...

  5. Hbase技术笔记

    一.Hbase介绍 二.Hbase的Region介绍 三.Hbase的写逻辑介绍 四.Hbase的故障恢复 五.Hbase的拆分和合并 如下ppt所示: 下面就来针对各个部分的内容来进行详细的介绍: ...

  6. Java 前后端List传值

    js代码 function click(){ var arrays = new Array(); for (var i = 0; i < arr.length; i++) { arrays.pu ...

  7. SpringBoot 数据篇之使用JDBC

    SpringBootTutorial :: Data :: Jdbc 简介 API execute update query 实战 配置数据源 完整示例 引申和引用 简介 Spring Data 包含 ...

  8. .Net Core应用框架Util介绍(二)

    Util的开源地址 https://github.com/dotnetcore/util Util的开源协议 Util以MIT协议开源,这是目前最宽松的开源协议,你不仅可以用于商业项目,还能把Util ...

  9. MariaDB第四章:视图,事务,索引,外键--小白博客

    视图 对于复杂的查询,在多个地方被使用,如果需求发生了改变,需要更改sql语句,则需要在多个地方进行修改,维护起来非常麻烦 假如因为某种需求,需要将user拆房表usera和表userb,该两张表的结 ...

  10. element ui 时间 date 差一天

    let BirthdayYMD = common.formatDate(this.addForm.Dendline); this.addForm.Dendline = new Date(Birthda ...