DirectX11 With Windows SDK–31 陰影映射

前言

陰影既暗示着光源相對於觀察者的位置關係,也從側面傳達了場景中各物體之間的相對位置。本章將起底最基礎的陰影映射算法,而像複雜如級聯陰影映射這樣的技術,也是在陰影映射的基礎上發展而來的。

學習目標:

  1. 掌握基本的陰影映射算法
  2. 熟悉投影紋理貼圖的工作原理
  3. 了解陰影圖走樣的問題並學習修正該問題的常用策略

DirectX11 With Windows SDK完整目錄

Github項目源碼

歡迎加入QQ群: 727623616 可以一起探討DX11,以及有什麼問題也可以在這裏彙報。

核心思想

陰影映射技術的核心思想其實不複雜。對於場景中的一點,如果該點能夠被攝像機觀察到,卻不能被光源定義的虛擬攝像機所觀察到,那麼場景中的這一點則可以被判定為光源所照射不到的陰影區域。

以下圖為例,眼睛觀察到地面上最左邊的一點,並且從光源處觀察也能看到該點。因此該點不會產生陰影。

再看下面的圖,眼睛可以觀察到地面上中間那一點,但是從光源處觀察不能看到該點。因此該點會產生陰影。

具體落實下來應該怎麼做呢?對於點光源來說,由於它的光是朝所有方向四射散開的,但為了方便,我們可以像攝像機那樣選取視錐體區域(使用一個觀察矩陣 + 透視投影矩陣來定義),然後經過正常的變換后就能計算出光源到區域內物體的深度值;而對於平行光(方向光)來說,我們可以採用正交投影的方式來選取一個長方體區域(使用一個觀察矩陣 + 正交投影矩陣定義)。和一般的渲染流程不同的是,我們只需要記錄深度值到深度緩衝區,而不需要將顏色繪製到後備緩衝區。

陰影貼圖

陰影貼圖技術也是一種變相的“渲染到紋理”技術。它以光源的視角來渲染場景深度信息,即在光源處有一個虛擬攝像機,它將觀察到的物體的深度信息保存到深度緩衝區中。這樣我們就可以知道那些離光源最近的像素片元信息,同時這些點自然是不在陰影範圍之中。

通常該技術需要用到一個深度/模板緩衝區、一個與之對應的視口、針對該深度/模板緩衝區的着色器資源視圖(SRV)和深度/模板視圖(DSV),而用於陰影貼圖的那個深度/模板緩衝區也被稱為陰影貼圖

光源的投影

在考慮點光源的投影和方向光的投影時可能會有些困難,但這兩個問題其實可以轉化成虛擬攝像機的透視投影和正交投影。

對於透視投影來說,其實我們也已經非常熟悉了。在這種做法下我們只考慮虛擬攝像機的視錐體區域(即儘管點光源是朝任意方向照射的,但我們只看點光源往該視錐體範圍內照射的區域),然後對物體慣例進行世界變換、以光源為視角的觀察變換、光源的透視投影變換,這樣物體就被變換到了以光源為視角的NDC空間。

而對於正交投影而言,我們也是一樣的做法。正交投影的視景體是一個軸對齊於觀察坐標系的長方體。儘管我們不好描述一個方向光的光源,但為了方便,我們把光源定義在視景體xOy切面中心所處的那條直線上。這樣我們就只需要給出視景體的寬度、高度、近平面、遠平面信息就可以構造出一個正交投影矩陣了。

我們可以看到,正交投影的投影線均平行於觀察空間的z軸。

正交投影矩陣在第四章變換已經講過,就不再贅述。

投影紋理坐標

投影紋理貼圖技術能夠將紋理投射到任意形狀的幾何體上,又因為其原理與投影機的工作方式比較相似,由此得名。例如下圖中,右邊的骷髏頭紋理被投射到左邊場景中的多個幾何體上。

投影紋理貼圖的關鍵在於為每個像素生成對應的投影紋理坐標,從視覺上給人一種紋理被投射到幾何體上的感覺。

下圖是光源觀察的視野,其中點p是待渲染的一點,而紋理坐標(u, v)則指定了應當被投射到3D點p上的紋素,並且坐標(u, v)與投影到屏幕上的NDC坐標有特定聯繫。我們可以將投影紋理坐標的生成過程分為如下步驟:

  1. 將3D空間中一點p投影到光源的投影窗口,並將其變換到NDC空間。
  2. 將投影坐標從NDC空間變換到紋理空間,以此將它們轉換為紋理坐標

而步驟2中的變換過程則取決於下面的坐標變換:

\[u=0.5x+0.5\\ v=-0.5y+0.5 \]

即從x, y∈[-1, 1]映射到u, v∈[0, 1]。(y軸和v軸是相反的)

這種線性變換可以用矩陣表示:

\[\mathbf{T}=\begin{bmatrix} 0.5 & 0 & 0 & 0 \\ 0 & -0.5 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0.5 & 0.5 & 0 & 1 \\ \end{bmatrix}\\ \begin{bmatrix} x & y & 0 & 1 \end{bmatrix}\mathbf{T}=\begin{bmatrix} u & v & 0 & 1 \end{bmatrix} \]

那麼物體上的一點p從局部坐標繫到最終的紋理坐標點t的變換過程為:

\[\mathbf{p}\mathbf{W_{Obj}}\mathbf{V_{Light}}\mathbf{P_{Light}}\mathbf{T}=\mathbf{t} \]

這裏補上了世界變換矩陣,是因為這一步容易在後面的代碼實踐中被漏掉。但此時的t還需要經過透視除法,才是我們最終需要的紋理坐標。

HLSL代碼

下面的HLSL代碼展示了頂點着色器計算投影紋理坐標的過程:

// 頂點着色器
VertexPosHWNormalTexShadowPosH VS(VertexPosNormalTex vIn)
{
    VertexPosHWNormalTexShadowPosH vOut;
    
    matrix viewProj = mul(g_View, g_Proj);
    vector posW = mul(float4(vIn.PosL, 1.0f), g_World);

    vOut.PosW = posW.xyz;
    
    // ...
    
    // 把頂點變換到光源的投影空間
    vOut.ShadowPosH = mul(posW, g_ShadowTransform);
    return vOut;
}

// 像素着色器
float4 PS(VertexPosHWNormalTexShadowPosH pIn) : SV_Target
{
    // 透視除法
    pIn.ShadowPosH.xyz /= pIn.ShadowPosH.w;
    
    // NDC空間中的深度值
    float depth = pIn.ShadowPosH.z;
    
    // 通過投影紋理坐標來對紋理採樣
    // 採樣出的r分量即為光源觀察該點時的深度值
    float4 c = g_ShadowMap.Sample(g_Sam, pIn.ShadowPosH.xy);
    
    // ...
}

視錐體之外的點

