美文网首页
在Unity中实现水体交互

在Unity中实现水体交互

作者: Uonfan | 来源:发表于2020-02-18 13:30 被阅读0次

    本文转自Unity Connect博主 dreamfairy

    ​效果图

    制作可交互的水体,大致分为三步

    1.标记水体碰撞的位置

    2.计算水波的传递 通过波动公式,3D或者2D 波动公式都行

    3.水面顶点采样波动传递结果计算结果做顶点Y轴偏移

    本文参考的波动相关资料https://en.wikipedia.org/wiki/Wave_equationhttps://www.amazon.com/Mathematics-Programming-Computer-Graphics-Third/dp/1435458869 流体 章节

    相关公式

    根据公式可知波的下次一次传递 z(i,j,k+1) 为 当前波值+上一次波值+周围波值

    当前波值 *= (4-8*c^2*t^2/d^2/d^2)/(u*t)

    上一次波值 *= (ut-2) / (ut + 2)

    四周波值 *= (2c^2t^2/d^2) / (ut + 2)

    其中各参数含义为 c 波速, u 粘度, d 波的递进距离, t 为递进时间

    ok~ 我们重头开始

    首先要建立水面

    这里直接用Unity Wiki的已有轮子的创建平面,下载wiki上的代码,传到项目中https://wiki.unity3d.com/index.php/CreatePlane

    这里我们直接创建一个宽10米,长10米,间隔100的平面, 间隔越多,水体的颗粒感越小

    对应本文开头描述的三大步骤

    创建3个纹理

    对应水体碰撞标记,传递,渲染

            m_waterWaveMarkTexture = new RenderTexture(WaveTextureResolution, WaveTextureResolution, 0, RenderTextureFormat.Default);

            m_waterWaveMarkTexture.name = "m_waterWaveMarkTexture";

            m_waveTransmitTexture = new RenderTexture(WaveTextureResolution, WaveTextureResolution, 0, RenderTextureFormat.Default);

            m_waveTransmitTexture.name = "m_waveTransmitTexture";

            m_prevWaveMarkTexture = new RenderTexture(WaveTextureResolution, WaveTextureResolution, 0, RenderTextureFormat.Default);

            m_prevWaveMarkTexture.name = "m_prevWaveMarkTexture";

    标记水体碰撞位置

    void WaterPlaneCollider()

        {

            hasHit = false;

            if (Input.GetMouseButton(0))

            {

                Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

                RaycastHit hitInfo = new RaycastHit();

                bool ret = Physics.Raycast(ray.origin, ray.direction, out hitInfo);

                if (ret)

                {

                    Vector3 waterPlaneSpacePos = WaterPlane.transform.worldToLocalMatrix * new Vector4(hitInfo.point.x, hitInfo.point.y, hitInfo.point.z, 1);

                    float dx = (waterPlaneSpacePos.x / WaterPlaneWidth) + 0.5f;

                    float dy = (waterPlaneSpacePos.z / WaterPlaneLength) + 0.5f;

                    hitPos.Set(dx, dy);

                    m_waveMarkParams.Set(dx, dy, WaveRadius * WaveRadius, WaveHeight);

                    hasHit = true;

                }

            }

        }

    由于我们默认Raycast 获取的是碰撞的世界坐标,我们期望的是直接获取到 [0-1] 范围的数值用来映射到uv空间,直接在 m_waterWaveMarkTexture 进行标记, 因此我们乘以一个 world2Local 矩阵变换到本地, 又因为CreatePlane默认创建的Pivot 位于中心,再除以宽高缩放到1区间时,值域落在[-0.5,0.5]上,因此我们还要做 + 0.5偏移

    标记水体碰撞Shader

                    float dx = i.uv.x - _WaveMarkParams.x;

                    float dy = i.uv.y - _WaveMarkParams.y;

                    float disSqr = dx * dx + dy * dy;

                    int hasCol = step(0, _WaveMarkParams.z - disSqr);

                    float waveValue = DecodeHeight(tex2D(_MainTex, i.uv));

                    if (hasCol == 1) {

                        waveValue = _WaveMarkParams.w;

                    }

    根据传入的_WaveMarkParams.xy 跟当前uv 对比,在笔刷范围内的像素标记位默认波高度

    波的传递Shader

                    static const float2 WAVE_DIR[4] = { float2(1, 0), float2(0, 1), float2(-1, 0), float2(0, -1) };

                    float dx = _WaveTransmitParams.w;

                    float avgWaveHeight = 0;

                    for (int s = 0; s < 4; s++)

                    {

                        avgWaveHeight += DecodeHeight(tex2D(_MainTex, i.uv + WAVE_DIR[s] * dx));

                    }

                    //(2 * c^2 * t^2 / d ^2) / (u * t + 2)*(z(x + dx, y, t) + z(x - dx, y, t) + z(x, y + dy, t) + z(x, y - dy, t);

                    float agWave = _WaveTransmitParams.z * avgWaveHeight;

                    // (4 - 8 * c^2 * t^2 / d^2) / (u * t + 2)

                    float curWave = _WaveTransmitParams.x *  DecodeHeight(tex2D(_MainTex, i.uv));

                    // (u * t - 2) / (u * t + 2) * z(x,y,z, t - dt) 上一次波浪值 t - dt

                    float prevWave = _WaveTransmitParams.y * DecodeHeight(tex2D(_PrevWaveMarkTex, i.uv));

                    //波衰减

                    float waveValue = (curWave + prevWave + agWave) * _WaveAtten;

    最后就是水体的呈现,因为需要做顶点纹理采样,因此需要至少ES3.0 硬体

    v2f vert (appdata v)

                {

                    v2f o;

                    float4 localPos = v.vertex;

                    float4 waveTransmit = tex2Dlod(_WaveResult, float4(v.uv, 0, 0));

                    float waveHeight = DecodeFloatRGBA(waveTransmit);

                    localPos.y += waveHeight * _WaveScale;

                    float3 worldPos = mul(unity_ObjectToWorld, localPos);

                    float3 worldSpaceNormal = mul(unity_ObjectToWorld, v.normal);

                    float3 worldSpaceViewDir = UnityWorldSpaceViewDir(worldPos);

                    o.vertex = mul(UNITY_MATRIX_VP, float4(worldPos, 1));

                    o.uv = v.uv;

                    o.worldSpaceReflect = reflect(-worldSpaceViewDir, worldSpaceNormal);

                    return o;

                }

    github地址https://github.com/dreamfairy/interactivity-waterplane

    原文链接:https://connect.unity.com/p/zai-unityzhong-shi-xian-shui-ti-jiao-hu?app=true

    欢迎戳上方原文链接,下载Unity官方技术社区app,发现更多资源干货~

    相关文章

      网友评论

          本文标题:在Unity中实现水体交互

          本文链接:https://www.haomeiwen.com/subject/tlzwfhtx.html