-
Notifications
You must be signed in to change notification settings - Fork 16
In Memory Textures
In this step, we will draw the wall based on some texture. This texture will be written inside the code so, it will be an integer matrix, where each number will identifier some color. This is the concept of memory texture. This step is not so hard, but I recommend you to understand about module (%) operator. This operator is used to get the position in some number interval, without the number gets out of the interval bounds.
Note: We will not load external textures in this step to make our work easier, but in other tutorial we will use external texture files to put in out RayCasting.
To start this step, we will add some new attributes for out data. The new root atributte will be a list of textures, and each list item will have some data of the textures like width
, height
, bitmap
and colors list
. Check the table to understand what each attribute is.
Attribute | Description |
---|---|
Texture width | The width of the bitmap |
Texture height | The height of the bitmap |
Texture bitmap | The bitmap (integer matrix) of the texture |
Texture colors | The list of the colors that correspond to the integer in the bitmap matrix position |
Note: We will use just one texture, but you can add more to use in your map.
The addictional code will be:
// Data
let data = {
// ...
textures: [
{
width: 8,
height: 8,
bitmap: [
[1,1,1,1,1,1,1,1],
[0,0,0,1,0,0,0,1],
[1,1,1,1,1,1,1,1],
[0,1,0,0,0,1,0,0],
[1,1,1,1,1,1,1,1],
[0,0,0,1,0,0,0,1],
[1,1,1,1,1,1,1,1],
[0,1,0,0,0,1,0,0]
],
colors: [
"rgb(255, 241, 232)",
"rgb(194, 195, 199)",
]
}
]
}
The textured we used has 8x8 dimension, but you can create bigger textures. Don't forget that bigger textures will need more render processing. This texture represents a brick wall, but you can change the bitmap as you want.
The logic of the texture processing in RayCasting can be separated as topics. This logic will works in the rayCasting()
function:
- Get the texture X position based on each throwed ray coordinates
- Change the wall drawer function to it uses the texture colors
After discover the wall height, we will get the x coordinate of the texture based on ray coordinates. This is necessary to discover what is the texture strip we will use to draw in our projection. We have to multiply the position by the texture width to make the texture has the same wall width. After it, we will use the module (%) operator to keep the x-axis inside the texture width interval. For example:
- Ray coordinates:
ray.x = 15
andray.y = 23
- Texture size:
texture.width = 8
- Texture width position:
position = (ray.x + ray.y) * texture.width
,position = (15 + 23) * 8
,position = 304
- Texture interval offset:
position = position % texture.width
,position = 304 % 8
,position = 0
- The column zero
0
of our texture will be used for the strip render
In our code, we will add these lines after get the wall height value. The first line is for get the texture that will be processed by the map integer value position. The second line is the calc to discover the x coodinate of the texture:
// ...
// Wall height
let wallHeight = Math.floor(data.projection.halfHeight / distance);
// Get texture
let texture = data.textures[wall - 1];
// Calcule texture position
let texturePositionX = Math.floor((texture.width * (ray.x + ray.y)) % texture.width);
// ...
Note: Remember that we have to parse the value to integer because this value will be used to get the color in the texture bitmax (integer matrix). The function we used is
Math.floor()
After it, we will create the function to draw the texture. The function will have a loop to iterate the texture height and some calcs to divide the texture height to discover the positions to draw the line. The parameters of this function are:
-
x
: the x-axis coordinate to draw the strip -
wallHeight
: To know the strip height -
texturePositionX
: The position x of the texture we calculed before -
texture
: the texture we got from ray collide
/**
* Draw texture
* @param {*} x
* @param {*} wallHeight
* @param {*} texturePositionX
* @param {*} texture
*/
function drawTexture(x, wallHeight, texturePositionX, texture) {
}
Now, we will define two variables inside the function. The first variable is yIncrementer
to know what is the value we will increment to our y-axis render cursor. The second is the y cursor that will be used to draw the line.
-
yIncrementer
: This value is calculed based in thewallHeight
. We have to divide the wall height by the texture height to discover what the value we will increment to our y cursor. Remender we need to multiply the wall height by 2 (two) because the wall height is defined based in the projection halfHeight. -
y
: This is the render cursor. We divided the strip by the texture height and we will use this variable to control the position of the lines we need to draw to create our strip.
/**
* Draw texture
* @param {*} x
* @param {*} wallHeight
* @param {*} texturePositionX
* @param {*} texture
*/
function drawTexture(x, wallHeight, texturePositionX, texture) {
let yIncrementer = (wallHeight * 2) / texture.height;
let y = data.projection.halfHeight - wallHeight;
}
Now we just need to create the loop to iterate the texture height and draw the strip lines usign the cursor. Inside the loop, we need to get the color that will be used for the strip. To get the color, we will check the texture bitmap using the coordinates we have.
/**
* Draw texture
* @param {*} x
* @param {*} wallHeight
* @param {*} texturePositionX
* @param {*} texture
*/
function drawTexture(x, wallHeight, texturePositionX, texture) {
let yIncrementer = (wallHeight * 2) / texture.height;
let y = data.projection.halfHeight - wallHeight;
for(let i = 0; i < texture.height; i++) {
screenContext.strokeStyle = texture.colors[texture.bitmap[i][texturePositionX]];
}
}
All right! The next step is draw the line. Now we have the color, the strip cursor and the incrementer.
/**
* Draw texture
* @param {*} x
* @param {*} wallHeight
* @param {*} texturePositionX
* @param {*} texture
*/
function drawTexture(x, wallHeight, texturePositionX, texture) {
let yIncrementer = (wallHeight * 2) / texture.height;
let y = data.projection.halfHeight - wallHeight;
for(let i = 0; i < texture.height; i++) {
screenContext.strokeStyle = texture.colors[texture.bitmap[i][texturePositionX]];
screenContext.beginPath();
screenContext.moveTo(x, y);
screenContext.lineTo(x, y + (yIncrementer + 0.5));
screenContext.stroke();
y += yIncrementer;
}
}
Note: The plus value '0.5' in the
yIncrementer
is used to draw the line in the entire pixel. HTML5 canvas considers the half of the pixel for drawing. If you remove it, the textures can shows some white pixels after render.
The last thing to do is just change the wall drawer of our rayCasting()
function to use the drawTexture()
function. Let's enjoy and change the old colors we used to render the ceiling and floor to better colors.
// ...
// Draw
drawLine(rayCount, 0, rayCount, data.projection.halfHeight - wallHeight, "black");
drawTexture(rayCount, wallHeight, texturePositionX, texture);
drawLine(rayCount, data.projection.halfHeight + wallHeight, rayCount, data.projection.height, "rgb(95, 87, 79)");
// ...
Congratulations! We can text our texture processing now running the code!
Scale: 4
Scale: 1
The entire code of this step is:
/**
* Raycasting logic
*/
function rayCasting() {
// ...
// Get texture
let texture = data.textures[wall - 1];
// Calcule texture position
let texturePositionX = Math.floor((texture.width * (ray.x + ray.y)) % texture.width);
// Draw
drawLine(rayCount, 0, rayCount, data.projection.halfHeight - wallHeight, "black");
drawTexture(rayCount, wallHeight, texturePositionX, texture);
drawLine(rayCount, data.projection.halfHeight + wallHeight, rayCount, data.projection.height, "rgb(95, 87, 79)");
// ...
}
/**
* Draw texture
* @param {*} x
* @param {*} wallHeight
* @param {*} texturePositionX
* @param {*} texture
*/
function drawTexture(x, wallHeight, texturePositionX, texture) {
let yIncrementer = (wallHeight * 2) / texture.height;
let y = data.projection.halfHeight - wallHeight;
for(let i = 0; i < texture.height; i++) {
screenContext.strokeStyle = texture.colors[texture.bitmap[i][texturePositionX]];
screenContext.beginPath();
screenContext.moveTo(x, y);
screenContext.lineTo(x, y + (yIncrementer + 0.5));
screenContext.stroke();
y += yIncrementer;
}
}
Well done. The next tutorial will teach how to use external textures.
Copyright © 2018 Vinícius Reif Biavatti
- Home
- RayTrancing
- Examples
- Basic Tutorial
- Intermediary Tutorial
- Advanced Tutorial