-->

分類

2017年2月12日 星期日

Notes about depth texture(using Unity shader code)

Depth texture used for effects like fog normally uses camera's depth texture, the shader code is as the following:

float depth01 = Linear01Depth(UNITY_SAMPLE_DEPTH(tex2D(_CameraDepthTexture, i.depthUV)));

Then you get the float in the range (0,1) for the depth value.

But the problem is that the depth texture of camera doesn't contains information about transparent objects, so we need to use fragment's depth in clip space, the shader code is as the following:

    struct appdata 
   {
          float4 vertex : POSITION;
  half2 texcoord : TEXCOORD0;
    };

    struct v2f 
    {
   float4 pos : SV_POSITION;
   float2 uv: TEXCOORD0;
        float2 depthUV : TEXCOORD1;
        float3 cameraToFarPlane : TEXCOORD2;
    float4 screenPos : TEXCOORD3;
    };
    
   v2f vert(appdata v) 
  {
    v2f o;
    o.pos = UnityObjectToClipPos(v.vertex);
o.screenPos = ComputeScreenPos(o.pos);
       ...
  }

  fixed4 frag (v2f i) : SV_Target 
 {
float depth01 = i.screenPos.w / (_ProjectionParams.z - _ProjectionParams.y);//normalize to the range(0,1)
  //i.screenPos.w: depth(z) in camera space, range(n, f), i think(could be wrong). Please check http://blog.csdn.net/zhao_92221/article/details/46844267 and http://www.songho.ca/opengl/gl_projectionmatrix.html for details. I guess unity uses projection matrix to map (n,f) to (0,1), could be wrong
  //_ProjectionParams.z - _ProjectionParams.y: camera far - near
  }

One use case of these two different values is that you can use it to measure the thickness of the transparent objects(e.g. the depth of water object). Simply use the depth for fragment in clip space to subtract the depth in camera's depth texture.

2017年1月23日 星期一

fixed Unity standard asset water reflection in VR environmemt(SteamVR only)

[DOWNLOAD the unity package] (note: need to import Unity SteamVR plugin first, tested with SteamVR plugin 1.1.1 and SteamVR plugin 1.2. Unity may show compilation error if the SteamVR plugin is not compatible with the script for the water, you can fix the script by following the instructions at the line where compilation error occurs)
The water module  from Unity standard asset has problem with reflection under VR environment.
This link has the solution to reflection under VR environment in Unity.
https://forum.unity3d.com/threads/5-4-beta15-reflection-rendering-wrong-in-openvr-htc-vive.398756/

Simply apply this solution to the water module's source code then the problem is solved.

02/10/2017 update(package is also updated for the download link):
Fixed the missing prefab problem from previous unity package. Added instructions to fix SteamVR plugin incompatible problem.

02/03/2017 update(package is also updated for the download link):
some modifications for single pass stereo rendering, removed the discontinuous texture sampling artifacts when single pass stereo rendering turned on.

Some notes:
The reflection use a camera for reflection. The reflection camera is at the reflection pose of the main camera by the reflection surface.

The reflection camera makes a render texture for the reflection surface plane.

The reflection camera modified it's projection matrix so that its clip plane will be the reflection plane(so that object between reflection camera and the reflection surface will not be rendered). It transforms it's view frustum to be oblique view frustum.
Oblique view frustum derivation
view frustum culling(the relationship between view frustum plane and projection matrix)
reflection matrix
oblique view frustum transform implemented by C#

The reflection camera has different pose for left eye and right eye in VR enivronment:
Vector3 eyePos = cam.transform.TransformPoint(SteamVR.instance.eyes[0].pos);
Quaternion eyeRot = cam.transform.rotation * SteamVR.instance.eyes[0].rot;
Matrix4x4 projectionMatrix = GetSteamVRProjectionMatrix(cam, Valve.VR.EVREye.Eye_Left);

 Vector3 eyePos = cam.transform.TransformPoint(SteamVR.instance.eyes[1].pos);
 Quaternion eyeRot = cam.transform.rotation * SteamVR.instance.eyes[1].rot;
 Matrix4x4 projectionMatrix = GetSteamVRProjectionMatrix(cam, Valve.VR.EVREye.Eye_Right);

The reflection camera has different render textures for left and right eye:
render the render texture for left and right eye on the same texture by specifying the range where the texture of each eye should be drawn:
    private static readonly Rect LeftEyeRect = new Rect(0.0f, 0.0f, 0.5f, 1.0f);
    private static readonly Rect RightEyeRect = new Rect(0.5f, 0.0f, 0.5f, 1.0f);
    ...
    m_ReflectionCamera.rect = camViewport;//camViewport is LeftEyeRect or RightEyeRect
