Generalize gdaldataconverter so it can handle any data type.

This commit is contained in:
Kalle Bladin
2016-05-03 16:00:29 -04:00
parent ce5876c2c5
commit 6f4e39cc7d
10 changed files with 234 additions and 219 deletions

View File

@@ -2,14 +2,7 @@
<Service name="TiledWMS">
<ServerUrl>http://map1.vis.earthdata.nasa.gov/twms-geo/twms.cgi?</ServerUrl>
<TiledGroupName>MODIS TERRA tileset</TiledGroupName>
<Change key="${time}">2016-04-27</Change>
<Change key="${time}">2016-04-12</Change>
</Service>
<DataWindow>
<UpperLeftX>-180.0</UpperLeftX>
<UpperLeftY>90.0</UpperLeftY>
<LowerRightX>180.0</LowerRightX>
<LowerRightY>-90.0</LowerRightY>
<YOrigin>bottom</YOrigin>
</DataWindow>
<MaxConnections>20</MaxConnections>
</GDAL_WMS>

View File

@@ -82,7 +82,7 @@ set(SOURCE_FILES
${CMAKE_CURRENT_SOURCE_DIR}/other/texturetileset.cpp
${CMAKE_CURRENT_SOURCE_DIR}/other/patchcoverageprovider.cpp
${CMAKE_CURRENT_SOURCE_DIR}/other/tileprovider.cpp
${CMAKE_CURRENT_SOURCE_DIR}/other/gdaldataconverter.cpp
${CMAKE_CURRENT_SOURCE_DIR}/other/gdaldataconverter.inl
${CMAKE_CURRENT_SOURCE_DIR}/other/lrucache.inl
${CMAKE_CURRENT_SOURCE_DIR}/other/concurrentjobmanager.inl

View File

@@ -49,7 +49,8 @@ namespace openspace {
: _clipMapPyramid(Geodetic2(M_PI / 2, M_PI / 2))
, _ellipsoid(ellipsoid)
{
_tileProvider = shared_ptr<TileProvider>(new TileProvider("map_service_configs/TERRAIN.wms", 100));
_tileProvider = shared_ptr<TileProvider>(new TileProvider(
"map_service_configs/TERRAIN.wms", 100));
// init Renderer
auto outerPatchRenderer = new ClipMapPatchRenderer(
shared_ptr<OuterClipMapGrid>(new OuterClipMapGrid(512)),

View File

@@ -36,24 +36,11 @@
#include <memory>
namespace openspace {
namespace openspace {
using namespace ghoul::opengl;
// forward declaration
class GeodeticTileIndex;
//class Geodetic2;
class GdalDataConverter
{
public:
GdalDataConverter();
~GdalDataConverter();
std::shared_ptr<Texture> convertToOpenGLTexture(
GDALDataset* dataSet,
const GeodeticTileIndex& tileIndex,
int GLType);
struct UninitializedTextureTile {
struct TextureFormat
{
@@ -61,17 +48,49 @@ namespace openspace {
GLuint glFormat;
};
struct TextureDataType
UninitializedTextureTile(
void* data,
glm::uvec3 dims,
TextureFormat format,
GLuint glType,
const GeodeticTileIndex& ti)
: imageData(data)
, dimensions(dims)
, texFormat(format)
, glType(glType)
, tileIndex(ti)
{
Texture::Format ghoulFormat;
GLuint glFormat;
};
TextureFormat getTextureFormatFromRasterCount(int rasterCount);
}
void* imageData;
glm::uvec3 dimensions;
TextureFormat texFormat;
GLuint glType;
const GeodeticTileIndex tileIndex;
};
template<class T>
class GdalDataConverter
{
public:
GdalDataConverter();
~GdalDataConverter();
std::shared_ptr<UninitializedTextureTile> getUninitializedTextureTile(
GDALDataset * dataSet,
const GeodeticTileIndex & tileIndex);
UninitializedTextureTile::TextureFormat getTextureFormatFromRasterCount(
int rasterCount);
GLuint getGlDataTypeFromGdalDataType(GDALDataType gdalType);
glm::uvec2 geodeticToPixel(GDALDataset* dataSet, const Geodetic2& geo);
};
} // namespace openspace
#include <modules/globebrowsing/other/gdaldataconverter.inl>
#endif // __GDALDATACONVERTER_H__

View File

@@ -27,92 +27,106 @@
#include <modules/globebrowsing/other/tileprovider.h>
#include <modules/globebrowsing/geodetics/angle.h>
namespace {
const std::string _loggerCat = "GdalDataConverter";
}
namespace openspace {
GdalDataConverter::GdalDataConverter()
template<class T>
GdalDataConverter<T>::GdalDataConverter()
{
}
GdalDataConverter::~GdalDataConverter()
template<class T>
GdalDataConverter<T>::~GdalDataConverter()
{
}
std::shared_ptr<Texture> GdalDataConverter::convertToOpenGLTexture(
template<class T>
std::shared_ptr<UninitializedTextureTile> GdalDataConverter<T>::getUninitializedTextureTile(
GDALDataset* dataSet,
const GeodeticTileIndex& tileIndex,
int GLType)
{
const GeodeticTileIndex& tileIndex) {
int nRasters = dataSet->GetRasterCount();
ghoul_assert(nRasters > 0, "Bad dataset. Contains no rasterband.");
GDALRasterBand* firstBand = dataSet->GetRasterBand(1);
// Assume all raster bands have the same data type
GDALDataType gdalType = firstBand->GetRasterDataType();
// Level = overviewCount - overview
int overviewCount = firstBand->GetOverviewCount();
int overview = overviewCount - tileIndex.level - 1;
int numOverviews = firstBand->GetOverviewCount();
// The output texture will have this size
int xSizelevel0 = firstBand->GetOverview(overviewCount - 1)->GetXSize();
int ySizelevel0 = firstBand->GetOverview(overviewCount - 1)->GetYSize();
int xSize0 = firstBand->GetXSize();
int ySize0 = firstBand->GetYSize();
GeodeticPatch patch = GeodeticPatch(tileIndex);
glm::uvec2 pixelStart0 = geodeticToPixel(dataSet, patch.northWestCorner());
glm::uvec2 pixelEnd0 = geodeticToPixel(dataSet, patch.southEastCorner());
glm::uvec2 numberOfPixels0 = pixelEnd0 - pixelStart0;
int minNumPixels0 = glm::min(numberOfPixels0.x, numberOfPixels0.y);
int minNumPixelsRequired = 512;
int ov = log2(minNumPixels0) - log2(minNumPixelsRequired + 1);
// The data that the texture should read
GLubyte* imageData = new GLubyte[xSizelevel0 * ySizelevel0 * nRasters];
ov = glm::clamp(ov, 0, numOverviews - 1);
glm::uvec2 pixelStart(pixelStart0.x >> (ov + 1), pixelStart0.y >> (ov + 1));
glm::uvec2 numberOfPixels(numberOfPixels0.x >> (ov + 1), numberOfPixels0.y >> (ov + 1));
// GDAL reads image data top to bottom
T* imageData = new T[numberOfPixels.x * numberOfPixels.y * nRasters];
// Read the data (each rasterband is a separate channel)
for (size_t i = 0; i < nRasters; i++)
{
GDALRasterBand* rasterBand = dataSet->GetRasterBand(i + 1)->GetOverview(overview);
int xBeginRead = tileIndex.x * pow(2, tileIndex.level) * xSizelevel0;
int yBeginRead = tileIndex.y * pow(2, tileIndex.level) * ySizelevel0;
for (size_t i = 0; i < nRasters; i++) {
GDALRasterBand* rasterBand = dataSet->GetRasterBand(i + 1)->GetOverview(ov);
int xSize = rasterBand->GetXSize();
int ySize = rasterBand->GetYSize();
rasterBand->RasterIO(
GF_Read,
xBeginRead, // Begin read x
yBeginRead, // Begin read y
xSizelevel0, // width to read x
ySizelevel0, // width to read y
imageData + i, // Where to put data
xSizelevel0, // width to read x in destination
ySizelevel0, // width to read y in destination
GDT_Byte, // Type
sizeof(GLubyte) * nRasters, // Pixel spacing
0); // Line spacing
pixelStart.x, // Begin read x
pixelStart.y, // Begin read y
numberOfPixels.x, // width to read x
numberOfPixels.y, // width to read y
imageData + i, // Where to put data
numberOfPixels.x, // width to write x in destination
numberOfPixels.y, // width to write y in destination
gdalType, // Type
sizeof(T) * nRasters, // Pixel spacing
0); // Line spacing
}
GdalDataConverter::TextureFormat textrureFormat =
// GDAL reads image data top to bottom. We want the opposite.
T* imageDataYflipped = new T[numberOfPixels.x * numberOfPixels.y * nRasters];
for (size_t y = 0; y < numberOfPixels.y; y++) {
for (size_t x = 0; x < numberOfPixels.x; x++) {
imageDataYflipped[x + y * numberOfPixels.x] =
imageData[x + (numberOfPixels.y - y) * numberOfPixels.x];
}
}
delete[] imageData;
glm::uvec3 dims(numberOfPixels.x, numberOfPixels.y, 1);
UninitializedTextureTile::TextureFormat textrureFormat =
getTextureFormatFromRasterCount(nRasters);
Texture* tex = new Texture(
static_cast<void*>(imageData),
glm::uvec3(xSizelevel0, ySizelevel0, 1),
textrureFormat.ghoulFormat,
textrureFormat.glFormat,
GL_UNSIGNED_BYTE,
Texture::FilterMode::Linear,
Texture::WrappingMode::Repeat);
// The texture should take ownership of the data
std::shared_ptr<Texture> texture = std::shared_ptr<Texture>(tex);
// Do not free imageData since the texture now has ownership of it
return texture;
GLuint glType = getGlDataTypeFromGdalDataType(gdalType);
UninitializedTextureTile* uninitedTexPtr = new UninitializedTextureTile(
imageDataYflipped,
dims,
textrureFormat,
glType,
tileIndex);
std::shared_ptr<UninitializedTextureTile> uninitedTex =
std::shared_ptr<UninitializedTextureTile>(uninitedTexPtr);
return uninitedTex;
}
glm::uvec2 GdalDataConverter::geodeticToPixel(GDALDataset* dataSet, const Geodetic2& geo) {
template<class T>
glm::uvec2 GdalDataConverter<T>::geodeticToPixel(GDALDataset* dataSet, const Geodetic2& geo) {
double padfTransform[6];
CPLErr err = dataSet->GetGeoTransform(padfTransform);
@@ -140,19 +154,17 @@ namespace openspace {
double L = (-a[0]*b[1] + a[1]*b[0] - a[1]*Y + b[1]*X) / divisor;
// ref: https://www.wolframalpha.com/input/?i=X+%3D+a0+%2B+a1P+%2B+a2L,+Y+%3D+b0+%2B+b1P+%2B+b2L,+solve+for+P+and+L
double Xp = a[0] + P*a[1] + L*a[2];
double Yp = b[0] + P*b[1] + L*b[2];
return glm::uvec2(P, L);
}
GdalDataConverter::TextureFormat GdalDataConverter::getTextureFormatFromRasterCount(
template<class T>
UninitializedTextureTile::TextureFormat GdalDataConverter<T>::getTextureFormatFromRasterCount(
int rasterCount)
{
TextureFormat format;
UninitializedTextureTile::TextureFormat format;
switch (rasterCount)
{
@@ -173,10 +185,42 @@ namespace openspace {
format.glFormat = GL_RGBA;
break;
default:
LERROR("Too many channels for OpenGL.");
break;
}
return format;
}
template<class T>
GLuint GdalDataConverter<T>::getGlDataTypeFromGdalDataType(GDALDataType gdalType)
{
switch (gdalType)
{
case GDT_Byte:
return GL_BYTE;
break;
case GDT_UInt16:
return GL_UNSIGNED_SHORT;
break;
case GDT_Int16:
return GL_SHORT;
break;
case GDT_UInt32:
return GL_UNSIGNED_INT;
break;
case GDT_Int32:
return GL_INT;
break;
case GDT_Float32:
return GL_FLOAT;
break;
case GDT_Float64:
return GL_DOUBLE;
break;
default:
LERROR("GDAL data type unknown to OpenGL: " << gdalType);
return GL_UNSIGNED_BYTE;
}
}
} // namespace openspace

View File

@@ -52,7 +52,7 @@ namespace openspace {
, _offsetLevel0(offsetLevel0)
, _depth(depth)
{
/*
// Read using GDAL
@@ -76,7 +76,7 @@ namespace openspace {
_testTexture->uploadTexture();
_testTexture->setFilter(ghoul::opengl::Texture::FilterMode::Linear);
*/
/*
// Set e texture to test
std::string fileName = "textures/earth_bluemarble.jpg";

View File

@@ -51,7 +51,9 @@ namespace openspace {
{
int downloadApplicationVersion = 1;
if (!DownloadManager::isInitialized()) {
DownloadManager::initialize("../tmp_openspace_downloads/", downloadApplicationVersion);
DownloadManager::initialize(
"../tmp_openspace_downloads/",
downloadApplicationVersion);
}
if (!hasInitializedGDAL) {
@@ -64,25 +66,23 @@ namespace openspace {
//auto desc = _gdalDataSet->GetDescription();
ghoul_assert(_gdalDataSet != nullptr, "Failed to load dataset: " << filePath);
}
TileProvider::~TileProvider(){
delete _gdalDataSet;
}
void TileProvider::prerender() {
while (_tileLoadManager.numFinishedJobs() > 0) {
auto finishedJob = _tileLoadManager.popFinishedJob();
std::shared_ptr<UninitializedTextureTile> uninitedTex = finishedJob->product();
std::shared_ptr<UninitializedTextureTile> uninitedTex =
finishedJob->product();
HashKey key = uninitedTex->tileIndex.hashKey();
std::shared_ptr<Texture> texture = initializeTexture(uninitedTex);
_tileCache.put(key, texture);
}
}
std::shared_ptr<Texture> TileProvider::getTile(const GeodeticTileIndex& tileIndex) {
HashKey hashkey = tileIndex.hashKey();
@@ -102,101 +102,60 @@ namespace openspace {
}
}
std::shared_ptr<UninitializedTextureTile> TileProvider::getUninitializedTextureTile(
const GeodeticTileIndex& tileIndex) {
// We assume here that all rasterbands have the same data type
GDALDataType gdalType = _gdalDataSet->GetRasterBand(1)->GetRasterDataType();
std::shared_ptr<UninitializedTextureTile> TileProvider::getUninitializedTextureTile(const GeodeticTileIndex& tileIndex) {
int nRasters = _gdalDataSet->GetRasterCount();
ghoul_assert(nRasters > 0, "Bad dataset. Contains no rasterband.");
GDALRasterBand* firstBand = _gdalDataSet->GetRasterBand(1);
// Level = overviewCount - overview
int numOverviews = firstBand->GetOverviewCount();
int xSize0 = firstBand->GetXSize();
int ySize0 = firstBand->GetYSize();
GeodeticPatch patch = GeodeticPatch(tileIndex);
glm::uvec2 pixelStart0 = _converter.geodeticToPixel(_gdalDataSet, patch.northWestCorner());
glm::uvec2 pixelEnd0 = _converter.geodeticToPixel(_gdalDataSet, patch.southEastCorner());
glm::uvec2 numberOfPixels0 = pixelEnd0 - pixelStart0;
int minNumPixels0 = glm::min(numberOfPixels0.x, numberOfPixels0.y);
int minNumPixelsRequired = 512;
int ov = log2(minNumPixels0) - log2(minNumPixelsRequired);
ov = glm::clamp(ov, 0, numOverviews - 1);
glm::uvec2 pixelStart(pixelStart0.x >> (ov + 1), pixelStart0.y >> (ov + 1));
glm::uvec2 numberOfPixels(numberOfPixels0.x >> (ov + 1), numberOfPixels0.y >> (ov + 1));
// GDAL reads image data top to bottom
GLubyte* imageData = new GLubyte[numberOfPixels.x * numberOfPixels.y * nRasters];
// Read the data (each rasterband is a separate channel)
for (size_t i = 0; i < nRasters; i++) {
GDALRasterBand* rasterBand = _gdalDataSet->GetRasterBand(i + 1)->GetOverview(ov);
int xSize = rasterBand->GetXSize();
int ySize = rasterBand->GetYSize();
rasterBand->RasterIO(
GF_Read,
pixelStart.x, // Begin read x
pixelStart.y, // Begin read y
numberOfPixels.x, // width to read x
numberOfPixels.y, // width to read y
imageData + i, // Where to put data
numberOfPixels.x, // width to write x in destination
numberOfPixels.y, // width to write y in destination
GDT_Byte, // Type
sizeof(GLubyte) * nRasters, // Pixel spacing
0); // Line spacing
switch (gdalType)
{
case GDT_Byte:
return _uByteConverter.getUninitializedTextureTile(_gdalDataSet, tileIndex);
break;
case GDT_UInt16:
return _uShortConverter.getUninitializedTextureTile(_gdalDataSet, tileIndex);
break;
case GDT_Int16:
return _shortConverter.getUninitializedTextureTile(_gdalDataSet, tileIndex);
break;
case GDT_UInt32:
return _uIntConverter.getUninitializedTextureTile(_gdalDataSet, tileIndex);
break;
case GDT_Int32:
return _intConverter.getUninitializedTextureTile(_gdalDataSet, tileIndex);
break;
case GDT_Float32:
return _floatConverter.getUninitializedTextureTile(_gdalDataSet, tileIndex);
break;
case GDT_Float64:
return _doubleConverter.getUninitializedTextureTile(_gdalDataSet, tileIndex);
break;
default:
LERROR("GDAL data type unknown to OpenGL: " << gdalType);
}
// GDAL reads image data top to bottom. We want the opposite.
GLubyte* imageDataYflipped = new GLubyte[numberOfPixels.x * numberOfPixels.y * nRasters];
for (size_t y = 0; y < numberOfPixels.y; y++) {
for (size_t x = 0; x < numberOfPixels.x; x++) {
imageDataYflipped[x + y * numberOfPixels.x] = imageData[x + (numberOfPixels.y - y) * numberOfPixels.x];
}
}
delete[] imageData;
glm::uvec3 dims(numberOfPixels.x, numberOfPixels.y, 1);
GdalDataConverter::TextureFormat textrureFormat = _converter.getTextureFormatFromRasterCount(nRasters);
UninitializedTextureTile* uninitedTexPtr = new UninitializedTextureTile(imageDataYflipped, dims, textrureFormat, tileIndex);
std::shared_ptr<UninitializedTextureTile> uninitedTex = std::shared_ptr<UninitializedTextureTile>(uninitedTexPtr);
return uninitedTex;
return nullptr;
}
std::shared_ptr<Texture> TileProvider::initializeTexture(std::shared_ptr<UninitializedTextureTile> uninitedTexture) {
std::shared_ptr<Texture> TileProvider::initializeTexture(
std::shared_ptr<UninitializedTextureTile> uninitedTexture) {
Texture* tex = new Texture(
static_cast<void*>(uninitedTexture->imageData),
uninitedTexture->imageData,
uninitedTexture->dimensions,
uninitedTexture->texFormat.ghoulFormat,
uninitedTexture->texFormat.glFormat,
GL_UNSIGNED_BYTE,
uninitedTexture->glType,
Texture::FilterMode::Linear,
Texture::WrappingMode::Repeat);
Texture::WrappingMode::ClampToBorder);
// The texture should take ownership of the data
std::shared_ptr<Texture> texture = std::shared_ptr<Texture>(tex);
texture->uploadTexture();
/*
texture->setWrapping(ghoul::opengl::Texture::WrappingMode::ClampToBorder);
texture->setFilter(ghoul::opengl::Texture::FilterMode::Linear);
*/
return texture;
}

View File

@@ -39,39 +39,18 @@
#include <modules/globebrowsing/other/concurrentjobmanager.h>
#include <modules/globebrowsing/other/gdaldataconverter.h>
namespace openspace {
struct UninitializedTextureTile {
UninitializedTextureTile(GLubyte* data, glm::uvec3 dims,
GdalDataConverter::TextureFormat format, const GeodeticTileIndex& ti)
: imageData(data)
, dimensions(dims)
, texFormat(format)
, tileIndex(ti){
}
GLubyte * imageData;
glm::uvec3 dimensions;
GdalDataConverter::TextureFormat texFormat;
const GeodeticTileIndex tileIndex;
};
}
//////////////////////////////////////////////////////////////////////////////////////////
// TILE PROVIDER //
//////////////////////////////////////////////////////////////////////////////////////////
namespace openspace {
using namespace ghoul::opengl;
/**
Provides tiles through GDAL datasets which can be defined with xml files
for example for wms.
*/
class TileProvider {
public:
TileProvider(const std::string& fileName, int tileCacheSize);
@@ -81,26 +60,39 @@ namespace openspace {
void prerender();
private:
friend class TextureTileLoadJob;
std::shared_ptr<UninitializedTextureTile> getUninitializedTextureTile(const GeodeticTileIndex& tileIndex);
std::shared_ptr<Texture> initializeTexture(std::shared_ptr<UninitializedTextureTile> uninitedTexture);
/**
Fetches all the needeed texture data from the GDAL dataset.
*/
std::shared_ptr<UninitializedTextureTile> getUninitializedTextureTile(
const GeodeticTileIndex& tileIndex);
/**
Creates an OpenGL texture and pushes the data to the GPU.
*/
std::shared_ptr<Texture> initializeTexture(
std::shared_ptr<UninitializedTextureTile> uninitedTexture);
LRUCache<HashKey, std::shared_ptr<Texture>> _tileCache;
const std::string _filePath;
static bool hasInitializedGDAL;
GDALDataset* _gdalDataSet;
GdalDataConverter _converter;
// Converters are needed for all different data types since they are templated.
GdalDataConverter<GLbyte> _uByteConverter;
GdalDataConverter<GLushort> _uShortConverter;
GdalDataConverter<GLshort> _shortConverter;
GdalDataConverter<GLuint> _uIntConverter;
GdalDataConverter<GLint> _intConverter;
GdalDataConverter<GLfloat> _floatConverter;
GdalDataConverter<GLdouble> _doubleConverter;
ConcurrentJobManager<UninitializedTextureTile> _tileLoadManager;
};
} // namespace openspace

View File

@@ -114,18 +114,21 @@ void main()
PositionNormalPair pair = globalInterpolation(fs_uv);
vec4 textureColor = vec4(0);
float sampledHeight00, sampledHeight10, sampledHeight01, sampledHeight11;
vec2 uv00 = vec2(uvTransformPatchToTile00 * vec3(fs_uv.s, fs_uv.t, 1));
textureColor += texture(textureSampler00, uv00);
sampledHeight00 = texture(textureSampler00, uv00).r;
vec2 uv10 = vec2(uvTransformPatchToTile10 * vec3(fs_uv.s, fs_uv.t, 1));
textureColor += texture(textureSampler10, uv10);
sampledHeight10 = texture(textureSampler10, uv10).r;
vec2 uv01 = vec2(uvTransformPatchToTile01 * vec3(fs_uv.s, fs_uv.t, 1));
textureColor += texture(textureSampler01, uv01);
sampledHeight01 = texture(textureSampler01, uv01).r;
vec2 uv11 = vec2(uvTransformPatchToTile11 * vec3(fs_uv.s, fs_uv.t, 1));
textureColor += texture(textureSampler11, uv11);
sampledHeight11 = texture(textureSampler11, uv11).r;
pair.position += pair.normal * textureColor.r * 1e4;
float sampledHeight = max(sampledHeight00, max(sampledHeight10, max(sampledHeight01, sampledHeight11)));
pair.position += pair.normal * sampledHeight * 1e5;
vec4 position = modelViewProjectionTransform * vec4(pair.position, 1);
fs_position = pair.position;

View File

@@ -58,18 +58,22 @@ Fragment getFragment() {
Fragment frag;
frag.color = vec4(0);
vec4 color00, color10, color01, color11;
vec2 uv00 = vec2(uvTransformPatchToTile00 * vec3(fs_uv.s, fs_uv.t, 1));
frag.color += texture(textureSampler00, uv00);
color00 = texture(textureSampler00, uv00);
vec2 uv10 = vec2(uvTransformPatchToTile10 * vec3(fs_uv.s, fs_uv.t, 1));
frag.color += texture(textureSampler10, uv10);
color10 += texture(textureSampler10, uv10);
vec2 uv01 = vec2(uvTransformPatchToTile01 * vec3(fs_uv.s, fs_uv.t, 1));
frag.color += texture(textureSampler01, uv01);
color01 += texture(textureSampler01, uv01);
vec2 uv11 = vec2(uvTransformPatchToTile11 * vec3(fs_uv.s, fs_uv.t, 1));
frag.color += texture(textureSampler11, uv11);
color11 += texture(textureSampler11, uv11);
frag.color = max(color00, max(color10, max(color01, color11))) * 10;
//frag.color = frag.color * 0.5 + 0.999*texture(textureSampler, fs_uv);
//frag.color /= 4;