分類
發燒車訊

NVIDIA 終於出招,RTX 3060 限制挖礦算力減半,並推出挖礦專用 GPU 系列

從去年到現在,雖然 NVIDIA 有不斷釋出會想辦法提升顯示卡產量的消息,但加密貨幣價格持續上漲,即便產能真的有增加,依舊滿足不了挖礦市場,進而影響真正想買的遊戲玩家。為此,隨著 RTX 3060 確定會在 2 月 25 日上市,NVIDIA 也終於正式出招,將透過驅動程式來限制 RTX 3060 的以太幣挖礦算力,並宣佈推出專為挖礦打造的 NVIDIA CMP HX GPU 系列,沒意外 RTX 3060 應該會好買很多。

RTX 3060 將限制挖礦算力減半,並推出挖礦專用 GPU 系列

稍早 NVIDIA 宣布,RTX 3060 的驅動程式如果檢測到使用者正在進行 ETH 以太幣挖礦行為的特定屬性時,就會把挖礦算力限制在 50% 左右,也就是直接減半。

至於減半之後會剩下多少算力,NVIDIA 就沒有特別提到,不過既然少這麼多,礦主當然就不會想買這張顯示卡,也因此可大幅提升遊戲玩家買到的可能性。

根據 tom’s Hardware 實測數據,RTX 3060 Ti 的以太幣挖礦算力大約在 60.7MH/s,換算挖礦效率後,是目前最好的一張顯卡,達到 0.524 MHps/w(點我閱讀以太幣各顯卡挖礦算力的詳細介紹文章):

有些人可能會問,那已經開賣的 RTX 30 系列顯卡會不會採取同樣作法?基本上應該不會,因為就算推出也沒用,礦工不要更新驅動程式就好,也可能會有法律上的問題。

當然,NVIDIA 不會就這麼放棄龐大的挖礦市場,他們也同步推出專為挖礦打造的 CMP HX GPU 系列,共有四款:「30HX、40HX、50HX 與 90HX」,跟一般顯卡不同在,這系列取消了影像輸出介面,並優化採礦算力與效率。

下圖是官方提供的以太幣挖礦算力數據,最好的 90HX 可達到 86 MH/s、50HX 為 65MH/s、40HX 為 36MH/s、30HX 則是 26MH/s:

30HX 與 40HX 預計 Q1 就會推出,50HX、90HX 則要等到 Q2,皆時也會交由合作夥伴進行生產,如:華碩、憾訊等等,價格部分就還不知道,但應該會比現行的顯示卡還要便宜。

資料來源:NVIDIA

國外網咖受疫情影響生意慘淡,開始轉型成加密貨幣挖礦中心

您也許會喜歡:

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

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

【其他文章推薦】

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

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

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

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

分類
發燒車訊

防熊出沒 日本神奈川縣抓到熊先教怕人再野放

摘錄自2020年11月10日中央社報導

日本今年(2020年)熊出沒事件頻傳,半年來上萬件,不過神奈川縣的熊出沒卻逆勢大減,原因之一是實施「學習野放」策略,讓熊學會「怕人」再放歸山林。

讀賣新聞報導,與日本全國熊出沒大增的情況相反,神奈川縣內的熊出沒件數今年大減,原因之一是日本多處橡實歉收,熊因為缺乏食物而往人居處覓食,但神奈川縣內主要的熊棲息地,今年橡實豐收。另一個理由就是神奈川縣推動的「學習野放」策略奏效。

從2006年起,神奈川縣內若是抓到熊,會讓熊聞辣椒味、聽鞭炮聲等,讓熊接觸討厭的氣味與聲音、懂得害怕人後進行野放,到去年為止,共野放了28頭熊。

生物多樣性
國際新聞
日本
神奈川縣
人熊衝突
野放
人與動物衝突事件簿

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

【其他文章推薦】

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

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

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

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

分類
發燒車訊

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網頁設計為架站首選

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

※回頭車貨運收費標準

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

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

分類
發燒車訊

FAO:5月全球糧食供應鏈恐中斷 A股農業族群飆

摘錄自2020年3月31日聯合報報導

聯合國糧農組織(FAO)日前稱,受全球疫情影響,全球糧食供應鏈將於4月至5月中斷。而俄國、越南等國家為保證糧食供給,已宣布停止出口。A股農業族群今(31)日走強,金健米業再度飆漲停,京糧控股大漲8%,傲農生物、農發種業、萬向德農紛紛拉升。

金融網報導,其中,柬埔寨首相宣布受新冠肺炎疫情影響,將自4月5日起禁止部分大米出口。埃及自3月28日起未來3個月內停止各種豆類產品的出口;哈薩克斯坦禁止出口小麥麵粉、紅蘿蔔、糖和馬鈴薯;越南暫停簽署新的大米出口合約;俄國將每周評估情況,再決定是否實施出口禁令。

聯合國糧農組織(FAO)日前稱,受全球疫情影響,全球糧食供應鏈將於4月至5月中斷。而俄國、越南等國家為保證糧食供給,已宣布停止出口。