So the reflection render texture's left half part is for left eye, right half part is for right eye.
And when sampling the reflection render texture, the uv coordinates need to be modified according to which eye is being used:
vert shader
o.screenPos = ComputeScreenPos(o.pos);
frag shader
half4 screenWithOffset = i.screenPos;
#ifndef UNITY_SINGLE_PASS_STEREO
if (unity_CameraProjection[0][2] < 0)
{
screenWithOffset.x = (screenWithOffset.x * 0.5f);//make x as 0 ~ 0.5
}
else if (unity_CameraProjection[0][2] > 0)
{
screenWithOffset.x = (screenWithOffset.x * 0.5f) + (screenWithOffset.w * 0.5f);//0.5~1
}
#endif

for Single Pass Stereo Rendering case, the "screenWithOffset" will be handled automatically since single pass stereo rendering treats the texture as a combined texture for left and right eye. It will use left half texture for left eye, right half texture for right eye, we don't need to modify the screenWithOffset.x as the case of non single pass stereo rendering

2016年9月7日 星期三

Unity 2d wave-like motion mesh

reference

this needs one script attached to an empty GameObject and one material with texture

result video




public class WaveMesh : MonoBehaviour {
    private Mesh m_Mesh;
    private float size = 0.5f;
    private int gridSize = 8;
    public float waveFrequency = 5.0f;
    public float sizeScale = 0.5f;
    // Use this for initialization
    void Start () {
 
 }
 
 // Update is called once per frame
 void Update () {
        int dotsPerRow = gridSize + 1;
        int halfGridSize = gridSize / 2;
        Vector3[] verts = m_Mesh.vertices;
        float time = Time.time;
        for (int i = 0; i < dotsPerRow; i++)
        {
            for (int j = 0; j < dotsPerRow; j++)
            {
                int xIndex = j - halfGridSize;
                int yIndex = halfGridSize - i;
                float xSign = Mathf.Sign(xIndex);
                float ySign = Mathf.Sign(yIndex);
                float xPosTarget = xIndex;
                float yPosTarget = yIndex;
                //if (xIndex != 0 && yIndex != 0)
                {
                    Vector2 wave = waveCoord(xIndex, yIndex, halfGridSize, time);
                    verts[i * dotsPerRow + j] = new Vector3(wave.x, wave.y, 0.0f);
                }           
            }
        }
        m_Mesh.vertices = verts;
    }

    private Vector2 waveCoord(float xIndex, float yIndex, float halfGridSize, float time)
    {
        Vector2 p = new Vector2(xIndex, yIndex);
        
        p = p * sizeScale;
        float len = p.magnitude;
        float sincValue = sinc(p.magnitude * 0.5f);
        float temp = Mathf.Cos(time * waveFrequency - len / sizeScale) * 0.5f * sizeScale * sincValue;
        Vector2 offset = new Vector2(Mathf.Sign(xIndex) * temp, Mathf.Sign(yIndex) * temp);
        return offset + p;
    }

    private float clampTimeToPeriod(float time, float period)
    {
        return time - Mathf.Floor(time / period);
    }

    private float sinc(float x)
    {
        if(Mathf.Abs(x) < 0.0001f)
        {
            return Mathf.Sin(x);
        }
        return Mathf.Sin(x) / x;
    }

    void Awake()
    {
        GameObject plane = new GameObject("CreatedWaveMesh");
        MeshFilter meshFilter = (MeshFilter)plane.AddComponent(typeof(MeshFilter));
        meshFilter.mesh = CreateMesh();
        m_Mesh = meshFilter.mesh;
        MeshRenderer renderer = plane.AddComponent(typeof(MeshRenderer)) as MeshRenderer;
        //Load your material here
        Material newMat = Resources.Load("Materials/WaveStandardMaterial", typeof(Material)) as Material;
        renderer.material = newMat;
        //plane.transform.localScale = new Vector3(desiredSize / gridSize, desiredSize / gridSize, -1);
        plane.transform.localScale = new Vector3(1, 1, -1);
        plane.transform.localPosition = new Vector3(0.64f, 0.38f, -3);
    }