在渲染管線中,位於視錐體之外的幾何體是要被裁剪掉的。但是,在我們以光源設置的視角投影幾何體而為之生成投影紋理坐標時,並不需要執行裁剪操作——只需要簡單投影頂點即可。因此,位於視錐體之外的幾何體頂點會得到[0, 1]區間之外的投影紋理坐標。然後具體的採樣行為則需要依賴於我們設置的採樣器。

一般來說,我們並不希望對位於視錐體外的幾何體頂點進行貼圖,因為這並沒有任何意義。考慮到可視深度在NDC空間的最大值為1.0f,我們可以採用邊界深度值為1.0f的邊框尋址模式

另一種做法則是結合聚光燈的策略,使聚光燈照射範圍之外的部分不受光照,亦即不在陰影的計算範圍內。

透視除法與投影的其他問題

來到正交投影,因為我們依然是要計算出NDC坐標,對於NDC空間範圍外的點,我們依然可以採用上面的尋址模式策略,但聚光燈的策略就不適用了。

此外,正交投影無需進行透視除法,因為正交投影后的坐標w值總是1.0f。但保留透視除法可以讓我們的這套着色器可以同時工作在正交投影和透視投影上。如果沒有透視除法,則只能在正交投影中工作。

算法思路

  1. 從光源的視角將場景深度以“渲染到紋理”的形式繪製到名為陰影貼圖的深度緩衝區中
  2. 從玩家攝像機的視角渲染場景,計算出該點在光源視角下NDC坐標,其中z值為深度值,記為d(p)
  3. 上面算出的NDC坐標的xy分量變換為陰影貼圖的紋理坐標uv,然後進行深度值採樣,得到s(p)
  4. 當d(p) > s(p)時, 像素p位於陰影範圍之內;自然相反地,當d(p) <= s(p)時,像素p位於陰影範圍之外(至於為什麼還有<,後面會提到)

改進TextureRender

既然陰影貼圖和RTT有着許多相似的地方,那何不把它也放到TextureRender裏面共用呢?只要添加一個開關控制該RTT是否用作陰影貼圖即可。

class TextureRender
{
public:
    template<class T>
    using ComPtr = Microsoft::WRL::ComPtr<T>;

    TextureRender() = default;
    ~TextureRender() = default;
    // 不允許拷貝,允許移動
    TextureRender(const TextureRender&) = delete;
    TextureRender& operator=(const TextureRender&) = delete;
    TextureRender(TextureRender&&) = default;
    TextureRender& operator=(TextureRender&&) = default;


    HRESULT InitResource(ID3D11Device* device,
        int texWidth,
        int texHeight,
        bool shadowMap = false,
        bool generateMips = false);

    // 開始對當前紋理進行渲染
    // 陰影貼圖無需提供背景色
    void Begin(ID3D11DeviceContext* deviceContext, const FLOAT backgroundColor[4]);
    // 結束對當前紋理的渲染,還原狀態
    void End(ID3D11DeviceContext * deviceContext);
    // 獲取渲染好的紋理的着色器資源視圖
    // 陰影貼圖返回的是深度緩衝區
    // 引用數不增加,僅用於傳參
    ID3D11ShaderResourceView* GetOutputTexture();

    // 設置調試對象名
    void SetDebugObjectName(const std::string& name);

private:
    ComPtr<ID3D11ShaderResourceView>        m_pOutputTextureSRV;          // 輸出的紋理(或陰影貼圖)對應的着色器資源視圖
    ComPtr<ID3D11RenderTargetView>          m_pOutputTextureRTV;          // 輸出的紋理對應的渲染目標視圖
    ComPtr<ID3D11DepthStencilView>          m_pOutputTextureDSV;          // 輸出紋理所用的深度/模板視圖(或陰影貼圖)
    D3D11_VIEWPORT                          m_OutputViewPort = {};        // 輸出所用的視口

    ComPtr<ID3D11RenderTargetView>          m_pCacheRTV;                  // 臨時緩存的後備緩衝區
    ComPtr<ID3D11DepthStencilView>          m_pCacheDSV;                  // 臨時緩存的深度/模板緩衝區
    D3D11_VIEWPORT                          m_CacheViewPort = {};         // 臨時緩存的視口

    bool                                    m_GenerateMips = false;       // 是否生成mipmap鏈
    bool                                    m_ShadowMap = false;          // 是否為陰影貼圖

};

在作為RTT時,需要創建紋理與它的SRV和RTV、深度/模板緩衝區和它的DSV、視口

而作為陰影貼圖時,需要創建深度緩衝區與它的SRV和DSV、視口

下面的代碼只關注創建陰影貼圖的部分:

HRESULT TextureRender::InitResource(ID3D11Device* device, int texWidth, int texHeight, bool shadowMap, bool generateMips)
{
    // 防止重複初始化造成內存泄漏
    m_pOutputTextureSRV.Reset();
    m_pOutputTextureRTV.Reset();
    m_pOutputTextureDSV.Reset();
    m_pCacheRTV.Reset();
    m_pCacheDSV.Reset();

    m_ShadowMap = shadowMap;
    m_GenerateMips = false;
    HRESULT hr;
    
    // ...
    
    // ******************
    // 創建與紋理等寬高的深度/模板緩衝區或陰影貼圖,以及對應的視圖
    //
    CD3D11_TEXTURE2D_DESC texDesc((m_ShadowMap ? DXGI_FORMAT_R24G8_TYPELESS : DXGI_FORMAT_D24_UNORM_S8_UINT),
        texWidth, texHeight, 1, 1,
        D3D11_BIND_DEPTH_STENCIL | (m_ShadowMap ? D3D11_BIND_SHADER_RESOURCE : 0));

    ComPtr<ID3D11Texture2D> depthTex;
    hr = device->CreateTexture2D(&texDesc, nullptr, depthTex.GetAddressOf());
    if (FAILED(hr))
        return hr;

    CD3D11_DEPTH_STENCIL_VIEW_DESC dsvDesc(depthTex.Get(), D3D11_DSV_DIMENSION_TEXTURE2D, DXGI_FORMAT_D24_UNORM_S8_UINT);

    hr = device->CreateDepthStencilView(depthTex.Get(), &dsvDesc,
        m_pOutputTextureDSV.GetAddressOf());
    if (FAILED(hr))
        return hr;

    if (m_ShadowMap)
    {
        // 陰影貼圖的SRV
        CD3D11_SHADER_RESOURCE_VIEW_DESC srvDesc(depthTex.Get(), D3D11_SRV_DIMENSION_TEXTURE2D, DXGI_FORMAT_R24_UNORM_X8_TYPELESS);

        hr = device->CreateShaderResourceView(depthTex.Get(), &srvDesc,
            m_pOutputTextureSRV.GetAddressOf());
        if (FAILED(hr))
            return hr;
    }

    // ******************
    // 初始化視口
    //
    m_OutputViewPort.TopLeftX = 0.0f;
    m_OutputViewPort.TopLeftY = 0.0f;
    m_OutputViewPort.Width = static_cast<float>(texWidth);
    m_OutputViewPort.Height = static_cast<float>(texHeight);
    m_OutputViewPort.MinDepth = 0.0f;
    m_OutputViewPort.MaxDepth = 1.0f;

    return S_OK;
}

