Unity 3D 海水的實現2 折射與反射 離屏相機的渲染

來源:互聯網
上載者:User

標籤:2.0   博文   寬高   文章   alt   ide   dir   cin   tag   

版本:unity 5.4.1  語言:C#

 

在上節博文實現後,我添加了一些情境的元素,海水的效果大概是這個樣子的:


接下來的目標是實現海水的折射和反射,書中使用的Unity應該是4.x時代的,Unity內建基礎包是5.x的,然後我再在網上看了一個例子,看了下來基本原理都差不多。

 

還記得移動端簡單陰影的實現吧,對,就是添加一個相機把照到的玩家傳給Shader後,顯示在地上,原理是一樣的。

 

首先擷取到玩家的相機,建立相機到玩家當前相機,經過一個反射矩陣的旋轉後,截取海平面以上的渲染,然後再將渲染出來的Texture傳遞給Shader處理;折射更加簡單,不用矩陣旋轉,當前位置的海平面以下渲染出Texture,再傳遞給Shader。

 

下面是代碼,產生Mesh的代碼,我就去掉了:

public class Ocean : MonoBehaviour{    // 一片地區網格橫縱數量    public int width = 32;    public int height = 32;    int g_height;   // 組成網格橫縱的線條數量    int g_width;    Vector2 sizeInv;    // 地區的數量和大小    public int tiles = 2;    public Vector3 size = new Vector3(150f, 1f, 150f);    // 材質    public Material material;    public Shader oceanShader;    public Transform player;    public Transform sun;    public Vector4 SunDir;    // 網格相關    Vector3[] vertices; //頂點    Vector3[] normals;  //法線    Vector4[] tangents; //三角    Mesh baseMesh;    // LOD,越在靠後List的Mesh,網格越少    int maxLOD = 4;    List<List<Mesh>> tiles_LOD;    // 折射反射相關    public bool renderReflection = true; //是否啟用反射折射    public int renderTexWidth = 128;    public int renderTexHeight = 128;    RenderTexture reflectionTexture = null;    RenderTexture refractionTexture = null;    Camera offscreenCam = null;bool reflectionRefractionEnabled = false;   //是否初始化完成// Use this for initialization    void Start()    {        // 折射反射        sizeInv = new Vector2(1f / size.x, 1f / size.z);        SetupOffscreenRendering();  // 添加離屏相機        if (!renderReflection)            EnableReflection(false);        else            EnableReflection(true);        // 計算線條數量        g_height = height + 1;        g_width = width + 1;        // LOD,Mesh所在的List的LOD List編號越小,Mesh的網格越多        tiles_LOD = new List<List<Mesh>>();        for (int LOD = 0; LOD < maxLOD; LOD++)        {            tiles_LOD.Add(new List<Mesh>());        }        for (int y = 0; y < tiles; ++y)        {            for (int x = 0; x < tiles; ++x)            {                Debug.Log("建立了一片水");                float cy = y - Mathf.Floor(tiles * 0.5f);                float cx = x - Mathf.Floor(tiles * 0.5f);                // 建立一片水                GameObject tile = new GameObject("WaterTile");                // 座標以當前節點為中心                tile.transform.parent = transform;                tile.transform.localPosition = new Vector3(cx * size.x, 0f, cy * size.z);                // 添加Mesh渲染組件                tile.AddComponent<MeshFilter>();                tile.AddComponent<MeshRenderer>().material = material;                tile.layer = LayerMask.NameToLayer("Water");                tiles_LOD[0].Add(tile.GetComponent<MeshFilter>().mesh);            }        }        GenerateHeightmap();    }    // 設定折射反射    void SetupOffscreenRendering()    {        // 建立折射反射圖        RecalculateRenderTextures();        // 建立Camera實現離屏渲染        GameObject cam = new GameObject();        cam.name = "DeepWaterOffscreenCam";        cam.transform.parent = transform;        offscreenCam = cam.AddComponent<Camera>();        offscreenCam.clearFlags = CameraClearFlags.Color;        offscreenCam.depth = -1;        offscreenCam.enabled = false;}    // 當設定reflection和refraction被禁用的時候,設定lod為1    void EnableReflection(bool isActive)    {        renderReflection = isActive;        if (!isActive)  // 關閉反射折射,使用波光粼粼的圖片替代        {            material.SetTexture("_Reflection", null);            material.SetTexture("_Refraction", null);            oceanShader.maximumLOD = 1;        }        else    // 啟用反射折射        {            OnDisable();            oceanShader.maximumLOD = 2;            RecalculateRenderTextures();        }    }    // 重建反射折射的緩衝圖片    void RecalculateRenderTextures()    {        if (renderReflection)        {            reflectionTexture = new RenderTexture(renderTexWidth, renderTexHeight, 0);            refractionTexture = new RenderTexture(renderTexWidth, renderTexHeight, 0);            reflectionTexture.wrapMode = TextureWrapMode.Clamp;            refractionTexture.wrapMode = TextureWrapMode.Clamp;            reflectionTexture.isPowerOfTwo = true;            refractionTexture.isPowerOfTwo = true;            material.SetTexture("_Reflection", reflectionTexture);            material.SetTexture("_Refraction", refractionTexture);            material.SetVector("_Size", new Vector4(size.x, size.y, size.z, 0f));        }    }    // 刪除反射折射使用的緩衝圖片    void OnDisable()    {        if (reflectionTexture != null)        {            DestroyImmediate(reflectionTexture);        }        if (refractionTexture != null)        {            DestroyImmediate(refractionTexture);        }        reflectionTexture = null;        refractionTexture = null;    }    // 折射反射渲染物體    void RenderObject()    {        if (Camera.current == offscreenCam)            return;        if (reflectionTexture == null || refractionTexture == null)            return;        if (renderReflection)            RenderReflectionAndRefraction();    }    public LayerMask renderLayers = -1;    // 具體的渲染,使用第二個相機拷貝當前相機的設定    void RenderReflectionAndRefraction()    {        // 擷取當前角色身上的主相機        Camera renderCamera = Camera.main;        Matrix4x4 originalWorldToCam = renderCamera.worldToCameraMatrix;    // 擷取世界到相機的矩陣,如果改變了相機的矩陣就不會再根據原Transform渲染,除非調用ResetWorldToCameraMatrix        int cullingMask = ~(1 << 4) & renderLayers.value;   //剪裁Mask,忽略水本身        // 計算反射矩陣        float d = -transform.position.y;        Matrix4x4 reflection = Matrix4x4.zero;        CameraHelper.CalculateReflectionMatrix(ref reflection, new Vector4(0f, 1f, 0f, d)); //這裡不明白,總是弄出了反射矩陣        // 根據反射矩陣計算離屏相機位置和矩陣        offscreenCam.backgroundColor = RenderSettings.fogColor;        offscreenCam.transform.position = reflection.MultiplyPoint(renderCamera.transform.position);    //當前相機置換到反射矩陣中        offscreenCam.transform.rotation = renderCamera.transform.rotation;        offscreenCam.worldToCameraMatrix = originalWorldToCam * reflection;        offscreenCam.cullingMask = cullingMask; //設定剪裁mask        offscreenCam.targetTexture = reflectionTexture; //將反射緩衝圖片添加到離屏相機裡,跟之前陰影是一個原理        // 因為反射截取到的圖片是翻轉的,所以需要設定翻轉        GL.SetRevertBackfacing(true);        // 擷取剪裁平面,transform.position.y是當前海水的高度,最後兩個值的正負表示剪裁的方向        Vector4 cameraSpaceClipPlane = CameraHelper.CameraSpacePlane(offscreenCam, new Vector3(0.0f, transform.position.y, 0.0f), Vector3.up, 1.0f);        Matrix4x4 projection = renderCamera.projectionMatrix;   //獲得渲染相機的投影矩陣        Matrix4x4 obliqueProjection = projection;        offscreenCam.fieldOfView = renderCamera.fieldOfView;    //設定FOV        offscreenCam.aspect = renderCamera.aspect;  //設定寬高比        CameraHelper.CalculateObliqueMatrix(ref obliqueProjection, cameraSpaceClipPlane);        // 開始真正的渲染        offscreenCam.projectionMatrix = obliqueProjection;        if (!renderReflection)            offscreenCam.cullingMask = 0;        offscreenCam.Render();        GL.SetRevertBackfacing(false);        // 折射渲染             offscreenCam.cullingMask = cullingMask;        offscreenCam.targetTexture = refractionTexture;        obliqueProjection = projection;        // 將渲染相機的各個參數設定給離屏相機        offscreenCam.transform.position = renderCamera.transform.position;        offscreenCam.transform.rotation = renderCamera.transform.rotation;        offscreenCam.worldToCameraMatrix = originalWorldToCam;        // 擷取剪裁平面,計算投影矩陣        cameraSpaceClipPlane = CameraHelper.CameraSpacePlane(offscreenCam, new Vector3(0.0f, transform.position.y, 0.0f), Vector3.up, -1.0f);        CameraHelper.CalculateObliqueMatrix(ref obliqueProjection, cameraSpaceClipPlane);        offscreenCam.projectionMatrix = obliqueProjection;        offscreenCam.Render();        offscreenCam.projectionMatrix = projection;        offscreenCam.targetTexture = null;    }// 初始化Mesh資訊,請參考上一節    void GenerateHeightmap(){}    // 這邊應該是Update的,但寫在Update中會報GUI Window tries to begin rendering while something else has not finished rendering的錯誤    void OnGUI()    {        // 設定玩家、太陽角度,並更新反射折射,折射反射是根據玩家視角來計算的        if (player == null)            player = GameObject.FindGameObjectWithTag("Player").GetComponent<Transform>();        if (sun != null)        {            SunDir = sun.transform.forward;            material.SetVector("_SunDir", SunDir);        }        if (renderReflection)            RenderObject();    }}

然後是CameraHelper的指令碼:

public class CameraHelper{    private static float sgn(float a)    {        if (a > 0.0f)            return 1.0f;        if (a < 0.0f)            return -1.0f;        return 0.0f;    }    public static void CalculateObliqueMatrix(ref Matrix4x4 projection, Vector4 clipPlane)    {        Vector4 q = projection.inverse * new Vector4(sgn(clipPlane.x), sgn(clipPlane.y), 1.0f, 1.0f);        Vector4 c = clipPlane * (2.0F / (Vector4.Dot(clipPlane, q)));        projection[2] = c.x - projection[3];        projection[6] = c.y - projection[7];        projection[10] = c.z - projection[11];        projection[14] = c.w - projection[15];    }    public static Vector4 CameraSpacePlane(Camera cam, Vector3 pos, Vector3 normal, float sideSign)    {        Vector3 offsetPos = pos + normal * 0.02f;        Matrix4x4 m = cam.worldToCameraMatrix;        Vector3 cpos = m.MultiplyPoint(offsetPos);        Vector3 cnormal = m.MultiplyVector(normal).normalized * sideSign;        return new Vector4(cnormal.x, cnormal.y, cnormal.z, -Vector3.Dot(cpos, cnormal));    }    public static void CalculateReflectionMatrix(ref Matrix4x4 reflectionMat, Vector4 plane)    {        reflectionMat.m00 = (1F - 2F * plane[0] * plane[0]);        reflectionMat.m01 = (-2F * plane[0] * plane[1]);        reflectionMat.m02 = (-2F * plane[0] * plane[2]);        reflectionMat.m03 = (-2F * plane[0] * plane[3]);        reflectionMat.m10 = (-2F * plane[1] * plane[0]);        reflectionMat.m11 = (1F - 2F * plane[1] * plane[1]);        reflectionMat.m12 = (-2F * plane[1] * plane[2]);        reflectionMat.m13 = (-2F * plane[1] * plane[3]);        reflectionMat.m20 = (-2F * plane[2] * plane[0]);        reflectionMat.m21 = (-2F * plane[2] * plane[1]);        reflectionMat.m22 = (1F - 2F * plane[2] * plane[2]);        reflectionMat.m23 = (-2F * plane[2] * plane[3]);        reflectionMat.m30 = 0F;        reflectionMat.m31 = 0F;        reflectionMat.m32 = 0F;        reflectionMat.m33 = 1F;    }}

花了三天時間終於整理出來了比較精簡的代碼,不過渲染部分的矩陣,我還是沒有太理解。

 

這邊提一下作者的Shader,因為是老版本建立的Shader,所以如果色彩空間不使用Gamma的話,反射的倒影會用問題。還有作者的代碼是有點問題的,如果想直接用他的代碼,最好對比一下我這邊的代碼,把一些錯誤排掉。

 

完成之後的效果是這樣的:



有折射和反射效果,但總感覺這個顏色很不對勁,沒錯,這是上一節留下的一個BUG,Mesh中沒有添加法線,你可以自己嘗試在Mesh初始化的時候添加,或者像我這樣馬後炮:

normals = new Vector3[baseMesh.vertices.Length];for (int i = 0; i < baseMesh.vertices.Length; ++i)    normals[i] = Vector3.Normalize(new Vector3(0, 1f, 0));for (int k = 0; k < tiles_LOD[0].Count; ++k){    Mesh meshLOD = tiles_LOD[0][k];    meshLOD.vertices = baseMesh.vertices;    meshLOD.normals = normals;    meshLOD.tangents = baseMesh.tangents;}

這段代碼放到OnGUI中,不過只要運行一次就好,不然電腦會很卡。。

 

最終效果:




功夫不負有心人啊,花了我很長的時間,不過對比第一張圖和最後一張圖的效果,我感覺一切都是值得的,也瞭解了Unity4.x和5.x的一些區別,總的來說大概明白了折射反射的原理。

 

下一節的文章,我想挑戰一下自己深入瞭解一下反射矩陣,波浪的東西留到後面再做。


Unity 3D 海水的實現2 折射與反射 離屏相機的渲染

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.