    Mesh CreateMesh()
    {
        Mesh m = new Mesh();
        m.name = "ScriptedMesh";
        /*
        case for gridSize == 4, there are 4x4=16 grids, dotsPerRow is 4+1=5
        .....
        .....
        .....
        .....
        .....
        */
        int dotsPerRow = gridSize + 1;
        int halfGridSize = gridSize / 2;
        Vector3[] verts = new Vector3[dotsPerRow * dotsPerRow];
        for (int i = 0; i < dotsPerRow; i++)
        {
            for(int j = 0; j < dotsPerRow; j++)
            {
                verts[i * dotsPerRow + j] = new Vector3(j - halfGridSize, halfGridSize - i, 0.0f);
            }
        }
        m.vertices = verts;

        Vector2[] uvs = new Vector2[verts.Length];
        float dotsPerRowf = (float)dotsPerRow;
        for (int i = 0; i < dotsPerRow; i++)
        {
            for (int j = 0; j < dotsPerRow; j++)
            {
                uvs[i * dotsPerRow + j] = new Vector2((float)j / dotsPerRowf, 1.0f - (float)i / dotsPerRowf);
            }
        }
        m.uv = uvs;

        int numTris = gridSize * gridSize * 2;
        int[] tris = new int[numTris * 3];
        
        for(int i = 0; i < gridSize; i++)
        {
            for(int j = 0; j < gridSize; j++)
            {
                int startIndex = 6 * (i * gridSize + j);
                tris[startIndex] = i * dotsPerRow + j;
                tris[startIndex + 1] = (i + 1) * dotsPerRow + j;
                tris[startIndex + 2] = i * dotsPerRow + j + 1;
                tris[startIndex + 3] = (i + 1) * dotsPerRow + j;
                tris[startIndex + 4] = (i + 1) * dotsPerRow + j + 1;
                tris[startIndex + 5] = i * dotsPerRow + j + 1;
            }
        }
        m.triangles = tris;
        m.RecalculateNormals();

        return m;
    }
}

2016年8月25日 星期四

Android Local unit test/Instrumentation Test

reference 1
reference for unit test

basic:

differences between Local unit test and Instrumentation Test:
Local unit test: can just run on JVM, doesn't need Android framework
Instrumentation Test: can test Android component(Activity, Service...) and UI

create Class for testing(both local unit test and instrumentation test) in app/src/androidTest/java/<package_name>

set build.gradle:
dependencies {
    androidTestCompile 'junit:junit:4.12'
    androidTestCompile 'com.android.support.test:runner:0.4'
    // Set this dependency to use JUnit 4 rules
    androidTestCompile 'com.android.support.test:rules:0.4'
    androidTestCompile 'com.android.support:support-annotations:23.1.1'
}