需要注意的是,在創建深度緩衝區時,如果還想為他創建SRV,就不能將DXGI格式定義成DXGI_FORMAT_D24_UNORM_S8_UINT這些帶D的類型,而應該是DXGI_FORMAT_R24G8_TYPELESS

然後在創建陰影貼圖的SRV時,則需要指定為DXGI_FORMAT_R24_UNORM_X8_TYPELESS

開始陰影貼圖的渲染前,不需要設置RTV,只需要綁定DSV。

void TextureRender::Begin(ID3D11DeviceContext* deviceContext, const FLOAT backgroundColor[4])
{
    // 緩存渲染目標和深度模板視圖
    deviceContext->OMGetRenderTargets(1, m_pCacheRTV.GetAddressOf(), m_pCacheDSV.GetAddressOf());
    // 緩存視口
    UINT num_Viewports = 1;
    deviceContext->RSGetViewports(&num_Viewports, &m_CacheViewPort);

    // 清空緩衝區
    // ... 
    deviceContext->ClearDepthStencilView(m_pOutputTextureDSV.Get(), D3D11_CLEAR_DEPTH | (m_ShadowMap ? 0 : D3D11_CLEAR_STENCIL), 1.0f, 0);
    
    // 設置渲染目標和深度模板視圖
    deviceContext->OMSetRenderTargets((m_ShadowMap ? 0 : 1), 
        (m_ShadowMap ? nullptr : m_pOutputTextureRTV.GetAddressOf()), 
        m_pOutputTextureDSV.Get());
    // 設置視口
    deviceContext->RSSetViewports(1, &m_OutputViewPort);
}

渲染完成后,和往常一樣還原即可。

偏移與走樣

陰影圖存儲的是距離光源最近的可視像素深度值,但是它的分辨率有限,導致每一個陰影圖紋素都要表示場景中的一片區域。因此,陰影圖只是以光源視角針對場景深度進行的離散採樣,這將會導致所謂的陰影粉刺等圖像走樣問題。如下圖所示(注意圖中地面上光影之間輪流交替的“階梯狀”條紋):

而下圖則簡單展示了為什麼會發生陰影粉刺這種現象。由於陰影圖的分辨率有限,所以每個陰影圖紋素要對應於長江中的一塊區域(而不是點對點的關係,一個坡面代表陰影圖中一個紋素的對應範圍)。從觀察點E查看場景中的兩個點p1與p2,它們分別對應於兩個不同的屏幕像素。但是,從光源的觀察角度來看,它們卻都有着相同的陰影圖紋素(即s(p1)=s(p2)=s,由於分辨率的原因)。當我們在執行陰影圖檢測時,會得到d(p1) > s 及 d(p2) <= s這兩個測試結果,這樣一來,p1將會被繪製為如同它在陰影中的顏色,p2將被渲染為好似它在陰影之外的顏色,從而導致陰影粉刺。

因此,我們可以通過偏移陰影圖中的深度值來防止出現錯誤的陰影效果。此時我們就可以保證d(p1) <= s 及 d(p2) <= s。但是尋找合適的深度偏移需要反覆嘗試。

偏移量過大會導致名為peter-panning(彼得·潘,即小飛俠,他曾在一次逃跑時弄丟了自己的影子)的失真效果,使得陰影看起來與物體相分離。

然而,並沒有哪一種固定的偏移量可以正確地運用於所有幾何體的陰影繪製。特別是下圖那種(從光源的角度來看)有着極大斜率的三角形,這時候就需要選取更大的偏移量。但是,如果試圖通過一個過大的深度偏移量來處理所有的斜邊,則又會造成peter-panning問題。

因此,我們繪製陰影的方式就是先以光源視角度量多邊形斜面的斜率,併為斜率較大的多邊形應用更大的偏移量。而圖形硬件內部對此有相關技術的支持,我們通過名為斜率縮放偏移的光柵化狀態屬性就能夠輕鬆實現。

typedef struct D3D11_RASTERIZER_DESC {
    // ...
    INT             DepthBias;
    FLOAT           DepthBiasClamp;
    FLOAT           SlopeScaledDepthBias;
    BOOL            DepthClipEnable;
    // ...
} D3D11_RASTERIZER_DESC;
  1. DepthBias:一個固定的應用偏移量。
  2. DepthBiasClamp:所允許的最大深度偏移量。以此來設置深度偏移量的上限。不難想象,及其陡峭的傾斜度會導致斜率縮放偏移量過大,從而造成peter-panning失真
  3. SlopeScaledDepthBias:根據多邊形的斜率來控制偏移程度的縮放因子。

注意,在將場景渲染至陰影貼圖時,便會應用該斜率縮放偏移量。這是由於我們希望以光源的視角基於多邊形的斜率而進行偏移操作,從而避免陰影失真。因此,我們就會對陰影圖中的數值進行偏移計算(即由硬件將像素的深度值與偏移值相加)。在本Demo中採用的具體數值如下:

// [出自MSDN]
// 如果當前的深度緩衝區採用UNORM格式並且綁定在輸出合併階段,或深度緩衝區還沒有被綁定
// 則偏移量的計算過程如下:
//
// Bias = (float)DepthBias * r + SlopeScaledDepthBias * MaxDepthSlope;
//
// 這裏的r是在深度緩衝區格式轉換為float32類型后,其深度值可取到大於0的最小可表示的值
// MaxDepthSlope則是像素在水平方向和豎直方向上的深度斜率的最大值
// [結束MSDN引用]
//
// 對於一個24位的深度緩衝區來說, r = 1 / 2^24
//
// 例如:DepthBias = 100000 ==> 實際的DepthBias = 100000/2^24 = .006
//
// 本Demo中的方向光始終與地面法線呈45度夾角,故取斜率為1.0f
// 以下數據極其依賴於實際場景,因此我們需要對特定場景反覆嘗試才能找到最合適
rsDesc.DepthBias = 100000;
rsDesc.DepthBiasClamp = 0.0f;
rsDesc.SlopeScaledDepthBias = 1.0f

注意:深度偏移發生在光柵化期間(裁剪之後),因此不會對幾何體裁剪造成影響。

RenderStates中我們添加了這樣一個光柵化狀態:

// 深度偏移模式
rasterizerDesc.FillMode = D3D11_FILL_SOLID;
rasterizerDesc.CullMode = D3D11_CULL_BACK;
rasterizerDesc.FrontCounterClockwise = false;
rasterizerDesc.DepthClipEnable = true;
rasterizerDesc.DepthBias = 100000;
rasterizerDesc.DepthBiasClamp = 0.0f;
rasterizerDesc.SlopeScaledDepthBias = 1.0f;
HR(device->CreateRasterizerState(&rasterizerDesc, RSDepth.GetAddressOf()));

