Unity Normal Maps don't work on Android device - android

I'm an experienced native iOS developer making my first foray into Android through Unity. I'm trying to set up a custom shader, but I'm having some trouble with the Normal maps. I've got them working perfectly in the Unity simulator on my computer, but when I build to an actual device (Samsung Galaxy S8+), the Normal maps don't work at all.
I'm using Mars as my test case. Here's the model running in the simulator on my computer:
And here's a screenshot from my device, running exactly the same code.
I've done a LOT of research, and apparently using Normal maps on Android with Unity is not an easy thing. There are a lot of people asking about it, but almost every answer I've found has said the trick is to override the texture import settings, and force it to be "Truecolor" which seems to be "RGBA 32 Bit" according to Unity's documentation. This hasn't helped me, though.
Another thread suggested reducing the Asino Level to zero, and another suggested turning off Mip Maps. I don't know what either of those are, but neither helped.
Here's my shader code, simplified but containing all references to Normal mapping:
void surf (Input IN, inout SurfaceOutputStandard o) {
half4 d = tex2D (_MainTex , IN.uv_MainTex);
half4 n = tex2D (_BumpMap , IN.uv_BumpMap);
o.Albedo = d.rgb;
o.Normal = UnpackNormal(n);
o.Metallic = 0.0;
o.Smoothness = 0.0;
}
I've seen some threads suggesting replacements for the "UnpackNormal()" function in the shader code, indicating that it might not be the thing to do on Android or mobile in general, but none of the suggested replacements have changed anything for better or worse: the normal maps continue to work in the simulator, but not on the device.
I've even tried making my own normal maps programmatically from a grayscale heightmap, to try to circumvent any import settings I may have done wrong. Here's the code I used, and again it works in the simulator but not on the device.
public Texture2D NormalMap(Texture2D source, float strength = 10.0f) {
Texture2D normalTexture;
float xLeft;
float xRight;
float yUp;
float yDown;
float yDelta;
float xDelta;
normalTexture = new Texture2D (source.width, source.height, TextureFormat.RGBA32, false, true);
for (int y=0; y<source.height; y++) {
for (int x=0; x<source.width; x++) {
xLeft = source.GetPixel (x - 1, y).grayscale * strength;
xRight = source.GetPixel (x + 1, y).grayscale * strength;
yUp = source.GetPixel (x, y - 1).grayscale * strength;
yDown = source.GetPixel (x, y + 1).grayscale * strength;
xDelta = ((xLeft - xRight) + 1) * 0.5f;
yDelta = ((yUp - yDown) + 1) * 0.5f;
normalTexture.SetPixel(x,y,new Color(xDelta,yDelta,1.0f,yDelta));
}
}
normalTexture.Apply();
return normalTexture;
}
Lastly, in the Build Settings, I've got the Platform set to Android and I've tried it using Texture Compression set to both "Don't Override" and "ETC (default)". The former was the original setting and the latter seemed to be Unity's suggestion both by the name and in the documentation.
I'm sure there's just some flag I haven't checked or some switch I haven't flipped, but I can't for the life of me figure out what I'm doing wrong here, or why there would be such a stubborn difference between the simulator and the device.
Can anyone help a Unity newbie out, and show me how these damn Normal maps are supposed to work on Android?

Check under:
Edit -> Project Settings -> Quality
Android is usually set to Fastest.

Related

Threejs - Import collada model looks funny on Chrome Android, textures are not properly loaded

I tried to import a .dae model using the provided ColladaLoader, it worked great on desktop but on Chrome on Android I'm issuing some problems. The Geometry is loaded, but the textures are not properly loaded. It seems that textures are applied to the object but they are not rendered with the proper colors , it looks all light blue.
Any one has an idea what I'm doing wrong? Is something related with anisotropic filtering (since I noticed that is not supported on chrome mobile ?)
The answer from the three.js creator himself is that this is a bug in the Qualcomm GPU driver related to the fog glsl code. To avoid this bug, turn off all phong mateirals' fog attribute.
phongMaterial.fog = false;
No fog on android until this bug is fixed.
Comparing the working and non-working examples on threejs.org site, I found the culprit for the bluish texture. It is the shadow mapping of directional light. Somehow this code below is causing problem on android devices.
light.castShadow = true;
light.shadowMapWidth = 2048;
light.shadowMapHeight = 2048;
var d = 390;
light.shadowCameraLeft = -d * 2;
light.shadowCameraRight = d * 2;
light.shadowCameraTop = d * 1.5;
light.shadowCameraBottom = -d;
light.shadowCameraFar = 3500;

Is it a big difference to use Box2D in native Android game instead of Box2Dweb with HTML5 canvas?