永續發展
土地利用
國際新聞
糧食

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

【其他文章推薦】

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

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

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

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

分類
發燒車訊

Google 密碼檢查工具將下放至舊系統,提供老手機更強化的安全性

Google 在近日宣布為 Android 推出著重於安全性的新功能,其中最重要的就是把過去於電腦版 Chrome 中推出的密碼檢查工具推廣到 Android 設備上,並且擴大下放到運行舊版 Android 系統的設備上,為用戶密碼提供跨越設備的安全把關。

Google 密碼檢查工具將下放至舊系統,提供老手機更強化的安全性

在過去,人們為了方便、好記,於是往往用最便利的方式來設定密碼,像是 Abc123、123456 等,66% 的美國人承認在多網站上使用同樣的較弱密碼,相信台灣很多人也是如此,使用這些共用密碼的帳戶也成為更容易受到攻擊的目標,因為只需攻破一個就能於擁有多個,每天也有很多新的數據外洩新聞讓人心惶惶。

在密碼成為過去式之前,Google 在 Google 帳戶與 Chrome 瀏覽器中推出了密碼檢查工具來幫助用戶檢查記憶於其中的密碼,而現在Google 更將這項功能推廣到 Android 上,在保護 Google 帳戶之餘進而更快速、輕鬆地使用自動填入來登入各種應用程式與服務。這項工具將會提供給搭載 Android 9 以上的設備,當你使用 Google 在手機應用中輸入密碼時,系統會根據已經洩漏的密碼清單(就是可能已經被竊取併發布在網路上的密碼)來進行檢查,告訴用戶該密碼是否在之前已經遭到外洩,並且引導用戶以正確的步驟來處理密碼問題。

除此之外,Google 還為 Android 推出另外五種功能,包含排程發送訊息,幫助盲人與難以查看螢幕的人士所設計的新版本 Talkback,更多免持的 Google 語音助理操作以及更多 Android Auto 新功能等,大家可以密切期待這些新功能的到來。

◎資料來源:Google

您也許會喜歡:

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

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

【其他文章推薦】

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

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

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

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

分類
發燒車訊

HUAWEI MateBook 14 開箱!輕薄筆電 性能首選

這次為大家介紹輕薄筆電性能首選的HUAWEI MateBook 14 開箱,在去年我們有介紹過華為MateBook 系列各種不同的款式,但還有獨缺一台我們一直都沒有機會開箱,這次總算是有機會跟大家介紹這款超高CP值的HUAWEI MateBook 14,採用AMD Ryzen™ 4000H系列行動處理器的MateBook 14,因此在擁有高效能的同時,價格也相對便宜很多,跟我們一起來看看這台CP值與效能兼具的HUAWEI MateBook 14。

HUAWEI MateBook 14 開箱!!高CP值兼具高效能的超輕薄筆電

外包裝採用相同設計的全白色基底,中間有著玫瑰金HUAWEI MateBook系列的燙金字樣。內容物包含了65W PD充電器與USB Type-C充電線。


65W PD快充充電器除了可為華為筆電充電以外,更可為各種平板、手機等其他使用Type-C 充電的設備充電,體積小巧通用的萬用充電器讓外出攜帶更為便利。

上蓋的金屬材質採用了磨砂處理,配色方面則是維持一貫的深空灰配色。

掀開螢幕以後就可以看到HUAWEI MateBook 14所有用的2K全面屏,高達90%的螢幕佔比搭配3:2的螢幕比例,高生產力螢幕讓整體使用工作區又大、體驗又舒服。

往下看到鍵盤區,首先是開機鍵整合了指紋辨識,非常實用。

透過Windows Hello的設定,即可使用指紋登入。

HUAWEI MateBook 14的鍵盤採用了全尺寸背光鍵盤,三段背光調整、優化的鍵程與鍵帽,使用起來非常舒服,不過值得一提的是在右邊因為整體尺寸的關係,往內縮的情況下使得右方按鍵有些變化,在使用上需要習慣一下。(市售版本為有註音符號的鍵盤)

高螢幕佔比的祕密就在於把攝像頭藏起來了,在鍵盤的F6與F7中間,有一個隱藏的攝像頭(100萬畫素、720p HD),只要按一下就可以彈起,不使用時壓下去即可收納,使用起來安全又方便。

而觸控區域的部分則是更大了,有著大面積的手勢觸控板,讓使用起來更順手,同時也是一碰傳的感應區域。

I/O配置部分,左側配置了一個USB Type-C (支援充電、數據傳輸與外接顯示)、3.5mm耳機孔以及一個HDMI孔。

右側則是配置了兩個USB 3.2 Gen 1,相容性極高。

翻到背面,首先可以看到配合著雙風扇的散熱孔,確保風流的走向。

左右開孔,則是藏著兩顆2W的揚聲器。

接著卸下背後螺絲後,即可打開背蓋,首先可以看到的就是剛剛提到的雙風扇設計,搭配翼形設計的兩根導熱管,在最短的散熱路徑下,可以有效的增加散熱效率。