MSDN文檔Depth Bias講述了該技術相關的全部規則,並且介紹了如何使用浮點深度緩衝區進行工作。

百分比漸近過濾(PCF)

在使用投影紋理坐標(u, v)對陰影圖進行採樣時,往往不會命中陰影圖中紋素的準確位置,而是通常位於陰影圖中的4個紋素之間。然而,我們不應該對深度值採用雙線性插值法,因為4個紋素之間的深度值不一定滿足線性過渡,插值出來的深度值跟實際的深度值有偏差,這樣可能會導致把像素錯誤標入陰影中這樣的錯誤結果(因此我們也不能為陰影圖生成mipmap)。

出於這樣的原因,我們應該對採樣的結果進行插值,而不是對深度值進行插值。這種做法稱為——百分比漸近過濾。即我們以點過濾(MIN_MAG_MIP_POINT)的方式在坐標(u, v)、(u+△x, v)、(u, v+△x)、(u+△x, v+△x)處對紋理進行採樣,其中△x=1/SHADOW_MAP_SIZE(除以的是引用貼圖的寬高)。由於是點採樣,這4個採樣點分別命中的是圍繞坐標(u, v)最近的4個陰影圖紋素s0、s1、s2、s3,如下圖所示。

接下來,我們會對這些採集的深度值進行陰影圖檢測,並對測試的結果展開雙線性插值。

static const float SMAP_SIZE = 2048.0f;
static const float SMAP_DX = 1.0f / SMAP_SIZE;

// ...

//
// 採樣操作
//

// 對陰影圖進行採樣以獲取離光源最近的深度值
float s0 = g_ShadowMap.Sample(g_SamShadow, tex.xy).r;
float s1 = g_ShadowMap.Sample(g_SamShadow, tex.xy + float2(SMAP_DX, 0)).r;
float s2 = g_ShadowMap.Sample(g_SamShadow, tex.xy + float2(0, SMAP_DX)).r;
float s3 = g_ShadowMap.Sample(g_SamShadow, tex.xy + float2(SMAP_DX, SMAP_DX)).r;

// 該像素的深度值是否小於等於陰影圖中的深度值
float r0 = (depth <= s0);
float r1 = (depth <= s1);
float r2 = (depth <= s2);
float r3 = (depth <= s3);

//
// 雙線性插值操作
//

// 變換到紋素空間
float2 texelPos = SMAP_SIZE * tex.xy;

// 確定插值係數(frac()返回浮點數的小數部分)
float2 t = frac(texelPos);

// 對比較結果進行雙線性插值
return lerp(lerp(r0, r1, t.x), lerp(r2, r3, t.x), t.y);

若採用這種計算方法,則一個像素就可能局部處於陰影之中,而不是非0即1.例如,若有4個樣本,三個在陰影中,一個在陰影外,那麼該像素有75%處於陰影之中。這就讓陰影內外的像素之間有了更加平滑的過渡,而不是稜角分明。

但這種過濾方法產生的陰影看起來仍然非常生硬,且鋸齒失真問題的最終處理效果還是不能令人十分滿意。PCF的主要缺點是需要4個紋理樣本,而紋理採樣本身就是現代GPU代價較高的操作之一,因為存儲器的帶寬與延遲並沒有隨着GPU計算能力的劇增而得到相近程度的巨大改良。幸運的是,Direct3D 11+版本的圖形硬件對PCF技術已經有了內部支持,上面的一大堆代碼可以用SampleCmpLevelZero函數來替代。

float percentage = g_ShadowMap.SampleCmpLevelZero(g_SamShadow, shadowPosH.xy, depth).r;

方法中的LevelZero部分意味着它只能在最高的mipmap層級中進行採樣。另外,該方法使用的並非一般的採樣器對象,而是比較採樣器。這使得硬件能夠執行陰影圖的比較測試,並且需要在過濾採樣結果之前完成。對於PCF技術來說,我們需要使用的是D3D11_FILTER_COMPARISON_MIN_MAG_LINEAR_MIP_POINT過濾器,並將比較函數設置為LESS_EQUAL(由於對深度值進行了偏移,所以也要用到LESS比較函數)。

函數中傳入的depth將會出現在比較運算符的左邊,即:

depth <= sampleDepth

RenderStates中我們添加了這樣一個採樣器:

ComPtr<ID3D11SamplerState> RenderStates::SSShadow = nullptr;

// 採樣器狀態:深度比較與Border模式
ZeroMemory(&sampDesc, sizeof(sampDesc));
sampDesc.Filter = D3D11_FILTER_COMPARISON_MIN_MAG_LINEAR_MIP_POINT;
sampDesc.AddressU = D3D11_TEXTURE_ADDRESS_BORDER;
sampDesc.AddressV = D3D11_TEXTURE_ADDRESS_BORDER;
sampDesc.AddressW = D3D11_TEXTURE_ADDRESS_BORDER;
sampDesc.ComparisonFunc = D3D11_COMPARISON_LESS_EQUAL;
sampDesc.BorderColor[0] = { 1.0f };
sampDesc.MinLOD = 0;
sampDesc.MaxLOD = D3D11_FLOAT32_MAX;
HR(device->CreateSamplerState(&sampDesc, SSShadow.GetAddressOf()));

注意:根據SDK文檔所述,只有R32_FLOAT_X8X24_TYPELESSR32_FLOATR24_UNORM_X8_TYPELESSR16_UNORM格式才能用於比較過濾器。

在PCF的基礎上進行均值濾波

到目前為止,我們在本節中一直使用的是4-tap PCF核(輸入4個樣本來執行的PCF)。PCF核越大,陰影的邊緣輪廓也就越豐滿、越平滑,當然,花費在SampleCmpLevelZero函數上的開銷也就越大。在本Demo中,我們是按3×3正方形的均值濾波方式來執行PCF。由於每次調用SampleCmpLevelZero函數實際所執行的都是4-tap PCF,所以一共採樣了36次,其中有4×4個獨立採樣點。此外,採用過大的濾波核還會導致之前所述的陰影粉刺問題,但本章不打算講述,有興趣可以回到龍書閱讀(過大的PCF核)。

顯然,PCF技術一般來說只需在陰影的邊緣進行,因為陰影內外兩部分並不涉及混合操作(只有陰影邊緣才是漸變的)。基於此,只要能對陰影邊緣的PCF設計相應的處理方案就好了。但這種做法一般要求我們所用的PCF核足夠大(5×5及更大)時才划算(因為動態分支也有開銷)。不過最終是要效率還是要畫質還是取決於你自己。

注意:實際工程中所用的PCF核不一定是方形的過濾柵格。不少文獻也指出,隨機的拾取點也可以作為PCF核。

考慮到在做比較時,如果處於陰影外的值為1,在陰影內的值為0,在採用SampleCmpLevelZero和均值濾波后,我們用範圍值0~1來表示處於陰影外的程度。隨着值的增加,該點也變得越亮。我們可以使用下面的函數來計算3×3正方形的均值濾波下的陰影因子:

