I wrote a function that takes the subpixels of an image for the purpose of upscaling, and the subpixel is generated by bilinear interpolation, but I am having some weird artifacts.
Here is my code:
public static int getSubPixel(BufferedImage bi, double x, double y) {
float[] topleft = new Color(bi.getRGB((int) Math.floor(x), (int) Math.floor(y))).getColorComponents(null);
float[] topright = new Color(bi.getRGB(Math.min(bi.getWidth() - 1, (int) Math.ceil(x)), (int) Math.floor(y))).getColorComponents(null);
float[] bottomleft = new Color(bi.getRGB((int) Math.floor(x), Math.min(bi.getHeight() - 1, (int) Math.ceil(y)))).getColorComponents(null);
float[] bottomright = new Color(bi.getRGB(Math.min(bi.getWidth() - 1, (int) Math.ceil(x)), Math.min(bi.getHeight() - 1, (int) Math.ceil(y)))).getColorComponents(null);
for (int i = 0; i < 3; i++) {
topleft[i] *= topleft[i];
topright[i] *= topright[i];
bottomleft[i] *= bottomleft[i];
bottomright[i] *= bottomright[i];
}
double decX = x % 1;
double decY = y % 1;
double inv_DecX = 1 - decX;
double inv_DecY = 1 - decY;
float red = (float) Math.sqrt((topleft[0] * inv_DecX + topright[0] * decX) * inv_DecY + (bottomleft[0] * inv_DecX + bottomright[0] * decX) * decY);
float green = (float) Math.sqrt((topleft[1] * inv_DecX + topright[1] * decX) * inv_DecY + (bottomleft[1] * inv_DecX + bottomright[1] * decX) * decY);
float blue = (float) Math.sqrt((topleft[2] * inv_DecX + topright[2] * decX) * inv_DecY + (bottomleft[2] * inv_DecX + bottomright[2] * decX) * decY);
return new Color(red, green, blue).getRGB();
}
This is the result of scaling up a 16x16 image 20 times:
As you can see, there is weird streaking going on. I did go out of my way to square the colors before averaging, then taking the square root of the result, but something does not seem right here. Any insight?
PS: I understand functions already exist to do this. This is an educational exercise. I am trying to understand the process by doing it on my own.
The stripe artifacts that you are seeing are caused by the linear interpolation scheme. Your implementation is correct (except for the squaring, which is unnecessary and causes the stripes to be stronger in darker regions of the image). This is what I'm seeing with a correct linear interpolation (16x instead of 20x as in the OP, I goofed) but without squaring (note less stripes in the dark blue parts):
If you want to get rid of the stripes, use a better interpolation scheme, such as cubic spline interpolation:
Related
I'm currently working on a raycaster in Java, and so far, I have the floor correctly textured. The problem, however, is that the floor doesn't scroll. In other words, when I move the camera in the projection, the floor stays the same, yet the walls move as expected. I'm really not sure what I'm doing wrong. I took almost all the code from this reference. Note that I took some liberties when pasting the code in that I used some pseudocode.
I tried applying a player offset to the tileX and tileY variables, e.g., tileX += player.x, and all I got was a floor that scrolls far too quickly and incorrectly.
for every ray:
... // other stuff relating to the walls above here.
int start = (int)(wallY + wallHeight + 1);
double directionCos = cos(rad(ray.getAngle()));
double directionSin = sin(rad(ray.getAngle()));
int textureDim = 16;
for (int y = start; y < screenHeight; y++) {
double distance = screenHeight / (2.f * y - screenHeight);
distance /= cos(rad(player.getAngle()) - rad(ray.getAngle()));
// The source I grabbed the code from actually appends the player's x and y to the tileX and tileY variables, but this completely messes up the textures when I try to.
double tileX = distance * directionCos;
double tileY = distance * directionSin;
int textureX = Math.floorMod((int)(tileX * textureDim), textureDim);
int textureY = Math.floorMod((int)(tileY * textureDim), textureDim);
int rgb = floorTexture.getRGB(textureX, textureY);
projectionFloor.setRGB((int)wallX, y, rgb);
}
Below is an image of the floor.
Below is an animation visualizing the problem.
Below is an animation visualizing what happens if I try to apply a player position offset:
Fixed it on my own. Turns out that, yes, you do have to account for the player's position (shocker!); the source I got the code from just didn't do it correctly.
DTPP = distance to projection plane.
for every pixel y from wallY + wallHeight + 1 to projectionHeight:
double r = y - this.getPreferredSize().height / 2.f;
double d = (CAMERA_HEIGHT * DTPP / r) / ANGLE;
double tileX = CAMERA_X + d * RAY_COSANGLE;
double tileY = CAMERA_Y + d * RAY_SINANGLE;
int textureX = Math.floorMod((int) (tileX * TEXTURE_SIZE /
TEXTURE_SCALE), TEXTURE_SIZE);
int textureY = Math.floorMod((int) (tileY * TEXTURE_SIZE /
TEXTURE_SCALE), TEXTURE_SIZE);
... (drawing occurs here)
I want to render a cylinder in Opengl. For that i wrote an simple algorithm, that
generates me the points mesh by the parameters radius, height, xSubDivisions and ySubDivisions:
(Java)
for(int yDivision = 0; yDivision < yDivisionCount; yDivision++){
for(int xDivision = 0; xDivision < xDivisionCount; xDivision++){
float line[] = getVboLine(xDivision, yDivision, radius, height, xDivisionCount, yDivisionCount);
string.append(line[0] + ", " + line[1] + ", " + line[2] + ", " + line[3] + ", " + line[4] + ", ");
}
}
public float[] getVboLine(int xDivision, int yDivision, float radius, float height, int xDivisionCount, int yDivisionCount){
float xDegrees = 360.0f / xDivisionCount * xDivision;
float xRadian = (float) Math.toRadians(xDegrees);
float x = (float) Math.sin(xRadian) * radius;
float z = (float) Math.cos(xRadian) * radius;
float y = (float) yDivision * (height / (yDivisionCount - 1));
float s = xDegrees * (1.0f / 360.0f);
float t = yDivision * (1.0f / (yDivisionCount - 1));
return new float[]{
x, y, z, s, t
};
}
The result is actually an cylinder, (i created an IBO to render this points) but sometimes, with different inputs for x and yDivisions there is a strange gap in it.
I couldn't find a rule, but the values i found this bug with were 200, 100.
To debug i rendered only the points. The result was:
How is this possible? One points is just missing (where i added the reed circle with paint).
Where is the problem with my algorithm?
I am not JAVA coder but you are mixing int and float together
for example:
xDegrees = 360.0f / xDivisionCount * xDivision
[float] [float] [int] [int]
I would rather use this:
xDegrees = float(360*xDivision)/float(xDivisionCount)
multiplication should go always first (if operands are >= 1)
and division after that to preserve accuracy
some weird rounding could cause your problem but it would be more noticeable for lower xDivisionCount not bigger one
Bug breakpoint
add to your code last generated point
after new point computation compute the distance from last point
add if (|distance-some_avg_distance|>1e-10)
and add breakpoint inside
some_avg_distance set by distance that should be there (get it from trace)
this way you can breakpoint the point causing problems (or the next point to it)
so you can actually see what is wrong
my bet is that by rounding you get the same angle as prev/next point
and therefore you do not have missing point but some duplicate instead
you can check that also by Blending
I would like to produce a projection matrix that will render an object from an arbitrary camera. I've managed to setup a viewMatrix that will look at the object from an arbitrary eye position, but I'm having difficulty setting up the projection matrix.
Given that an object centred at (x,y,z) who's furthest point is r from the centre can for any arbitrary orientation can be entirely enclosed within a sphere of radius r with origin (x,y,z), I calculated my perspective matrix as follows:
float dist2Object = (float) Math.sqrt(vec.lengthSquared());
float objectRadius = (float) Math.sqrt(3 * Math.pow(0.5, 2));
float far = dist2Object + objectRadius;
float near = dist2Object - objectRadius;
// fov_2 = FOV / 2
float fov_2 = (float) (Math.asin(objectRadius / dist2Object));
float r = (float) (Math.tan(fov_2));
float frustum_length = far - near;
depthProjectionMatrix.m00 = 1 / r;
depthProjectionMatrix.m11 = depthProjectionMatrix.m00;
depthProjectionMatrix.m22 = -((far + near) / frustum_length);
depthProjectionMatrix.m23 = -1;
depthProjectionMatrix.m32 = -((2 * near * far) / frustum_length);
depthProjectionMatrix.m33 = 0;
In my example:
vec is a vector from the camera to the object
the object is a cube who's furthest vertex is (0.5, 0.5, 0.5), giving a r of sqrt(0.75)
As far as I can tell, the geometry and trigonometry should be correct, but rendering the coordinates using the following fragment shader:
#version 150 core
in vec3 pCoord;
out vec4 out_Color;
void main(void) {
out_Color = vec4(0,1,0,1);
if(pCoord.x <= -1){
out_Color = vec4(1,0,0,1);
}
if(pCoord.x >= 1){
out_Color = vec4(0,0,1,1);
}
if(pCoord.z <= -1){
out_Color = vec4(1,0,1,1);
}
if(pCoord.z >= 1){
out_Color = vec4(1,0,1,1);
}
}
shown at image shows that the FOV is too narrow and that the near and far planes are also too narrow.
How can I fix this?
Haven't done this in a while, but it seem you are missing the viewport size in your calculation. Here is what I used for a project. Mind that the matrix here starts with _11 where you use m00:
double aspectRatio = (double)this.viewPort.Width /(double)this.viewPort.Height;
double fov = Math.toRadians(fieldOfView/2.0);
double size = nearClip * Math.tan(fov);
double left = -size* aspectRatio, right = size* aspectRatio, bottom = -size , top = size ;
projectionMatrix.resetToZero();
// the values in comments are for non symetrical frustrum
// First Column
projectionMatrix._11 = (float) (nearClip/right);//(float) ((2 * nearClip )/ (double)(right - left));
// Second Column
projectionMatrix._22 = (float)(nearClip/top);//(float) (2 * nearClip / (double)(top - bottom));
// Third Column
projectionMatrix._31 = 0;//(float) ((right + left) / (right - left));
projectionMatrix._32 = 0;//(float) ((top + bottom) / (top - bottom));
projectionMatrix._33 = -1*(farClip + nearClip) / (float)(farClip - nearClip);
projectionMatrix._34 = -1;
// Fourth Column
projectionMatrix._43 = -(2 * farClip * nearClip) / (float)(farClip - nearClip);
I started developing a custom Image class for a game which consists of three basic fields, width, height and a unidimensional array of int's which represent the color in the following order ARGB.
About two days ago i started trying to rotate images, and i was able to do that by converting this to a BufferedImage, rotate using Graphics2D and transforming it back to my own Image class, however setRGB and getRGB seem to be too slow and when i have to rotate about 10-20 images of 64*64 pixels the computer starts to struggle to maintain the fps.
So naturally i started developing my own image rotation function and i found a great post on gamedev.stackexchange.
https://gamedev.stackexchange.com/questions/67613/how-can-i-rotate-a-bitmap-without-d3d-or-opengl
The answer explains clearly what i should do to rotate an image even with different rotation points (which i intend to implement later).
However when following a similar formula to the one he explained (I had to change due to using a different coordinate system)
i find myself getting a strange wrapping at the top
Example (55 degrees): http://i.imgur.com/BBq83wV.png (The Black area represents the image size)
So i tried to distanciate the image from the top, and added
yDstPixel += this.height*sin;
Which sorta worked, but now the image gets clipped in half instead of wrapped
Example (35 degrees):http://i.imgur.com/Ap4aqrn.png
I'm almost sure the solution is very simple, but i cant seem to figure it out, a nudge in the right direction would be appreciated.
public Bitmap getRotatedCopy(double radians){
if(radians==0 || radians==(2*Math.PI)) return this;
double sin = Math.abs(Math.sin(radians));
double cos = Math.abs(Math.cos(radians));
int newWidth = (int) (this.width * cos + this.height * sin);
int newHeight = (int) (this.width * sin + this.height * cos);
Bitmap returnMap = new Bitmap(newWidth,newHeight); //set size of the returned bitmap to the smallest size possible
returnMap.fill(0xFF000000);
for (int y = 0; y < this.height; y++){
for(int x = 0; x < this.width; x++){
int srcPixel = x + (y * this.width);
int color= this.pixels[srcPixel];
if(color>0) continue;
int xDstPixel = (int) Math.abs((x * cos + y * sin));
int yDstPixel = (int) Math.abs((x * sin - y * cos));
//yDstPixel += this.height*sin;
int dstPixel = xDstPixel + (yDstPixel * newWidth);
returnMap.pixels[dstPixel]=color;
}
}
return returnMap;
}
You'll need to implement what you were planning to do later i.e. set the rotation origin and translation after the rotation.
I have modified your code to add them. (I didn't test running it but hope it works.) Please refer to the code below:
int newWidth = (int) (this.width * cos + this.height * sin);
int newHeight = (int) (this.width * sin + this.height * cos);
// After setting the new width and height...
// set rotation origin
double rox = this.width/2;
double roy = this.height/2;
// set translation center
double tcx = newWidth/2;
double tcy = newHeight/2;
Bitmap returnMap = new Bitmap(newWidth,newHeight);
returnMap.fill(0xFF000000);
for (int y = 0; y < this.height; y++){
double yy = y - roy;
for(int x = 0; x < this.width; x++){
double xx = x - rox;
int srcPixel = x + (y * this.width);
int color= this.pixels[srcPixel];
if(color>0) continue;
// following two lines are modified
int xDstPixel = (int) (xx * cos + yy * sin) + tcx;
int yDstPixel = (int) (xx * sin - yy * cos) + tcy;
// prevent negative index : maybe it is not needed at all
if (xDstPixel<0 || yDstPixel<0)
continue;
int dstPixel = xDstPixel + (yDstPixel * newWidth);
returnMap.pixels[dstPixel]=color;
}
}
I've been working recently on a fractal generator, and have been specifically working on the Mandelbrot set. Unfortunately, zooming and moving seems to be very inneficient and takes quite a while to refresh. I am generating it every time I zoom, and I know this is probably not the most efficient way of doing this, and I can't seem to find code that uses another method that I understand.
These are the following methods I use, the first being an intial generation, the second being a refresh method.
private void genMandelbrot(Dimension size) {
for(int x=0;x<size.width;x++) {
for(int y=0;y<size.height;y++) {
double moveX=globalx;
double moveY=globalx;
//zoom and x/y offset.
double real = 1.5 * (x - size.width / 2) / (0.5 * zoom * size.width) + moveX;
double imaginary=(y - size.height / 2) / (0.5 * zoom * size.height) + moveY;
double newRe=0,newIm=0,oldRe=0,oldIm=0;
int i;
for(i=0;i<8000;i++) {
oldRe = newRe;
oldIm = newIm;
newRe = oldRe * oldRe - oldIm * oldIm + real;
newIm = 2 * oldRe * oldIm + imaginary;
if((newRe * newRe + newIm * newIm) > 4) break;
}
Cell c = new Cell(Color.getHSBColor(i % 256, i % 255, 255 * ((i<20)? 1:0)), new Dimension(1,1), new Point(x,y));
cells.add(c);
}
}
}
public void refreshMandelbrot(Dimension size) {
for(Cell c : cells) {
double moveX=globalx;
double moveY=globalx;
int x=c.x;
int y=c.y;
//zoom and x/y offset.
double real = 1.5 * (x - size.width / 2) / (0.5 * zoom * size.width) + moveX;
double imaginary=(y - size.height / 2) / (0.5 * zoom * size.height) + moveY;
double newRe=0,newIm=0,oldRe=0,oldIm=0;
int i;
for(i=0;i<8000;i++) {
oldRe = newRe;
oldIm = newIm;
newRe = oldRe * oldRe - oldIm * oldIm + real;
newIm = 2 * oldRe * oldIm + imaginary;
if((newRe * newRe + newIm * newIm) > 4) break;
}
cells.set(cells.indexOf(c), new Cell(Color.getHSBColor(i % 256, i % 255, 255 * ((i<20)? 1:0)), new Dimension(1,1), new Point(x,y)));
}
System.out.println("Set refreshed.");
}
I suppose that cells is some kind of List implementation?
In that case, the most time of your refresh method is spent in this line:
cells.set(cells.indexOf(c), new Cell(Color.getHSBColor(i % 256, i % 255, 255 * ((i<20)? 1:0)), new Dimension(1,1), new Point(x,y)));
More precisely in cells.indexOf(c), where the entire list is iterated to find the correct index of c.
Since you are just changing the colour of each cell, the easiest fix is to change the colour of the cell you are currently working with. I don't know the actual implementation of your Cell class, but if it had a method setColor(...), you could replace the above line with
c.setColor(Color.getHSBColor(i % 256, i % 255, 255 * ((i<20)? 1:0)));
This reduces the runtime of the refreshMandelbrot method to the same as for the genMandelbrot method.
I don't know the purpose of the Cell class, but if you are only using it as a wrapper for a colour, you might gain some more performance if you store the computed colours for each pixel in a two-dimensional array or write directly to a Graphics or Raster object instead of handling a flat list of cell wrappers.
Most likely you need to subdivide the fractal and compute the less interesting tiles less intense. 8000 repetiton is a lot. You can also simplify the calculation a bit.