How I can set repeated texture on an object in And

2019-03-03 16:11发布

问题:

I have successfully made a line between two vectors in the AR Scene.

My code:

private void addLineBetweenPoints(Scene scene, Vector3 from, Vector3 to) {
        // prepare an anchor position
        Quaternion camQ = scene.getCamera().getWorldRotation();
        float[] f1 = new float[]{to.x, to.y, to.z};
        float[] f2 = new float[]{camQ.x, camQ.y, camQ.z, camQ.w};
        Pose anchorPose = new Pose(f1, f2);

        // make an ARCore Anchor
        Anchor anchor = mCallback.getSession().createAnchor(anchorPose);
        // Node that is automatically positioned in world space based on the ARCore Anchor.
        AnchorNode anchorNode = new AnchorNode(anchor);
        anchorNode.setParent(scene);

        // Compute a line's length
        float lineLength = Vector3.subtract(from, to).length();

        // Prepare a sampler
        Texture.Sampler sampler = Texture.Sampler.builder()
                .setMinFilter(Texture.Sampler.MinFilter.LINEAR_MIPMAP_LINEAR)
                .setMagFilter(Texture.Sampler.MagFilter.LINEAR)
                .setWrapModeR(Texture.Sampler.WrapMode.REPEAT)
                .setWrapModeS(Texture.Sampler.WrapMode.REPEAT)
                .setWrapModeT(Texture.Sampler.WrapMode.REPEAT)
                .build();

        // 1. Make a texture
        Texture.builder()
                .setSource(() -> getContext().getAssets().open("textures/aim_line.png"))
                .setSampler(sampler)
                .build().thenAccept(texture -> {
                    // 2. make a material by the texture
                    MaterialFactory.makeTransparentWithTexture(getContext(), texture)
                        .thenAccept(material -> {
                            // 3. make a model by the material
                            ModelRenderable model = ShapeFactory.makeCylinder(0.0025f, lineLength,
                                    new Vector3(0f, lineLength / 2, 0f), material);
                            model.setShadowReceiver(false);
                            model.setShadowCaster(false);

                            // make node
                            Node node = new Node();
                            node.setRenderable(model);
                            node.setParent(anchorNode);

                            // set rotation
                            final Vector3 difference = Vector3.subtract(to, from);
                            final Vector3 directionFromTopToBottom = difference.normalized();
                            final Quaternion rotationFromAToB =
                                    Quaternion.lookRotation(directionFromTopToBottom, Vector3.up());
                            node.setWorldRotation(Quaternion.multiply(rotationFromAToB,
                                    Quaternion.axisAngle(new Vector3(1.0f, 0.0f, 0.0f), 90)));
                    });
        });
    }

It works perfectly, but I have a mistake texture. In the file "textures/aim_line.png" contains PNG: (the line's half is transparent the other half is orange.)

My current result:

But I expected next result:

So, I have use the Sampler where was wrote "WrapMode.REPEAT" but the texture does not repeat, only stretched.

How I can set repeated texture on an object in Android ArCore Sceneform API?

回答1:

Looking at the cylinder model, it has a UV map of 0 to 1. This is used to map the texture onto the mesh. 0,0 is the bottom left of the texture and 1,1 is the top right. The wrapping configuration on the sampler is only used when the UV coordinates on the model are > 1.0. In that case it clamps or repeats based on the setting. Since the cylinder is already constrained to 0,1, the texture is always stretched.

Your alternatives to fix this are either to model your own cylinder and set the UV coordinates as you need, or to use a custom material to manipulate the UV coordinates before sampling.

You can use Blender or Maya or another 3D modeling tool make the model.

The custom material is specific to Sceneform, so here's the steps:

  1. Create a dummy model to use when loading the custom material
  2. Write the custom material that repeats the texture
  3. Load the dummy model at runtime and get the material
  4. Set the parameters on the custom material.

Create a dummy model

I used a plane OBJ model I had around. It does not matter what the model is, we just need it to load the material. Create a file in app/sampledata/materials named dummy.obj

o Plane
v 0.500000 0.500000 0.000000
v  -0.500000 0.500000 0.000000
v  0.500000 -0.500000 0.000000
v  -0.500000 -0.500000 0.000000
vt 0.000000 1.000000
vt 0.000000 0.000000
vt 1.000000 0.000000
vt 1.000000 1.000000
vn 0.0000 0.0000 1.0000
usemtl None
s off
f 1/1/1 2/2/1 4/3/1 3/4/1

Write the custom material

The custom material reference describes each of the elements in the repeating_texture.mat:

// Sample material for repeating a texture.
//
// the repeating factor is given as repeat_x,
// repeat_y as a factor multipled by the UV
// coordinate.
material {
    "name" : "RepeatingTexture",
   parameters : [
   {
      type : sampler2d,
      name : texture
   },

    {
        type: float,
        name:"repeat_x"
    },
    {
            type: float,
            name: "repeat_y"
    }
   ],
   requires : [
       "position",
       "uv0"
   ],

}
fragment {
    void material(inout MaterialInputs material) {
        prepareMaterial(material);

        vec2 uv = getUV0();
        uv.x = uv.x * materialParams.repeat_x;
        uv.y = uv.y * materialParams.repeat_y;

        material.baseColor = texture(materialParams_texture, uv).rgba;
    }
}

Add the model and material to the build

This adds the step to compile the model and material into a .sfb file. In app/build.gradle add:

apply plugin: 'com.google.ar.sceneform.plugin'

sceneform.asset('sampledata/materials/dummy.obj',
        "sampledata/materials/repeating_texture.mat",
        'sampledata/materials/dummy.sfa',
        'src/main/res/raw/material_holder')

You will also need to add Sceneform to the buildscript class path in the top level build.gradle:

    dependencies {
        classpath 'com.android.tools.build:gradle:3.2.1'
        classpath 'com.google.ar.sceneform:plugin:1.5.1'
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

Load the material at runtime

In onCreate() call:

ModelRenderable.builder().setSource(this, R.raw.material_holder) .build().thenAccept( modelRenderable -> repeatingMaterial = modelRenderable.getMaterial());

This stores the material in the member field repeatingMaterial.

Set the parameters on the material

Modifying your original code to be:

  private void addLineBetweenPoints(AnchorNode from, Vector3 to) {
    // Compute a line's length
    float lineLength = Vector3.subtract(from.getWorldPosition(), to).length();
    // repeat the pattern every 10cm
    float lengthCM = lineLength * 100;

    repeatingMaterial.setFloat("repeat_x", lengthCM/10);
    repeatingMaterial.setFloat("repeat_y", lengthCM/10);
                // 3. make a model by the material
                ModelRenderable model = ShapeFactory.makeCylinder(0.0025f, lineLength,
                        new Vector3(0f, lineLength / 2, 0f), repeatingMaterial);
                model.setShadowReceiver(false);
                model.setShadowCaster(false);
                // make node
                Node node = new Node();
                node.setRenderable(model);
                node.setParent(from);
                // set rotation
                final Vector3 difference = Vector3.subtract(from.getWorldPosition(), to);
                final Vector3 directionFromTopToBottom = difference.normalized();
                final Quaternion rotationFromAToB =
                        Quaternion.lookRotation(directionFromTopToBottom, Vector3.up());
                node.setWorldRotation(Quaternion.multiply(rotationFromAToB,
                        Quaternion.axisAngle(new Vector3(1.0f, 0.0f, 0.0f), 90)));
  }