float CalcShadowFactor(SamplerComparisonState samShadow, Texture2D shadowMap, float4 shadowPosH)
{
	// 透視除法
    shadowPosH.xyz /= shadowPosH.w;
	
	// NDC空間的深度值
    float depth = shadowPosH.z;

	// 紋素在紋理坐標下的寬高
    const float dx = SMAP_DX;

    float percentLit = 0.0f;
    const float2 offsets[9] =
    {
        float2(-dx, -dx), float2(0.0f, -dx), float2(dx, -dx),
		float2(-dx, 0.0f), float2(0.0f, 0.0f), float2(dx, 0.0f),
		float2(-dx, +dx), float2(0.0f, +dx), float2(dx, +dx)
    };
                      
	[unroll]
    for (int i = 0; i < 9; ++i)
    {
        percentLit += shadowMap.SampleCmpLevelZero(samShadow,
			shadowPosH.xy + offsets[i], depth).r;
    }
    
    return percentLit /= 9.0f;
}

然後在我們的光照模型中,只有第一個方向光才參与到陰影的計算,並且陰影因子將與直接光照(漫反射和鏡面反射光)項相乘。

// ...
float shadow[5] = { 1.0f, 1.0f, 1.0f, 1.0f, 1.0f };
 
// 僅第一個方向光用於計算陰影
shadow[0] = CalcShadowFactor(g_SamShadow, g_ShadowMap, pIn.ShadowPosH);
    
[unroll]
for (i = 0; i < 5; ++i)
{
    ComputeDirectionalLight(g_Material, g_DirLight[i], pIn.NormalW, toEyeW, A, D, S);
    ambient += A;
    diffuse += shadow[i] * D;
    spec += shadow[i] * S;
}

// ...

由於環境光是間接光,所以陰影因子不受影響。並且,陰影因子也不會對來自環境映射的反射光構成影響。

C++端代碼實現

EffectHelper的引入

本章開始的代碼引入了EffectHelper來管理着色器所需的資源(我們可以無需手動創建並交給它來託管),並應用在了所有的Effect類當中。除了IEffect接口類,目前還引入了IEffectTransform接口類來統一變換的設置。隨着抽象類的增加,像GameObject這樣的類就可以對IEffect接口類對象查詢是否有某一特定接口類或具體類來執行額外的複雜操作。

此外,SkyRender類也因此有了輕微的變動。具體想了解還是去源碼翻閱,這裏不展開。

構建陰影貼圖與更新

首先我們要在GameApp::InitResource中創建一副2048×2048的陰影貼圖:

m_pShadowMap = std::make_unique<TextureRender>();
HR(m_pShadowMap->InitResource(m_pd3dDevice.Get(), 2048, 2048, true));

在本Demo中,光照方向每幀都在變動,我們希望讓投影立方體與光照所屬的變換軸對齊,並且中心能夠坐落在原點。因此在GameApp::UpdateScene可以這麼做:

//
// 投影區域為正方體,以原點為中心,以方向光為+Z朝向
//
XMMATRIX LightView = XMMatrixLookAtLH(dirVec * 20.0f * (-2.0f), g_XMZero, g_XMIdentityR1);
m_pShadowEffect->SetViewMatrix(LightView);

// 將NDC空間 [-1, +1]^2 變換到紋理坐標空間 [0, 1]^2
static XMMATRIX T(
    0.5f, 0.0f, 0.0f, 0.0f,
    0.0f, -0.5f, 0.0f, 0.0f,
    0.0f, 0.0f, 1.0f, 0.0f,
    0.5f, 0.5f, 0.0f, 1.0f);
// S = V * P * T
m_pBasicEffect->SetShadowTransformMatrix(LightView * XMMatrixOrthographicLH(40.0f, 40.0f, 20.0f, 60.0f) * T);

至於繪製部分,本Demo將和陰影有聯繫的場景對象放入了另一個重載函數DrawScene中(具體實現不在這給出),總體情況如下:

void GameApp::DrawScene()
{
    // ...

    // ******************
    // 繪製到陰影貼圖

    m_pShadowMap->Begin(m_pd3dImmediateContext.Get(), nullptr);
    {
        DrawScene(true);
    }
    m_pShadowMap->End(m_pd3dImmediateContext.Get());

    // ******************
    // 正常繪製場景
    m_pBasicEffect->SetTextureShadowMap(m_pShadowMap->GetOutputTexture());
    DrawScene(false, m_EnableNormalMap);

    // 繪製天空盒
    m_pDesert->Draw(m_pd3dImmediateContext.Get(), *m_pSkyEffect, *m_pCamera);

    // 解除深度緩衝區綁定
    m_pBasicEffect->SetTextureShadowMap(nullptr);
    m_pBasicEffect->Apply(m_pd3dImmediateContext.Get());

    // ...

}

演示

本Demo提供了5種斜率下的方向光,對應主鍵盤数字鍵1-5,Q鍵開關法線貼圖,E鍵開關陰影貼圖的显示,G鍵切換陰影貼圖的显示模式。

透明物體的陰影繪製

但我們的例程還沒有處理透明物體的陰影繪製。如果我們直接在場景中繪製一顆樹(貼圖存在Alpha值為0的部分),可以看到下圖的陰影並不正確:

因此,我們需要在繪製陰影貼圖的時候增加一個像素着色器用以進行Alpha裁剪,把Alpha值低於0.1的紋素給剔除掉,不要讓其寫入到陰影貼圖:

Texture2D g_DiffuseMap : register(t0);
SamplerState g_Sam : register(s0);

struct VertexPosHTex
{
    float4 PosH : SV_POSITION;
    float2 Tex : TEXCOORD;
};

// 這僅僅用於Alpha幾何裁剪,以保證陰影的显示正確。
// 對於不需要進行紋理採樣操作的幾何體可以直接將像素
// 着色器設為nullptr
void PS(VertexPosHTex pIn)
{
    float4 diffuse = g_DiffuseMap.Sample(g_Sam, pIn.Tex);
    
    // 不要將透明像素寫入深度貼圖
    clip(diffuse.a - 0.1f);
}

我們只在繪製樹的時候使用帶有像素着色器的版本,其餘物體照常繪製。並且因為我們的BasicEffect默認繪製就帶有Alpha裁剪,無需做這部分改動。最終效果如下:

練習題

  1. 嘗試4096×4096、1024×1024、512×512、256×256這幾種不同分辨率的陰影貼圖
  2. 嘗試以單次點採樣陰影檢測來修改本演示程序(即不採用PCF)。我們將欣賞到硬陰影與鋸齒狀的陰影邊緣
  3. 關閉斜率縮放偏移來觀察陰影粉刺
  4. 將斜率縮放偏移值放大10倍,觀察peter panning失真的效果
  5. 實現單點光源下的陰影(必要時可以考慮像CubeMap那樣使用6個正方形貼圖)
  6. 修改項目代碼,把繪製房屋改成繪製上圖中的樹(模型已給出),要求陰影显示正確