在散熱模組的下方,則可以看到主硬碟採用了M.2 PCIe SSD 512GB。

WiFi則是使用了2×2 MIMO雙天線WLAN。

MateBook 14的電池規格,採用了56Wh的配置,搭配華為快充技術,充電15分鐘使用2.5hr,即使在外面突然沒電也不用擔心。

MateBook 14整機的重量為1.46公斤,攜帶上非常便利。

 

HUAWEI MateBook 14 效能測試

HUAWEI MateBook 14搭載了採用了台積電 7奈米工藝打造的 AMD Ryzen™ 5 4600H (6C/12T) 處理器。

透過裝置管理員,可以看到AMD Ryzen™ 5 4600H 六核心12執行緒配置。

記憶體部分,則是使用了雙通道設計DDR4 16GB。

首先以CPU-Z進行核心效能測試,單核的分數為479,而在全核心的分數則是可以跑到3940.6。

接著透過CineBench R15同樣進行CPU測試,在CPU單核的分數為164 cb,而全核心的表現則為1292 cb。

針對多核心,則透過CineBench R20進行CPU測試,在CPU單核的分數為444 cb,而全核心的表現則為3010 cb。

接著透過x264 FHD BENCHMARK進行轉檔測試,Ryzen 5 4600H的表現為42 fps。

接著透過x265 FHD BENCHMARK進行轉檔測試,Ryzen 5 4600H的表現為25.9 fps。

由以上的CPU效能測試可以看出,單核心的效能雖然沒有拉到很高,但Ryzen 5 4600H有著多處理緒的優勢,在整體運算效能上還是有著非常高的運算能力。

接著我們就來看看Ryzen 5 4600H自帶的內顯效能吧,首先在3DMARK的跑分中,使用DirectX 12為核心架構的Time Spy測試,MateBook 14的跑分為960。

而使用DirectX 11為核心架構的Fire Strike測試,MateBook 14的跑分為2494。

接著是跨平台的Wild Life測試,MateBook 14的分數為5096。

最後則是採用Direct X 12的Night Raid測試,MateBook 14的分數為11105。

在儲存的部分,首先使用DiskInfo查看硬碟資訊,本次測試機台預設是搭載三星的512 GB PCIe® NVMe™ 3.0 x4 M.2 SSD。

透過CrystalDiskMark進行測試,其讀寫速度為3571.5 MB/s與2988.5 MB/s。

接著使用TxBENCH同樣進行讀寫測試,最高讀寫速度則是3563.7 MB/s與2997.2 MB/s。

整機部分則使用PCMARK 10進行測試,在針對辦公室PC的基準測試當中跑分為4975。

而針對完整的辦公室PC基準測試PCMark 10 Express中,跑分為4045。

接著我們透過PCMARK 8進行續航力測試,HUAWEI MateBook 14可以連續使用400分鐘左右,而在實際使用上則是可以連續看影片超過10小時,搭配快充使用非常方便。

而使用Google 分析模擬持續聯網下的工作續航,由100%到跳出警告的10%也有約9.5小時的表現,相當的長效:

 

HUAWEI MateBook 14 軟體介紹

首先,HUAWEI MateBook 14內建了護眼模式,在桌面上點擊右鍵即可進入設定。

在開啟後,畫面會顯得較為偏黃,但也可以針對個人喜好,去調整護眼模式的強弱、護眼模式的色彩。

HUAWEI MateBook 14也內建了PC Manager,可以透過此程式一鍵更新驅動以及發現系統軟、硬件問題,直接更新處裡,非常便利。

點擊START按鈕即會開始自動為電腦做健康檢查。

如果對於系統或是軟體有個別問題的話,也可以在下面的分頁內進行檢測。

在PC Manager的最下方,則是與華為自家手機的連結:一碰傳。

按下連結以後,即會開始搜尋手機並進行配對。

連結以後即可啟動一碰傳,除了可以同步手機畫面到電腦上進行工作傳輸,而且無論傳各式檔案都非常快速,而且還擁有圖片辨識文字功能,如果有想要辨識的文字,只要用手機拍張照,透過一碰傳傳到電腦就可以快速轉換成文字了,對於文書處理來說非常方便。

透過PC Manager也可以直接從手機撈檔案。

 

總結

HUAWEI MateBook 14是一台高CP值與高效能兼具的超輕薄筆電,雖說沒有搭配獨立顯卡,但基於AMD自身APU的強大效能,無論是在基本文書、影音創作上,都可以有著非常不錯的運算能力,並且在螢幕的配置上,3:2的高生產力螢幕比例搭配90%的螢幕佔比,在使用上非常的舒服,並且搭配了超高續航力與快充技術,在外工作不擔心不用再為了快沒電這件事情所困擾,如果又是使用華為的手機,搭配一碰傳功能可以說是非常的方便。