mokito for mocking context seems useless if we use the sample code snippet here (the Context is null and MockitoAnnotations.initMocks can't work

sample Local unit test

import org.junit.Test;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
public class MyUnitTest {
    @Test
    public void testOne() {

        assertTrue("123".equals("123"));
    }
}


sample Instrumentation test
import android.test.ActivityInstrumentationTestCase2;
import android.test.UiThreadTest;
import android.widget.Toast;

//JUnit4
public class ActivityFunctionTest extends ActivityInstrumentationTestCase2 {
    public ActivityFunctionTest(Class activityClass) {
        super(activityClass);
    }

    public ActivityFunctionTest(){
        super(MainMenuAcitivity.class);
    }

    public void testSetText() throws Exception {

        // set text
        getActivity().runOnUiThread(new Runnable() {

            @Override
            public void run() {
                MainMenuAcitivity activity = getActivity();
                Toast.makeText(activity, "test toast 2", Toast.LENGTH_SHORT).show();
            }
        });

        getInstrumentation().waitForIdleSync();

    }

    @UiThreadTest
    public void testSetTextWithAnnotation() throws Exception {

        MainMenuAcitivity activity = getActivity();
        Toast.makeText(activity, "test toast", Toast.LENGTH_SHORT).show();

    }
}

monkey command:
adb shell monkey -p <package_name> -v <number_of_events>

2016年7月25日 星期一

Android view static inner class in layout

    <view xmlns:android="http://schemas.android.com/apk/res/android"
          class="com.package.OuterClassName$InnerViewClassName">
</view>

2016年7月19日 星期二

Unity failed wireframe shader

tried to assign uv for each vertex of triangle as (0,0), (0,1), (1,0) then use interpolated uv value in fragment shader to determine if it should be drawn as edge of the triangle

C# script:


using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class SetTriangleVertUV4ForWireframe : MonoBehaviour {
    private enum UVType
    {
        none,
        t00,
        t01,
        t10
    }
    private Mesh mMesh;
    private int[] triangleIndices;
    private Vector2[] mUV;
    // Use this for initialization
    void Start()
    {
        mMesh = GetComponent().mesh;
        triangleIndices = mMesh.triangles;
        mUV = new Vector2[mMesh.vertices.Length];
        for (int i = 0; i < mUV.Length; i++)
        {
            //UVType.none
            mUV[i] = new Vector2(-1.0f, -1.0f);
        }
        for (int i = 0; i < triangleIndices.Length; i += 3)
        {
            Vector2 v0 = mUV[triangleIndices[i]];
            Vector2 v1 = mUV[triangleIndices[i + 1]];
            Vector2 v2 = mUV[triangleIndices[i + 2]];
            HashSet uvTypeSet = new HashSet();
            HashSet vertIndexSet = new HashSet();
            uvTypeSet.Add(UVType.t00);
            uvTypeSet.Add(UVType.t01);
            uvTypeSet.Add(UVType.t10);
            vertIndexSet.Add(i);
            vertIndexSet.Add(i + 1);
            vertIndexSet.Add(i + 2);

            Dictionary uvTypeDic = new Dictionary();
            addUVType(uvTypeDic, v0, i);
            addUVType(uvTypeDic, v1, i + 1);
            addUVType(uvTypeDic, v2, i + 2);


            foreach (KeyValuePair pair in uvTypeDic)
            {
                UVType type = pair.Key;
                if (uvTypeSet.Contains(type))
                {
                    uvTypeSet.Remove(type);
                    vertIndexSet.Remove(pair.Value);
                }
            }

            //uvTypeSet now contains UVType that are not used
            List resultUVType = new List();
            List resultIndices = new List();
            foreach (UVType type in uvTypeSet)
            {
                resultUVType.Add(type);
            }
            resultUVType.Sort();
            foreach (int index in vertIndexSet)
            {
                resultIndices.Add(index);
            }
            resultIndices.Sort();
            //convert unused UVType to Vector2 as UV coord and save to uv4
            for (int j = 0; j < resultIndices.Count; j++)
            {
                mUV[triangleIndices[resultIndices[j]]] = buildVector2ByUVType(resultUVType[j]);
            }
        }
        mMesh.uv4 = mUV;
    }

    private Vector2 buildVector2ByUVType(UVType type)
    {
        switch (type)
        {
            case UVType.t00:
                return new Vector2(0.0f, 0.0f);
            case UVType.t01:
                return new Vector2(0.0f, 1.0f);
            case UVType.t10:
                return new Vector2(1.0f, 0.0f);
            default:
                return new Vector2(-1.0f, -1.0f);
        }
    }

    private void addUVType(Dictionary dic, Vector2 v, int index)
    {
        UVType uvType = checkUVType(v);
        if (uvType != UVType.none && !dic.ContainsKey(uvType))
        {
            dic.Add(uvType, index);
        }
    }

    private UVType checkUVType(Vector2 v)
    {
        if (Mathf.Approximately(v.x, -1.0f))
        {
            return UVType.none;
        }
        if (Mathf.Approximately(v.x, 1.0f))
        {
            return UVType.t10;
        }
        if (Mathf.Approximately(v.y, 1.0f))
        {
            return UVType.t01;
        }
        return UVType.t00;
    }
}


Shader code:

Shader "Unlit/TestWireframeUV4"
{
 Properties
 {
  _MainTex ("Texture", 2D) = "white" {}
 }
 SubShader
 {
  Tags { "RenderType"="Opaque" }
  LOD 100

  Pass
  {
   CGPROGRAM
   #pragma vertex vert
   #pragma fragment frag
   // make fog work
   #pragma multi_compile_fog
   
   #include "UnityCG.cginc"

   struct appdata
   {
    float4 vertex : POSITION;
    float2 uv : TEXCOORD0;
    float2 uv4 : TEXCOORD3;
   };

   struct v2f
   {
    float2 uv : TEXCOORD0;
    float2 uv4 : TEXCOORD3;
    float4 vertex : SV_POSITION;
   };

   sampler2D _MainTex;
   float4 _MainTex_ST;
   
   v2f vert (appdata v)
   {
    v2f o;
    o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
    o.uv = TRANSFORM_TEX(v.uv, _MainTex);
    o.uv4 = v.uv4;
    return o;
   }
   
   fixed4 frag (v2f i) : SV_Target
   {
    fixed4 col = tex2D(_MainTex, i.uv);
    if (length(i.uv4) > 0.7 && length(i.uv4) < 0.9) {
     //treated as edge of triangle
     return fixed4(0, 0, 0, 1);
    }
    return col;
   }
   ENDCG
  }
 }
}


result:
as you can see, some triangles don't have correct UV(at the top of the sphere). This is because sometimes the UV coordinates have conflicts in one triangle (e.g. (1,0), (1,0) for two vertices, in that case the second UV coordinate is discarded)