DirectX11 With Windows SDK完整目錄

Github項目源碼

歡迎加入QQ群: 727623616 可以一起探討DX11,以及有什麼問題也可以在這裏彙報。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

網頁設計最專業,超強功能平台可客製化

※別再煩惱如何寫文案,掌握八大原則!

樹莓派使用 OLED 屏显示圖片及文字

樹莓派默認是不帶显示屏的,如果想要查看系統的一些信息,需要使用電腦登錄到樹莓派,或者通過 HDMI 連接外接显示器查看。這樣做總是有點麻煩,我們可以通過外接一個 OLED 屏來显示一些關鍵參數或者圖片。本文將詳細介紹操作方法。

OLED 模組介紹

OLED 屏主要有兩種:128×32 和 128×64 ,主要顏色是白、黃、藍。OLED 屏的特點是功耗低,價格便宜,使用 I2C 接口與主機連接。本文所使用的是 128×64 尺寸的屏幕,外形如下:

它的接口很簡單,只有 4 個 Pin 腳:VCC,GND,SDA,SCL,所以它與樹莓派的連接也很簡單,如下圖所示(本文使用樹莓派 3B):

開啟 I2C 接口

樹莓派默認是不開啟 I2C 接口的,所以我們需要手動打開它。執行以下命令:

$ sudo apt-get install -y python-smbus
$ sudo apt-get install -y i2c-tools
$ sudo raspi-config

然後按以下動圖方式開啟 I2C 功能。

安裝 OLED 屏的 Python 庫

為了在 OLED 屏上显示文字或圖像,我們需要使用到 Adafruit 的 Python 庫,這個庫支持所有的 SSD1306 相關的显示屏,包括 128×32 和 128×64 屏幕。

首先我們需要下載這個庫:

$ git clone https://github.com/adafruit/Adafruit_Python_SSD1306.git

然後進入到目錄,再進行安裝:

$ cd Adafruit_Python_SSD1306
$ sudo python3 setup.py install	# 如果使用 Python2 ,則使用對應命令

找到 OLED 模組地址

安裝好 Python 庫之後,我們可以使用 i2cdetect 命令來找到 OLED 屏的物理地址:

$ i2cdetect -y 1

我們會得到類似這樣的輸出:

不同的模組可能得到不同的輸出。這裏我們的 I2C 地址就是 0x3C。如果你使用的是初代樹莓派(256MB的樹莓派1代B),那麼需要使用下面的命令:

$ i2cdetect -y 0

使用 OLED 屏显示圖像及文字

在剛剛下載的那個庫文件里,有一個 examples 目錄,在那個目錄里,我們可以看到以下內容:

  • animate.py
  • buttons.py
  • image.py
  • shapes.py
  • stats.py

這幾個文件我們可以直接運行,比如:

$ python3 shapes.py

這幾個文件運行的結果如下圖示:

屏幕尺寸適配

以上的幾個示例文件里,它們都默認你的屏幕尺寸是 128×32 ,但它們依然可以跑在 128×64 的屏幕上。如果我們想要看得更舒服一點,我們可以將尺寸進行適配。

在每個 Python 文件里,都有下面這麼一段代碼:

這裏也寫得很清楚了,上下兩行代碼分別代表 128×32 和 128×64 的屏幕,使用何種尺寸的屏幕,只需將對應的代碼前面的 # 去掉即可。

公眾號:良許Linux

有收穫?希望老鐵們來個三連擊,給更多的人看到這篇文章

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

※教你寫出一流的銷售文案?

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

新冠病毒有味可尋? 英國計劃訓練狗狗嗅出感染者

摘錄自2020年4月9日香港01 報導

新冠病毒(COVID-19)疫情持續,英國慈善組織「醫療偵測犬」(Medical Detection Dogs)與倫敦衞生與熱帶醫學院(London School of Hygiene and Tropical Medicine)及杜倫大學(University of Durham)合作,研究能否利用狗檢測新冠病毒。

該組織表示,狗是世上最強大的生物探測器之一,能檢測出毒品、爆炸品和食物的氣味,且早已被利用在公共衛生範疇。主席葛斯特博士(Dr. Claire Guest)稱,原則上,一定數量的病毒會改變感染者身上氣味,狗能嗅出當中分別,以識別患者甚至沒有徵狀的感染者;現在研究人員正在尋找方法,讓狗狗可以在安全情況下,嗅出病人身上病毒的氣味;如果研究成功,這種快捷準確且非入侵性的檢測方法,可以大大減輕國民保健服務(NHS)的負擔。

研究人員表示,經訓練的狗約可於六星期內投入檢測病毒工作,預計一隻可在1小時內平均檢測約250人,長遠而言,這些狗日後更可在機場或其他出入境關口為旅客檢測,以預防新一波的疫情爆發。

生活環境
國際新聞
英國
武漢肺炎
公共衛生
動物福利

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

網頁設計最專業,超強功能平台可客製化

※別再煩惱如何寫文案,掌握八大原則!

商業停擺意外有好空氣 印度人30年來首見喜馬拉雅山

摘錄自2020年4月10日自由時報報導

許多國家運用封鎖地區、暫停商家營業等方式防止武漢肺炎疫情傳播,意外造成空氣污染減緩的狀況,印度北部甚至可以看見數十年來未見的喜馬拉雅山,讓居民相當驚喜。

《CNN》報導,數十年來印度人首度可見喜馬拉雅山,原因在於地區封鎖減緩了空氣污染,印度北邊旁遮普邦居民現在可以看到喜馬拉雅山。有居民表示,這是近30年來第一次可以清楚看見山脈,太神奇了。近來印度因為商家關閉、汽車停駛、航空公司取消航班,空氣污染大幅降低。

報導指出,在印度全國封閉的第一週有85個城市空氣變好,據去年報告顯示,印度在世界空氣污染最嚴重的城市中,共有6個城市排名在前10名中。

公害污染
空氣污染
污染治理
國際新聞
印度
武漢肺炎
喜馬拉雅山
疫情看氣候與能源

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

※教你寫出一流的銷售文案?

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

小嬰兒三更半夜找爸爸 Alexa:已經將爸爸放到購物車了

雖然在台灣比較少見,但是亞馬遜所推出的數位語音助理「Alexa」除了可以幫你啟動各種家電外,同時也可以幫你從亞馬遜的商場上搜尋你要的東西,並且放入購物車中。而近日就有國外的小孩,三更半夜吵著想要找爸爸,沒想到意外的將「爸爸」放到購物車中。

根據《每日星報》報導指出,一位媽媽近日在TikTok上上傳一段影片,影片的內容是他們家的嬰兒監視器,只見影片中小寶寶從嬰兒床上爬起來,對著一旁的數位助理Alexa說「Alexa,我需要爸爸!」