而且除了筆電本身的強悍外,HUAWEI MateBook 14現在還有搭配了HUAWEI MateBook系列的保固啟動服務,可以享有7大筆電安心服務,只要購買HUAWEI MateBook系列並且上網登錄( https://www.huaweifans .com.tw/events/register/matebook-d/ ),再審核過後即可啟動保固服務,內容包含了無亮點保固、1+1年延長保固、60min快修、全台維修(不含離島地區)、到府收送(不含離島地區)、甲地送修乙地取件(不含離島地區)以及專人專線不中斷7大服務,相信對於筆電的後續服務上可以更放心的使用,而且上市期間,購買HUAWEI MateBook 14,加贈Microsoft 365個人版(贈品建議售價NTD 2,190)及羅技M235無線滑鼠(贈品建議售價NTD 399),2/28之前到華為品牌店與燦坤門市購買(請點我)再送市價6,988 元的ViewSonic 27 吋2K 薄型顯示器一部,強力推薦給大家。

【其他文章推薦】

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

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

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

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

分類
發燒車訊

關於 鎖的四種狀態與鎖升級過程 圖文詳解

一、前言

鎖的狀態總共有四種,級別由低到高依次為:無鎖、偏向鎖、輕量級鎖、重量級鎖,這四種鎖狀態分別代表什麼,為什麼會有鎖升級?其實在 JDK 1.6之前,synchronized 還是一個重量級鎖,是一個效率比較低下的鎖,但是在JDK 1.6后,Jvm為了提高鎖的獲取與釋放效率對(synchronized )進行了優化,引入了 偏向鎖 和 輕量級鎖 ,從此以後鎖的狀態就有了四種(無鎖、偏向鎖、輕量級鎖、重量級鎖),並且四種狀態會隨着競爭的情況逐漸升級,而且是不可逆的過程,即不可降級,也就是說只能進行鎖升級(從低級別到高級別),不能鎖降級(高級別到低級別),意味着偏向鎖升級成輕量級鎖后不能降級成偏向鎖。這種鎖升級卻不能降級的策略,目的是為了提高獲得鎖和釋放鎖的效率。

二、鎖的四種狀態

synchronized 最初的實現方式是 “阻塞或喚醒一個Java線程需要操作系統切換CPU狀態來完成,這種狀態切換需要耗費處理器時間,如果同步代碼塊中內容過於簡單,這種切換的時間可能比用戶代碼執行的時間還長”,這種方式就是 synchronized實現同步最初的方式,這也是當初開發者詬病的地方,這也是在JDK6以前 synchronized效率低下的原因,JDK6中為了減少獲得鎖和釋放鎖帶來的性能消耗,引入了“偏向鎖”和“輕量級鎖”。

所以目前鎖狀態一種有四種,從級別由低到高依次是:無鎖、偏向鎖,輕量級鎖,重量級鎖,鎖狀態只能升級,不能降級

如圖所示:

三、鎖狀態的思路以及特點

鎖狀態 存儲內容 標誌位
無鎖 對象的hashCode、對象分代年齡、是否是偏向鎖(0) 01
偏向鎖 偏向線程ID、偏向時間戳、對象分代年齡、是否是偏向鎖(1) 01
輕量級鎖 指向棧中鎖記錄的指針 00
重量級鎖 指向互斥量的指針 11

四、鎖對比

優點 缺點 適用場景
偏向鎖 加鎖和解鎖不需要額外的消耗,和執行非同步方法相比僅存在納秒級的差距 如果線程間存在鎖競爭,會帶來額外的鎖撤銷的消耗 適用於只有一個線程訪問同步塊場景
輕量級鎖 競爭的線程不會阻塞,提高了程序的響應速度 如果始終得不到索競爭的線程,使用自旋會消耗CPU 追求響應速度,同步塊執行速度非常快
重量級鎖 線程競爭不使用自旋,不會消耗CPU 線程阻塞,響應時間緩慢 追求吞吐量,同步塊執行速度較慢

五、Synchronized鎖

synchronized 用的鎖是存在Java對象頭裡的,那麼什麼是對象頭呢?

5.1 Java 對象頭

我們以 Hotspot 虛擬機為例,Hopspot 對象頭主要包括兩部分數據:Mark Word(標記字段) 和 Klass Pointer(類型指針)

Mark Word:默認存儲對象的HashCode,分代年齡和鎖標誌位信息。這些信息都是與對象自身定義無關的數據,所以Mark Word被設計成一個非固定的數據結構以便在極小的空間內存存儲盡量多的數據。它會根據對象的狀態復用自己的存儲空間,也就是說在運行期間Mark Word里存儲的數據會隨着鎖標誌位的變化而變化。

Klass Point:對象指向它的類元數據的指針,虛擬機通過這個指針來確定這個對象是哪個類的實例。

在上面中我們知道了,synchronized 用的鎖是存在Java對象頭裡的,那麼具體是存在對象頭哪裡呢?答案是:存在鎖對象的對象頭的Mark Word中,那麼MarkWord在對象頭中到底長什麼樣,它到底存儲了什麼呢?

在64位的虛擬機中:

在32位的虛擬機中:

下面我們以 32位虛擬機為例,來看一下其 Mark Word 的字節具體是如何分配的

無鎖:對象頭開闢 25bit 的空間用來存儲對象的 hashcode ,4bit 用於存放對象分代年齡,1bit 用來存放是否偏向鎖的標識位,2bit 用來存放鎖標識位為01

偏向鎖: 在偏向鎖中劃分更細,還是開闢 25bit 的空間,其中23bit 用來存放線程ID,2bit 用來存放 Epoch,4bit 存放對象分代年齡,1bit 存放是否偏向鎖標識, 0表示無鎖,1表示偏向鎖,鎖的標識位還是01

輕量級鎖:在輕量級鎖中直接開闢 30bit 的空間存放指向棧中鎖記錄的指針,2bit 存放鎖的標誌位,其標誌位為00

重量級鎖: 在重量級鎖中和輕量級鎖一樣,30bit 的空間用來存放指向重量級鎖的指針,2bit 存放鎖的標識位,為11

GC標記: 開闢30bit 的內存空間卻沒有佔用,2bit 空間存放鎖標誌位為11。

其中無鎖和偏向鎖的鎖標誌位都是01,只是在前面的1bit區分了這是無鎖狀態還是偏向鎖狀態

關於內存的分配,我們可以在git中openJDK中 markOop.hpp 可以看出:

public:
  // Constants
  enum { age_bits                 = 4,
         lock_bits                = 2,
         biased_lock_bits         = 1,
         max_hash_bits            = BitsPerWord - age_bits - lock_bits - biased_lock_bits,
         hash_bits                = max_hash_bits > 31 ? 31 : max_hash_bits,
         cms_bits                 = LP64_ONLY(1) NOT_LP64(0),
         epoch_bits               = 2
  };
  • age_bits: 就是我們說的分代回收的標識,佔用4字節
  • lock_bits: 是鎖的標誌位,佔用2個字節
  • biased_lock_bits: 是是否偏向鎖的標識,佔用1個字節
  • max_hash_bits: 是針對無鎖計算的hashcode 佔用字節數量,如果是32位虛擬機,就是 32 – 4 – 2 -1 = 25 byte,如果是64 位虛擬機,64 – 4 – 2 – 1 = 57 byte,但是會有 25 字節未使用,所以64位的 hashcode 佔用 31 byte
  • hash_bits: 是針對 64 位虛擬機來說,如果最大字節數大於 31,則取31,否則取真實的字節數
  • cms_bits: 不是64位虛擬機就佔用 0 byte,是64位就佔用 1byte
  • epoch_bits: 就是 epoch 所佔用的字節大小,2字節。

5.2 Monitor

Monitor 可以理解為一個同步工具或一種同步機制,通常被描述為一個對象。每一個 Java 對象就有一把看不見的鎖,稱為內部鎖或者 Monitor 鎖。

Monitor 是線程私有的數據結構,每一個線程都有一個可用 monitor record 列表,同時還有一個全局的可用列表。每一個被鎖住的對象都會和一個 monitor 關聯,同時 monitor 中有一個 Owner 字段存放擁有該鎖的線程的唯一標識,表示該鎖被這個線程佔用。

Synchronized是通過對象內部的一個叫做監視器鎖(monitor)來實現的,監視器鎖本質又是依賴於底層的操作系統的 Mutex Lock(互斥鎖)來實現的。而操作系統實現線程之間的切換需要從用戶態轉換到核心態,這個成本非常高,狀態之間的轉換需要相對比較長的時間,這就是為什麼 Synchronized 效率低的原因。因此,這種依賴於操作系統 Mutex Lock 所實現的鎖我們稱之為重量級鎖。

隨着鎖的競爭,鎖可以從偏向鎖升級到輕量級鎖,再升級的重量級鎖(但是鎖的升級是單向的,也就是說只能從低到高升級,不會出現鎖的降級)。JDK 1.6中默認是開啟偏向鎖和輕量級鎖的,我們也可以通過-XX:-UseBiasedLocking=false來禁用偏向鎖。

六、鎖的分類

6.2 無鎖

無鎖是指沒有對資源進行鎖定,所有的線程都能訪問並修改同一個資源,但同時只有一個線程能修改成功。

無鎖的特點是修改操作會在循環內進行,線程會不斷的嘗試修改共享資源。如果沒有衝突就修改成功並退出,否則就會繼續循環嘗試。如果有多個線程修改同一個值,必定會有一個線程能修改成功,而其他修改失敗的線程會不斷重試直到修改成功。

6.3 偏向鎖

初次執行到synchronized代碼塊的時候,鎖對象變成偏向鎖(通過CAS修改對象頭裡的鎖標誌位),字面意思是“偏向於第一個獲得它的線程”的鎖。執行完同步代碼塊后,線程並不會主動釋放偏向鎖。當第二次到達同步代碼塊時,線程會判斷此時持有鎖的線程是否就是自己(持有鎖的線程ID也在對象頭裡),如果是則正常往下執行。由於之前沒有釋放鎖,這裏也就不需要重新加鎖。如果自始至終使用鎖的線程只有一個,很明顯偏向鎖幾乎沒有額外開銷,性能極高。

偏向鎖是指當一段同步代碼一直被同一個線程所訪問時,即不存在多個線程的競爭時,那麼該線程在後續訪問時便會自動獲得鎖,從而降低獲取鎖帶來的消耗,即提高性能。

當一個線程訪問同步代碼塊並獲取鎖時,會在 Mark Word 里存儲鎖偏向的線程 ID。在線程進入和退出同步塊時不再通過 CAS 操作來加鎖和解鎖,而是檢測 Mark Word 里是否存儲着指向當前線程的偏向鎖。輕量級鎖的獲取及釋放依賴多次 CAS 原子指令,而偏向鎖只需要在置換 ThreadID 的時候依賴一次 CAS 原子指令即可。

偏向鎖只有遇到其他線程嘗試競爭偏向鎖時,持有偏向鎖的線程才會釋放鎖,線程是不會主動釋放偏向鎖的。

關於偏向鎖的撤銷,需要等待全局安全點,即在某個時間點上沒有字節碼正在執行時,它會先暫停擁有偏向鎖的線程,然後判斷鎖對象是否處於被鎖定狀態。如果線程不處於活動狀態,則將對象頭設置成無鎖狀態,並撤銷偏向鎖,恢復到無鎖(標誌位為01)或輕量級鎖(標誌位為00)的狀態。

6.4 輕量級鎖(自旋鎖)

輕量級鎖是指當鎖是偏向鎖的時候,卻被另外的線程所訪問,此時偏向鎖就會升級為輕量級鎖,其他線程會通過自旋(關於自旋的介紹見文末)的形式嘗試獲取鎖,線程不會阻塞,從而提高性能。

輕量級鎖的獲取主要由兩種情況:
① 當關閉偏向鎖功能時;
② 由於多個線程競爭偏向鎖導致偏向鎖升級為輕量級鎖。

一旦有第二個線程加入鎖競爭,偏向鎖就升級為輕量級鎖(自旋鎖)。這裏要明確一下什麼是鎖競爭:如果多個線程輪流獲取一個鎖,但是每次獲取鎖的時候都很順利,沒有發生阻塞,那麼就不存在鎖競爭。只有當某線程嘗試獲取鎖的時候,發現該鎖已經被佔用,只能等待其釋放,這才發生了鎖競爭。

在輕量級鎖狀態下繼續鎖競爭,沒有搶到鎖的線程將自旋,即不停地循環判斷鎖是否能夠被成功獲取。獲取鎖的操作,其實就是通過CAS修改對象頭裡的鎖標誌位。先比較當前鎖標誌位是否為“釋放”,如果是則將其設置為“鎖定”,比較並設置是原子性發生的。這就算搶到鎖了,然後線程將當前鎖的持有者信息修改為自己。

長時間的自旋操作是非常消耗資源的,一個線程持有鎖,其他線程就只能在原地空耗CPU,執行不了任何有效的任務,這種現象叫做忙等(busy-waiting)。如果多個線程用一個鎖,但是沒有發生鎖競爭,或者發生了很輕微的鎖競爭,那麼synchronized就用輕量級鎖,允許短時間的忙等現象。這是一種折衷的想法,短時間的忙等,換取線程在用戶態和內核態之間切換的開銷。

6.4 重量級鎖

重量級鎖顯然,此忙等是有限度的(有個計數器記錄自旋次數,默認允許循環10次,可以通過虛擬機參數更改)。如果鎖競爭情況嚴重,某個達到最大自旋次數的線程,會將輕量級鎖升級為重量級鎖(依然是CAS修改鎖標誌位,但不修改持有鎖的線程ID)。當後續線程嘗試獲取鎖時,發現被佔用的鎖是重量級鎖,則直接將自己掛起(而不是忙等),等待將來被喚醒。

重量級鎖是指當有一個線程獲取鎖之後,其餘所有等待獲取該鎖的線程都會處於阻塞狀態。

簡言之,就是所有的控制權都交給了操作系統,由操作系統來負責線程間的調度和線程的狀態變更。而這樣會出現頻繁地對線程運行狀態的切換,線程的掛起和喚醒,從而消耗大量的系統資

五、總結

文中講述了鎖的四種狀態以及鎖是如何一步一步升級的過程,文中有理解不到位或者有問題的地方,歡迎大家在評論區中下方指出和交流,謝謝大家

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

【其他文章推薦】

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

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

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

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

分類
發燒車訊

既能穿西裝又能幹苦活的全能选手,教授試駕全新菱智M5

賬面數據來看,它不會讓你有很高昂的駕駛激情,但實際駕駛過程中,也是足夠使用的。菱智M5的離合器匹配很成熟,離合踏板幅度較大,需要稍微適應一下,等離合器到位后順勢給油就能完美起步,熟練的司機可以做到毫無頓挫感。

自2007年第一代菱智面世以來,這款MpV已經在市場上走了9年之久。親民的定價、務實的用途,使得菱智在三四線城市中備受歡迎,而在大城市的貨運車中,也常能看見它的身影。但說實在的,把菱智系列當商務用車、甚至家用車的車主屈指可少,很大原因是因為它的產品價值被死死定在了貨運上,“撐不住面子”的它,很難上檯面。

在新款菱智M5上,我們能看到它悄悄打上了“新商務”的標籤,不僅可以貨物運輸,還能兼顧商務接待,這不禁讓對這次試駕充滿了期待,畢竟之前試駕都以乘用車為主,MpV還真的不多。

外觀設計、空間表現如何?

新款菱智M5的外觀設計比起舊款有了不少的提升,鷹眼似的前大燈看上去銳利無比,四條格柵呈“展翼”狀,採用鍍鉻處理。外觀的升級是值得肯定的,起碼整體的氣質變化很明顯。

菱智M5車身尺寸為:4745*1720*1940,軸距2800。內部空間是十分夠用的,特別是第三排座椅,1米83的身高坐進去一點壓迫感都沒有,別說更寬敞的第二排了。不過有利有弊,如果第三排座椅立起的情況下,後備箱的空間就顯得不足了。

不過如果放下後排座椅,這輛菱智M5也會很好地發揮它的貨運本能,可輕鬆放進一台電腦桌。

開起來是什麼感覺?

試駕的這款菱智M5,搭載的是三菱4A92發動機以及5MT變速箱,最大轉速6000轉,最大馬力122,最高扭矩153牛米。賬面數據來看,它不會讓你有很高昂的駕駛激情,但實際駕駛過程中,也是足夠使用的。

菱智M5的離合器匹配很成熟,離合踏板幅度較大,需要稍微適應一下,等離合器到位后順勢給油就能完美起步,熟練的司機可以做到毫無頓挫感。

試駕的時候車上載着5個成年人,並開啟着空調。油門響應比較平緩,需要稍微深踩,降檔地板油時,轉速也不會提高的很积極,所以超車能力一般,但滿載情況下,也滿足市區需求了。不過如果做商務接待的話,平穩的駕駛才是最重要的,即便是貨運的話,這個動力也完全足夠了。

菱智M5的底盤調教功底還是有的,採用雙橫臂扭桿彈簧獨立前懸架和鋼板彈簧后懸架。這樣的懸挂設計,側重承載能力,為商用物流運輸提供了可靠的保障,而且耐用性更能從容應對山路、爛路等複雜路況。

順帶說一句,新款菱智M5的隔音做得不錯,怠速抖動在接受範圍內,基本上只有細微的風噪和胎噪,做工用料值得肯定。

最後總結:

菱智系列一直以來的銷量都很穩定,今年以來一直在1萬浮動上下,這與它空間大、外觀好看、內飾美觀、底盤耐用…等等優點是分不開的。更何況菱智M5的定價僅為7.19萬~8.49萬,所以也不必用更苛刻的眼光去看待了。務實耐用,貨運和商務都兼顧,這款車已經做得足夠好了。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

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

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

分類
發燒車訊

c#中的值類型和引用類型

值類型和引用類型,是c#比較基礎,也必須掌握的知識點,但是也不是那麼輕易就能掌握,今天跟着老胡一起來看看吧。
 

典型類型

首先我們看看這兩種不同的類型有哪些比較典型的代表。
 

典型值類型

int, long, float, double等原始類型中表示数字的類型都是值類型,表示時間的datatime也是值類型,除此之外我們還可以通過關鍵字struct自定義值類型。
 

典型引用類型

原始類型中,array, list, dictionary, queue, stack和string都是引用類型,除此之外我們通過關鍵字class自定義引用類型。
 

基類

c#中所有的類型都最終繼承自Object,這是沒有疑問的,但是這其中還有些微區別。
 

值類型基類

對於值類型來說,除了最終繼承自Object,還繼承自ValueType,繼承鏈如下

但是請不要誤解,這裏僅僅指的是值類型天然是ValueType,但是不代表值類型能夠這麼聲明

struct Struct1 : ValueType
{

}

這樣是會引起編譯錯誤的,值類型不能繼承任何其他類型,值類型只能實現接口,不能繼承自其它類型。只有引用類型既可以實現接口也能繼承自其它類型。順便說一下,還有一點比較重要的是,ValueType重寫了Object基類的Equals方法和GetHashCode方法,所以當使用Equals比較兩個值類型的時候,系統會比較兩個值類型的各個屬性是否相等,再返回結果,這就是所謂的相等性。與此相對,引用類型在使用Equals的時候,會在後台調用object.ReferenceEquals,換言之,引用類型在比較相等性的時候會考慮同一性
 

引用類型基類

對於引用類型就沒有那麼麻煩,引用類型不會繼承自ValueType。引用類型可以繼承其他類型。
 

在內存中的表現

我們都知道,C#將內存分為了兩部分,一個是Stack,另外一個是Managed Heap。一般來說,用於函數調用進棧,函數返回出棧,用的是Stack,而當創造一個新的實例時,會根據創建的實例屬於值類型還是引用類型決定使用Stack還是Managed Heap。
 

值類型在內存中

當創建一個值類型對象時,c#會在Stack上面創建一塊空間,這塊空間就存放這個值類型對象。
int是一個典型的值類型,如下語句

int age = 10;

會存在於內存中的Stack上面。

如果把值類型的實例賦值給另外一個值類型,那麼效果就是複製一個新的值類型實例。

int myAge = age;

 

引用類型在內存中

與值類型在內存中的表現不一樣,創建一個引用類型的實例,不但會在Stack上面新建一個引用,還會在Heap上面劃分出內存以容納該引用類型實例。用戶在使用的時候通過Stack上面的變量間接引用該實例。

class Author
{
	public string Name{get;set;}
	public int Age{get;set;}
}

Author author = new Author(){Name="deatharthas", Age= 32};

注意看和值類型在內存中的區別,引用類型通過Stack上的變量訪問位於Heap上面的實例。
在賦值的時候,拷貝的僅僅是Stack上面的變量,新拷貝出來的對象和舊的對象指向的是同一塊內存。

Author myAuthor = author;

這個時候,author和myAuthor指向同一塊內存,稱為同一性,通過調用

object.ReferenceEquals(myAuthor, author);

可以得到驗證。
 
但可能有細心的朋友會有疑問了,不是說int是值類型,值類型是存在於Stack上面的嗎?為什麼在author類裏面,它會在Heap裏面呢?贊一個細心!值類型一般存在於Stack上面,但如果某個值類型包含於引用類型,那麼它也會隨着那個引用類型存放在Heap上面。
 

當參數時的行為區別

c#中的參數傳遞默認都是傳值(by value),但是根據所傳遞對象是值類型還是引用類型,它們的行為還是有所區別,現在我們來看看。

值類型當參數

值類型當參數的時候,傳遞到函數內部的是一份值類型的拷貝,所以在函數內部修改這個拷貝不會影響原對象。除非我們在傳遞參數的時候使用了ref或者out。
 

引用類型當參數

如果參數是引用類型,傳遞到函數內部的依然是一份拷貝,但是這個拷貝是其在Stack上面的變量的拷貝,就像上面的賦值那個例子。所以這個時候這份拷貝其實和原對象指向同一塊內存(指向同一性),修改這個對象可以反映到原對象上面。
 

謹慎返回引用類型

編程是一項需要謹慎的工作,有時候我們經常會犯一些錯誤,而這些錯誤又是那麼的不明顯以至於不摔坑幾次,我們根本察覺不了,考慮下面一個例子。

    class People
    {
        public string Name { get; set; }
        public int Age { get; set; }
        private People _Father = null;
        public People Father { get { return _Father; } }
        public People(People father)
        {
            _Father = father;
        }
        public void ShowFather()
        {
            Console.WriteLine("father's name is " + Father.Name + " and his age is " + Father.Age);
        }
    }

    class Program
    {        
        static void Main(string[] args)
        {
            People father = new People(null) { Name = "father", Age = 60 };
            People son = new People(father);
            son.ShowFather();
            Console.ReadLine();
        }
    }

看起來沒什麼問題,對吧?Father沒有提供setter,似乎是安全的。但是我們試試下面的代碼。

	static void Main(string[] args)
        {
            People father = new People(null) { Name = "father", Age = 60 };
            People son = new People(father);
            var f = son.Father;
            f.Name="Changed";
            son.ShowFather();
            Console.ReadLine();
        }

看,發現了什麼,外部改變了本來應該被封裝所保護的Father屬性,封裝被破壞了!
稍微一想我們應該能明白這個道理,Father屬性返回的拷貝的變量和原Father變量指向同一塊實例。要想解決這個問題,我們要麼返回一個值類型,要麼返回一個全新的對象。修改Father屬性如下:

public People Father { get { return new People(_Father._Father) { Name = _Father.Name, Age = _Father.Age }; } }

再次測試,

這次封裝就沒問題了。
 

總結

我們大概知道了值類型和引用類型的區別,包括它們的行為,在內存的居住方式,以及使用引用類型時可能會遇到的暗坑,希望大家通過閱讀這篇文章,能夠加深一些對它們的了解,少走一些彎路。
今天也簡單的提到了比較時的同一性,和預防封裝被破壞所採用的返回一個新的實例拷貝的策略(這個時候適合使用DeepCopy),我們之後有機會再詳細聊。

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

【其他文章推薦】

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

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

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

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

分類
發燒車訊

真不是隨便選的,原來車漆顏色的選擇有那麼多門道

而且合適的車漆能讓我們的愛車有着更好的外觀效果。

筆者總結:

所以說車漆的選擇是有一定門道,這是我們在購車前就應該了解的,畢竟這關乎到我們用車養車的各個方面。而且合適的車漆能讓我們的愛車有着更好的外觀效果。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

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

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