I have to write a simulation of balls falling in a container for Android. First, I tried using Box2Dweb in a HTML5 canvas, but with 3 solid bodies and 50 balls, it performs really slow, even in desktop computer with Firefox (curiously, with Chrome it performs really well). Here is the live demo.
And here is the code.
var b2Vec2 = Box2D.Common.Math.b2Vec2
, b2BodyDef = Box2D.Dynamics.b2BodyDef
, b2Body = Box2D.Dynamics.b2Body
, b2FixtureDef = Box2D.Dynamics.b2FixtureDef
, b2Fixture = Box2D.Dynamics.b2Fixture
, b2World = Box2D.Dynamics.b2World
, b2PolygonShape = Box2D.Collision.Shapes.b2PolygonShape
, b2CircleShape = Box2D.Collision.Shapes.b2CircleShape
, b2DebugDraw = Box2D.Dynamics.b2DebugDraw
;
var world = new b2World(
new b2Vec2(0, 10) //gravity
, true //allow sleep
);
var fixDef = new b2FixtureDef;
fixDef.density = 1.0;
fixDef.friction = 0.5;
fixDef.restitution = 0.2;
var bodyDef = new b2BodyDef;
//create ground
var canvas = $('#canvas'),
offsetX = (canvas.width() / 30) / 4,
offsetY = (canvas.height() / 30) / 5; //center the machine on the screen.
bodyDef.type = b2Body.b2_staticBody;
fixDef.shape = new b2PolygonShape;
fixDef.shape.SetAsBox(5, 0.5);
bodyDef.position.Set(5 + offsetX, 10 + offsetY);
world.CreateBody(bodyDef).CreateFixture(fixDef);
fixDef.shape.SetAsBox(0.5, 7);
bodyDef.position.Set(0 + offsetX, 3 + offsetY);
world.CreateBody(bodyDef).CreateFixture(fixDef);
bodyDef.position.Set(10 + offsetX, 3 + offsetY);
world.CreateBody(bodyDef).CreateFixture(fixDef);
//create some objects
var numObjects = 50;
bodyDef.type = b2Body.b2_dynamicBody;
for(var i = 0; i < numObjects; ++i) {
fixDef.shape = new b2CircleShape(
0.6 //Math.random() + 0.1 //radius
);
bodyDef.position.x = Math.random() * 9 + offsetX;
bodyDef.position.y = Math.random() * 9 - 2;
world.CreateBody(bodyDef).CreateFixture(fixDef);
}
//setup debug draw
var debugDraw = new b2DebugDraw();
debugDraw.SetSprite(document.getElementById("canvas").getContext("2d"));
debugDraw.SetDrawScale(30.0);
debugDraw.SetFillAlpha(0.5);
debugDraw.SetLineThickness(1.0);
debugDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit);
world.SetDebugDraw(debugDraw);
var rate = 60;
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
function( callback ){
window.setTimeout(callback, 1000 / rate);
};
})();
//update
(function update() {
requestAnimFrame(update);
world.Step(1 / rate, 10, 10);
world.DrawDebugData();
world.ClearForces();
})();
My question is, what if I write a native implementation using Android canvas (no the HTML5 one) and Box2D? Will I achieve a smooth movement for the balls?
And the hidden cuestion is: Is the performance so poor because of drawing or because of so many physical calculus? Usually, how much performance can I win going to native when there are physical calculus involved?
The main difference is that with HTML5 and Box2DWeb your game is limited by the browser optimizations and your own code optimizations.
Some browsers doesn't have hardware accelerated canvas; or the browser's Javascript Engine are not optimized enough. You can see that difference even in desktop browsers. Google Chrome for instance do a lot of optimizations behind the scene inside his V8 engine.
Because there's so many differences between Browser's Javascript Engines (as you notice with Firefox and Chrome) it's harder to do code optimizations for all of them.
Since mobile hardware is usually very limited and the mobile browsers are not evolved enough, make optimizations to active high frame rates is very painful and may not be accomplished at all.
For instance, the code you provide might suffer in browsers that haven't no native requestAnimationFrame. Also, drawing shapes on the fly is too expensive for low hardware devices. So the answer for your last question might be: both, drawing and the physics calculation are killing the performance. But the major bottleneck is the drawing for sure.
The use of native Android canvas allow you quick responses since the game will use the device hardware more efficiently than browsers.
In addiction, the Box2D for android is much more efficient than the Box2DWeb(a nice port of the original Box2D but still suffer with performance gaps).
Bottomline, if your target is primary Android devices you should go with the native implementation. But if you want to target a huge range of browsers and devices without do code again for every platform, go with the beautiful HTML5. (Every choice implies consequences, you have to choose that which best suit your needs).
If you decide goes with HTML5 canvas see this answer. (It's you own question, by the way :) )
And if you are really engaged learn a little about WebGL and OpenGL ES.

Canvas not displaying all drawn parts in Custom View?

I'm working on a custom view for an android application, similar to the Analog Gauge sample code available from Mind the Robot.
Running the code from listed site, I get see this on my screen:
(Motorola Droid, 2.2.3), (Emulator, 4.0.3)
(Xoom, 4.0.3)(Other phone, 4.0.3)
The hand is missing!
The drawing calls are being made (I can see them in logcat), but the canvas elements the calls draw are invisible.
It's not API level dependent, though; if I import it the right way into a project, it will hand will show up when I run it on the Xoom.
But, when I move the files to a different project folder (same source code, same layouts) it goes back to missing the dial.
What's going on? How could the same code be producing such different outcomes on different devices?
So, the key clue in my mystery seemed to be that it worked on the emulator, but not on the hardware devices.
Hardware Rendering
I did peruse the hardware rendering page on the Android Developer's website, but apparently not closely enough.
http://developer.android.com/guide/topics/graphics/hardware-accel.html
While it does mention that the API's are available beginning version 11, it does not say that Hardware Rendering is turned on for all applications by default, starting with API Level 14 (ICS).
What does this mean for us?
Almost everything is faster; except for the few things that don't work.
I managed to violate two of these, without realizing it:
Canvas.DrawTextOnPath()
Paint.setShadowLayer()
It's not mentioned in the API reference (or anywhere else I can find, and certainly not checked by Lint), but using any of the listed operations can do weird things.
In my case, Canvas.DrawTextOnPath() seemed to work just fine.
But when Android notice that the paint that I used on the hand had shadow layer set, it silently ignored it.
How do I know if my View is hardware accelerated?
From the documentation link above:
There are two different ways to check whether the application is hardware accelerated:
View.isHardwareAccelerated() returns true if the View is attached to a hardware accelerated window.
Canvas.isHardwareAccelerated() returns true if the Canvas is hardware accelerated
If you must do this check in your drawing code, use Canvas.isHardwareAccelerated() instead >of View.isHardwareAccelerated() when possible. When a view is attached to a hardware >accelerated window, it can still be drawn using a non-hardware accelerated Canvas. This >happens, for instance, when drawing a view into a bitmap for caching purposes.
In my case, the opposite appears to have occurred.
The custom view logs that it is not Hardware-accelerated; however, the canvas reports that it is hardware-accelerated.
Work Arounds and Fixings
The simplest fix is forcing the custom view to do software rendering. Per the documentation this can be accomplished by:
myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
Alternatively, you could remove the offending operations, and keep hardware rendering turned on.
Learn from my misfortune. Good luck, all.
I put it into init() and worked fine after that.
private void init() {
setLayerType(myView.LAYER_TYPE_SOFTWARE, null);
....
}
With myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null); suggestion I can see hand. But I have still a problem: I see scale with only 0 written! As in the picture and two strage zeros out of the schema: (GALAXY NEXUS 4.2.1)
My drawScale() method is as in the example:
private void drawScale(Canvas canvas) {
canvas.drawOval(scaleRect, scalePaint);
canvas.save(Canvas.MATRIX_SAVE_FLAG);
for (int i = 0; i < totalNicks; ++i) {
float y1 = scaleRect.top;
float y2 = y1 - 0.020f;
canvas.drawLine(0.5f, y1, 0.5f, y2, scalePaint);
if ((i % 5) == 0) {
int value = nickToDegree(i);
if ((value >= minDegrees) && (value <= maxDegrees)) {
String valueString = Integer.toString(value);
canvas.drawText(valueString, 0.5f, y2 - 0.015f, scalePaint);
}
}
canvas.rotate(degreesPerNick, 0.5f, 0.5f);
}
canvas.restore();
}
in my case i made this:
AnalogView bar = (AnalogView) findViewById(R.id.AnalogBar);
bar.setLayerType(bar.LAYER_TYPE_SOFTWARE, null);
if (value_list.size()>0) bar.SetData(Double.parseDouble(value_list.get(value_list.size()-1)));
where SetData in AnalogView is
public void SetData(double data) {
setHandTarget((float)data);
invalidate();
}
On Galaxy S4 Android 4.4.2
TYPE_TEMPERATURE is deprecated
use
TYPE_AMBIENT_TEMPERATURE
For anyone having problems with text drawing on scale in the initialisation do this:
scalePaint.setLinearText(true);