Alexa在聆聽了男童充滿童言童語的請求後,竟然是直接回應說「我應該加入什麼?」顯然的Alexa把男童的請求,聽成了某種購物的需求。而男童聽到Alexa的回應後,再一次的表示「爸爸」。沒想到Alexa竟然回應男童「好的!已經將爸爸放入購物車中,請問還需要什麼嗎?」面對Alexa的回應,男童也瞬間呆滯。

由於男童與Alexa之間的對話實在過於暴笑,影片從18日上傳後,至今已經有370多萬次觀看,也吸引不少網友留言。小編原本還想說,如果Alexa在聽到男童的需求後,就打電話給爸爸那就真的非常厲害了。但是現在,小編更好奇的是,Alexa到底放了哪家商店提供的「爸爸」在購物車中呢?

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

【其他文章推薦】

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

網頁設計最專業,超強功能平台可客製化

※別再煩惱如何寫文案,掌握八大原則!

Pixel「車禍偵測」助翻車意外昏迷者報警脫困!雖然他開的是山貓起重機(咦)

雖然因為還不開放其他國家使用,而讓人覺得這是「真 · 他國事務」(苦笑)。不過 Google Pixel 傳說中的車禍偵測功能(Car Crash Detection),現在也真的像許多其他的科技裝置的緊急功能那樣,開始有了拯救人命的案例。繼續閱讀 Pixel「車禍偵測」助翻車意外昏迷者報警脫困!雖然他開的是山貓起重機(咦)報導內文。

▲圖片來源:9to5Google

Pixel「車禍偵測」助翻車意外昏迷者報警脫困!

科技觸角延伸到了各種領域後,不僅對於生活有了新的幫忙,更能在緊急事件時發揮不同於以往的對應可能。除了特斯拉能讓一家取暖在車庫渡過寒冬夜晚外,像是 Apple Watch 等穿戴裝置的健康偵測功能,也隨著產品被更多人所用,而出現即時提醒就診而避免更嚴重健康問題的實例。這次,Pixel 手機更新獲得的「車禍偵測」功能,也讓因為翻車事故而短暫失去意識的 Chuck Walker,能夠獲得及時的幫助。

▲圖片來源:Reddit

他在 Reddit 分享了這個不算真正「車禍」但仍讓自己陷入險境的意外事故。至於為什麼說不算一般車禍事故呢?主要是因為 Chuck Walker 開的是起重機(Bobcat Loader),而且進行相關作業時,並非在一般道路,而是自家土地區域內。然而這樣的「條件」,反而讓隻身在家的他在意外時陷入更難求救的窘境。

去年 11 月時,Chuck Walker 在美國密蘇里州的自家土地開著山貓進行工作時,意外翻覆下小山坡。事故發生當下整台車的狀態是倒栽蔥的狀態,過程也導致 Walker 失去意識。此時,他所使用的 Google Pixel 4 XL 因為偵測到了類似車禍的狀態(一般車禍大概也不會有這麼嚴重的…「位移?」),因此啟動了緊急報警的功能。

▲圖片來源:9to5Google

漸漸恢復意識的他,則是在聽到了藍牙耳機傳來救護單位的聲音後發出求救訊號。幸運的是,救護單位在收到這個緊急通話後早已機警派出救援人員前往。同時也是前護理人員的 Walker 描述,雖然他判斷自己的情況並不會有立即的生命危險,事後也的確僅花了數週醫治骨折狀況。但他回憶自己當時的處境。

不僅預期可能得要等數個小時的時間才會被人發現(畢竟算是在家裡,其他人很難經過…),手機也被甩到了卡在車中的他無法觸及的位置;不論傷勢的話,如果想自己脫困,起重機的緊急逃生口被遮蔽,原本的出口也呈現半遮蔽狀態。

簡而言之,就是如果沒有車禍偵測功能及時報案,也許相對於骨折的痛,他還得要想辦法在長時間的低溫環境生存,更別說車輛可能會有漏油起火的危險。

不過講起來,要是真的沒有車禍偵測,其實以他的狀況來說應該還是可以用「OK, Google」指令撥打緊急電話?只是除了得真的在意外當下能冷靜思考到此方法,危急的情況下也的確是分秒必爭(不然救護單位也不會還沒確定就先派人前往了),真的是不怕一萬只怕萬一。

唯一可惜的是,這個 Pixel 3 以上機型應該都支援的功能,目前還僅支援美國、英國與澳洲….

引用來源

延伸閱讀:

讓學習贏在起跑點,來看學霸分享 iPad 搭 Apple Pencil 的高效率讀書筆記法

老 Go 變出新把戲?Gogoro VIVA MIX 想變成你的形狀(試騎心得)

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

※教你寫出一流的銷售文案?

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

小米10T Lite 5G 在台推出:高通 Snapdragon 750G 5G 處理器、120Hz 更新率螢幕、64MP 四鏡頭主相機,萬元有找輕鬆入手

目前在台灣手機市場的5G 手機選擇從入門、中階到旗艦級都有,今(26)日稍早小米台灣也舉行「小米5G手機超值選」直播,帶大家看看在不同預算、使用需求考量下,消費者該選擇哪款5G 手機。另外,小米台灣也在今日直播迎來一款全新登台的5G 手機「小米10T Lite」,搭載高通Snapdragon 750 5G 行動平台、120Hz 更新率螢幕、6400 萬像素四鏡頭主相機,價格也特別訂在萬元有找相當超值。

小米10T Lite 5G 在台推出:高通Snapdragon 750G 5G 處理器、120Hz 更新率螢幕、64MP 四鏡頭主相機,萬元有找輕鬆入手

日前我們才報導過「小米10T Lite」這款中階5G手機將在台灣推出新機的消息,今(26)日也稍早小米台灣也已直播方式揭曉這款新機的上市資訊。
小米10T Lite搭載6.67吋20:9顯示比例、 FHD+(2400×1080)解析度的居中挖孔全螢幕,機身前後採用康寧第五代大猩猩玻璃保護:

顯示方面,小米10T Lite除了支持1500:1對比度、 NTSC 84%色域、 120Hz螢幕更新率、 240Hz觸控採樣率,螢幕也通過TÜVRheinland德國萊茵低藍光認證。
另外,小米10T Lite前後配備智慧環境光感應器,能提供最舒適的視覺體驗。螢幕採用陽光螢幕3.0 ,在陽光直射下自動增強對比度以確保畫面清晰可見:

相機方面,小米10T Lite 搭載6400 萬像素四鏡頭主相機,採用6400 萬像素F/1.89 光圈主鏡頭(SONY IMX682 感光元件)、800 萬像素120° 超廣角鏡頭、200 萬像素4cm 微距鏡頭以及200 萬像素景深鏡頭的搭配。

前鏡頭則配備1600 萬像素前置自拍相機:

小米10T Lite 相機應用功能也相當豐富,像是也支持前後鏡頭同時錄影:

內建訂十連拍功能,最高可一分鐘600 張照片並轉換成影片:

另外也內建長曝光模式,讓拍攝初學者也能拍出專業感的照片:

