<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                我們已經完善了光照的參數,以及模型空間-世界空間-視角空間-裁剪空間-屏幕空間的轉換。并且搭建了我們demo的框架,即一個正方體的各部分的信息結構,如頂點+索引+貼圖+材質等。這次,我們來把一切聯系起來,創建出真正的軟渲染demo。 在上述結構中,我們定義出Moe3DDevice與Moe3DDeviceContext,用來表示設備信息。 設備上下文DC是一個Windows數據結構,它包含了某個設備的繪制屬性。通常,繪制調用都是借助于上下文對象,而這些設備上下文對象封裝了用于畫線、形狀、文本等的Windows API。設備上下文是設備無關的,所以它既可以用于繪制屏幕,也可以用于繪制打印機甚至元文件。設備上下文在內存中創建,而內存經常受到擾動,所以它的地址是不固定的。因此,一個設備上下文句柄不是直接指向設備上下文對象,而是指向另外一個跟蹤設備上下文地址的指針。 我個人認為設備上下文相當于畫圖過程中的畫布(畫紙),在VS中,這個畫布可以是顯示器,也可以使打印機,設備上下文決定了畫布的屬性,而且封裝了在畫布上畫畫的方法,比如畫線,畫點,等等,例如: pDc->LineTo(512,0); //從左下角到右上角的一條紅色直線 。我們在VS中畫圖時,首先要得到這塊畫布才可以畫畫,所以要進行獲取設備環境。 1.1Moe3DDevice.h 描述設備信息 #pragma once #include <windows.h> #include "MVector.h" #include "Vertex.h" class Moe3DDevice { public: Moe3DDevice(int width, int height); ~Moe3DDevice(); public: void DrawPixel(int x, int y, MVector color); float GetZ(int x, int y) const; void SetZ(int x, int y, float z); inline UINT*& GetFrameBuffer() { return m_pFramebuffer; } inline int GetClientWidth() { return m_width; } inline int getClientHeight() { return m_height; } void ClearBuffer(MVector color); private: int m_width; int m_height; UINT* m_pFramebuffer; float **m_zBuffer; //z緩存 }; 1.2Moe3DDevice.cpp 獲取設備信息 #include "Moe3DDevice.h" #include "MathUtil.h" using namespace MathUtil; Moe3DDevice::Moe3DDevice(int width, int height) { m_width = width; m_height = height; m_zBuffer = new float*[width]; for (int i = 0; i < width; ++i) { m_zBuffer[i] = new float[height]; } } Moe3DDevice::~Moe3DDevice() { if (m_pFramebuffer) delete m_pFramebuffer; if (m_zBuffer) for (int i = 0; i < m_width; ++i) { delete[] m_zBuffer[i]; } } //畫像素 void Moe3DDevice::DrawPixel(int x, int y, MVector color) { m_pFramebuffer[m_width*y + x] = MathUtil::ColorToUINT(color); } float Moe3DDevice::GetZ(int x, int y) const { if (x >= 0 && x < m_width && y >= 0 && y < m_height) return m_zBuffer[x][y]; else return 1.f; } void Moe3DDevice::SetZ(int x, int y, float z) { if (x >= 0 && x < m_width && y >= 0 && y < m_height) { m_zBuffer[x][y] = z; } } void Moe3DDevice::ClearBuffer(MVector color) { for (int x = 0; x < m_width; ++x) { for (int y = 0; y < m_height; ++y) { m_pFramebuffer[m_width*y + x] = MathUtil::ColorToUINT(color); m_zBuffer[x][y] = 0; } } } 2.1Moe3DDeviceContext.h 相當于在此作畫 #pragma once #include "Moe3DDevice.h" #include "Vertex.h" #include <vector> #include "ShaderBase.h" enum MOE3D_FILL_MODE //渲染模式 { MOE3D_FILL_WIREFRAME,//線框模式 MOE3D_FILL_SOLIDE //實體模式 }; class Moe3DDeviceContext { public: Moe3DDeviceContext(); ~Moe3DDeviceContext(); public: void Init(Moe3DDevice* pDevice); //初始化 void SetRenderMode(MOE3D_FILL_MODE mode); //設置渲染模式 void SetVertexBuffer(std::vector<VertexIn> vertices); //設置頂點緩沖 void SetCameraPos(const MVector& pos); //設置相機位置 void SetIndexBuffer(std::vector<UINT> indices); //設置索引緩沖 void SetShader(ShaderBase* base); //設置著色器 void DrawIndexed(UINT indexCount,UINT startIndexLocation,UINT startVertexLocation); //繪制圖形 private: void ToCVV(VertexOut& v); //投影后的坐標轉化為cvv bool Clip(const VertexOut& v); //cvv裁剪 VertexOut TransformToProj(const VertexIn& v); //轉到齊次裁剪空間 void TransformToScreen(const MMatrix& m,VertexOut& v); //轉換到屏幕坐標 bool BackFaceCulling(const VertexIn& v1, const VertexIn& v2, const VertexIn& v3); //背面消隱測試 void BresenhamDrawLine(int x1, int y1, int x2, int y2); //畫線 void ScanlineFill(const VertexOut& left, const VertexOut& right, int yIndex); //掃描線 void DrawTriangle(const VertexOut& v1, const VertexOut& v2, const VertexOut& v3); //畫三角形 void DrawTriangleTop(const VertexOut& v1, const VertexOut& v2, const VertexOut& v3); //畫平頂三角形 void DrawTriangleBottom(const VertexOut& v1, const VertexOut& v2, const VertexOut& v3); //畫平底三角形 void TriangleRasterization(const VertexOut& v1, const VertexOut& v2, const VertexOut& v3); //光柵化三角形 private: Moe3DDevice* m_pDevice; //設備 MOE3D_FILL_MODE m_renderMode; //渲染狀態 std::vector<VertexIn> m_vertices; //頂點緩沖 std::vector<UINT> m_indices; //索引緩沖 ShaderBase* m_pShader; //著色器 MVector m_cameraPos; //相機位置 用于背面消隱 }; 2.2Moe3DDeviceContext.h 具體實現 #include "Moe3DDeviceContext.h" #include "MathUtil.h" #include <algorithm> Moe3DDeviceContext::Moe3DDeviceContext():m_renderMode(MOE3D_FILL_WIREFRAME),m_cameraPos(MVector(0.f,0.f,0.f,1.f)) { } Moe3DDeviceContext::~Moe3DDeviceContext() { } void Moe3DDeviceContext::Init(Moe3DDevice* pDevice) { m_pDevice = pDevice; } //設置渲染模式 void Moe3DDeviceContext::SetRenderMode(MOE3D_FILL_MODE mode) { m_renderMode = mode; } //設置頂點緩沖 void Moe3DDeviceContext::SetVertexBuffer(std::vector<VertexIn> vertices) { m_vertices = vertices; } //設置相機位置 void Moe3DDeviceContext::SetCameraPos(const MVector& pos) { m_cameraPos = pos; } //設置索引緩沖 void Moe3DDeviceContext::SetIndexBuffer(std::vector<UINT> indices) { m_indices = indices; } //設置著色器 void Moe3DDeviceContext::SetShader(ShaderBase* base) { m_pShader = base; } //繪制頂點緩沖中的三角形 void Moe3DDeviceContext::DrawIndexed(UINT indexCount, UINT startIndexLocation, UINT startVertexLocation) { //得到屏幕變換矩陣 MMatrix screenTransformMat = MathUtil::MMatrixScreenTransform(m_pDevice->GetClientWidth(), m_pDevice->getClientHeight()); for (int i = startIndexLocation; i < indexCount / 3; ++i) { VertexIn p1 = m_vertices[startVertexLocation + m_indices[3 * i]]; VertexIn p2 = m_vertices[startVertexLocation + m_indices[3 * i + 1]]; VertexIn p3 = m_vertices[startVertexLocation + m_indices[3 * i + 2]]; //背面消隱 if (BackFaceCulling(p1, p2, p3) == false) { continue; } //轉換到齊次裁剪空間,即投影后的坐標 VertexOut v1 = TransformToProj(p1); VertexOut v2 = TransformToProj(p2); VertexOut v3 = TransformToProj(p3); //判斷是否通過cvv裁剪 if (Clip(v1) == false || Clip(v2) == false || Clip(v3) == false) { continue; } //進行透視除法 轉到cvv ToCVV(v1); ToCVV(v2); ToCVV(v3); //將投影得到的坐標轉化為屏幕坐標 TransformToScreen(screenTransformMat, v1); TransformToScreen(screenTransformMat, v2); TransformToScreen(screenTransformMat, v3); DrawTriangle(v1, v2, v3); } } //轉化到cvv void Moe3DDeviceContext::ToCVV(VertexOut& v) { v.posH.x /= v.posH.w; v.posH.y /= v.posH.w; v.posH.z /= v.posH.w; v.posH.w = 1; } //簡單cvv裁剪 bool Moe3DDeviceContext::Clip(const VertexOut& v) { //cvv為 x-1,1 y-1,1 z0,1 if (v.posH.x >= -v.posH.w && v.posH.x <= v.posH.w && v.posH.y >= -v.posH.w && v.posH.y <= v.posH.w && v.posH.z >= 0.f && v.posH.z <= v.posH.w) { return true; } return false; } //轉到齊次裁剪空間 VertexOut Moe3DDeviceContext::TransformToProj(const VertexIn& v) { VertexOut out = m_pShader->VS(v); //設置oneDivZ out.oneDivZ = 1 / out.posH.w; //由于1/z和x,y成線性關系 //這里將需要插值的信息都乘以1/z 得到 s/z和t/z等,方便光柵化階段進行插值 out.color.x *= out.oneDivZ; out.color.y *= out.oneDivZ; out.color.z *= out.oneDivZ; out.normal.x *= out.oneDivZ; out.normal.y *= out.oneDivZ; out.normal.z *= out.oneDivZ; out.tex.u *= out.oneDivZ; out.tex.v *= out.oneDivZ; return out; } //轉換到屏幕坐標 void Moe3DDeviceContext::TransformToScreen(const MMatrix& m, VertexOut& v) { v.posH = v.posH * m; } //背面消隱 bool Moe3DDeviceContext::BackFaceCulling(const VertexIn& v1, const VertexIn& v2, const VertexIn& v3) { //線框模式不進行背面消隱 if (m_renderMode == MOE3D_FILL_WIREFRAME) { return true; } else { MVector vector1 = v2.pos - v1.pos; MVector vector2 = v3.pos - v2.pos; //頂點緩存中順序為順時針 //叉積得到的方向與右手系一致 MVector normal = vector1.Cross(vector2); MVector viewDir = v1.pos - m_cameraPos; if (normal.Dot(viewDir) < 0) { return true; } return false; } } //畫三角形 void Moe3DDeviceContext::DrawTriangle(const VertexOut& v1, const VertexOut& v2, const VertexOut& v3) { //線框模式 if (m_renderMode == MOE3D_FILL_WIREFRAME) { BresenhamDrawLine(v1.posH.x, v1.posH.y, v2.posH.x, v2.posH.y); BresenhamDrawLine(v1.posH.x, v1.posH.y, v3.posH.x, v3.posH.y); BresenhamDrawLine(v2.posH.x, v2.posH.y, v3.posH.x, v3.posH.y); } else if (m_renderMode == MOE3D_FILL_SOLIDE) { TriangleRasterization(v1, v2, v3); } } //bresenham畫線 void Moe3DDeviceContext::BresenhamDrawLine(int x1, int y1, int x2, int y2) { int dx = x2 - x1; int dy = y2 - y1; int stepx = 1; int stepy = 1; if (dx >= 0) { stepx = 1; } else { stepx = -1; dx = abs(dx); } if (dy >= 0) { stepy = 1; } else { stepy = -1; dy = abs(dy); } int deltaX = 2 * dx; int deltaY = 2 * dy; if (dx > dy) { int error = deltaY - dx; for (int i = 0; i <= dx; ++i) { if(x1 >= 0 && x1 < m_pDevice->GetClientWidth() && y1 >= 0 && y1 < m_pDevice->getClientHeight()) m_pDevice->DrawPixel(x1, y1, MVector(0.f, 0.f, 0.f,1.f)); if (error >= 0) { error -= deltaX; y1 += stepy; } error += deltaY; x1 += stepx; } } else { int error = deltaX - dy; for (int i = 0; i <= dy; i++) { if (x1 >= 0 && x1 < m_pDevice->GetClientWidth() && y1 >= 0 && y1 < m_pDevice->getClientHeight()) m_pDevice->DrawPixel(x1, y1, MVector(0.f, 0.f, 0.f,1.f)); if (error >= 0) { error -= deltaY; x1 += stepx; } error += deltaX; y1 += stepy; } } } //掃描線填充 //left 左端點 right 右端點 void Moe3DDeviceContext::ScanlineFill(const VertexOut& left, const VertexOut& right, int yIndex) { float dx = right.posH.x - left.posH.x; for (float x = left.posH.x; x <= right.posH.x; x += 1.f) { //四舍五入 int xIndex = static_cast<int>(x + .5f); if(xIndex >= 0 && xIndex < m_pDevice->GetClientWidth()) { //插值系數 float lerpFactor = 0; if (dx != 0) { lerpFactor = (x - left.posH.x) / dx; } //深度測試 //1/z’與x’和y'是線性關系的 float oneDivZ = MathUtil::Lerp(left.oneDivZ, right.oneDivZ, lerpFactor); if (oneDivZ >= m_pDevice->GetZ(xIndex,yIndex)) { m_pDevice->SetZ(xIndex, yIndex, oneDivZ); float w = 1 / oneDivZ; //插值頂點 原先需要插值的信息均乘以oneDivZ //現在得到插值后的信息需要除以oneDivZ得到真實值 VertexOut out = MathUtil::Lerp(left, right, lerpFactor); out.posH.y = yIndex; out.tex.u *= w; out.tex.v *= w; out.normal.x *= w; out.normal.y *= w; out.normal.z *= w; out.color.x *= w; out.color.y *= w; out.color.z *= w; //畫像素點顏色 m_pDevice->DrawPixel(xIndex, yIndex, m_pShader->PS(out)); } } } } //畫平頂三角形 v3為下頂點 //y方向每次增加一個像素 根據y插值頂點 void Moe3DDeviceContext::DrawTriangleTop(const VertexOut& v1, const VertexOut& v2, const VertexOut& v3) { float dy = 0;//每次y增加一個像素 for (float y = v1.posH.y; y <= v3.posH.y; y += 1.f) { //四舍五入 int yIndex = static_cast<int>(y + 0.5f); if (yIndex >= 0 && yIndex < m_pDevice->getClientHeight()) { float t = dy / (v3.posH.y - v1.posH.y); //插值生成左右頂點 VertexOut new1 = MathUtil::Lerp(v1, v3, t); VertexOut new2 = MathUtil::Lerp(v2, v3, t); dy += 1.f; //掃描線填充 if (new1.posH.x < new2.posH.x) { ScanlineFill(new1, new2, yIndex); } else { ScanlineFill(new2, new1, yIndex); } } } } //畫平底三角形 v1為上頂點 void Moe3DDeviceContext::DrawTriangleBottom(const VertexOut& v1, const VertexOut& v2, const VertexOut& v3) { float dy = 0;//每次y增加一個像素 for (float y = v1.posH.y; y <= v2.posH.y; y += 1.f) { //四舍五入 int yIndex = static_cast<int>(y + 0.5f); if (yIndex >= 0 && yIndex < m_pDevice->getClientHeight()) { float t = dy / (v2.posH.y - v1.posH.y); //插值左右頂點 VertexOut new1 = MathUtil::Lerp(v1, v2, t); VertexOut new2 = MathUtil::Lerp(v1, v3, t); dy += 1.f; //掃描線填充 if (new1.posH.x < new2.posH.x) { ScanlineFill(new1, new2, yIndex); } else { ScanlineFill(new2, new1, yIndex); } } } } //光柵化三角形 void Moe3DDeviceContext::TriangleRasterization(const VertexOut& v1, const VertexOut& v2, const VertexOut& v3) { //判斷是否是平底或者平頂三角形 if (v1.posH.y == v2.posH.y) { if (v1.posH.y < v3.posH.y) {//平頂 DrawTriangleTop(v1, v2, v3); } else {//平底 DrawTriangleBottom(v3, v1, v2); } } else if (v1.posH.y == v3.posH.y) { if (v1.posH.y < v2.posH.y) {//平頂 DrawTriangleTop(v1, v3, v2); } else {//平底 DrawTriangleBottom(v2, v1, v3); } } else if (v2.posH.y == v3.posH.y) { if (v2.posH.y < v1.posH.y) {//平頂 DrawTriangleTop(v2, v3, v1); } else {//平底 DrawTriangleBottom(v1, v2, v3); } } //一般三角形 將其分割成平底三角形和平頂三角形 else { //根據y值將三個頂點排序 std::vector<VertexOut> vertices{v1,v2,v3}; std::sort(vertices.begin(), vertices.end(), [](VertexOut v1,VertexOut v2) { return v1.posH.y < v2.posH.y;}); VertexOut top = vertices[0]; VertexOut middle = vertices[1]; VertexOut bottom = vertices[2]; //插值求中間點 float middleX = (middle.posH.y - top.posH.y) * (bottom.posH.x - top.posH.x) / (bottom.posH.y - top.posH.y) + top.posH.x; float dy = middle.posH.y - top.posH.y; float t = dy / (bottom.posH.y - top.posH.y); VertexOut newMiddle = MathUtil::Lerp(top, bottom, t); newMiddle.posH.x = middleX; newMiddle.posH.y = middle.posH.y; //平頂 DrawTriangleTop(middle, newMiddle, bottom); //平底 DrawTriangleBottom(top, middle, newMiddle); } } 3.1BoxDemo.cpp 實現我們的demo 過程:頂點->索引->著色器->紋理->光源->材質。 并且實現鼠標操作,來改變我們的視角。 #include <vector> #include "BoxDemo.h" #include "BoxShader.h" #include "Moe3DDevice.h" #include "Moe3DDeviceContext.h" BoxDemo::BoxDemo():m_theta(1.5f * MathUtil::PI),m_phi(0.4*MathUtil::PI),m_radius(5.0f) { m_lastMousePos.x = 0; m_lastMousePos.y = 0; m_world = MathUtil::MMatrixIdentity(); //平行光 m_dirLight.ambient = MVector(0.2f, 0.2f, 0.2f, 1.0f); m_dirLight.diffuse = MVector(0.5f, 0.5f, 0.5f, 1.0f); m_dirLight.specular = MVector(0.5f, 0.5f, 0.5f, 1.0f); m_dirLight.direction = MVector(0.57735f, 0.57735f, 0.57735f); //材質 m_material.ambient = MVector(0.7f, 0.85f, 0.7f, 1.0f); m_material.diffuse = MVector(0.7f, 0.85f, 0.7f, 1.0f); m_material.specular = MVector(0.8f, 0.8f, 0.8f, 16.0f); } BoxDemo::~BoxDemo() { Clear(); } bool BoxDemo::Init(HINSTANCE hInstance,HWND hWnd) { m_hWnd = hWnd; m_hInstance = hInstance; RECT rc; GetClientRect(m_hWnd, &rc); m_width = rc.right - rc.left; m_height = rc.bottom - rc.top; m_pDevice = new Moe3DDevice(m_width, m_height); m_pImmediateContext = new Moe3DDeviceContext(); m_pImmediateContext->Init(m_pDevice); m_pShader = new BoxShader(); //創建頂點緩存 GeometryGenerator::GetInstance()->CreateBox(2.f, 2.f, 2.f, m_box); m_vertices.resize(m_box.vertices.size()); for (UINT i = 0; i < m_box.vertices.size(); ++i) { m_vertices[i].pos = m_box.vertices[i].pos; m_vertices[i].tex = m_box.vertices[i].tex; m_vertices[i].normal = m_box.vertices[i].normal; m_vertices[i].color = m_box.vertices[i].color; } m_pImmediateContext->SetVertexBuffer(m_vertices); //創建索引緩存 //create index buffer m_indices.resize(m_box.indices.size()); for (UINT i = 0; i < m_box.indices.size(); ++i) { m_indices[i] = m_box.indices[i]; } m_pImmediateContext->SetIndexBuffer(m_indices); //設置著色器 m_pImmediateContext->SetShader(m_pShader); //加載紋理 m_tex = MathUtil::LoadBitmapToColorArray(L"../Texture/nico.bmp"); //設置著色器紋理 //由于紋理加載一次不在改變,故不用再Update中設置 m_pShader->SetTexture(m_tex); //設置著色器光源、材質 m_pShader->SetDirLight(m_dirLight); m_pShader->SetMaterial(m_material); return true; } void BoxDemo::Update(float dt) { float x = m_radius * sinf(m_phi) * cosf(m_theta); float z = m_radius * sinf(m_phi) * sinf(m_theta); float y = m_radius * cosf(m_phi); MVector pos(x, y, z, 1.f); MVector target(0.f, 0.f, 0.f, 1.f); MVector up(0.f, 1.f, 0.f, 0.f); MMatrix view = MathUtil::MMatrixLookAtLH(pos, target, up); MMatrix proj = MathUtil::MMatrixPerspectiveFovLH(MathUtil::PI / 4, m_pDevice->GetClientWidth() / static_cast<float>(m_pDevice->getClientHeight()), 1.f, 100.f); MMatrix world = MathUtil::MMatrixIdentity(); m_worldViewProj = world*view*proj; m_world = world; //計算逆矩陣的轉置 m_worldInvTranspose = MathUtil::MMatrixTranspose(MathUtil::MMatrixInverse(world)); //設置相機位置 以便背面消隱 m_pImmediateContext->SetCameraPos(pos); //設置著色器中eyePos m_pShader->SetEyePos(pos); } void BoxDemo::Render() { //清空后緩沖圖片 m_pDevice->ClearBuffer(MVector(0.75f, 0.75f, 0.75f,1.f)); //設置渲染狀態 m_pImmediateContext->SetRenderMode(MOE3D_FILL_SOLIDE); //設置著色器變量 m_pShader->SetWorldViewProj(m_worldViewProj); m_pShader->SetWorld(m_world); m_pShader->SetWorldInvTranspose(m_worldInvTranspose); m_pImmediateContext->DrawIndexed(m_indices.size(), 0, 0); } void BoxDemo::Clear() { if(m_pDevice) delete m_pDevice; if(m_pImmediateContext) delete m_pImmediateContext; if (m_pShader) delete m_pShader; } void BoxDemo::OnMouseDown(WPARAM btnState, int x, int y) { m_lastMousePos.x = x; m_lastMousePos.y = y; SetCapture(m_hWnd); } void BoxDemo::OnMouseUp(WPARAM btnState, int x, int y) { ReleaseCapture(); } void BoxDemo::OnMouseMove(WPARAM btnState, int x, int y) { if ((btnState & MK_LBUTTON) != 0) { //角度轉弧度 float dx = MathUtil::MConvertToRadians(0.25f*static_cast<float>(x - m_lastMousePos.x)); float dy = MathUtil::MConvertToRadians(0.25f*static_cast<float>(y - m_lastMousePos.y)); m_theta -= dx; m_phi += dy; m_phi = MathUtil::Clamp(m_phi, 0.1f, MathUtil::PI - 0.1f); } else if ((btnState & MK_RBUTTON) != 0) { float dx = 0.2f*static_cast<float>(x - m_lastMousePos.x); float dy = 0.2f*static_cast<float>(y - m_lastMousePos.y); m_radius += dx - dy; m_radius = MathUtil::Clamp(m_radius, 3.0f, 300.0f); } m_lastMousePos.x = x; m_lastMousePos.y = y; } 以上,我們已經完成基本所有的軟渲染demo條件,包括軟渲染的流程、設備上下文模擬、以及demo的結構。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看