Position interpolation not working properly

I am developing an android game with box2d and use a fixed timestep system for advancing the physics.
However as I use this system it requires the box2d positions to be interpolates. I read this article
and have implemented an interpolation method very much like the one in the article.
The method seems to work nicely on the computer but on my phone the positions of objects are very jumpy. There is of course a big frame rate difference between PC and phone, but I think this algorithm should not mind that.
Here is the just of the code if you don't feel like looking at the article :
void PhysicsSystem::smoothStates_ ()
{
const float oneMinusRatio = 1.f - fixedTimestepAccumulatorRatio_;
for (b2Body * b = world_->GetBodyList (); b != NULL; b = b->GetNext ())
{
if (b->GetType () == b2_staticBody)
{
continue;
}
PhysicsComponent & c = PhysicsComponent::b2BodyToPhysicsComponent (* b);
c.smoothedPosition_ =
fixedTimestepAccumulatorRatio_ * b->GetPosition () +
oneMinusRatio * c.previousPosition_;
c.smoothedAngle_ =
fixedTimestepAccumulatorRatio_ * b->GetAngle () +
oneMinusRatio * c.previousAngle_;
}
}
Does anyone know why my game is acting like this?
Thanks for the help
That code in and of itself doesn't appear to have any issues as compared to the article. You might want to try posting this on https://gamedev.stackexchange.com/ and see if they have any recommendations.
Alternatively, here is a very well written article about having a semi-fixed time step, and decoupling physics and frame rate, which I imagine could be the problem. It isn't for Box2D, but reading over it might help you pinpoint the issue with your physics.