延時攝影也支持自拍影片功能:

性能方面,小米10T Lite 搭載Qualcomm Snapdragon 750G 5G 處理器, 6GB RAM 和128GB ROM :

小米10T Lite 內建4820mAh 大電量電池,支持33W 快速充電能在59 分鐘充電至100% :

配色方面,小米10 Lite在台共推出玫瑰金、珍珠灰以及海洋藍三種配色選擇。
玫瑰金:

珍珠灰:

海洋藍:

銷售資訊

最後就是大家最關心的售價囉!小米10T Lite 在台推出6GB+128GB 單一規格,售價為NT$9,999 ,將於3 月2 日起於小米專賣店、遠傳電信、PChome24h購物小米旗艦館、法雅客網路商店、friDay購物與燦坤3C陸續開賣。

即日起至3 月7 日止,購買任一款小米5G手機並出示指定截圖,即贈Redmi行動電源

為慶祝小米10T Lite 5G 的上市,進一步完整小米在台灣市場5G手機的佈局,凡於活動期間於全台小米專賣及燦坤3C實體門市購買任一小米5G手機(包含小米10T Pro、小米10T 5G、小米10T Lite 5G及Redmi Note 9T 5G),出示小米台灣官方Facebook粉絲頁上2月26日直播截圖,即可獲贈Redmi行動電源10000 標準版白色乙個(價值NT$345)。

小米5G手機超值選

【其他文章推薦】

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

網頁設計最專業,超強功能平台可客製化

※別再煩惱如何寫文案,掌握八大原則!

Google 居然放任 Wear OS 語音助理功能壞了數個月… 外媒細數種種「不妙」徵兆

還記得Wear OS,那個從Android Wear變身而來的Google穿戴系統嗎?其實,小編以往是真的超級喜歡這個系統的產品的,特別是騎車停紅綠燈時超方便觀看通知的手勢操作,還能支援iOS也是十分不錯的特色。不過…繼續閱讀Google居然放任Wear OS語音助理功能壞了數個月…外媒細數種種「不妙」徵兆報導內文。

▲圖片來源:Google

Google 居然放任Wear OS 語音助理功能壞了數個月….

還有在使用Google的智慧穿戴產品?那你最近是否很想因為「Hey, Google」語音助理功能無法使用的問題,而真的想敲敲Google肩膀問聲「嘿!它到底怎麼了?」其實這個已經持續了數個月的故障狀況,也有很多人發現。只不過,到了被媒體揭露之後才開始聽到Google著手進行處理的動作。對於這樣的狀況,外媒感到有些「不妙」。

根據回報資料,這個在Wear OS上無法正常聲控啟動Google語音助理的問題,前前後後已經有不少人在官方頁面回報。然而真正炸裂的契機,反而是數個月後的近日,此事在Reddit被大幅度討論並引起外媒注意才得到了官方的正式回覆。更是因為裡面根本沒有提到任何預計時間讓用戶無法接受。

畢竟別的App或功能也就算了,這可是Google招牌的功能啊。外媒ArsTechnica更根據種種症狀,認為最近才收購Fitbit的Google也許真的無心經營Wear OS生態系。

像是Play Music與Hangouts在Google智慧錶系統中都沒有替代的方案,新的Google Chat則是尚未支援Wear OS。更諷刺的是,Apple Watch上反而找得到YouTube Music…再加上這次躺了幾個月叫不醒的Google Assistant的狀況,該網站認為這都是Wear OS平台死透的跡象。

是說,Android Wear也曾是小編非常喜歡搭配手機使用的穿戴裝置產品。如果問我對於Google穿戴系統的不好印象,主要還是在於系統更新沒幾次就不再支援舊硬體的狀況。變成如果想持續更新用到Android Wear / Wear OS的新功能的話,就得要時常購買新手錶。以上,再加上能支援Android的穿戴裝置也算百花齊放各有各的特色拿不出太多差異性,也許Wear OS才會走到目前這步吧…

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

※教你寫出一流的銷售文案?

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

前驅,后驅,四驅的家用車到底該怎麼選?

前驅,后驅,四驅到底該怎麼選擇。每當我們在選一輛車的時候會碰到各種各樣的問題跟翻牌子一樣老師還是護士歐美還是日本現代汽車的驅動方式主要有兩輪驅動和四輪驅動當然不排除有少數六輪驅動的猛物而兩輪驅動根據發動機在車身上的安裝位置又細分為前置前驅(FF)前置后驅(FR)中置后驅(MR)後置后驅(RR)四驅又分為分時四驅全時四驅和適時四驅介於現在大多數家用車都是FF、FR和全時四驅三種今天就和大家聊一下家用車裡前驅車、后驅車與四驅車各有什麼優缺點到底怎麼選前置前驅發動機在車頭前面,也直接由前輪驅動,相當於老漢拖車現

前驅,后驅,四驅到底該怎麼選擇?

每當我們在選一輛車的時候

會碰到各種各樣的問題

跟翻牌子一樣

老師還是護士

歐美還是日本

現代汽車的驅動方式

主要有兩輪驅動

和四輪驅動

當然不排除有少數六輪驅動的猛物

而兩輪驅動

根據發動機在車身上的安裝位置

又細分為前置前驅(FF)

前置后驅(FR)

中置后驅(MR)

後置后驅(RR)

四驅又分為分時四驅

全時四驅

和適時四驅

介於現在大多數家用車

都是FF、FR和全時四驅三種

今天就和大家聊一下家用車裡

前驅車、后驅車與四驅車

各有什麼優缺點

到底怎麼選

前置前驅

發動機在車頭前面,也直接由前輪驅動,相當於老漢拖車

現代汽車最常見的一種驅動方式

市面上百分之八十的家用車

都採用了這種驅動方式

優點:

前置前驅的驅動方式

發動機大多採用了橫置的設計

且無需額外的傳動軸

發動機的動力能夠更直接地傳遞到前輪

前排的空間也會相對地寬裕不少

有些車能夠做到第二排底盤完全平整

大大增加了後排乘客的舒適性

缺點

由於前輪需要同時負責驅動以及轉向

因此在過彎時往往就出現了轉向不足的現象

就是感覺快拐不過彎來

另外在上陡坡的時候

由於重心靠後導致前輪由於附着力的降低

會出現輪胎打滑的現象

前置后驅

發動機在車頭

動力通過傳動軸由後輪驅動

相當於老漢推車運不運動你們說

優點

由於傳動軸的增本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

網頁設計最專業,超強功能平台可客製化

※別再煩惱如何寫文案,掌握八大原則!

1.0T的車型到底值不值得買?

不過,發動機在3千多轉時,依舊可以感受到一絲生澀感。1。6L發動機艙悅翔V7的1。6L發動機最大輸出124馬力和156牛米,最大扭矩轉速在3500到4500轉。雖然扭矩不及1。0T發動機,但是馬力也多了一點點。同為手動擋車型,可以發現1。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

※教你寫出一流的銷售文案?

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!