- Added Volume texture 3D loading support to the DDSLoader
- Added a test case for texture 3D loading
- fixes a minor log issue in Material.java

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@8003 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
3.0
rem..om 14 years ago
parent 6c7082a7e3
commit 91477b73f5
  1. 256
      engine/src/core-plugins/com/jme3/texture/plugins/DDSLoader.java
  2. 49
      engine/src/core/com/jme3/asset/TextureKey.java
  3. 7
      engine/src/core/com/jme3/material/Material.java
  4. BIN
      engine/src/test-data/Textures/3D/flame.dds
  5. 65
      engine/src/test/jme3test/texture/TestTexture3DLoading.java
  6. 14
      engine/src/test/jme3test/texture/tex3DThumb.frag
  7. 18
      engine/src/test/jme3test/texture/tex3DThumb.j3md
  8. 11
      engine/src/test/jme3test/texture/tex3DThumb.vert

@ -29,7 +29,6 @@
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.texture.plugins;
import com.jme3.asset.AssetInfo;
@ -62,14 +61,11 @@ public class DDSLoader implements AssetLoader {
private static final Logger logger = Logger.getLogger(DDSLoader.class.getName());
private static final boolean forceRGBA = false;
private static final int DDSD_MANDATORY = 0x1007;
private static final int DDSD_MANDATORY_DX10 = 0x6;
private static final int DDSD_MIPMAPCOUNT = 0x20000;
private static final int DDSD_LINEARSIZE = 0x80000;
private static final int DDSD_DEPTH = 0x800000;
private static final int DDPF_ALPHAPIXELS = 0x1;
private static final int DDPF_FOURCC = 0x4;
private static final int DDPF_RGB = 0x40;
@ -79,31 +75,24 @@ public class DDSLoader implements AssetLoader {
private static final int DDPF_ALPHA = 0x2;
// used by NVTextureTools to mark normal images.
private static final int DDPF_NORMAL = 0x80000000;
private static final int SWIZZLE_xGxR = 0x78477852;
private static final int DDSCAPS_COMPLEX = 0x8;
private static final int DDSCAPS_TEXTURE = 0x1000;
private static final int DDSCAPS_MIPMAP = 0x400000;
private static final int DDSCAPS2_CUBEMAP = 0x200;
private static final int DDSCAPS2_VOLUME = 0x200000;
private static final int PF_DXT1 = 0x31545844;
private static final int PF_DXT3 = 0x33545844;
private static final int PF_DXT5 = 0x35545844;
private static final int PF_ATI1 = 0x31495441;
private static final int PF_ATI2 = 0x32495441; // 0x41544932;
private static final int PF_DX10 = 0x30315844; // a DX10 format
private static final int DX10DIM_BUFFER = 0x1,
DX10DIM_TEXTURE1D = 0x2,
DX10DIM_TEXTURE2D = 0x3,
DX10DIM_TEXTURE3D = 0x4;
DX10DIM_TEXTURE1D = 0x2,
DX10DIM_TEXTURE2D = 0x3,
DX10DIM_TEXTURE3D = 0x4;
private static final int DX10MISC_GENERATE_MIPS = 0x1,
DX10MISC_TEXTURECUBE = 0x4;
DX10MISC_TEXTURECUBE = 0x4;
private static final double LOG2 = Math.log(2);
private int width;
private int height;
@ -115,60 +104,62 @@ public class DDSLoader implements AssetLoader {
private int caps2;
private boolean directx10;
private boolean compressed;
private boolean texture3D;
private boolean grayscaleOrAlpha;
private boolean normal;
private Format pixelFormat;
private int bpp;
private int[] sizes;
private int redMask, greenMask, blueMask, alphaMask;
private int redMask, greenMask, blueMask, alphaMask;
private DataInput in;
public DDSLoader() {
}
public Object load(AssetInfo info) throws IOException{
if (!(info.getKey() instanceof TextureKey))
public Object load(AssetInfo info) throws IOException {
if (!(info.getKey() instanceof TextureKey)) {
throw new IllegalArgumentException("Texture assets must be loaded using a TextureKey");
}
InputStream stream = info.openStream();
in = new LittleEndien(stream);
loadHeader();
ArrayList<ByteBuffer> data = readData( ((TextureKey)info.getKey()).isFlipY() );
stream.close();
return new Image(pixelFormat, width, height, 0, data, sizes);
ArrayList<ByteBuffer> data = readData(((TextureKey) info.getKey()).isFlipY());
stream.close();
return new Image(pixelFormat, width, height, depth, data, sizes);
}
public Image load(InputStream stream) throws IOException{
public Image load(InputStream stream) throws IOException {
in = new LittleEndien(stream);
loadHeader();
ArrayList<ByteBuffer> data = readData(false);
return new Image(pixelFormat, width, height, 0, data, sizes);
return new Image(pixelFormat, width, height, depth, data, sizes);
}
private void loadDX10Header() throws IOException{
private void loadDX10Header() throws IOException {
int dxgiFormat = in.readInt();
if (dxgiFormat != 83){
throw new IOException("Only DXGI_FORMAT_BC5_UNORM " +
"is supported for DirectX10 DDS! Got: "+dxgiFormat);
if (dxgiFormat != 83) {
throw new IOException("Only DXGI_FORMAT_BC5_UNORM "
+ "is supported for DirectX10 DDS! Got: " + dxgiFormat);
}
pixelFormat = Format.LATC;
bpp = 8;
compressed = true;
int resDim = in.readInt();
if (resDim == DX10DIM_TEXTURE3D){
// mark texture as 3D
if (resDim == DX10DIM_TEXTURE3D) {
texture3D = true;
}
int miscFlag = in.readInt();
int arraySize = in.readInt();
if (is(miscFlag, DX10MISC_TEXTURECUBE)){
if (is(miscFlag, DX10MISC_TEXTURECUBE)) {
// mark texture as cube
if (arraySize != 6){
if (arraySize != 6) {
throw new IOException("Cubemaps should consist of 6 images!");
}
}
in.skipBytes(4); // skip reserved value
}
@ -185,7 +176,7 @@ public class DDSLoader implements AssetLoader {
if (!is(flags, DDSD_MANDATORY) && !is(flags, DDSD_MANDATORY_DX10)) {
throw new IOException("Mandatory flags missing");
}
height = in.readInt();
width = in.readInt();
pitchOrSize = in.readInt();
@ -199,17 +190,23 @@ public class DDSLoader implements AssetLoader {
caps2 = in.readInt();
in.skipBytes(12);
if (!directx10){
if (!directx10) {
if (!is(caps1, DDSCAPS_TEXTURE)) {
throw new IOException("File is not a texture");
}
if (depth <= 0)
if (depth <= 0) {
depth = 1;
}
if (is(caps2, DDSCAPS2_CUBEMAP)) {
depth = 6; // somewhat of a hack, force loading 6 textures if a cubemap
}
if (is(caps2, DDSCAPS2_VOLUME)) {
texture3D = true;
}
}
int expectedMipmaps = 1 + (int) Math.ceil(Math.log(Math.max(height, width)) / LOG2);
@ -220,14 +217,14 @@ public class DDSLoader implements AssetLoader {
} else if (mipMapCount != expectedMipmaps) {
// changed to warning- images often do not have the required amount,
// or specify that they have mipmaps but include only the top level..
logger.log(Level.WARNING, "Got {0} mipmaps, expected {1}",
logger.log(Level.WARNING, "Got {0} mipmaps, expected {1}",
new Object[]{mipMapCount, expectedMipmaps});
}
} else {
mipMapCount = 1;
}
if (directx10){
if (directx10) {
loadDX10Header();
}
@ -268,7 +265,7 @@ public class DDSLoader implements AssetLoader {
case PF_DXT5:
bpp = 8;
pixelFormat = Image.Format.DXT5;
if (swizzle == SWIZZLE_xGxR){
if (swizzle == SWIZZLE_xGxR) {
normal = true;
}
break;
@ -297,7 +294,7 @@ public class DDSLoader implements AssetLoader {
logger.warning("Must use linear size with fourcc");
pitchOrSize = size;
} else if (pitchOrSize != size) {
logger.log(Level.WARNING, "Expected size = {0}, real = {1}",
logger.log(Level.WARNING, "Expected size = {0}, real = {1}",
new Object[]{size, pitchOrSize});
}
} else {
@ -321,7 +318,7 @@ public class DDSLoader implements AssetLoader {
} else {
pixelFormat = Format.RGB8;
}
} else if (is(pfFlags, DDPF_GRAYSCALE) && is(pfFlags, DDPF_ALPHAPIXELS)){
} else if (is(pfFlags, DDPF_GRAYSCALE) && is(pfFlags, DDPF_ALPHAPIXELS)) {
switch (bpp) {
case 16:
pixelFormat = Format.Luminance8Alpha8;
@ -368,7 +365,7 @@ public class DDSLoader implements AssetLoader {
logger.warning("Linear size said to contain valid value but does not");
pitchOrSize = size;
} else if (pitchOrSize != size) {
logger.log(Level.WARNING, "Expected size = {0}, real = {1}",
logger.log(Level.WARNING, "Expected size = {0}, real = {1}",
new Object[]{size, pitchOrSize});
}
} else {
@ -464,9 +461,9 @@ public class DDSLoader implements AssetLoader {
*/
public ByteBuffer readRGB2D(boolean flip, int totalSize) throws IOException {
int redCount = count(redMask),
blueCount = count(blueMask),
greenCount = count(greenMask),
alphaCount = count(alphaMask);
blueCount = count(blueMask),
greenCount = count(greenMask),
alphaCount = count(alphaMask);
if (redMask == 0x00FF0000 && greenMask == 0x0000FF00 && blueMask == 0x000000FF) {
if (alphaMask == 0xFF000000 && bpp == 32) {
@ -536,23 +533,20 @@ public class DDSLoader implements AssetLoader {
int mipWidth = width;
int mipHeight = height;
int offset = 0;
for (int mip = 0; mip < mipMapCount; mip++) {
if (flip){
if (flip) {
byte[] data = new byte[sizes[mip]];
in.readFully(data);
ByteBuffer wrapped = ByteBuffer.wrap(data);
wrapped.rewind();
ByteBuffer flipped = DXTFlipper.flipDXT(wrapped, mipWidth, mipHeight, pixelFormat);
buffer.put(flipped);
}else{
} else {
byte[] data = new byte[sizes[mip]];
in.readFully(data);
buffer.put(data);
}
offset += sizes[mip];
mipWidth = Math.max(mipWidth / 2, 1);
mipHeight = Math.max(mipHeight / 2, 1);
}
@ -561,6 +555,153 @@ public class DDSLoader implements AssetLoader {
return buffer;
}
/**
* Reads a grayscale image with mipmaps from the InputStream
* @param flip Flip the loaded image by Y axis
* @param totalSize Total size of the image in bytes including the mipmaps
* @return A ByteBuffer containing the grayscale image data with mips.
* @throws java.io.IOException If an error occured while reading from InputStream
*/
public ByteBuffer readGrayscale3D(boolean flip, int totalSize) throws IOException {
ByteBuffer buffer = BufferUtils.createByteBuffer(totalSize * depth);
if (bpp == 8) {
logger.finest("Source image format: R8");
}
assert bpp == pixelFormat.getBitsPerPixel();
for (int i = 0; i < depth; i++) {
int mipWidth = width;
int mipHeight = height;
for (int mip = 0; mip < mipMapCount; mip++) {
byte[] data = new byte[sizes[mip]];
in.readFully(data);
if (flip) {
data = flipData(data, mipWidth * bpp / 8, mipHeight);
}
buffer.put(data);
mipWidth = Math.max(mipWidth / 2, 1);
mipHeight = Math.max(mipHeight / 2, 1);
}
}
buffer.rewind();
return buffer;
}
/**
* Reads an uncompressed RGB or RGBA image.
*
* @param flip Flip the image on the Y axis
* @param totalSize Size of the image in bytes including mipmaps
* @return ByteBuffer containing image data with mipmaps in the format specified by pixelFormat_
* @throws java.io.IOException If an error occured while reading from InputStream
*/
public ByteBuffer readRGB3D(boolean flip, int totalSize) throws IOException {
int redCount = count(redMask),
blueCount = count(blueMask),
greenCount = count(greenMask),
alphaCount = count(alphaMask);
if (redMask == 0x00FF0000 && greenMask == 0x0000FF00 && blueMask == 0x000000FF) {
if (alphaMask == 0xFF000000 && bpp == 32) {
logger.finest("Data source format: BGRA8");
} else if (bpp == 24) {
logger.finest("Data source format: BGR8");
}
}
int sourcebytesPP = bpp / 8;
int targetBytesPP = pixelFormat.getBitsPerPixel() / 8;
ByteBuffer dataBuffer = BufferUtils.createByteBuffer(totalSize * depth);
for (int k = 0; k < depth; k++) {
// ByteBuffer dataBuffer = BufferUtils.createByteBuffer(totalSize);
int mipWidth = width;
int mipHeight = height;
int offset = k * totalSize;
byte[] b = new byte[sourcebytesPP];
for (int mip = 0; mip < mipMapCount; mip++) {
for (int y = 0; y < mipHeight; y++) {
for (int x = 0; x < mipWidth; x++) {
in.readFully(b);
int i = byte2int(b);
byte red = (byte) (((i & redMask) >> redCount));
byte green = (byte) (((i & greenMask) >> greenCount));
byte blue = (byte) (((i & blueMask) >> blueCount));
byte alpha = (byte) (((i & alphaMask) >> alphaCount));
if (flip) {
dataBuffer.position(offset + ((mipHeight - y - 1) * mipWidth + x) * targetBytesPP);
}
//else
// dataBuffer.position(offset + (y * width + x) * targetBytesPP);
if (alphaMask == 0) {
dataBuffer.put(red).put(green).put(blue);
} else {
dataBuffer.put(red).put(green).put(blue).put(alpha);
}
}
}
offset += (mipWidth * mipHeight * targetBytesPP);
mipWidth = Math.max(mipWidth / 2, 1);
mipHeight = Math.max(mipHeight / 2, 1);
}
}
dataBuffer.rewind();
return dataBuffer;
}
/**
* Reads a DXT compressed image from the InputStream
*
* @param totalSize Total size of the image in bytes, including mipmaps
* @return ByteBuffer containing compressed DXT image in the format specified by pixelFormat_
* @throws java.io.IOException If an error occured while reading from InputStream
*/
public ByteBuffer readDXT3D(boolean flip, int totalSize) throws IOException {
logger.finest("Source image format: DXT");
ByteBuffer bufferAll = BufferUtils.createByteBuffer(totalSize * depth);
for (int i = 0; i < depth; i++) {
ByteBuffer buffer = BufferUtils.createByteBuffer(totalSize);
int mipWidth = width;
int mipHeight = height;
for (int mip = 0; mip < mipMapCount; mip++) {
if (flip) {
byte[] data = new byte[sizes[mip]];
in.readFully(data);
ByteBuffer wrapped = ByteBuffer.wrap(data);
wrapped.rewind();
ByteBuffer flipped = DXTFlipper.flipDXT(wrapped, mipWidth, mipHeight, pixelFormat);
flipped.rewind();
buffer.put(flipped);
} else {
byte[] data = new byte[sizes[mip]];
in.readFully(data);
buffer.put(data);
}
mipWidth = Math.max(mipWidth / 2, 1);
mipHeight = Math.max(mipHeight / 2, 1);
}
buffer.rewind();
bufferAll.put(buffer);
}
return bufferAll;
}
/**
* Reads the image data from the InputStream in the required format.
* If the file contains a cubemap image, it is loaded as 6 ByteBuffers
@ -583,19 +724,28 @@ public class DDSLoader implements AssetLoader {
}
ArrayList<ByteBuffer> allMaps = new ArrayList<ByteBuffer>();
if (depth > 1){
for (int i = 0; i < depth; i++){
if (depth > 1 && !texture3D) {
for (int i = 0; i < depth; i++) {
if (compressed) {
allMaps.add(readDXT2D(flip,totalSize));
allMaps.add(readDXT2D(flip, totalSize));
} else if (grayscaleOrAlpha) {
allMaps.add(readGrayscale2D(flip, totalSize));
} else {
allMaps.add(readRGB2D(flip, totalSize));
}
}
} else if (texture3D) {
if (compressed) {
allMaps.add(readDXT3D(flip, totalSize));
} else if (grayscaleOrAlpha) {
allMaps.add(readGrayscale3D(flip, totalSize));
} else {
allMaps.add(readRGB3D(flip, totalSize));
}
} else {
if (compressed) {
allMaps.add(readDXT2D(flip,totalSize));
allMaps.add(readDXT2D(flip, totalSize));
} else if (grayscaleOrAlpha) {
allMaps.add(readGrayscale2D(flip, totalSize));
} else {

@ -29,7 +29,6 @@
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.asset;
import com.jme3.export.JmeExporter;
@ -39,6 +38,7 @@ import com.jme3.export.OutputCapsule;
import com.jme3.texture.Image;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture2D;
import com.jme3.texture.Texture3D;
import com.jme3.texture.TextureCubeMap;
import java.io.IOException;
import java.nio.ByteBuffer;
@ -48,23 +48,24 @@ public class TextureKey extends AssetKey<Texture> {
private boolean generateMips;
private boolean flipY;
private boolean asCube;
private boolean asTexture3D;
private int anisotropy;
public TextureKey(String name, boolean flipY){
public TextureKey(String name, boolean flipY) {
super(name);
this.flipY = flipY;
}
public TextureKey(String name){
public TextureKey(String name) {
super(name);
this.flipY = true;
}
public TextureKey(){
public TextureKey() {
}
@Override
public String toString(){
public String toString() {
return name + (flipY ? " (Flipped)" : "") + (asCube ? " (Cube)" : "") + (generateMips ? " (Mipmaped)" : "");
}
@ -73,39 +74,43 @@ public class TextureKey extends AssetKey<Texture> {
* @return true to enable smart cache
*/
@Override
public boolean useSmartCache(){
public boolean useSmartCache() {
return true;
}
@Override
public Object createClonedInstance(Object asset){
public Object createClonedInstance(Object asset) {
Texture tex = (Texture) asset;
return tex.createSimpleClone();
}
@Override
public Object postProcess(Object asset){
public Object postProcess(Object asset) {
Image img = (Image) asset;
if (img == null)
if (img == null) {
return null;
}
Texture tex;
if (isAsCube()){
if (isFlipY()){
if (isAsCube()) {
if (isFlipY()) {
// also flip -y and +y image in cubemap
ByteBuffer pos_y = img.getData(2);
img.setData(2, img.getData(3));
img.setData(3, pos_y);
}
tex = new TextureCubeMap();
}else{
} else if (isAsTexture3D()) {
tex = new Texture3D();
} else {
tex = new Texture2D();
}
// enable mipmaps if image has them
// or generate them if requested by user
if (img.hasMipmaps() || isGenerateMips())
if (img.hasMipmaps() || isGenerateMips()) {
tex.setMinFilter(Texture.MinFilter.Trilinear);
}
tex.setAnisotropicFilter(getAnisotropy());
tex.setName(getName());
@ -141,15 +146,23 @@ public class TextureKey extends AssetKey<Texture> {
this.generateMips = generateMips;
}
public boolean isAsTexture3D() {
return asTexture3D;
}
public void setAsTexture3D(boolean asTexture3D) {
this.asTexture3D = asTexture3D;
}
@Override
public boolean equals(Object other){
if (!(other instanceof TextureKey)){
public boolean equals(Object other) {
if (!(other instanceof TextureKey)) {
return false;
}
return super.equals(other) && isFlipY() == ((TextureKey)other).isFlipY();
return super.equals(other) && isFlipY() == ((TextureKey) other).isFlipY();
}
public void write(JmeExporter ex) throws IOException{
public void write(JmeExporter ex) throws IOException {
super.write(ex);
OutputCapsule oc = ex.getCapsule(this);
oc.write(flipY, "flip_y", false);
@ -159,7 +172,7 @@ public class TextureKey extends AssetKey<Texture> {
}
@Override
public void read(JmeImporter im) throws IOException{
public void read(JmeImporter im) throws IOException {
super.read(im);
InputCapsule ic = im.getCapsule(this);
flipY = ic.readBoolean("flip_y", false);

@ -368,9 +368,8 @@ public class Material implements Asset, Cloneable, Savable, Comparable<Material>
}
if (type != null && paramDef.getVarType() != type) {
logger.logp(Level.WARNING, "Material parameter being set: {0} with "
+ "type {1} doesn't match definition type {2}",
name, type.name(), paramDef.getVarType());
logger.log(Level.WARNING, "Material parameter being set: {0} with "
+ "type {1} doesn't match definition type {2}", new Object[]{name, type.name(), paramDef.getVarType()} );
}
return newName;
@ -497,7 +496,7 @@ public class Material implements Asset, Cloneable, Savable, Comparable<Material>
return;
}
VarType paramType = null;
VarType paramType = null;
switch (value.getType()) {
case TwoDimensional:
paramType = VarType.Texture2D;

@ -0,0 +1,65 @@
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package jme3test.texture;
import com.jme3.app.SimpleApplication;
import com.jme3.asset.TextureKey;
import com.jme3.material.Material;
import com.jme3.material.RenderState.BlendMode;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.Quad;
import com.jme3.texture.Image;
import com.jme3.texture.Image.Format;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture3D;
import com.jme3.util.BufferUtils;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
public class TestTexture3DLoading extends SimpleApplication {
public static void main(String[] args) {
TestTexture3DLoading app = new TestTexture3DLoading();
app.start();
}
@Override
public void simpleInitApp() {
viewPort.setBackgroundColor(ColorRGBA.DarkGray);
flyCam.setEnabled(false);
Quad q = new Quad(10, 10);
Geometry geom = new Geometry("Quad", q);
Material material = new Material(assetManager, "jme3test/texture/tex3DThumb.j3md");
TextureKey key = new TextureKey("Textures/3D/flame.dds");
key.setGenerateMips(true);
key.setAsTexture3D(true);
Texture t = assetManager.loadTexture(key);
int rows = 4;//4 * 4
q.scaleTextureCoordinates(new Vector2f(rows, rows));
//The image only have 8 pictures and we have 16 thumbs, the data will be interpolated by the GPU
material.setFloat("InvDepth", 1f / 16f);
material.setInt("Rows", rows);
material.setTexture("Texture", t);
geom.setMaterial(material);
rootNode.attachChild(geom);
cam.setLocation(new Vector3f(4.7444625f, 5.160054f, 13.1939f));
}
}

@ -0,0 +1,14 @@
uniform sampler3D m_Texture;
uniform int m_Rows;
uniform float m_InvDepth;
varying vec2 texCoord;
void main(){
float depthx=floor(texCoord.x);
float depthy=(m_Rows-1.0) - floor(texCoord.y);
//vec3 texC=vec3(texCoord.x,texCoord.y ,0.7);//
vec3 texC=vec3(fract(texCoord.x),fract(texCoord.y),(depthy*m_Rows+depthx)*m_InvDepth);//
gl_FragColor= texture3D(m_Texture,texC);
}

@ -0,0 +1,18 @@
MaterialDef Tex3DThumb {
MaterialParameters {
Texture3D Texture
Int Rows;
Float InvDepth;
}
Technique {
VertexShader GLSL100: jme3test/texture/tex3DThumb.vert
FragmentShader GLSL100: jme3test/texture/tex3DThumb.frag
WorldParameters {
WorldViewProjectionMatrix
}
}
}

@ -0,0 +1,11 @@
uniform mat4 g_WorldViewProjectionMatrix;
attribute vec2 inTexCoord;
attribute vec3 inPosition;
varying vec2 texCoord;
void main(){
gl_Position = g_WorldViewProjectionMatrix * vec4(inPosition,1.0);
texCoord=inTexCoord;
}
Loading…
Cancel
Save