Issues with screen sizes and activity rendering

I'm currently developing my first android app, and my first game. I've been developing on a netbook with a CliqXT (HVGA). Things are going well, it renders perfectly on the smaller screen. I knew I'd have some issues when rendering on larger screens, but the issues I'm having are not what I was expecting and I'm kind of stuck.
So basically the game consists of a main SurfaceView which I'm rendering the tiled game world on to. I followed this tutorial to get started, and my structure is still pretty similar except that it calculates the boundries based on the player location:
http://www.droidnova.com/create-a-scrollable-map-with-cells-part-i,654.html
The game also has various buildings the player can enter. Upon entering it launches another activity for that particular building. The building activities are just normal Views with Android UI stuff defined in XML (Buttons, TextViews, etc).
What I expected to happen:
So I expected the the building UIs to render correctly on the larger screen. I specified all dimensions in "dp" and fonts in "sp" in hopes that they'd scale correctly. I expected the actual game tilemap to render generally correctly, but maybe be really tiny due to the higher resolution / dpi. I'm using a very similar function to the tutorial linked above (calculateLoopBorders(), my version is pasted below) to calculate how many tiles to render based on screen height and width (getHeight() and getWidth()).
What is actually happening:
The whole game is just being rendered as if it's HVGA. The tilemap, and the building UIs are just scaled down to the smaller screen size, leaving black borders around the left, right, and bottom (see images).
If anyone can point me in the right direction it'd be greatly appreciated, thanks a lot!
(Some of you may recognize this public domain DOS classic)
Edit: Thanks Christian for fixing code formatting.
mCellHeight and mCellWidth are the width/height of the cells in pixels
mMapHeight and mMapWidth are the width/height of the total game world in number of tiles
public void calculateLoopBorders() {
mWidth = getWidth();
mHeight = getHeight();
mStartRow = (int) Math.max(0, mPlayer.mRow - ((int) (mHeight / 2) / mCellHeight));
mStartCol = (int) Math.max(0, mPlayer.mCol - ((int) (mWidth / 2) / mCellWidth));
mMaxRow = (int) Math.min(mMapHeight, mStartRow + (mHeight / mCellHeight)) + 1;
mMaxCol = (int) Math.min(mMapWidth, mStartCol + (mWidth / mCellWidth));
if (mMaxCol >= mMapWidth) {
mStartCol = mMaxCol - (mWidth / mCellWidth);
}
if (mMaxRow >= mMapHeight) {
mStartRow = mMaxRow - (mHeight / mCellHeight);
}
int x1 = mStartCol * mCellWidth;
int y1 = mStartRow * mCellHeight;
int x2 = x1 + mWidth;
int y2 = y1 + mHeight;
mBgSrcRect = new Rect(x1, y1, x2, y2);
mBgDestRect = new Rect(0,0, mWidth, mHeight);
}
I figured it out. I was targeting 1.5 in the Project so it was assuming HVGA. Targeting 2.1 fixes the issue and the bitmaps even seem to scale correctly using some kind of android magic.
I still have a question though, when I finish this game I want it to work with 1.5+ devices. Do I need to put separate builds into the market, one for each device class? This seems like a lot of trouble for something that could be handled in a line or 2 of code in the app itself... but I've never released an app so maybe it's easily handled in the process.

Categories

Resources